[VM/Runtime] - Add Dart C API function to toggle timeline categories.

Add Dart C API Function Dart_EnableTimelineCategory that enables
toggling of timeline categories at runtime.

https://github.com/dart-lang/sdk/issues/47601

TEST=Dart C API tests added

Change-Id: Ib38c8ab0a8d43c9180e9cb9ade107f8bad5f5e63
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/219943
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
diff --git a/runtime/include/dart_tools_api.h b/runtime/include/dart_tools_api.h
index f36ec6b..6d02750 100644
--- a/runtime/include/dart_tools_api.h
+++ b/runtime/include/dart_tools_api.h
@@ -363,6 +363,37 @@
  */
 
 /**
+ * Enable tracking of specified timeline category. This is operational
+ * only when systrace timeline functionality is turned on.
+ *
+ * \param categories A comma seperated list of categories that need to
+ *   be enabled, the categories are
+ *   "all" : All categories
+ *   "API" - Execution of Dart C API functions
+ *   "Compiler" - Execution of Dart JIT compiler
+ *   "CompilerVerbose" - More detailed Execution of Dart JIT compiler
+ *   "Dart" - Execution of Dart code
+ *   "Debugger" - Execution of Dart debugger
+ *   "Embedder" - Execution of Dart embedder code
+ *   "GC" - Execution of Dart Garbage Collector
+ *   "Isolate" - Dart Isolate lifecycle execution
+ *   "VM" - Excution in Dart VM runtime code
+ *   "" - None
+ *
+ *  When "all" is specified all the categories are enabled.
+ *  When a comma seperated list of categories is specified, the categories
+ *   that are specified will be enabled and the rest will be disabled. 
+ *  When "" is specified all the categories are disabled.
+ *  The category names are case sensitive.
+ *  eg:  Dart_EnableTimelineCategory("all");
+ *       Dart_EnableTimelineCategory("GC,API,Isolate");
+ *       Dart_EnableTimelineCategory("GC,Debugger,Dart");
+ *
+ * \return True if the categories were successfully enabled, False otherwise.
+ */
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories);
+
+/**
  * Returns a timestamp in microseconds. This timestamp is suitable for
  * passing into the timeline system, and uses the same monotonic clock
  * as dart:developer's Timeline.now.
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 88dc441..437ce6f 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -6272,6 +6272,20 @@
 #endif
 }
 
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories) {
+#if defined(SUPPORT_TIMELINE)
+  bool result = false;
+  if (categories != nullptr) {
+    char* carray = Utils::SCreate("[%s]", categories);
+    result = Service::EnableTimelineStreams(carray);
+    free(carray);
+  }
+  return result;
+#else
+  return false;
+#endif
+}
+
 DART_EXPORT int64_t Dart_TimelineGetMicros() {
   return OS::GetCurrentMonotonicMicros();
 }
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 7074ded..5a9050a 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -9323,6 +9323,84 @@
   EXPECT_EQ(frequency1, frequency2);
 }
 
+TEST_CASE(DartAPI_TimelineCategories) {
+  bool result;
+  {
+    result = Dart_SetEnabledTimelineCategory("all");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+#define TIMELINE_STREAM_CHECK(name, fuchsia_name)                              \
+  EXPECT_SUBSTRING(#name, js_str);
+    TIMELINE_STREAM_LIST(TIMELINE_STREAM_CHECK)
+#undef TIMELINE_STREAM_CHECK
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory(nullptr);
+    EXPECT_EQ(false, result);
+    result = Dart_SetEnabledTimelineCategory("GC,api,compiler");
+    EXPECT_EQ(false, result);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("GC,API,Compiler");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_SUBSTRING("GC", js_str);
+    EXPECT_SUBSTRING("API", js_str);
+    EXPECT_SUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_NOTSUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("Isolate");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_NOTSUBSTRING("GC", js_str);
+    EXPECT_NOTSUBSTRING("API", js_str);
+    EXPECT_NOTSUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_SUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_NOTSUBSTRING("GC", js_str);
+    EXPECT_NOTSUBSTRING("API", js_str);
+    EXPECT_NOTSUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_NOTSUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+}
+
 static void HintFreedNative(Dart_NativeArguments args) {
   int64_t size = 0;
   EXPECT_VALID(Dart_GetNativeIntegerArgument(args, 0, &size));
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index a75737e..b70dea0 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -72,6 +72,231 @@
             "Print a message when an isolate is paused but there is no "
             "debugger attached.");
 
+static void PrintInvalidParamError(JSONStream* js, const char* param) {
+#if !defined(PRODUCT)
+  js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
+                 param, js->LookupParam(param));
+#endif
+}
+
+// TODO(johnmccutchan): Split into separate file and write unit tests.
+class MethodParameter {
+ public:
+  MethodParameter(const char* name, bool required)
+      : name_(name), required_(required) {}
+
+  virtual ~MethodParameter() {}
+
+  virtual bool Validate(const char* value) const { return true; }
+
+  virtual bool ValidateObject(const Object& value) const { return true; }
+
+  const char* name() const { return name_; }
+
+  bool required() const { return required_; }
+
+  virtual void PrintError(const char* name,
+                          const char* value,
+                          JSONStream* js) const {
+    PrintInvalidParamError(js, name);
+  }
+
+  virtual void PrintErrorObject(const char* name,
+                                const Object& value,
+                                JSONStream* js) const {
+    PrintInvalidParamError(js, name);
+  }
+
+ private:
+  const char* name_;
+  bool required_;
+};
+
+class NoSuchParameter : public MethodParameter {
+ public:
+  explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
+
+  virtual bool Validate(const char* value) const { return (value == NULL); }
+
+  virtual bool ValidateObject(const Object& value) const {
+    return value.IsNull();
+  }
+};
+
+#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
+#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
+#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
+#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
+
+class EnumListParameter : public MethodParameter {
+ public:
+  EnumListParameter(const char* name, bool required, const char* const* enums)
+      : MethodParameter(name, required), enums_(enums) {}
+
+  virtual bool Validate(const char* value) const {
+    return ElementCount(value) >= 0;
+  }
+
+  const char** Parse(char* value) const {
+    const char* kJsonChars = " \t\r\n[,]";
+
+    // Make a writeable copy of the value.
+    intptr_t element_count = ElementCount(value);
+    if (element_count < 0) {
+      return nullptr;
+    }
+    intptr_t element_pos = 0;
+
+    // Allocate our element array.  +1 for NULL terminator.
+    // The caller is reponsible for deleting this memory.
+    char** elements = new char*[element_count + 1];
+    elements[element_count] = NULL;
+
+    // Parse the string destructively.  Build the list of elements.
+    while (element_pos < element_count) {
+      // Skip to the next element.
+      value += strspn(value, kJsonChars);
+
+      intptr_t len = strcspn(value, kJsonChars);
+      ASSERT(len > 0);  // We rely on the parameter being validated already.
+      value[len] = '\0';
+      elements[element_pos++] = value;
+
+      // Advance.  +1 for null terminator.
+      value += (len + 1);
+    }
+    return const_cast<const char**>(elements);
+  }
+
+ private:
+  // For now observatory enums are ascii letters plus underscore.
+  static bool IsEnumChar(char c) {
+    return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
+            (c == '_'));
+  }
+
+  // Returns number of elements in the list.  -1 on parse error.
+  intptr_t ElementCount(const char* value) const {
+    const char* kJsonWhitespaceChars = " \t\r\n";
+    if (value == NULL) {
+      return -1;
+    }
+    const char* cp = value;
+    cp += strspn(cp, kJsonWhitespaceChars);
+    if (*cp++ != '[') {
+      // Missing initial [.
+      return -1;
+    }
+    bool closed = false;
+    bool element_allowed = true;
+    intptr_t element_count = 0;
+    while (true) {
+      // Skip json whitespace.
+      cp += strspn(cp, kJsonWhitespaceChars);
+      switch (*cp) {
+        case '\0':
+          return closed ? element_count : -1;
+        case ']':
+          closed = true;
+          cp++;
+          break;
+        case ',':
+          if (element_allowed) {
+            return -1;
+          }
+          element_allowed = true;
+          cp++;
+          break;
+        default:
+          if (!element_allowed) {
+            return -1;
+          }
+          bool valid_enum = false;
+          const char* id_start = cp;
+          while (IsEnumChar(*cp)) {
+            cp++;
+          }
+          if (cp == id_start) {
+            // Empty identifier, something like this [,].
+            return -1;
+          }
+          intptr_t id_len = cp - id_start;
+          if (enums_ != NULL) {
+            for (intptr_t i = 0; enums_[i] != NULL; i++) {
+              intptr_t len = strlen(enums_[i]);
+              if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
+                element_count++;
+                valid_enum = true;
+                element_allowed = false;  // we need a comma first.
+                break;
+              }
+            }
+          }
+          if (!valid_enum) {
+            return -1;
+          }
+          break;
+      }
+    }
+  }
+
+  const char* const* enums_;
+};
+
+#if defined(SUPPORT_TIMELINE)
+static const char* const timeline_streams_enum_names[] = {
+    "all",
+#define DEFINE_NAME(name, unused) #name,
+    TIMELINE_STREAM_LIST(DEFINE_NAME)
+#undef DEFINE_NAME
+        NULL};
+
+static const MethodParameter* const set_vm_timeline_flags_params[] = {
+    NO_ISOLATE_PARAMETER,
+    new EnumListParameter("recordedStreams",
+                          false,
+                          timeline_streams_enum_names),
+    NULL,
+};
+
+static bool HasStream(const char** recorded_streams, const char* stream) {
+  while (*recorded_streams != NULL) {
+    if ((strstr(*recorded_streams, "all") != NULL) ||
+        (strstr(*recorded_streams, stream) != NULL)) {
+      return true;
+    }
+    recorded_streams++;
+  }
+  return false;
+}
+
+bool Service::EnableTimelineStreams(char* categories_list) {
+  const EnumListParameter* recorded_streams_param =
+      static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
+  const char** streams = recorded_streams_param->Parse(categories_list);
+  if (streams == nullptr) {
+    return false;
+  }
+
+#define SET_ENABLE_STREAM(name, unused)                                        \
+  Timeline::SetStream##name##Enabled(HasStream(streams, #name));
+  TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
+#undef SET_ENABLE_STREAM
+
+  delete[] streams;
+
+#if !defined(PRODUCT)
+  // Notify clients that the set of subscribed streams has been updated.
+  if (Service::timeline_stream.enabled()) {
+    ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
+    Service::HandleEvent(&event);
+  }
+#endif
+
+  return true;
+}
+#endif  // defined(SUPPORT_TIMELINE)
+
 #ifndef PRODUCT
 // The name of this of this vm as reported by the VM service protocol.
 static char* vm_name = NULL;
@@ -220,20 +445,6 @@
   return object.ptr();
 }
 
-static void PrintMissingParamError(JSONStream* js, const char* param) {
-  js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
-                 param);
-}
-
-static void PrintInvalidParamError(JSONStream* js, const char* param) {
-  js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
-                 param, js->LookupParam(param));
-}
-
-static void PrintUnrecognizedMethodError(JSONStream* js) {
-  js->PrintError(kMethodNotFound, NULL);
-}
-
 static void PrintSuccess(JSONStream* js) {
   JSONObject jsobj(js);
   jsobj.AddProperty("type", "Success");
@@ -430,39 +641,6 @@
   return class_table->At(cid);
 }
 
-// TODO(johnmccutchan): Split into separate file and write unit tests.
-class MethodParameter {
- public:
-  MethodParameter(const char* name, bool required)
-      : name_(name), required_(required) {}
-
-  virtual ~MethodParameter() {}
-
-  virtual bool Validate(const char* value) const { return true; }
-
-  virtual bool ValidateObject(const Object& value) const { return true; }
-
-  const char* name() const { return name_; }
-
-  bool required() const { return required_; }
-
-  virtual void PrintError(const char* name,
-                          const char* value,
-                          JSONStream* js) const {
-    PrintInvalidParamError(js, name);
-  }
-
-  virtual void PrintErrorObject(const char* name,
-                                const Object& value,
-                                JSONStream* js) const {
-    PrintInvalidParamError(js, name);
-  }
-
- private:
-  const char* name_;
-  bool required_;
-};
-
 class DartStringParameter : public MethodParameter {
  public:
   DartStringParameter(const char* name, bool required)
@@ -483,17 +661,6 @@
   }
 };
 
-class NoSuchParameter : public MethodParameter {
- public:
-  explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
-
-  virtual bool Validate(const char* value) const { return (value == NULL); }
-
-  virtual bool ValidateObject(const Object& value) const {
-    return value.IsNull();
-  }
-};
-
 class BoolParameter : public MethodParameter {
  public:
   BoolParameter(const char* name, bool required)
@@ -632,11 +799,6 @@
   }
 };
 
-#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
-#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
-#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
-#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
-
 class EnumParameter : public MethodParameter {
  public:
   EnumParameter(const char* name, bool required, const char* const* enums)
@@ -673,118 +835,6 @@
   return values[i];
 }
 
-class EnumListParameter : public MethodParameter {
- public:
-  EnumListParameter(const char* name, bool required, const char* const* enums)
-      : MethodParameter(name, required), enums_(enums) {}
-
-  virtual bool Validate(const char* value) const {
-    return ElementCount(value) >= 0;
-  }
-
-  const char** Parse(Zone* zone, const char* value_in) const {
-    const char* kJsonChars = " \t\r\n[,]";
-
-    // Make a writeable copy of the value.
-    char* value = zone->MakeCopyOfString(value_in);
-    intptr_t element_count = ElementCount(value);
-    intptr_t element_pos = 0;
-
-    // Allocate our element array.  +1 for NULL terminator.
-    char** elements = zone->Alloc<char*>(element_count + 1);
-    elements[element_count] = NULL;
-
-    // Parse the string destructively.  Build the list of elements.
-    while (element_pos < element_count) {
-      // Skip to the next element.
-      value += strspn(value, kJsonChars);
-
-      intptr_t len = strcspn(value, kJsonChars);
-      ASSERT(len > 0);  // We rely on the parameter being validated already.
-      value[len] = '\0';
-      elements[element_pos++] = value;
-
-      // Advance.  +1 for null terminator.
-      value += (len + 1);
-    }
-    return const_cast<const char**>(elements);
-  }
-
- private:
-  // For now observatory enums are ascii letters plus underscore.
-  static bool IsEnumChar(char c) {
-    return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
-            (c == '_'));
-  }
-
-  // Returns number of elements in the list.  -1 on parse error.
-  intptr_t ElementCount(const char* value) const {
-    const char* kJsonWhitespaceChars = " \t\r\n";
-    if (value == NULL) {
-      return -1;
-    }
-    const char* cp = value;
-    cp += strspn(cp, kJsonWhitespaceChars);
-    if (*cp++ != '[') {
-      // Missing initial [.
-      return -1;
-    }
-    bool closed = false;
-    bool element_allowed = true;
-    intptr_t element_count = 0;
-    while (true) {
-      // Skip json whitespace.
-      cp += strspn(cp, kJsonWhitespaceChars);
-      switch (*cp) {
-        case '\0':
-          return closed ? element_count : -1;
-        case ']':
-          closed = true;
-          cp++;
-          break;
-        case ',':
-          if (element_allowed) {
-            return -1;
-          }
-          element_allowed = true;
-          cp++;
-          break;
-        default:
-          if (!element_allowed) {
-            return -1;
-          }
-          bool valid_enum = false;
-          const char* id_start = cp;
-          while (IsEnumChar(*cp)) {
-            cp++;
-          }
-          if (cp == id_start) {
-            // Empty identifier, something like this [,].
-            return -1;
-          }
-          intptr_t id_len = cp - id_start;
-          if (enums_ != NULL) {
-            for (intptr_t i = 0; enums_[i] != NULL; i++) {
-              intptr_t len = strlen(enums_[i]);
-              if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
-                element_count++;
-                valid_enum = true;
-                element_allowed = false;  // we need a comma first.
-                break;
-              }
-            }
-          }
-          if (!valid_enum) {
-            return -1;
-          }
-          break;
-      }
-    }
-  }
-
-  const char* const* enums_;
-};
-
 typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
 
 struct ServiceMethodDescriptor {
@@ -793,6 +843,15 @@
   const MethodParameter* const* parameters;
 };
 
+static void PrintMissingParamError(JSONStream* js, const char* param) {
+  js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
+                 param);
+}
+
+static void PrintUnrecognizedMethodError(JSONStream* js) {
+  js->PrintError(kMethodNotFound, NULL);
+}
+
 // TODO(johnmccutchan): Do we reject unexpected parameters?
 static bool ValidateParameters(const MethodParameter* const* parameters,
                                JSONStream* js) {
@@ -3349,23 +3408,28 @@
     return;
   }
 
-  const char* reports_str = js->LookupParam("reports");
+  char* reports_str = Utils::StrDup(js->LookupParam("reports"));
   const EnumListParameter* reports_parameter =
       static_cast<const EnumListParameter*>(get_source_report_params[1]);
-  const char** reports = reports_parameter->Parse(thread->zone(), reports_str);
+  const char** reports = reports_parameter->Parse(reports_str);
+  const char** riter = reports;
   intptr_t report_set = 0;
-  while (*reports != NULL) {
-    if (strcmp(*reports, SourceReport::kCallSitesStr) == 0) {
+  while (*riter != NULL) {
+    if (strcmp(*riter, SourceReport::kCallSitesStr) == 0) {
       report_set |= SourceReport::kCallSites;
-    } else if (strcmp(*reports, SourceReport::kCoverageStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kCoverageStr) == 0) {
       report_set |= SourceReport::kCoverage;
-    } else if (strcmp(*reports, SourceReport::kPossibleBreakpointsStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kPossibleBreakpointsStr) == 0) {
       report_set |= SourceReport::kPossibleBreakpoints;
-    } else if (strcmp(*reports, SourceReport::kProfileStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kProfileStr) == 0) {
       report_set |= SourceReport::kProfile;
     }
-    reports++;
+    riter++;
   }
+  if (reports != nullptr) {
+    delete[] reports;
+  }
+  free(reports_str);
 
   SourceReport::CompileMode compile_mode = SourceReport::kNoCompile;
   if (BoolParameter::Parse(js->LookupParam("forceCompile"), false)) {
@@ -3803,34 +3867,6 @@
   }
 }
 
-static const char* const timeline_streams_enum_names[] = {
-    "all",
-#define DEFINE_NAME(name, unused) #name,
-    TIMELINE_STREAM_LIST(DEFINE_NAME)
-#undef DEFINE_NAME
-        NULL};
-
-static const MethodParameter* const set_vm_timeline_flags_params[] = {
-    NO_ISOLATE_PARAMETER,
-    new EnumListParameter("recordedStreams",
-                          false,
-                          timeline_streams_enum_names),
-    NULL,
-};
-
-#if defined(SUPPORT_TIMELINE)
-static bool HasStream(const char** recorded_streams, const char* stream) {
-  while (*recorded_streams != NULL) {
-    if ((strstr(*recorded_streams, "all") != NULL) ||
-        (strstr(*recorded_streams, stream) != NULL)) {
-      return true;
-    }
-    recorded_streams++;
-  }
-  return false;
-}
-#endif
-
 static void SetVMTimelineFlags(Thread* thread, JSONStream* js) {
 #if !defined(SUPPORT_TIMELINE)
   PrintSuccess(js);
@@ -3839,23 +3875,9 @@
   ASSERT(isolate != NULL);
   StackZone zone(thread);
 
-  const EnumListParameter* recorded_streams_param =
-      static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
-
-  const char* recorded_streams_str = js->LookupParam("recordedStreams");
-  const char** recorded_streams =
-      recorded_streams_param->Parse(thread->zone(), recorded_streams_str);
-
-#define SET_ENABLE_STREAM(name, unused)                                        \
-  Timeline::SetStream##name##Enabled(HasStream(recorded_streams, #name));
-  TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
-#undef SET_ENABLE_STREAM
-
-  // Notify clients that the set of subscribed streams has been updated.
-  if (Service::timeline_stream.enabled()) {
-    ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
-    Service::HandleEvent(&event);
-  }
+  char* recorded_streams = Utils::StrDup(js->LookupParam("recordedStreams"));
+  Service::EnableTimelineStreams(recorded_streams);
+  free(recorded_streams);
 
   PrintSuccess(js);
 #endif
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index c3fe1f2..f51eb10 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -161,6 +161,10 @@
                         const Instance& id,
                         const Error& error);
 
+  // Enable/Disable timeline categories.
+  // Returns True if the categories were successfully enabled, False otherwise.
+  static bool EnableTimelineStreams(char* categories_list);
+
   // Well-known streams.
   static StreamInfo vm_stream;
   static StreamInfo isolate_stream;