[ VM / Service ] Allow for `profile_period` flag to be set via the service protocol

`profile_period` has been added to the list of flags which can be
changed at runtime via the `setFlag` RPC. Changing this flag at runtime will
cause the ThreadInterrupter to be shutdown, reinitialized, and started with the
new profiling period.

Fixes #35654.

Change-Id: I2f365fdf8e404270f2eed1e172c8d21b48e0aff9
Reviewed-on: https://dart-review.googlesource.com/c/89445
Auto-Submit: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Jacob Richman <jacobr@google.com>
diff --git a/runtime/observatory/tests/service/get_flag_list_rpc_test.dart b/runtime/observatory/tests/service/get_flag_list_rpc_test.dart
index 2240e01..472bfbb 100644
--- a/runtime/observatory/tests/service/get_flag_list_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_flag_list_rpc_test.dart
@@ -7,6 +7,17 @@
 
 import 'test_helper.dart';
 
+Future getFlagValue(VM vm, String flagName) async {
+  var result = await vm.invokeRpcNoUpgrade('getFlagList', {});
+  expect(result['type'], equals('FlagList'));
+  final flags = result['flags'];
+  for (final flag in flags) {
+    if (flag['name'] == flagName) {
+      return flag['valueAsString'];
+    }
+  }
+}
+
 var tests = <VMTest>[
   (VM vm) async {
     var result = await vm.invokeRpcNoUpgrade('getFlagList', {});
@@ -45,6 +56,32 @@
     var result = await vm.invokeRpcNoUpgrade('setFlag', params);
     expect(result['type'], equals('Success'));
   },
+
+  // Modify a flag which cannot be set at runtime.
+  (VM vm) async {
+    var params = {
+      'name': 'random_seed',
+      'value': '42',
+    };
+    var result = await vm.invokeRpcNoUpgrade('setFlag', params);
+    expect(result['type'], equals('Error'));
+    expect(
+        result['message'], equals('Cannot set flag: cannot change at runtime'));
+  },
+
+  // Modify the profile_period at runtime.
+  (VM vm) async {
+    final kProfilePeriod = 'profile_period';
+    final kValue = 100;
+    expect(await getFlagValue(vm, kProfilePeriod), '1000');
+    var params = {
+      'name': '$kProfilePeriod',
+      'value': '$kValue',
+    };
+    var result = await vm.invokeRpcNoUpgrade('setFlag', params);
+    expect(result['type'], equals('Success'));
+    expect(await getFlagValue(vm, kProfilePeriod), kValue.toString());
+  }
 ];
 
 main(args) async => runVMTests(args, tests);
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 9b1fb1a..ca2028a 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
     var result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], equals('Version'));
     expect(result['major'], equals(3));
-    expect(result['minor'], equals(13));
+    expect(result['minor'], equals(14));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
index 482dac0..5ff50f6 100644
--- a/runtime/vm/profiler.cc
+++ b/runtime/vm/profiler.cc
@@ -65,7 +65,6 @@
 
 void Profiler::Init() {
   // Place some sane restrictions on user controlled flags.
-  SetSamplePeriod(FLAG_profile_period);
   SetSampleDepth(FLAG_max_profile_depth);
   Sample::Init();
   if (!FLAG_profiler) {
@@ -77,7 +76,7 @@
   // Zero counters.
   memset(&counters_, 0, sizeof(counters_));
   ThreadInterrupter::Init();
-  ThreadInterrupter::SetInterruptPeriod(FLAG_profile_period);
+  SetSamplePeriod(FLAG_profile_period);
   ThreadInterrupter::Startup();
   initialized_ = true;
 }
@@ -123,6 +122,11 @@
   } else {
     FLAG_profile_period = period;
   }
+  ThreadInterrupter::SetInterruptPeriod(FLAG_profile_period);
+}
+
+void Profiler::UpdateSamplePeriod() {
+  SetSamplePeriod(FLAG_profile_period);
 }
 
 intptr_t Sample::pcs_length_ = 0;
diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h
index 7ac6741..cb891a5 100644
--- a/runtime/vm/profiler.h
+++ b/runtime/vm/profiler.h
@@ -60,6 +60,9 @@
 
   static void SetSampleDepth(intptr_t depth);
   static void SetSamplePeriod(intptr_t period);
+  // Restarts sampling with a given profile period. This is called after the
+  // profile period is changed via the service protocol.
+  static void UpdateSamplePeriod();
 
   static SampleBuffer* sample_buffer() { return sample_buffer_; }
   static AllocationSampleBuffer* allocation_sample_buffer() {
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 282f198..580d290 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4545,16 +4545,20 @@
 
   // Changing most flags at runtime is dangerous because e.g., it may leave the
   // behavior generated code and the runtime out of sync.
+  const uintptr_t kProfilePeriodIndex = 3;
   const char* kAllowedFlags[] = {
       "pause_isolates_on_start",
       "pause_isolates_on_exit",
       "pause_isolates_on_unhandled_exceptions",
+      "profile_period",
   };
 
   bool allowed = false;
+  bool profile_period = false;
   for (size_t i = 0; i < ARRAY_SIZE(kAllowedFlags); i++) {
     if (strcmp(flag_name, kAllowedFlags[i]) == 0) {
       allowed = true;
+      profile_period = (i == kProfilePeriodIndex);
       break;
     }
   }
@@ -4569,6 +4573,11 @@
   const char* error = NULL;
   if (Flags::SetFlag(flag_name, flag_value, &error)) {
     PrintSuccess(js);
+    if (profile_period) {
+      // FLAG_profile_period has already been set to the new value. Now we need
+      // to notify the ThreadInterrupter to pick up the change.
+      Profiler::UpdateSamplePeriod();
+    }
     return true;
   } else {
     JSONObject jsobj(js);
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index 90f3e64..3e54a6c 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -15,7 +15,7 @@
 namespace dart {
 
 #define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 13
+#define SERVICE_PROTOCOL_MINOR_VERSION 14
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 015ecfb..9d90108 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.13
+# Dart VM Service Protocol 3.14
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.13_ of the Dart VM Service Protocol. This
+This document describes of _version 3.14_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -842,6 +842,10 @@
  * pause_isolates_on_start
  * pause_isolates_on_exit
  * pause_isolates_on_unhandled_exceptions
+ * profile_period
+
+Note: `profile_period` can be set to a minimum value of 50. Attempting to set
+`profile_period` to a lower value will result in a value of 50 being set.
 
 See [Success](#success).
 
@@ -2732,5 +2736,6 @@
 3.11 | Rename 'invoke' parameter 'receiverId' to 'targetId.
 3.12 | Add 'getScripts' RPC and `ScriptList` object.
 3.13 | Class 'mixin' field now properly set for kernel transformed mixin applications.
+3.14 | Flag 'profile_period' can now be set at runtime, allowing for the profiler sample rate to be changed while the program is running.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service/service_dev.md b/runtime/vm/service/service_dev.md
index 869811e..5d59f09 100644
--- a/runtime/vm/service/service_dev.md
+++ b/runtime/vm/service/service_dev.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.14-dev
+# Dart VM Service Protocol 3.15-dev
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.14-dev_ of the Dart VM Service Protocol. This
+This document describes of _version 3.15-dev_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -842,6 +842,10 @@
  * pause_isolates_on_start
  * pause_isolates_on_exit
  * pause_isolates_on_unhandled_exceptions
+ * profile_period
+
+Note: `profile_period` can be set to a minimum value of 50. Attempting to set
+`profile_period` to a lower value will result in a value of 50 being set.
 
 See [Success](#success).
 
@@ -2732,5 +2736,6 @@
 3.11 | Rename 'invoke' parameter 'receiverId' to 'targetId.
 3.12 | Add 'getScripts' RPC and `ScriptList` object.
 3.13 | Class 'mixin' field now properly set for kernel transformed mixin applications.
+3.14 | Flag 'profile_period' can now be set at runtime, allowing for the profiler sample rate to be changed while the program is running.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/thread_interrupter.cc b/runtime/vm/thread_interrupter.cc
index d8d56b1..ebb07bd 100644
--- a/runtime/vm/thread_interrupter.cc
+++ b/runtime/vm/thread_interrupter.cc
@@ -116,6 +116,7 @@
 
 // Delay between interrupts.
 void ThreadInterrupter::SetInterruptPeriod(intptr_t period) {
+  MonitorLocker ml(monitor_);
   if (shutdown_) {
     return;
   }
@@ -173,6 +174,9 @@
         ASSERT(interrupted_thread_count == 0);
         // Return to regular interrupts.
         current_wait_time_ = interrupt_period_;
+      } else if (current_wait_time_ != interrupt_period_) {
+        // The interrupt period may have been updated via the service protocol.
+        current_wait_time_ = interrupt_period_;
       }
 
       // Reset count before interrupting any threads.