diff --git a/pkg/vm/tool/precompiler2 b/pkg/vm/tool/precompiler2 index 2ac85c1f0a1..9a9c3d70a19 100755 --- a/pkg/vm/tool/precompiler2 +++ b/pkg/vm/tool/precompiler2 @@ -134,7 +134,7 @@ DART="$(find_dart)" function gen_kernel() { if [[ "$DART_GN_ARGS" == *"precompile_tools=true"* ]]; then # Precompile gen_kernel to an AOT app. - ninja -C "$BUILD_DIR" gen_kernel.exe + buildtools/ninja/ninja -C "$BUILD_DIR" gen_kernel.exe "$BUILD_DIR/gen_kernel.exe" $@ else $DART ${DART_VM_FLAGS} "${SDK_DIR}/pkg/vm/bin/gen_kernel.dart" $@ diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 7d2bce8b77f..be80ef1e097 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -360,13 +360,13 @@ source_set("dart_api") { library_for_all_configs("libdart") { target_type = dart_component_kind extra_nonproduct_deps = [] - if (dart_support_perfetto) { - extra_nonproduct_deps += [ "//third_party/perfetto:libprotozero" ] - } extra_deps = [ ":generate_version_cc_file", "../third_party/double-conversion/src:libdouble_conversion", ] + if (dart_support_perfetto) { + extra_deps += [ "//third_party/perfetto:libprotozero" ] + } if (is_fuchsia) { extra_deps += [ "$fuchsia_sdk/pkg/fdio", diff --git a/runtime/lib/developer.cc b/runtime/lib/developer.cc index 7626e0ada37..5258f2ab5f6 100644 --- a/runtime/lib/developer.cc +++ b/runtime/lib/developer.cc @@ -22,6 +22,8 @@ namespace dart { +DECLARE_FLAG(int, profile_period); + // Native implementations for the dart:developer library. DEFINE_NATIVE_ENTRY(Developer_debugger, 0, 2) { GET_NON_NULL_NATIVE_ARGUMENT(Bool, when, arguments->NativeArgAt(0)); @@ -226,7 +228,56 @@ DEFINE_NATIVE_ENTRY(Developer_NativeRuntime_writeHeapSnapshotToFile, 0, 1) { #else Exceptions::ThrowUnsupportedError( "Heap snapshots are only supported in non-product mode."); -#endif // !defined(PRODUCT) +#endif // !defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER) + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Developer_NativeRuntime_streamTimelineTo, 0, 5) { +#if defined(SUPPORT_TIMELINE) + const auto& recorder = String::CheckedHandle(zone, arguments->NativeArgAt(0)); + const auto& path = String::CheckedHandle(zone, arguments->NativeArgAt(1)); + const auto& streams = String::CheckedHandle(zone, arguments->NativeArgAt(2)); + +#if defined(DART_INCLUDE_PROFILER) + const auto& enable_profiler = + Bool::CheckedHandle(zone, arguments->NativeArgAt(3)); + const auto& sampling_interval = + Integer::CheckedHandle(zone, arguments->NativeArgAt(4)); + + if (enable_profiler.value()) { + FLAG_profiler = true; + FLAG_profile_period = sampling_interval.Value(); + Profiler::UpdateSamplePeriod(); + Profiler::UpdateRunningState(); + } +#endif + + const char* error = nullptr; + const bool ok = Timeline::StreamTo(recorder.ToCString(), path.ToCString(), + streams.ToCString(), &error); + if (!ok) { + Exceptions::ThrowUnsupportedError(error); + } +#else + Exceptions::ThrowUnsupportedError( + "Timeline support was excluded during build."); +#endif // !defined(SUPPORT_TIMELINE) + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Developer_NativeRuntime_stopStreamingTimeline, 0, 0) { +#if defined(SUPPORT_TIMELINE) +#if defined(DART_INCLUDE_PROFILER) + FLAG_profiler = false; + FLAG_profile_period = 1000; + Profiler::UpdateSamplePeriod(); + Profiler::UpdateRunningState(); +#endif + Timeline::StopStreaming(); +#else + Exceptions::ThrowUnsupportedError( + "Timeline support was excluded during build."); +#endif // !defined(SUPPORT_TIMELINE) return Object::null(); } diff --git a/runtime/runtime_args.gni b/runtime/runtime_args.gni index 3d72df96aad..ea0aaaed36e 100644 --- a/runtime/runtime_args.gni +++ b/runtime/runtime_args.gni @@ -75,20 +75,26 @@ declare_args() { # Whether the sampling heap profiler should be included in product mode. dart_include_sampling_heap_profiler = false - # Whether features that depend on Perfetto should be built. We need - # to define this to allow excluding code that depends on Perfetto - # from being built on platforms which have a problem linking in the - # Perfetto library, e.g. watchOS. - # - # is_watchos may be undefined when Dart SDK is built from the Flutter - # engine tree. - dart_support_perfetto = !defined(is_watchos) || !is_watchos - # Whether to support dynamic loading and interpretation of Dart bytecode. dart_dynamic_modules = false } declare_args() { + # Whether features that depend on Perfetto should be built. We need + # to define this to allow excluding code that depends on Perfetto + # from being built on platforms which have a problem linking in the + # Perfetto library, e.g. watchOS. + # + # We also exclude perfetto support when producing product builds for + # mobile OSes. + # + # is_watchos may be undefined when Dart SDK is built from the Flutter + # engine tree. + dart_support_perfetto = + !((defined(is_watchos) && is_watchos) || + (dart_runtime_mode == "release" && + ((defined(is_ios) && is_ios) || (defined(is_android) && is_android)))) + # The analyze_snapshot tool is only supported on 64 bit AOT builds that use # ELF. build_analyze_snapshot = diff --git a/runtime/tests/vm/dart/pubspec.yaml b/runtime/tests/vm/dart/pubspec.yaml index 6ee01f1204f..4335d212686 100644 --- a/runtime/tests/vm/dart/pubspec.yaml +++ b/runtime/tests/vm/dart/pubspec.yaml @@ -2,7 +2,7 @@ name: runtime_tests_vm_dart description: VM Dart tests environment: - sdk: ^3.8.0 + sdk: ^3.10.0 resolution: workspace publish_to: none diff --git a/runtime/tests/vm/dart/stream_timeline_to_test.dart b/runtime/tests/vm/dart/stream_timeline_to_test.dart new file mode 100644 index 00000000000..e8c4b55ee1a --- /dev/null +++ b/runtime/tests/vm/dart/stream_timeline_to_test.dart @@ -0,0 +1,205 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; +import 'package:vm_service_protos/vm_service_protos.dart'; + +import 'use_flag_test_helper.dart'; + +@pragma('vm:never-inline') +int workload() { + final sw = Stopwatch()..start(); + return Timeline.timeSync('workload-loop', () { + var sum = 0; + while (sw.elapsedMilliseconds < 50) { + final l = []; + for (var i = 0; i < 10000; i++) { + l.add(i * i); + } + sum += l[50]; + } + return sum; + }); +} + +Future testPerfettoRecorder({ + required String tempDir, + required bool withProfiler, +}) async { + final perfettoTimeline = File( + path.join(tempDir, 'timeline${withProfiler ? '-p' : ''}.pb'), + ); + + Expect.isFalse(perfettoTimeline.existsSync()); + + NativeRuntime.streamTimelineTo( + .perfetto, + path: perfettoTimeline.path, + enableProfiler: withProfiler, + ); + workload(); + NativeRuntime.stopStreamingTimeline(); + + Expect.isTrue( + perfettoTimeline.existsSync(), + '$perfettoTimeline does not exist', + ); + + final trace = Trace()..mergeFromBuffer(perfettoTimeline.readAsBytesSync()); + Expect.isNotEmpty(trace.packet); + + var state = IncrementalState(); + final seenEvents = {}; + final seenStacks = >{}; + for (var packet in trace.packet) { + if ((packet.sequenceFlags & + TracePacket_SequenceFlags.SEQ_INCREMENTAL_STATE_CLEARED.value) != + 0) { + state = IncrementalState(); + } + + if (packet.hasInternedData()) { + state.update(packet.internedData); + } + + if (packet.hasTrackEvent()) { + final trackEvent = packet.trackEvent; + if (trackEvent.type == TrackEvent_Type.TYPE_SLICE_BEGIN) { + final name = state.eventNames[packet.trackEvent.nameIid.toInt()]!; + seenEvents.add(name); + } + } + + if (packet.hasPerfSample()) { + seenStacks.add(state.stacks[packet.perfSample.callstackIid.toInt()]!); + } + } + + Expect.isTrue( + seenEvents.containsAll(['workload-loop', 'CollectNewGeneration']), + ); + + if (withProfiler) { + Expect.isNotNull( + seenStacks.firstWhereOrNull( + (stack) => + stackMatches(stack, ['main', 'workload', 'Timeline.timeSync']), + ), + ); + } else { + Expect.isEmpty(seenStacks); + } +} + +Future testChromeRecorder({required String tempDir}) async { + final chromeTimeline = File(path.join(tempDir, 'timeline.json')); + + Expect.isFalse(chromeTimeline.existsSync()); + + NativeRuntime.streamTimelineTo(.chrome, path: chromeTimeline.path); + workload(); + NativeRuntime.stopStreamingTimeline(); + + Expect.isTrue(chromeTimeline.existsSync(), '$chromeTimeline does not exist'); + + final timelineData = + jsonDecode(chromeTimeline.readAsStringSync()) as List; + final event = timelineData.firstWhereOrNull( + (e) => e['name'] == 'workload-loop', + ); + Expect.isNotNull(event); + Expect.equals('Dart', event!['cat']); + Expect.equals('B', event!['ph']); +} + +void main() async { + await withTempDir('stream_timeline_to_test', (tempDir) async { + await testPerfettoRecorder(tempDir: tempDir, withProfiler: true); + await testPerfettoRecorder(tempDir: tempDir, withProfiler: false); + await testChromeRecorder(tempDir: tempDir); + + // Perfetto and Chrome recorders require file path for output. + Expect.throws( + () => NativeRuntime.streamTimelineTo(.perfetto), + ); + Expect.throws(() => NativeRuntime.streamTimelineTo(.chrome)); + + // Systrace requires no-path. + Expect.throws( + () => NativeRuntime.streamTimelineTo(.systrace, path: 'whatever'), + ); + Expect.isFalse(File('whatever').existsSync()); + + // Only Android, Fuchsia, Linux and Mac OS X support systrace recorder. + if (!(Platform.isAndroid || + Platform.isFuchsia || + Platform.isLinux || + Platform.isMacOS)) { + Expect.throws( + () => NativeRuntime.streamTimelineTo(.systrace), + ); + } + + // Systrace and Chrome recorders do not support profiler. + Expect.throws( + () => NativeRuntime.streamTimelineTo(.systrace, enableProfiler: true), + ); + Expect.throws( + () => NativeRuntime.streamTimelineTo( + .chrome, + path: 'whatever', + enableProfiler: true, + ), + ); + Expect.isFalse(File('whatever').existsSync()); + }); +} + +bool stackMatches(List stack, List expected) { + var i = 0; + var j = 0; + while (j < expected.length) { + while (i < stack.length && stack[i] != expected[j]) { + i++; + } + if (i == stack.length) { + return false; + } + j++; + } + return true; +} + +class IncrementalState { + final eventNames = {}; + final functionNames = {}; + final frames = {}; + final stacks = >{}; + + void update(InternedData internedData) { + for (var eventName in internedData.eventNames) { + eventNames[eventName.iid.toInt()] = eventName.name; + } + + for (var functionName in internedData.functionNames) { + functionNames[functionName.iid.toInt()] = utf8.decode(functionName.str); + } + + for (var frame in internedData.frames) { + frames[frame.iid.toInt()] = functionNames[frame.functionNameId.toInt()]!; + } + + for (var stack in internedData.callstacks) { + stacks[stack.iid.toInt()] = stack.frameIds + .map((iid) => frames[iid.toInt()]!) + .toList(growable: false); + } + } +} diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn index 167deb7a71f..a03b9db2926 100644 --- a/runtime/vm/BUILD.gn +++ b/runtime/vm/BUILD.gn @@ -71,13 +71,13 @@ library_for_all_configs("libdart_vm") { target_type = "source_set" extra_product_deps = [] extra_nonproduct_deps = [] - if (dart_support_perfetto) { - extra_nonproduct_deps += [ "//third_party/perfetto:libprotozero" ] - } extra_deps = [ "//third_party/icu:icui18n", "//third_party/icu:icuuc", ] + if (dart_support_perfetto) { + extra_deps += [ "//third_party/perfetto:libprotozero" ] + } extra_precompiler_deps = [ # The Mach-O writer uses BoringSSL's SHA256 function for code signatures. "//third_party/boringssl", @@ -123,10 +123,10 @@ library_for_all_configs_with_compiler("libdart_compiler") { include_dirs = [ ".." ] extra_nonproduct_deps = [] - if (dart_support_perfetto) { - extra_nonproduct_deps += [ "//third_party/perfetto:libprotozero" ] - } extra_deps = [] + if (dart_support_perfetto) { + extra_deps += [ "//third_party/perfetto:libprotozero" ] + } if (is_fuchsia) { extra_deps += [ "$fuchsia_sdk/pkg/trace-engine" ] } @@ -135,13 +135,13 @@ library_for_all_configs_with_compiler("libdart_compiler") { library_for_all_configs("libdart_lib") { target_type = "source_set" extra_nonproduct_deps = [] - if (dart_support_perfetto) { - extra_nonproduct_deps += [ "//third_party/perfetto:libprotozero" ] - } extra_deps = [] if (is_fuchsia) { extra_deps += [ "$fuchsia_sdk/pkg/trace-engine" ] } + if (dart_support_perfetto) { + extra_deps += [ "//third_party/perfetto:libprotozero" ] + } include_dirs = [ ".." ] allsources = async_runtime_cc_files + concurrent_runtime_cc_files + core_runtime_cc_files + developer_runtime_cc_files + diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index eba3613b1a4..0ee2739f7ac 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -81,6 +81,8 @@ namespace dart { V(Developer_postEvent, 2) \ V(Developer_webServerControl, 3) \ V(Developer_NativeRuntime_buildId, 0) \ + V(Developer_NativeRuntime_streamTimelineTo, 5) \ + V(Developer_NativeRuntime_stopStreamingTimeline, 0) \ V(Developer_NativeRuntime_writeHeapSnapshotToFile, 1) \ V(Developer_reachability_barrier, 0) \ V(Double_getIsNegative, 1) \ diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 9514abe5407..085b8d9f2fe 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -7120,7 +7120,7 @@ DART_EXPORT bool Dart_IsPrecompiledRuntime() { } DART_EXPORT void Dart_DumpNativeStackTrace(void* context) { -#if !defined(PRODUCT) || defined(DART_PRECOMPILER) +#if defined(DART_INCLUDE_PROFILER) Profiler::DumpStackTrace(context); #endif } diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h index bf5823e4296..04b5ab71267 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -49,6 +49,14 @@ constexpr bool FLAG_support_il_printer = true; constexpr bool FLAG_support_il_printer = false; #endif // defined(INCLUDE_IL_PRINTER) +#if defined(DART_INCLUDE_PROFILER) +#define PROFILER_FLAGS(P, R, C, D) \ + P(profiler, bool, false, "Enable the profiler.") +#else +#define PROFILER_FLAGS(P, R, C, D) \ + R(profiler, false, bool, false, "Enable the profiler.") +#endif + // List of VM-global (i.e. non-isolate specific) flags. // // The value used for those flags at snapshot generation time needs to be the @@ -81,6 +89,7 @@ constexpr bool FLAG_support_il_printer = false; #define FLAG_LIST(P, R, C, D) \ VM_GLOBAL_FLAG_LIST(P, R, C, D) \ DISASSEMBLE_FLAGS(P, R, C, D) \ + PROFILER_FLAGS(P, R, C, D) \ P(abort_on_oom, bool, false, \ "Abort if memory allocation fails - use only with --old-gen-heap-size") \ P(add_readonly_data_symbols, bool, false, \ @@ -174,7 +183,6 @@ constexpr bool FLAG_support_il_printer = false; "Attempt to print a native stack trace when an API error is created.") \ D(print_variable_descriptors, bool, false, \ "Print variable descriptors in disassembly.") \ - R(profiler, false, bool, false, "Enable the profiler.") \ R(profiler_native_memory, false, bool, false, \ "Enable native memory statistic collection.") \ P(reorder_basic_blocks, bool, true, "Reorder basic blocks") \ diff --git a/runtime/vm/globals.h b/runtime/vm/globals.h index 4e392d5f7f0..370599f6e6b 100644 --- a/runtime/vm/globals.h +++ b/runtime/vm/globals.h @@ -105,13 +105,20 @@ const intptr_t kDefaultNewGenSemiMaxSize = (kWordSize <= 4) ? 8 : 16; #define NOT_IN_PRECOMPILED_RUNTIME(code) code #endif // defined(DART_PRECOMPILED_RUNTIME) -#if !defined(DART_DISABLE_TIMELINE) && \ - (defined(DART_ENABLE_TIMELINE) || !defined(PRODUCT) || \ - defined(DART_HOST_OS_FUCHSIA) || defined(DART_TARGET_OS_FUCHSIA) || \ - defined(DART_TARGET_OS_ANDROID) || defined(DART_TARGET_OS_MACOS)) +// Note: we include timeline support even in PRODUCT builds. +#if !defined(DART_DISABLE_TIMELINE) #define SUPPORT_TIMELINE 1 #endif +// All non-PRODUCT builds include profiler. We also include profiler +// whenever timeline with perfetto support is included as well as in +// precompiler builds (to enable stack dumping when precompiler crashes). +#if !defined(PRODUCT) || \ + (defined(SUPPORT_TIMELINE) && defined(SUPPORT_PERFETTO)) || \ + defined(DART_PRECOMPILER) +#define DART_INCLUDE_PROFILER 1 +#endif + // Include IL printer and disassembler functionality into non-PRODUCT builds, // in all AOT compiler builds or when forced. #if !defined(PRODUCT) || defined(DART_PRECOMPILER) || \ diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 918bad170e5..48d1707df3d 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -1188,35 +1188,6 @@ class Isolate : public IntrusiveDListEntry { #if !defined(PRODUCT) Debugger* debugger() const { return debugger_; } - // Returns the current SampleBlock used to track CPU profiling samples. - SampleBlock* current_sample_block() const { return current_sample_block_; } - void set_current_sample_block(SampleBlock* block) { - current_sample_block_ = block; - } - SampleBlock* exchange_current_sample_block(SampleBlock* block) { - return current_sample_block_.exchange(block, std::memory_order_acq_rel); - } - void ProcessFreeSampleBlocks(Thread* thread); - - // Returns the current SampleBlock used to track Dart allocation samples. - SampleBlock* current_allocation_sample_block() const { - return current_allocation_sample_block_; - } - void set_current_allocation_sample_block(SampleBlock* block) { - current_allocation_sample_block_ = block; - } - SampleBlock* exchange_current_allocation_sample_block(SampleBlock* block) { - return current_allocation_sample_block_.exchange(block, - std::memory_order_acq_rel); - } - - bool TakeHasCompletedBlocks() { - return has_completed_blocks_.exchange(0) != 0; - } - bool TrySetHasCompletedBlocks() { - return has_completed_blocks_.exchange(1) == 0; - } - void set_has_resumption_breakpoints(bool value) { has_resumption_breakpoints_ = value; } @@ -1247,6 +1218,37 @@ class Isolate : public IntrusiveDListEntry { } #endif +#if defined(DART_INCLUDE_PROFILER) + // Returns the current SampleBlock used to track CPU profiling samples. + SampleBlock* current_sample_block() const { return current_sample_block_; } + void set_current_sample_block(SampleBlock* block) { + current_sample_block_ = block; + } + SampleBlock* exchange_current_sample_block(SampleBlock* block) { + return current_sample_block_.exchange(block, std::memory_order_acq_rel); + } + void ProcessFreeSampleBlocks(Thread* thread); + + // Returns the current SampleBlock used to track Dart allocation samples. + SampleBlock* current_allocation_sample_block() const { + return current_allocation_sample_block_; + } + void set_current_allocation_sample_block(SampleBlock* block) { + current_allocation_sample_block_ = block; + } + SampleBlock* exchange_current_allocation_sample_block(SampleBlock* block) { + return current_allocation_sample_block_.exchange(block, + std::memory_order_acq_rel); + } + + bool TakeHasCompletedBlocks() { + return has_completed_blocks_.exchange(0) != 0; + } + bool TrySetHasCompletedBlocks() { + return has_completed_blocks_.exchange(1) == 0; + } +#endif + // Verify that the sender has the capability to pause or terminate the // isolate. bool VerifyPauseCapability(const Object& capability) const; @@ -1635,14 +1637,6 @@ class Isolate : public IntrusiveDListEntry { #if !defined(PRODUCT) Debugger* debugger_ = nullptr; - // SampleBlock containing CPU profiling samples. - RelaxedAtomic current_sample_block_ = nullptr; - - // SampleBlock containing Dart allocation profiling samples. - RelaxedAtomic current_allocation_sample_block_ = nullptr; - - RelaxedAtomic has_completed_blocks_ = {0}; - int64_t last_resume_timestamp_; VMTagCounters vm_tag_counters_; @@ -1679,6 +1673,16 @@ class Isolate : public IntrusiveDListEntry { #undef ISOLATE_METRIC_VARIABLE #endif // !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) + // SampleBlock containing CPU profiling samples. + RelaxedAtomic current_sample_block_ = nullptr; + + // SampleBlock containing Dart allocation profiling samples. + RelaxedAtomic current_allocation_sample_block_ = nullptr; + + RelaxedAtomic has_completed_blocks_ = {0}; +#endif + // All other fields go here. int64_t start_time_micros_; std::atomic message_notify_callback_; diff --git a/runtime/vm/os_thread.cc b/runtime/vm/os_thread.cc index 07cff87d33d..74e782f0413 100644 --- a/runtime/vm/os_thread.cc +++ b/runtime/vm/os_thread.cc @@ -151,7 +151,7 @@ uword OSThread::GetCurrentStackPointer() { #endif } -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) void OSThread::DisableThreadInterrupts() { ASSERT(OSThread::Current() == this); thread_interrupt_disabled_.fetch_add(1u); @@ -179,7 +179,7 @@ void OSThread::EnableThreadInterrupts() { bool OSThread::ThreadInterruptsEnabled() { return thread_interrupt_disabled_ == 0; } -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) static void DeleteThread(void* thread) { MSAN_UNPOISON(&thread, sizeof(thread)); diff --git a/runtime/vm/os_thread.h b/runtime/vm/os_thread.h index e00d81e7026..bbba518c90e 100644 --- a/runtime/vm/os_thread.h +++ b/runtime/vm/os_thread.h @@ -128,12 +128,12 @@ class OSThread : public BaseThread { static void SetCurrentSafestackPointer(uword ssp); #endif -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) // Used to temporarily disable or enable thread interrupts. void DisableThreadInterrupts(); void EnableThreadInterrupts(); bool ThreadInterruptsEnabled(); -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) // The currently executing thread, or nullptr if not yet initialized. static OSThread* TryCurrent() { @@ -289,12 +289,12 @@ class OSThread : public BaseThread { // All |Thread|s are registered in the thread list. OSThread* thread_list_next_ = nullptr; -#if !defined(PRODUCT) +#if defined(DART_INCLUDE_PROFILER) // Thread interrupts disabled by default. RelaxedAtomic thread_interrupt_disabled_ = {1}; bool prepared_for_interrupts_ = false; void* thread_interrupter_state_ = nullptr; -#endif // !defined(PRODUCT) +#endif // defined(DART_INCLUDE_PROFILER) Log* log_; uword stack_base_ = 0; diff --git a/runtime/vm/perfetto_utils.h b/runtime/vm/perfetto_utils.h index de33d8f4106..11e566c6cf2 100644 --- a/runtime/vm/perfetto_utils.h +++ b/runtime/vm/perfetto_utils.h @@ -5,7 +5,7 @@ #ifndef RUNTIME_VM_PERFETTO_UTILS_H_ #define RUNTIME_VM_PERFETTO_UTILS_H_ -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) #include #include @@ -15,9 +15,13 @@ #include "perfetto/protozero/scattered_heap_buffer.h" #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h" +#include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" +#include "third_party/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" +#include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h" #include "vm/hash_map.h" #include "vm/json_stream.h" #include "vm/os.h" @@ -103,24 +107,31 @@ GetProtoPreamble( return std::make_tuple(std::move(preamble), preamble_size); } -inline void AppendPacketToJSONBase64String( - JSONBase64String* jsonBase64String, - protozero::HeapBuffered* packet) { - ASSERT(jsonBase64String != nullptr); +template +inline void WritePacketBytes( + protozero::HeapBuffered* packet, + WriteBytesFunction&& write_bytes) { ASSERT(packet != nullptr); - const std::tuple, intptr_t>& response = perfetto_utils::GetProtoPreamble(packet); const uint8_t* preamble = std::get<0>(response).get(); const intptr_t preamble_length = std::get<1>(response); - jsonBase64String->AppendBytes(preamble, preamble_length); + write_bytes(preamble, preamble_length); for (const protozero::ScatteredHeapBuffer::Slice& slice : packet->GetSlices()) { - jsonBase64String->AppendBytes(slice.start(), - slice.size() - slice.unused_bytes()); + write_bytes(slice.start(), slice.size() - slice.unused_bytes()); } } +inline void AppendPacketToJSONBase64String( + JSONBase64String* jsonBase64String, + protozero::HeapBuffered* packet) { + ASSERT(jsonBase64String != nullptr); + WritePacketBytes(packet, [&](auto bytes, auto bytes_length) { + jsonBase64String->AppendBytes(bytes, bytes_length); + }); +} + // Sequence of elements which can be interned by |BytesInterner|. // // Equality and hash are defined in terms of raw byte content. @@ -154,6 +165,10 @@ struct InternedBytes { const uint64_t iid; }; +constexpr uint8_t kInternerWasUsed = 1 << 0; +constexpr uint8_t kInternerHasNewEntries = 1 << 1; +typedef uint8_t InternerStateBits; + // Interning dictionary used to construct various parts of |InternedData| // message. template @@ -179,11 +194,14 @@ class BytesInterner } uint64_t Intern(const T* data, const intptr_t length) { + state_ |= kInternerWasUsed; + InternedBytes key(data, length); if (auto interned = Base::Lookup(&key)) { return (*interned)->iid; } + state_ |= kInternerHasNewEntries; const uint64_t iid = Base::Size() + 1; Base::Insert(Copy(key, iid)); return iid; @@ -201,10 +219,10 @@ class BytesInterner first_to_flush_ = Base::next_pair_index_; } - // Returns |true| if there are entries added to this interner since the - // last call to |FlushNewlyInternedTo| - bool HasNewlyInternedEntries() const { - return first_to_flush_ < Base::next_pair_index_; + InternerStateBits TakeAndResetState() { + const auto result = state_; + state_ = 0; + return result; } private: @@ -229,6 +247,9 @@ class BytesInterner // The index of the first entry which was not flushed via // |FlushNewlyInternedTo|. uint32_t first_to_flush_ = 0; + + // Combination of |kInternerWasUsed| and |kInternerHasNewEntries|. + InternerStateBits state_ = 0; }; template @@ -242,8 +263,8 @@ class StringInterner : public ValueObject { return bytes_interner_.Intern(str, strlen(str) + 1); } - bool HasNewlyInternedEntries() const { - return bytes_interner_.HasNewlyInternedEntries(); + InternerStateBits TakeAndResetState() { + return bytes_interner_.TakeAndResetState(); } template @@ -258,10 +279,212 @@ class StringInterner : public ValueObject { BytesInterner bytes_interner_; }; +// Trait used to map 64-bit ids (e.g. isolate or isolate group id) to +// interned id of a corresponding string representation. +// +// This way we only need to generate formatted string once, instead of +// repeatedly formatting it and then interning resulting string to get an +// iid. +class IdToIidTrait { + public: + struct Pair { + uint64_t id; + uint64_t formatted_iid; + }; + using Key = uint64_t; + using Value = uint64_t; + + static Key KeyOf(const Pair& kv) { return kv.id; } + static Value ValueOf(const Pair& kv) { return kv.formatted_iid; } + static uword Hash(Key key) { + return Utils::WordHash(static_cast(key)); + } + static bool IsKeyEqual(const Pair& kv, Key key) { return kv.id == key; } +}; + +using IdToIidMap = MallocDirectChainedHashMap; + +class InternedDataBuilder : public ValueObject { + private: + using SequenceFlags = perfetto::protos::pbzero::TracePacket_SequenceFlags; + + public: + // InternedData contains multiple independent interning dictionaries which + // are used for different attributes. +#define PERFETTO_INTERNED_STRINGS_FIELDS_LIST(V) \ + V(event_categories, name) \ + V(event_names, name) \ + V(debug_annotation_names, name) \ + V(debug_annotation_string_values, str) \ + V(function_names, str) \ + V(mapping_paths, str) + +#define PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(V) \ + V(callstacks, uint64_t) \ + V(mappings, uint64_t) \ + V(frames, uint64_t) + + // Direct access for known strings. +#define PERFETTO_COMMON_INTERNED_STRINGS_LIST(V) \ + V(debug_annotation_names, isolateId) \ + V(debug_annotation_names, isolateGroupId) + + InternedDataBuilder() = default; + + // Emit all strings added since the last invocation of |AttachInternedDataTo| + // into |interned_data| of the given |TracePacket|. + // + // Mark the packet as depending on incremental state. + void AttachInternedDataTo(perfetto::protos::pbzero::TracePacket* packet) { + const auto interners_state = TakeAndResetStateOfAllInterners(); + if ((interners_state & kInternerWasUsed) != 0) { + // At least one interner was used. + packet->set_sequence_flags(sequence_flags_); + } + + if ((interners_state & kInternerHasNewEntries) == 0) { + // None of interners have new entries. + return; + } + + // The first packet will have SEQ_INCREMENTAL_STATE_CLEARED + // the rest will just have SEQ_NEEDS_INCREMENTAL_STATE. + sequence_flags_ &= ~SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED; + + auto interned_data = packet->set_interned_data(); + + // Flush individual interning dictionaries. +#define FLUSH_FIELD(name, proto_field) \ + name##_.FlushNewlyInternedTo([interned_data](auto& iid, auto& str) { \ + auto entry = interned_data->add_##name(); \ + entry->set_iid(iid); \ + entry->set_##proto_field(str); \ + }); + + PERFETTO_INTERNED_STRINGS_FIELDS_LIST(FLUSH_FIELD) +#undef FLUSH_FIELD + + callstacks_.FlushNewlyInternedTo([interned_data](const auto& interned) { + auto callstack = interned_data->add_callstacks(); + callstack->set_iid(interned.iid); + for (intptr_t i = 0; i < interned.length; i++) { + callstack->add_frame_ids(interned.data[i]); + } + }); + + mappings_.FlushNewlyInternedTo([interned_data](const auto& interned) { + auto mapping = interned_data->add_mappings(); + mapping->set_iid(interned.iid); + mapping->add_path_string_ids(interned.data[0]); + }); + + frames_.FlushNewlyInternedTo([interned_data](const auto& interned) { + auto frame = interned_data->add_frames(); + frame->set_iid(interned.iid); + frame->set_function_name_id(interned.data[0]); + if (interned.data[1] != 0) { + frame->set_mapping_id(interned.data[1]); + } + }); + } + +#define DEFINE_GETTER(name, ignored) \ + perfetto_utils::StringInterner& name() { return name##_; } + PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_GETTER) +#undef DEFINE_GETTER + +#define DEFINE_GETTER(name, element_type) \ + perfetto_utils::BytesInterner& name() { \ + return name##_; \ + } + PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(DEFINE_GETTER) +#undef DEFINE_GETTER + +#define DEFINE_GETTER_FOR_COMMON_STRING(category, str) \ + uint64_t iid_##str() { \ + if (iid_##str##_ == 0) { \ + iid_##str##_ = category().Intern(#str); \ + } \ + return iid_##str##_; \ + } + + PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_GETTER_FOR_COMMON_STRING) + +#undef DEFINE_GETTER_FOR_COMMON_STRING + + uint64_t InternFormattedIsolateId(uint64_t isolate_id) { + return InternFormattedIdForDebugAnnotation( + isolate_id_to_iid_of_formatted_string_, + ISOLATE_SERVICE_ID_FORMAT_STRING, isolate_id); + } + + uint64_t InternFormattedIsolateGroupId(uint64_t isolate_group_id) { + return InternFormattedIdForDebugAnnotation( + isolate_group_id_to_iid_of_formatted_string_, + ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, isolate_group_id); + } + + private: + template + uint64_t InternFormattedIdForDebugAnnotation(IdToIidMap& cache, + const char (&format)[kFormatLen], + uint64_t id) { + if (auto iid = cache.Lookup(id)) { + return iid->formatted_iid; + } + + // 20 characters is enough to format any uint64_t (or int64_t) value. + char formatted[kFormatLen + 20]; + Utils::SNPrint(formatted, ARRAY_SIZE(formatted), format, id); + + auto formatted_iid = debug_annotation_string_values().Intern(formatted); + cache.Insert({id, formatted_iid}); + return formatted_iid; + } + + // Returns the union of state of all interners. + InternerStateBits TakeAndResetStateOfAllInterners() { + InternerStateBits result = 0; + +#define TAKE_AND_RESET(name, ignored) result |= name##_.TakeAndResetState(); + + PERFETTO_INTERNED_STRINGS_FIELDS_LIST(TAKE_AND_RESET) + PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(TAKE_AND_RESET) +#undef TAKE_AND_RESET + + return result; + } + + uint32_t sequence_flags_ = SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED | + SequenceFlags::SEQ_NEEDS_INCREMENTAL_STATE; + + // These are interned in debug_annotation_string_values space. + IdToIidMap isolate_id_to_iid_of_formatted_string_; + IdToIidMap isolate_group_id_to_iid_of_formatted_string_; + +#define DEFINE_FIELD_FOR_COMMON_STRING(category, str) uint64_t iid_##str##_ = 0; + + PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_FIELD_FOR_COMMON_STRING) + +#undef DEFINE_FIELD_FOR_COMMON_STRING + +#define DEFINE_FIELD(name, proto_field) \ + perfetto_utils::StringInterner name##_; + PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_FIELD) +#undef DEFINE_FIELD + +#define DEFINE_FIELD(name, element_type) \ + perfetto_utils::BytesInterner name##_; + PERFETTO_INTERNED_RAW_BYTES_FIELDS_LIST(DEFINE_FIELD) +#undef DEFINE_FIELD + + DISALLOW_COPY_AND_ASSIGN(InternedDataBuilder); +}; + } // namespace perfetto_utils } // namespace dart -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) #endif // RUNTIME_VM_PERFETTO_UTILS_H_ diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc index e73077f5224..ed7f786f71f 100644 --- a/runtime/vm/profiler.cc +++ b/runtime/vm/profiler.cc @@ -74,10 +74,7 @@ DEFINE_FLAG( "the oldest ones. This flag itself does not enable the profiler; the " "profiler must be enabled separately, e.g. with --profiler."); -// Include native stack dumping helpers into AOT compiler even in PRODUCT -// mode. This allows to report more informative errors when gen_snapshot -// crashes. -#if !defined(PRODUCT) || defined(DART_PRECOMPILER) +#if defined(DART_INCLUDE_PROFILER) ProfilerCounters Profiler::counters_ = {}; static void DumpStackFrame(uword pc, uword fp, const char* name, uword offset) { @@ -374,7 +371,6 @@ static bool ValidateThreadStackBounds(uintptr_t fp, return true; } -#if !defined(PRODUCT) // Get |thread|'s stack boundary and verify that |sp| and |fp| are within // it. Return |false| if anything looks suspicious. static bool GetAndValidateThreadStackBounds(OSThread* os_thread, @@ -418,7 +414,6 @@ static bool GetAndValidateThreadStackBounds(OSThread* os_thread, return ValidateThreadStackBounds(fp, sp, *stack_lower, *stack_upper); } -#endif // !defined(PRODUCT) static bool GetAndValidateCurrentThreadStackBounds(uintptr_t fp, uintptr_t sp, @@ -609,15 +604,14 @@ void Profiler::DumpStackTrace(uword sp, uword fp, uword pc, bool for_crash) { DumpCompilerState(thread); } -#endif // !defined(PRODUCT) || defined(DART_PRECOMPILER) - -#ifndef PRODUCT RelaxedAtomic Profiler::initialized_ = false; SampleBlockBuffer* Profiler::sample_block_buffer_ = nullptr; - +Profiler::ProfileProcessorCallback Profiler::process_profile_callback_ = + nullptr; bool SampleBlockProcessor::initialized_ = false; bool SampleBlockProcessor::shutdown_ = false; +bool SampleBlockProcessor::drain_ = false; bool SampleBlockProcessor::thread_running_ = false; ThreadJoinId SampleBlockProcessor::processor_thread_id_ = OSThread::kInvalidThreadJoinId; @@ -661,9 +655,13 @@ void Profiler::Cleanup() { } ASSERT(initialized_); ThreadInterrupter::Cleanup(); - SampleBlockProcessor::Cleanup(); + + const bool should_drain = process_profile_callback_ != nullptr; + SampleBlockProcessor::Cleanup(should_drain); + SampleBlockCleanupVisitor visitor; Isolate::VisitIsolates(&visitor); + initialized_ = false; } @@ -793,16 +791,23 @@ void SampleBlockBuffer::FreeCompletedBlocks() { static void FlushSampleBlocks(Isolate* isolate) { ASSERT(isolate != nullptr); + bool flushed = false; SampleBlock* block = isolate->exchange_current_sample_block(nullptr); if (block != nullptr) { block->MarkCompleted(); + flushed = true; } block = isolate->exchange_current_allocation_sample_block(nullptr); if (block != nullptr) { // Allocation samples are collected synchronously. block->MarkCompleted(); + flushed = true; + } + + if (flushed) { + isolate->TrySetHasCompletedBlocks(); } } @@ -1400,16 +1405,18 @@ void Profiler::SampleThreadSingleFrame(Thread* thread, ASSERT(thread != nullptr); OSThread* os_thread = thread->os_thread(); ASSERT(os_thread != nullptr); - Isolate* isolate = thread->IGNORE_RACE(isolate)(); - ASSERT(Profiler::sample_block_buffer() != nullptr); +#if !defined(PRODUCT) + Isolate* isolate = thread->IGNORE_RACE(isolate)(); + // Increment counter for vm tag. VMTagCounters* counters = isolate->vm_tag_counters(); ASSERT(counters != nullptr); if (thread->IsDartMutatorThread()) { counters->Increment(sample->vm_tag()); } +#endif // Write the single pc value. sample->SetAt(0, pc); @@ -1531,12 +1538,14 @@ void Profiler::SampleThread(Thread* thread, // At this point we have a valid stack boundary for this isolate and // know that our initial stack and frame pointers are within the boundary. +#if !defined(PRODUCT) // Increment counter for vm tag. VMTagCounters* counters = isolate->vm_tag_counters(); ASSERT(counters != nullptr); if (thread->IsDartMutatorThread()) { counters->Increment(sample->vm_tag()); } +#endif ProfilerNativeStackWalker native_stack_walker( &counters_, (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT, @@ -1885,6 +1894,7 @@ void SampleBlockProcessor::Init() { ASSERT(monitor_ != nullptr); initialized_ = true; shutdown_ = false; + drain_ = false; } void SampleBlockProcessor::Startup() { @@ -1898,13 +1908,14 @@ void SampleBlockProcessor::Startup() { ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId); } -void SampleBlockProcessor::Cleanup() { +void SampleBlockProcessor::Cleanup(bool drain /* = false */) { { MonitorLocker shutdown_ml(monitor_); if (shutdown_) { // Already shutdown. return; } + drain_ = drain; shutdown_ = true; // Notify. shutdown_ml.Notify(); @@ -1913,13 +1924,39 @@ void SampleBlockProcessor::Cleanup() { // Join the thread. ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId); - OSThread::Join(processor_thread_id_); + auto thread = Thread::Current(); + if (thread != nullptr) { + TransitionVMToBlocked transition(thread); + OSThread::Join(processor_thread_id_); + } else { + OSThread::Join(processor_thread_id_); + } processor_thread_id_ = OSThread::kInvalidThreadJoinId; initialized_ = false; ASSERT(!thread_running_); } -void Profiler::ProcessCompletedBlocks(Isolate* isolate) {} +void Profiler::ProcessCompletedBlocks(Isolate* isolate) { + const auto process_profile_callback = process_profile_callback_; + if (process_profile_callback == nullptr) { + return; + } + + auto thread = Thread::Current(); + if (Isolate::IsSystemIsolate(isolate)) return; + + TIMELINE_DURATION(thread, Isolate, "Profiler::ProcessCompletedBlocks") + DisableThreadInterruptsScope dtis(thread); + StackZone zone(thread); + HandleScope handle_scope(thread); + + NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, + -1, -1); + Profile profile; + profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); + + process_profile_callback(profile); +} void Profiler::IsolateShutdown(Thread* thread) { FlushSampleBlocks(thread->isolate()); @@ -1943,7 +1980,7 @@ void SampleBlockProcessor::ThreadMain(uword parameters) { const int64_t wakeup_interval = 1000 * 100; while (true) { wait_ml.WaitMicros(wakeup_interval); - if (shutdown_) { + if (shutdown_ && !drain_) { break; } @@ -1954,17 +1991,24 @@ void SampleBlockProcessor::ThreadMain(uword parameters) { Thread::EnterIsolateGroupAsHelper(group, Thread::kSampleBlockTask, kBypassSafepoint); group->ForEachIsolate([&](Isolate* isolate) { + if (drain_) { + FlushSampleBlocks(isolate); + } if (isolate->TakeHasCompletedBlocks()) { Profiler::ProcessCompletedBlocks(isolate); } }); Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); }); + + if (shutdown_) { + break; + } } // Signal to main thread we are exiting. thread_running_ = false; } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h index 7ba2ddf6ca9..820abee9a4b 100644 --- a/runtime/vm/profiler.h +++ b/runtime/vm/profiler.h @@ -25,6 +25,7 @@ namespace dart { // Forward declarations. class ProcessedSample; class ProcessedSampleBuffer; +class Profile; class Sample; class SampleBlock; @@ -65,6 +66,12 @@ class Profiler : public AllStatic { // service protocol. static void UpdateRunningState(); + typedef void (*ProfileProcessorCallback)(Profile&); + + static void SetProfileProcessorCallback(ProfileProcessorCallback callback) { + process_profile_callback_ = callback; + } + static SampleBlockBuffer* sample_block_buffer() { return sample_block_buffer_; } @@ -121,6 +128,8 @@ class Profiler : public AllStatic { static ProfilerCounters counters_; + static ProfileProcessorCallback process_profile_callback_; + friend class Thread; }; @@ -923,12 +932,13 @@ class SampleBlockProcessor : public AllStatic { static void Init(); static void Startup(); - static void Cleanup(); + static void Cleanup(bool drain = false); private: static constexpr intptr_t kMaxThreads = 4096; static bool initialized_; static bool shutdown_; + static bool drain_; static bool thread_running_; static ThreadJoinId processor_thread_id_; static Monitor* monitor_; @@ -936,6 +946,20 @@ class SampleBlockProcessor : public AllStatic { static void ThreadMain(uword parameters); }; +class NoAllocationSampleFilter : public SampleFilter { + public: + NoAllocationSampleFilter(Dart_Port port, + intptr_t thread_task_mask, + int64_t time_origin_micros, + int64_t time_extent_micros) + : SampleFilter(port, + thread_task_mask, + time_origin_micros, + time_extent_micros) {} + + bool FilterSample(Sample* sample) { return !sample->is_allocation_sample(); } +}; + } // namespace dart #endif // RUNTIME_VM_PROFILER_H_ diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc index 7bc11546156..8fc49620f04 100644 --- a/runtime/vm/profiler_service.cc +++ b/runtime/vm/profiler_service.cc @@ -10,7 +10,9 @@ #include "vm/growable_array.h" #include "vm/hash_map.h" #include "vm/heap/safepoint.h" +#if !defined(PRODUCT) #include "vm/json_stream.h" +#endif #include "vm/log.h" #include "vm/native_symbol.h" #include "vm/object.h" @@ -22,7 +24,7 @@ #include "vm/service_event.h" #include "vm/timeline.h" -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) #include "perfetto/ext/tracing/core/trace_packet.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" @@ -31,7 +33,7 @@ #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" #include "vm/perfetto_utils.h" -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) namespace dart { @@ -39,7 +41,7 @@ DECLARE_FLAG(int, max_profile_depth); DECLARE_FLAG(int, profile_period); DECLARE_FLAG(bool, profile_vm); -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) ProfileFunctionSourcePosition::ProfileFunctionSourcePosition( TokenPosition token_pos) @@ -177,6 +179,7 @@ const char* ProfileFunction::KindToCString(Kind kind) { } } +#if !defined(PRODUCT) void ProfileFunction::PrintToJSONObject(JSONObject* func) { func->AddProperty("type", "NativeFunction"); func->AddProperty("name", name()); @@ -217,6 +220,7 @@ void ProfileFunction::PrintToJSONArray(JSONArray* functions, } } } +#endif // !defined(PRODUCT) void ProfileFunction::AddProfileCode(intptr_t code_table_index) { for (intptr_t i = 0; i < profile_codes_.length(); i++) { @@ -370,6 +374,7 @@ void ProfileCode::TickAddress(uword pc, bool exclusive) { } } +#if !defined(PRODUCT) void ProfileCode::PrintNativeCode(JSONObject* profile_code_obj) { ASSERT(kind() == kNativeCode); JSONObject obj(profile_code_obj, "code"); @@ -483,6 +488,7 @@ void ProfileCode::PrintToJSONArray(JSONArray* codes) { } } } +#endif // !defined(PRODUCT) class ProfileFunctionTable : public ZoneAllocated { public: @@ -1577,6 +1583,7 @@ ProfileCode* Profile::GetCodeFromPC(uword pc, int64_t timestamp) { return code; } +#if !defined(PRODUCT) void Profile::PrintHeaderJSON(JSONObject* obj) { intptr_t pid = OS::ProcessId(); @@ -1671,58 +1678,6 @@ void Profile::ProcessSampleFrameJSON(JSONArray* stack, } } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) -namespace { -void ProcessSampleFramePerfetto(Profile* profile, - GrowableArray& callstack, - ProfileCodeInlinedFunctionsCache* cache, - ProcessedSample* sample, - intptr_t frame_index) { - const uword pc = sample->At(frame_index); - ProfileCode* profile_code = profile->GetCodeFromPC(pc, sample->timestamp()); - ASSERT(profile_code != nullptr); - ProfileFunction* function = profile_code->function(); - ASSERT(function != nullptr); - - // Don't show stubs in stack traces. - if (!function->is_visible() || - (function->kind() == ProfileFunction::kStubFunction)) { - return; - } - - GrowableArray* inlined_functions = nullptr; - GrowableArray* inlined_token_positions = nullptr; - TokenPosition token_position = TokenPosition::kNoSource; - Code& code = Code::ZoneHandle(); - - if (profile_code->code().IsCode()) { - code ^= profile_code->code().ptr(); - cache->Get(pc, code, sample, frame_index, &inlined_functions, - &inlined_token_positions, &token_position); - } - - if (code.IsNull() || (inlined_functions == nullptr) || - (inlined_functions->length() <= 1)) { - // This is the ID of a |Frame| that was added to the interned data table in - // |ProfilerService::PrintProfilePerfetto|. See the comments in that method - // for more details. - callstack.Add(function->table_index() + 1); - return; - } - - for (intptr_t i = 0; i < inlined_functions->length(); ++i) { - const Function* inlined_function = (*inlined_functions)[i]; - ASSERT(inlined_function != NULL); - ASSERT(!inlined_function->IsNull()); - ProfileFunction* profile_function = - profile->FindFunction(*inlined_function); - ASSERT(profile_function != NULL); - callstack.Add(profile_function->table_index() + 1); - } -} -} // namespace -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) - void Profile::ProcessInlinedFunctionFrameJSON( JSONArray* stack, const Function* inlined_function) { @@ -1802,79 +1757,6 @@ void Profile::PrintSamplesJSON(JSONObject* obj, bool code_samples) { } } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) -void Profile::PrintSamplesPerfetto( - JSONBase64String* jsonBase64String, - protozero::HeapBuffered* - packet_ptr) { - ASSERT(jsonBase64String != nullptr); - ASSERT(packet_ptr != nullptr); - auto& packet = *packet_ptr; - - perfetto_utils::BytesInterner callstack_interner(zone_); - GrowableArray callstack(128); - - // Note that |cache| is zone-allocated, so it does not need to be deallocated - // manually. - auto* cache = new ProfileCodeInlinedFunctionsCache(); - for (intptr_t sample_index = 0; sample_index < samples_->length(); - ++sample_index) { - ProcessedSample* sample = samples_->At(sample_index); - - // Walk the sampled PCs and intern the stack. - callstack.Clear(); - for (intptr_t frame_index = sample->length() - 1; frame_index >= 0; - --frame_index) { - ASSERT(sample->At(frame_index) != 0); - ProcessSampleFramePerfetto(this, callstack, cache, sample, frame_index); - } - - // Empty sample (everything is invisible). - if (callstack.is_empty()) { - continue; - } - - const auto callstack_iid = - callstack_interner.Intern(&callstack[0], callstack.length()); - - perfetto_utils::SetTrustedPacketSequenceId(packet.get()); - // We set this flag to indicate that this packet reads from the interned - // data table. - packet->set_sequence_flags( - perfetto::protos::pbzero::TracePacket_SequenceFlags:: - SEQ_NEEDS_INCREMENTAL_STATE); - perfetto_utils::SetTimestampAndMonotonicClockId(packet.get(), - sample->timestamp()); - - // Populate |packet| with a |PerfSample| that is linked to the |Callstack| - // that we populated above. - auto& perf_sample = *packet->set_perf_sample(); - perf_sample.set_pid(OS::ProcessId()); - perf_sample.set_tid(OSThread::ThreadIdToIntPtr(sample->tid())); - perf_sample.set_callstack_iid(callstack_iid); - - if (callstack_interner.HasNewlyInternedEntries()) { - auto& interned_data = *packet->set_interned_data(); - callstack_interner.FlushNewlyInternedTo( - [&interned_data](const auto& interned) { - auto& callstack = *interned_data.add_callstacks(); - callstack.set_iid(interned.iid); - for (intptr_t i = 0; i < interned.length; i++) { - callstack.add_frame_ids(interned.data[i]); - } - }); - } - - perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet); - packet.Reset(); - } -} -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) - -ProfileFunction* Profile::FindFunction(const Function& function) { - return (functions_ != nullptr) ? functions_->Lookup(function) : nullptr; -} - void Profile::PrintProfileJSON(JSONStream* stream, bool include_code_samples) { JSONObject obj(stream); PrintProfileJSON(&obj, include_code_samples); @@ -1925,67 +1807,151 @@ void Profile::PrintProfileJSON(JSONObject* obj, PrintSamplesJSON(obj, include_code_samples); thread->CheckForSafepoint(); } +#endif // !defined(PRODUCT) -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) -void Profile::PrintProfilePerfetto(JSONStream* js) { - ScopeTimer sw("Profile::PrintProfilePerfetto", FLAG_trace_profiler); +#if defined(SUPPORT_PERFETTO) +namespace { +void ProcessSampleFramePerfetto(Profile* profile, + GrowableArray& callstack, + ProfileCodeInlinedFunctionsCache* cache, + ProcessedSample* sample, + intptr_t frame_index, + const GrowableArray& function_iids) { + const uword pc = sample->At(frame_index); + ProfileCode* profile_code = profile->GetCodeFromPC(pc, sample->timestamp()); + ASSERT(profile_code != nullptr); + ProfileFunction* function = profile_code->function(); + ASSERT(function != nullptr); + + // Don't show stubs in stack traces. + if (!function->is_visible() || + (function->kind() == ProfileFunction::kStubFunction)) { + return; + } + + GrowableArray* inlined_functions = nullptr; + GrowableArray* inlined_token_positions = nullptr; + TokenPosition token_position = TokenPosition::kNoSource; + Code& code = Code::ZoneHandle(); + + if (profile_code->code().IsCode()) { + code ^= profile_code->code().ptr(); + cache->Get(pc, code, sample, frame_index, &inlined_functions, + &inlined_token_positions, &token_position); + } + + if (code.IsNull() || (inlined_functions == nullptr) || + (inlined_functions->length() <= 1)) { + // This is the ID of a |Frame| that was added to the interned data table in + // |ProfilerService::PrintProfilePerfetto|. See the comments in that method + // for more details. + callstack.Add(function_iids[function->table_index()]); + return; + } + + for (intptr_t i = 0; i < inlined_functions->length(); ++i) { + const Function* inlined_function = (*inlined_functions)[i]; + ASSERT(inlined_function != NULL); + ASSERT(!inlined_function->IsNull()); + ProfileFunction* profile_function = + profile->FindFunction(*inlined_function); + ASSERT(profile_function != NULL); + callstack.Add(function_iids[profile_function->table_index()]); + } +} +} // namespace + +template +void Profile::PrintSamplesPerfetto( + protozero::HeapBuffered* packet_ptr, + WriteBytesFunction&& write_bytes, + perfetto_utils::InternedDataBuilder& interned_data_builder, + const GrowableArray& function_iids) { + ASSERT(packet_ptr != nullptr); + auto& packet = *packet_ptr; + + GrowableArray callstack(128); + + // Note that |cache| is zone-allocated, so it does not need to be deallocated + // manually. + auto* cache = new ProfileCodeInlinedFunctionsCache(); + for (intptr_t sample_index = 0; sample_index < samples_->length(); + ++sample_index) { + ProcessedSample* sample = samples_->At(sample_index); + + // Walk the sampled PCs and intern the stack. + callstack.Clear(); + for (intptr_t frame_index = sample->length() - 1; frame_index >= 0; + --frame_index) { + ASSERT(sample->At(frame_index) != 0); + ProcessSampleFramePerfetto(this, callstack, cache, sample, frame_index, + function_iids); + } + + // Empty sample (everything is invisible). + if (callstack.is_empty()) { + continue; + } + + const auto callstack_iid = interned_data_builder.callstacks().Intern( + &callstack[0], callstack.length()); + + perfetto_utils::SetTrustedPacketSequenceId(packet.get()); + perfetto_utils::SetTimestampAndMonotonicClockId(packet.get(), + sample->timestamp()); + + // Populate |packet| with a |PerfSample| that is linked to the |Callstack| + // that we populated above. + auto& perf_sample = *packet->set_perf_sample(); + perf_sample.set_pid(OS::ProcessId()); + perf_sample.set_tid(OSThread::ThreadIdToIntPtr(sample->tid())); + perf_sample.set_callstack_iid(callstack_iid); + + interned_data_builder.AttachInternedDataTo(packet.get()); + + perfetto_utils::WritePacketBytes(&packet, write_bytes); + packet.Reset(); + } +} +#endif // defined(SUPPORT_PERFETTO) + +ProfileFunction* Profile::FindFunction(const Function& function) { + return (functions_ != nullptr) ? functions_->Lookup(function) : nullptr; +} + +#if defined(SUPPORT_PERFETTO) +template +void Profile::PrintProfilePerfettoImpl( + WriteBytesFunction&& write_bytes, + perfetto_utils::InternedDataBuilder& interned_data_builder) { Thread* thread = Thread::Current(); - JSONObject jsobj_topLevel(js); - jsobj_topLevel.AddProperty("type", "PerfettoCpuSamples"); - PrintHeaderJSON(&jsobj_topLevel); - - js->AppendSerializedObject("\"samples\":"); - JSONBase64String jsonBase64String(js); - // We allocate one heap-buffered packet and continuously follow a cycle of // resetting the buffer and writing its contents. protozero::HeapBuffered packet; perfetto_utils::PopulateClockSnapshotPacket(packet.get()); - perfetto_utils::AppendPacketToJSONBase64String(&jsonBase64String, &packet); + perfetto_utils::WritePacketBytes(&packet, write_bytes); packet.Reset(); perfetto_utils::SetTrustedPacketSequenceId(packet.get()); - // We use |PerfSample|s to serialize our CPU sample information. Each - // |PerfSample| must be linked to a |Callstack| in the interned data table. - // When serializing a new profile, we set |SEQ_INCREMENTAL_STATE_CLEARED| on - // the first packet to clear the interned data table and avoid conflicts with - // any profiles that are combined with this one. - // - // See perfetto/trace/interned_data/interned_data.proto in - // third_party/perfetto/protos a detailed description of how the interned - // data table works. - packet->set_sequence_flags( - perfetto::protos::pbzero::TracePacket_SequenceFlags:: - SEQ_INCREMENTAL_STATE_CLEARED); - - perfetto::protos::pbzero::InternedData& interned_data = - *packet->set_interned_data(); - - // The Perfetto trace viewer will not be able to parse our trace if the - // mapping with iid 0 is not declared. - perfetto::protos::pbzero::Mapping& mapping = *interned_data.add_mappings(); - mapping.set_iid(0); + GrowableArray function_iids(functions_->length()); for (intptr_t i = 0; i < functions_->length(); ++i) { ProfileFunction* function = functions_->At(i); ASSERT(function != NULL); - const intptr_t common_iid = function->table_index() + 1; - perfetto::protos::pbzero::InternedString& function_name = - *interned_data.add_function_names(); - function_name.set_iid(common_iid); - function_name.set_str(function->Name()); + const uint64_t function_name_iid = + interned_data_builder.function_names().Intern(function->Name()); + uint64_t mapping_iid = 0; const char* resolved_script_url = function->ResolvedScriptUrl(); if (resolved_script_url != nullptr) { - perfetto::protos::pbzero::InternedString& mapping_path = - *interned_data.add_mapping_paths(); - mapping_path.set_iid(common_iid); const Script& script_handle = Script::Handle(function->function()->script()); TokenPosition token_pos = function->function()->token_pos(); + + uint64_t mapping_path_iid; if (!script_handle.IsNull() && token_pos.IsReal()) { intptr_t line = -1; intptr_t column = -1; @@ -1998,38 +1964,72 @@ void Profile::PrintProfilePerfetto(JSONStream* js) { std::make_unique(path_with_location_buffer_size); Utils::SNPrint(path_with_location.get(), path_with_location_buffer_size, "%s:%" Pd ":%" Pd, resolved_script_url, line, column); - mapping_path.set_str(path_with_location.get()); + + mapping_path_iid = interned_data_builder.mapping_paths().Intern( + path_with_location.get()); } else { - mapping_path.set_str(resolved_script_url); + mapping_path_iid = + interned_data_builder.mapping_paths().Intern(resolved_script_url); } - // TODO(derekx): Check if using profiled_frame_symbols instead of mapping - // provides any benefit. - perfetto::protos::pbzero::Mapping& mapping = - *interned_data.add_mappings(); - mapping.set_iid(common_iid); - mapping.add_path_string_ids(common_iid); + mapping_iid = interned_data_builder.mappings().Intern(&mapping_path_iid, + /*length=*/1); } // Add a |Frame| to the interned data table that is linked to |function|'s // name and source location (through the interned data table). A Perfetto // |Callstack| consists of a stack of |Frame|s, so the |Callstack|s // populated by |PrintSamplesPerfetto| will refer to these |Frame|s. - perfetto::protos::pbzero::Frame& frame = *interned_data.add_frames(); - frame.set_iid(common_iid); - frame.set_function_name_id(common_iid); - frame.set_mapping_id(resolved_script_url == nullptr ? 0 : common_iid); + uint64_t frame_info[2] = {function_name_iid, mapping_iid}; + function_iids.Add(interned_data_builder.frames().Intern( + frame_info, ARRAY_SIZE(frame_info))); thread->CheckForSafepoint(); } - perfetto_utils::AppendPacketToJSONBase64String(&jsonBase64String, &packet); + interned_data_builder.AttachInternedDataTo(packet.get()); + perfetto_utils::WritePacketBytes(&packet, write_bytes); packet.Reset(); - PrintSamplesPerfetto(&jsonBase64String, &packet); + PrintSamplesPerfetto(&packet, write_bytes, interned_data_builder, + function_iids); thread->CheckForSafepoint(); } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if !defined(PRODUCT) +void Profile::PrintProfilePerfetto(JSONStream* js) { + ScopeTimer sw("Profile::PrintProfilePerfetto", FLAG_trace_profiler); + + JSONObject jsobj_topLevel(js); + jsobj_topLevel.AddProperty("type", "PerfettoCpuSamples"); + PrintHeaderJSON(&jsobj_topLevel); + + js->AppendSerializedObject("\"samples\":"); + JSONBase64String jsonBase64String(js); + + perfetto_utils::InternedDataBuilder interned_data_builder; + + PrintProfilePerfettoImpl( + [&](auto bytes, auto bytes_length) { + jsonBase64String.AppendBytes(bytes, bytes_length); + }, + interned_data_builder); +} +#endif + +void Profile::PrintProfilePerfetto( + perfetto_utils::InternedDataBuilder& interned_data_builder, + void* file, + Dart_FileWriteCallback write_bytes) { + ScopeTimer sw("Profile::PrintProfilePerfetto", FLAG_trace_profiler); + PrintProfilePerfettoImpl( + [&](auto bytes, auto bytes_length) { + write_bytes(bytes, bytes_length, file); + }, + interned_data_builder); +} +#endif // defined(SUPPORT_PERFETTO) + +#if !defined(PRODUCT) void ProfilerService::PrintCommonImpl(PrintFormat format, Thread* thread, JSONStream* js, @@ -2046,7 +2046,7 @@ void ProfilerService::PrintCommonImpl(PrintFormat format, if (format == PrintFormat::JSON) { profile.PrintProfileJSON(js, include_code_samples); } else if (format == PrintFormat::Perfetto) { -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) // This branch will never be reached when SUPPORT_PERFETTO is not defined or // when PRODUCT is defined, because |PrintPerfetto| is not defined when // SUPPORT_PERFETTO is not defined or when PRODUCT is defined. @@ -2054,24 +2054,10 @@ void ProfilerService::PrintCommonImpl(PrintFormat format, #else UNREACHABLE(); -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) } } -class NoAllocationSampleFilter : public SampleFilter { - public: - NoAllocationSampleFilter(Dart_Port port, - intptr_t thread_task_mask, - int64_t time_origin_micros, - int64_t time_extent_micros) - : SampleFilter(port, - thread_task_mask, - time_origin_micros, - time_extent_micros) {} - - bool FilterSample(Sample* sample) { return !sample->is_allocation_sample(); } -}; - void ProfilerService::PrintCommon(PrintFormat format, JSONStream* js, int64_t time_origin_micros, @@ -2094,14 +2080,14 @@ void ProfilerService::PrintJSON(JSONStream* js, include_code_samples); } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) void ProfilerService::PrintPerfetto(JSONStream* js, int64_t time_origin_micros, int64_t time_extent_micros) { PrintCommon(PrintFormat::Perfetto, js, time_origin_micros, time_extent_micros); } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) class AllocationSampleFilter : public SampleFilter { public: @@ -2180,7 +2166,8 @@ void ProfilerService::ClearSamples() { ClearProfileVisitor clear_profile(isolate); sample_block_buffer->VisitSamples(&clear_profile); } +#endif // !defined(PRODUCT) -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h index d0da2c7cfea..e22e0dc6083 100644 --- a/runtime/vm/profiler_service.h +++ b/runtime/vm/profiler_service.h @@ -16,7 +16,7 @@ #include "vm/thread_interrupter.h" #include "vm/token_position.h" -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) #include "perfetto/protozero/scattered_heap_buffer.h" #include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" @@ -40,6 +40,10 @@ class ProcessedSample; class ProcessedSampleBuffer; class Profile; +namespace perfetto_utils { +class InternedDataBuilder; +} + class ProfileFunctionSourcePosition { public: explicit ProfileFunctionSourcePosition(TokenPosition token_pos); @@ -395,9 +399,14 @@ class Profile : public ValueObject { void PrintProfileJSON(JSONObject* obj, bool include_code_samples, bool is_event = false); -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + +#if defined(SUPPORT_PERFETTO) void PrintProfilePerfetto(JSONStream* js); -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + void PrintProfilePerfetto( + perfetto_utils::InternedDataBuilder& interned_data_builder, + void* file, + Dart_FileWriteCallback write_bytes); +#endif // defined(SUPPORT_PERFETTO) ProfileFunction* FindFunction(const Function& function); @@ -405,6 +414,13 @@ class Profile : public ValueObject { Isolate* isolate() const { return isolate_; } private: +#if defined(SUPPORT_PERFETTO) + template + void PrintProfilePerfettoImpl( + WriteBytesFunction&& write_bytes, + perfetto_utils::InternedDataBuilder& interned_data_builder); +#endif + void PrintHeaderJSON(JSONObject* obj); void ProcessSampleFrameJSON(JSONArray* stack, ProfileCodeInlinedFunctionsCache* cache, @@ -417,16 +433,22 @@ class Profile : public ValueObject { ProcessedSample* sample, intptr_t frame_index); void PrintSamplesJSON(JSONObject* obj, bool code_samples); -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) /* - * Appends Perfetto packets describing the CPU samples in this profile to - * |jsonBase64String|. The |packet| parameter allows us to reuse an existing + * Writes Perfetto packets describing the CPU samples in this profile using + * the given |write_bytes| function. + * + * The |packet| parameter allows us to reuse an existing * heap-buffered packet to avoid allocating a new one. */ + template void PrintSamplesPerfetto( - JSONBase64String* jsonBase64String, - protozero::HeapBuffered* packet); -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + protozero::HeapBuffered* + packet_ptr, + WriteBytesFunction&& write_bytes, + perfetto_utils::InternedDataBuilder& interned_data_builder, + const GrowableArray& function_iids); +#endif // defined(SUPPORT_PERFETTO) Thread* thread_; Isolate* isolate_; diff --git a/runtime/vm/thread_interrupter.cc b/runtime/vm/thread_interrupter.cc index 29fcc661755..b5bf493c3cd 100644 --- a/runtime/vm/thread_interrupter.cc +++ b/runtime/vm/thread_interrupter.cc @@ -11,7 +11,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) // Notes: // @@ -239,6 +239,6 @@ void* ThreadInterrupter::PrepareCurrentThread() { void ThreadInterrupter::CleanupCurrentThreadState(void* state) {} #endif -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/thread_interrupter_android.cc b/runtime/vm/thread_interrupter_android.cc index 56fcb022f97..60d54157c5a 100644 --- a/runtime/vm/thread_interrupter_android.cc +++ b/runtime/vm/thread_interrupter_android.cc @@ -16,7 +16,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) // Old linux kernels on ARM might require a trampoline to // work around incorrect Thumb -> ARM transitions. @@ -95,7 +95,7 @@ void ThreadInterrupter::CleanupCurrentThreadState(void* state) { SignalHandler::CleanupCurrentThreadState(state); } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/thread_interrupter_fuchsia.cc b/runtime/vm/thread_interrupter_fuchsia.cc index 430a25d866c..1049e536b31 100644 --- a/runtime/vm/thread_interrupter_fuchsia.cc +++ b/runtime/vm/thread_interrupter_fuchsia.cc @@ -21,7 +21,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) DECLARE_FLAG(bool, trace_thread_interrupter); @@ -242,7 +242,7 @@ void ThreadInterrupter::RemoveSignalHandler() { // Nothing to do on Fuchsia. } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/thread_interrupter_linux.cc b/runtime/vm/thread_interrupter_linux.cc index d88c21b65e1..9e160ca5030 100644 --- a/runtime/vm/thread_interrupter_linux.cc +++ b/runtime/vm/thread_interrupter_linux.cc @@ -15,7 +15,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) DECLARE_FLAG(bool, trace_thread_interrupter); @@ -62,7 +62,7 @@ void ThreadInterrupter::RemoveSignalHandler() { SignalHandler::Remove(); } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/thread_interrupter_macos.cc b/runtime/vm/thread_interrupter_macos.cc index 96c24084c35..c44e419b42b 100644 --- a/runtime/vm/thread_interrupter_macos.cc +++ b/runtime/vm/thread_interrupter_macos.cc @@ -23,7 +23,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) DECLARE_FLAG(bool, trace_thread_interrupter); @@ -143,7 +143,7 @@ void ThreadInterrupter::RemoveSignalHandler() { // Nothing to do on MacOS. } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/thread_interrupter_win.cc b/runtime/vm/thread_interrupter_win.cc index 37cf635d0b2..3cbcd41c2df 100644 --- a/runtime/vm/thread_interrupter_win.cc +++ b/runtime/vm/thread_interrupter_win.cc @@ -12,7 +12,7 @@ namespace dart { -#ifndef PRODUCT +#if defined(DART_INCLUDE_PROFILER) DECLARE_FLAG(bool, trace_thread_interrupter); @@ -126,7 +126,7 @@ void ThreadInterrupter::RemoveSignalHandler() { // Nothing to do on Windows. } -#endif // !PRODUCT +#endif // defined(DART_INCLUDE_PROFILER) } // namespace dart diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc index 94ce84b17bb..b3a97d5f856 100644 --- a/runtime/vm/timeline.cc +++ b/runtime/vm/timeline.cc @@ -31,11 +31,10 @@ #include "vm/service_event.h" #include "vm/thread.h" -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) #include "perfetto/ext/tracing/core/trace_packet.h" #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h" -#include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" @@ -43,7 +42,7 @@ #include "third_party/perfetto/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h" #include "vm/perfetto_utils.h" -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) namespace dart { @@ -150,9 +149,22 @@ static TimelineEventRecorder* CreateDefaultTimelineRecorder() { #endif } -#if !defined(PRODUCT) && defined(SUPPORT_PERFETTO) +static TimelineEventRecorder* CreateSystraceTimelineRecorder() { +#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) + return new TimelineEventSystraceRecorder(); +#elif defined(DART_HOST_OS_MACOS) + return new TimelineEventMacosRecorder(); +#elif defined(DART_HOST_OS_FUCHSIA) + return new TimelineEventFuchsiaRecorder(); +#else + return nullptr; +#endif +} + +#if defined(SUPPORT_PERFETTO) static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( - const char* filename); + const char* filename, + bool intern_strings); #endif static TimelineEventRecorder* CreateTimelineRecorder() { @@ -178,15 +190,11 @@ static TimelineEventRecorder* CreateTimelineRecorder() { // Systrace recorder. if (strcmp("systrace", flag) == 0) { -#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) - return new TimelineEventSystraceRecorder(); -#elif defined(DART_HOST_OS_MACOS) - return new TimelineEventMacosRecorder(); -#elif defined(DART_HOST_OS_FUCHSIA) - return new TimelineEventFuchsiaRecorder(); -#else - // Not supported. A warning will be emitted below. -#endif + const auto recorder = CreateSystraceTimelineRecorder(); + if (recorder != nullptr) { + return recorder; + } + // Fall-through to emit a warning about unsupported recorder. } if (Utils::StrStartsWith(flag, "file") && @@ -201,10 +209,7 @@ static TimelineEventRecorder* CreateTimelineRecorder() { return new TimelineEventEmbedderCallbackRecorder(); } -#if !defined(PRODUCT) #if defined(SUPPORT_PERFETTO) - // The Perfetto file recorder is disabled in PRODUCT mode to avoid the large - // binary size increase that it brings. { const intptr_t kPrefixLength = 12; if (Utils::StrStartsWith(flag, "perfettofile") && @@ -215,11 +220,13 @@ static TimelineEventRecorder* CreateTimelineRecorder() { : &flag[kPrefixLength + 1]; free(const_cast(FLAG_timeline_dir)); FLAG_timeline_dir = nullptr; - return CreateTimelineEventPerfettoFileRecorder(filename); + return CreateTimelineEventPerfettoFileRecorder( + filename, FLAG_intern_strings_when_writing_perfetto_timeline); } } #endif // defined(SUPPORT_PERFETTO) +#if !defined(PRODUCT) // Recorders below do nothing useful in PRODUCT mode. You can't extract // information available in them without vm-service. if (strcmp("endless", flag) == 0) { @@ -245,16 +252,17 @@ static TimelineEventRecorder* CreateTimelineRecorder() { return CreateDefaultTimelineRecorder(); } -// Returns a caller freed array of stream names in FLAG_timeline_streams. -static MallocGrowableArray* GetEnabledByDefaultTimelineStreams() { +// Returns a caller freed array of stream names in streams. +static MallocGrowableArray* GetEnabledByDefaultTimelineStreams( + const char* timeline_streams) { MallocGrowableArray* result = new MallocGrowableArray(); - if (FLAG_timeline_streams == nullptr) { + if (timeline_streams == nullptr) { // Nothing set. return result; } char* save_ptr; // Needed for strtok_r. // strtok modifies arg 1 so we make a copy of it. - char* streams = Utils::StrDup(FLAG_timeline_streams); + char* streams = Utils::StrDup(timeline_streams); char* token = strtok_r(streams, ",", &save_ptr); while (token != nullptr) { result->Add(Utils::StrDup(token)); @@ -293,8 +301,13 @@ static bool HasStream(MallocGrowableArray* streams, const char* stream) { } void Timeline::Init() { + InitWithRecorder(CreateTimelineRecorder(), FLAG_timeline_streams); +} + +void Timeline::InitWithRecorder(TimelineEventRecorder* recorder, + const char* timeline_streams) { ASSERT(recorder_ == nullptr); - recorder_ = CreateTimelineRecorder(); + recorder_ = recorder; RecorderSynchronizationLock::Init(); @@ -311,7 +324,7 @@ void Timeline::Init() { OS::PrintErr("Using the %s timeline recorder.\n", recorder_->name()); } ASSERT(recorder_ != nullptr); - enabled_streams_ = GetEnabledByDefaultTimelineStreams(); + enabled_streams_ = GetEnabledByDefaultTimelineStreams(timeline_streams); // Global overrides. #define TIMELINE_STREAM_FLAG_DEFAULT(name, ...) \ stream_##name##_.set_enabled(HasStream(enabled_streams_, #name)); @@ -810,159 +823,9 @@ void TimelineEvent::PrintJSON(JSONWriter* writer) const { writer->CloseObject(); } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) namespace { -// Trait used to map 64-bit ids (e.g. isolate or isolate group id) to -// interned id of a corresponding string representation. -// -// This way we only need to generate formatted string once, instead of -// repeatedly formatting it and then interning resulting string to get an -// iid. -class IdToIidTrait { - public: - struct Pair { - uint64_t id; - uint64_t formatted_iid; - }; - using Key = uint64_t; - using Value = uint64_t; - - static Key KeyOf(const Pair& kv) { return kv.id; } - static Value ValueOf(const Pair& kv) { return kv.formatted_iid; } - static uword Hash(Key key) { - return Utils::WordHash(static_cast(key)); - } - static bool IsKeyEqual(const Pair& kv, Key key) { return kv.id == key; } -}; - -using IdToIidMap = MallocDirectChainedHashMap; - -class InternedDataBuilder : public ValueObject { - private: - using SequenceFlags = perfetto::protos::pbzero::TracePacket_SequenceFlags; - - public: - // InternedData contains multiple independent interning dictionaries which - // are used for different attributes. -#define PERFETTO_INTERNED_STRINGS_FIELDS_LIST(V) \ - V(event_categories, name) \ - V(event_names, name) \ - V(debug_annotation_names, name) \ - V(debug_annotation_string_values, str) - - // Direct access for known strings. -#define PERFETTO_COMMON_INTERNED_STRINGS_LIST(V) \ - V(debug_annotation_names, isolateId) \ - V(debug_annotation_names, isolateGroupId) - - InternedDataBuilder() = default; - - // Emit all strings added since the last invocation of |AttachInternedDataTo| - // into |interned_data| of the given |TracePacket|. - // - // Mark the packet as depending on incremental state. - void AttachInternedDataTo(perfetto::protos::pbzero::TracePacket* packet) { - if (!AnyInternerHasNewlyInternedEntries()) { - return; - } - - packet->set_sequence_flags(sequence_flags_); - // The first packet will have SEQ_INCREMENTAL_STATE_CLEARED - // the rest will just have SEQ_NEEDS_INCREMENTAL_STATE. - sequence_flags_ &= ~SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED; - - auto interned_data = packet->set_interned_data(); - - // Flush individual interning dictionaries. -#define FLUSH_FIELD(name, proto_field) \ - name##_.FlushNewlyInternedTo([interned_data](auto& iid, auto& str) { \ - auto entry = interned_data->add_##name(); \ - entry->set_iid(iid); \ - entry->set_##proto_field(str); \ - }); - - PERFETTO_INTERNED_STRINGS_FIELDS_LIST(FLUSH_FIELD) -#undef FLUSH_FIELD - } - -#define DEFINE_GETTER(name, proto_field) \ - perfetto_utils::StringInterner& name() { return name##_; } - PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_GETTER) -#undef DEFINE_GETTER - -#define DEFINE_GETTER_FOR_COMMON_STRING(category, str) \ - uint64_t iid_##str() { \ - if (iid_##str##_ == 0) { \ - iid_##str##_ = category().Intern(#str); \ - } \ - return iid_##str##_; \ - } - - PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_GETTER_FOR_COMMON_STRING) - -#undef DEFINE_GETTER_FOR_COMMON_STRING - - uint64_t InternFormattedIsolateId(uint64_t isolate_id) { - return InternFormattedIdForDebugAnnotation( - isolate_id_to_iid_of_formatted_string_, - ISOLATE_SERVICE_ID_FORMAT_STRING, isolate_id); - } - - uint64_t InternFormattedIsolateGroupId(uint64_t isolate_group_id) { - return InternFormattedIdForDebugAnnotation( - isolate_group_id_to_iid_of_formatted_string_, - ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, isolate_group_id); - } - - private: - template - uint64_t InternFormattedIdForDebugAnnotation(IdToIidMap& cache, - const char (&format)[kFormatLen], - uint64_t id) { - if (auto iid = cache.Lookup(id)) { - return iid->formatted_iid; - } - - // 20 characters is enough to format any uint64_t (or int64_t) value. - char formatted[kFormatLen + 20]; - Utils::SNPrint(formatted, ARRAY_SIZE(formatted), format, id); - - auto formatted_iid = debug_annotation_string_values().Intern(formatted); - cache.Insert({id, formatted_iid}); - return formatted_iid; - } - - bool AnyInternerHasNewlyInternedEntries() const { -#define CHECK_FIELD(name, proto_field) \ - if (name##_.HasNewlyInternedEntries()) return true; - - PERFETTO_INTERNED_STRINGS_FIELDS_LIST(CHECK_FIELD) -#undef CHECK_FIELD - return false; - } - - uint32_t sequence_flags_ = SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED | - SequenceFlags::SEQ_NEEDS_INCREMENTAL_STATE; - - // These are interned in debug_annotation_string_values space. - IdToIidMap isolate_id_to_iid_of_formatted_string_; - IdToIidMap isolate_group_id_to_iid_of_formatted_string_; - -#define DEFINE_FIELD_FOR_COMMON_STRING(category, str) uint64_t iid_##str##_ = 0; - - PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_FIELD_FOR_COMMON_STRING) - -#undef DEFINE_FIELD_FOR_COMMON_STRING - -#define DEFINE_FIELD(name, proto_field) \ - perfetto_utils::StringInterner name##_; - PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_FIELD) -#undef DEFINE_FIELD - - DISALLOW_COPY_AND_ASSIGN(InternedDataBuilder); -}; - class TracePacketWriter : public ValueObject { public: using TracePacket = perfetto::protos::pbzero::TracePacket; @@ -997,6 +860,10 @@ class TracePacketWriter : public ValueObject { } } + perfetto_utils::InternedDataBuilder& interned_data_builder() { + return interned_data_builder_; + } + private: static TrackEvent::Type ToPerfettoType(TimelineEvent::EventType event_type) { switch (event_type) { @@ -1189,13 +1056,13 @@ class TracePacketWriter : public ValueObject { WriteCallback write_callback_; const bool intern_strings_; - InternedDataBuilder interned_data_builder_; + perfetto_utils::InternedDataBuilder interned_data_builder_; DISALLOW_COPY_AND_ASSIGN(TracePacketWriter); }; } // namespace -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) int64_t TimelineEvent::LowTime() const { return timestamp0_; @@ -1252,6 +1119,7 @@ void TimelineTrackMetadata::PrintJSON(const JSONArray& jsarr_events) const { jsobj_args.AddProperty("mode", "basic"); } } +#endif // !defined(PRODUCT) #if defined(SUPPORT_PERFETTO) void TimelineTrackMetadata::PopulateTracePacket( @@ -1269,14 +1137,11 @@ void TimelineTrackMetadata::PopulateTracePacket( thread_descriptor.set_tid(tid()); thread_descriptor.set_thread_name(track_name()); } -#endif // defined(SUPPORT_PERFETTO) -#endif // !defined(PRODUCT) AsyncTimelineTrackMetadata::AsyncTimelineTrackMetadata(intptr_t pid, intptr_t async_id) : pid_(pid), async_id_(async_id) {} -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) void AsyncTimelineTrackMetadata::PopulateTracePacket( perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const { perfetto_utils::SetTrustedPacketSequenceId(track_descriptor_packet); @@ -1285,7 +1150,7 @@ void AsyncTimelineTrackMetadata::PopulateTracePacket( track_descriptor.set_parent_uuid(pid()); track_descriptor.set_uuid(async_id()); } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) TimelineStream::TimelineStream(const char* name, const char* fuchsia_name, @@ -1498,10 +1363,6 @@ TimelineEventRecorder::TimelineEventRecorder() time_high_micros_(0), track_uuid_to_track_metadata_lock_(), track_uuid_to_track_metadata_( - &SimpleHashMap::SamePointerValue, - TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity), - async_track_uuid_to_track_metadata_lock_(), - async_track_uuid_to_track_metadata_( &SimpleHashMap::SamePointerValue, TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity) {} @@ -1515,14 +1376,6 @@ TimelineEventRecorder::~TimelineEventRecorder() { static_cast(entry->value); delete value; } - for (SimpleHashMap::Entry* entry = - async_track_uuid_to_track_metadata_.Start(); - entry != nullptr; - entry = async_track_uuid_to_track_metadata_.Next(entry)) { - AsyncTimelineTrackMetadata* value = - static_cast(entry->value); - delete value; - } } #ifndef PRODUCT @@ -1535,48 +1388,6 @@ void TimelineEventRecorder::PrintJSONMeta(const JSONArray& jsarr_events) { value->PrintJSON(jsarr_events); } } - -#if defined(SUPPORT_PERFETTO) -void TimelineEventRecorder::PrintPerfettoMeta( - JSONBase64String* jsonBase64String) { - ASSERT(jsonBase64String != nullptr); - - perfetto_utils::PopulateClockSnapshotPacket(packet_.get()); - perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); - packet_.Reset(); - perfetto_utils::PopulateProcessDescriptorPacket(packet_.get()); - perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); - packet_.Reset(); - - { - MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); - for (SimpleHashMap::Entry* entry = - async_track_uuid_to_track_metadata_.Start(); - entry != nullptr; - entry = async_track_uuid_to_track_metadata_.Next(entry)) { - AsyncTimelineTrackMetadata* value = - static_cast(entry->value); - value->PopulateTracePacket(packet_.get()); - perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, - &packet_); - packet_.Reset(); - } - } - - { - MutexLocker ml(&track_uuid_to_track_metadata_lock_); - for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); - entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { - TimelineTrackMetadata* value = - static_cast(entry->value); - value->PopulateTracePacket(packet_.get()); - perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, - &packet_); - packet_.Reset(); - } - } -} -#endif // defined(SUPPORT_PERFETTO) #endif // !defined(PRODUCT) TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() { @@ -1672,15 +1483,16 @@ void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) { if (event == nullptr) { return; } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + +#if defined(SUPPORT_PERFETTO) // Async track metadata is only written in Perfetto traces, and Perfetto - // traces cannot be written when SUPPORT_PERFETTO is not defined, or when - // PRODUCT is defined. + // traces cannot be written when SUPPORT_PERFETTO is not defined. if (event->event_type() == TimelineEvent::kAsyncBegin || event->event_type() == TimelineEvent::kAsyncInstant) { AddAsyncTrackMetadataBasedOnEvent(*event); } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) + // Grab the current thread. OSThread* thread = OSThread::Current(); ASSERT(thread != nullptr); @@ -1779,19 +1591,36 @@ void TimelineEventRecorder::AddTrackMetadataBasedOnThread( } } -#if !defined(PRODUCT) void TimelineEventRecorder::AddAsyncTrackMetadataBasedOnEvent( - const TimelineEvent& event) { - ASSERT(FLAG_timeline_recorder != nullptr); - if (strcmp("none", FLAG_timeline_recorder) == 0 || - strcmp("callback", FLAG_timeline_recorder) == 0 || - strcmp("systrace", FLAG_timeline_recorder) == 0 || - FLAG_systrace_timeline) { - // There is no way to retrieve track metadata when a no-op, callback, or - // systrace recorder is in use, so we don't need to update the map in - // these cases. - return; + const TimelineEvent& event) {} + +#if defined(SUPPORT_PERFETTO) +template +template +TimelineEventRecorderWithPerfettoSupport< + Base>::TimelineEventRecorderWithPerfettoSupport(Args&&... args) + : Base(std::forward(args)...), + async_track_uuid_to_track_metadata_lock_(), + async_track_uuid_to_track_metadata_( + &SimpleHashMap::SamePointerValue, + TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity) {} + +template +TimelineEventRecorderWithPerfettoSupport< + Base>::~TimelineEventRecorderWithPerfettoSupport() { + for (SimpleHashMap::Entry* entry = + async_track_uuid_to_track_metadata_.Start(); + entry != nullptr; + entry = async_track_uuid_to_track_metadata_.Next(entry)) { + AsyncTimelineTrackMetadata* value = + static_cast(entry->value); + delete value; } +} + +template +void TimelineEventRecorderWithPerfettoSupport< + Base>::AddAsyncTrackMetadataBasedOnEvent(const TimelineEvent& event) { MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); void* key = reinterpret_cast(event.Id()); @@ -1802,7 +1631,51 @@ void TimelineEventRecorder::AddAsyncTrackMetadataBasedOnEvent( entry->value = new AsyncTimelineTrackMetadata(OS::ProcessId(), event.Id()); } } -#endif // !defined(PRODUCT) + +template +void TimelineEventRecorderWithPerfettoSupport::PrintPerfettoMeta( + JSONBase64String* jsonBase64String) { + ASSERT(jsonBase64String != nullptr); + + perfetto_utils::PopulateClockSnapshotPacket(packet_.get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); + packet_.Reset(); + perfetto_utils::PopulateProcessDescriptorPacket(packet_.get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); + packet_.Reset(); + + { + MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); + for (SimpleHashMap::Entry* entry = + async_track_uuid_to_track_metadata_.Start(); + entry != nullptr; + entry = async_track_uuid_to_track_metadata_.Next(entry)) { + AsyncTimelineTrackMetadata* value = + static_cast(entry->value); + value->PopulateTracePacket(packet_.get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, + &packet_); + packet_.Reset(); + } + } + + { + MutexLocker ml(&TimelineEventRecorder::track_uuid_to_track_metadata_lock()); + for (SimpleHashMap::Entry* entry = + TimelineEventRecorder::track_uuid_to_track_metadata().Start(); + entry != nullptr; + entry = TimelineEventRecorder::track_uuid_to_track_metadata().Next( + entry)) { + TimelineTrackMetadata* value = + static_cast(entry->value); + value->PopulateTracePacket(packet_.get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, + &packet_); + packet_.Reset(); + } + } +} +#endif // defined(SUPPORT_PERFETTO) TimelineEventFixedBufferRecorder::TimelineEventFixedBufferRecorder( intptr_t capacity) @@ -2414,34 +2287,62 @@ void TimelineEventFileRecorder::DrainImpl(const TimelineEvent& event) { free(output); } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) -class TimelineEventPerfettoFileRecorder : public TimelineEventFileRecorderBase { +#if defined(SUPPORT_PERFETTO) +class TimelineEventPerfettoFileRecorder + : public TimelineEventRecorderWithPerfettoSupport< + TimelineEventFileRecorderBase> { public: - explicit TimelineEventPerfettoFileRecorder(const char* path); + explicit TimelineEventPerfettoFileRecorder(const char* path, + bool intern_strings); virtual ~TimelineEventPerfettoFileRecorder(); const char* name() const final { return PERFETTO_FILE_RECORDER_NAME; } +#if defined(DART_INCLUDE_PROFILER) + void WriteProfile(Profile& profile); +#endif + private: void WritePacket( protozero::HeapBuffered* packet); void DrainImpl(const TimelineEvent& event) final; + Mutex writer_mutex_; TracePacketWriter writer_; }; static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( - const char* filename) { - return new TimelineEventPerfettoFileRecorder(filename); + const char* filename, + bool intern_strings) { + return new TimelineEventPerfettoFileRecorder(filename, intern_strings); } +#if defined(DART_INCLUDE_PROFILER) +void TimelineEventPerfettoFileRecorder::WriteProfile(Profile& profile) { + // Profile conversion code checks for safepoints so we need to use safepoint + // aware mutex locker here to avoid deadlocks when two threads call + // |WriteProfile| and the third thread requests a safepoint. + // + // Note that Drain does not need this because it does not check for + // safepoint. + SafepointMutexLocker ml(&writer_mutex_); + profile.PrintProfilePerfetto( + writer_.interned_data_builder(), this, + [](auto buffer, auto length, auto stream) { + static_cast(stream)->Write( + static_cast(buffer), length); + }); +} +#endif + TimelineEventPerfettoFileRecorder::TimelineEventPerfettoFileRecorder( - const char* path) - : TimelineEventFileRecorderBase(path), + const char* path, + bool intern_strings) + : TimelineEventRecorderWithPerfettoSupport(path), writer_( packet(), [this](auto& packet) { this->WritePacket(&packet); }, - FLAG_intern_strings_when_writing_perfetto_timeline) { + intern_strings) { protozero::HeapBuffered& packet = this->packet(); @@ -2498,13 +2399,16 @@ void TimelineEventPerfettoFileRecorder::WritePacket( } void TimelineEventPerfettoFileRecorder::DrainImpl(const TimelineEvent& event) { - writer_.WriteEvent(event); + { + MutexLocker lock(&writer_mutex_); + writer_.WriteEvent(event); + } if (event.event_type() == TimelineEvent::kAsyncBegin || event.event_type() == TimelineEvent::kAsyncInstant) { AddAsyncTrackMetadataBasedOnEvent(event); } } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) TimelineEventEndlessRecorder::TimelineEventEndlessRecorder() : head_(nullptr), tail_(nullptr), block_index_(0) {} @@ -2571,6 +2475,60 @@ void TimelineEventEndlessRecorder::ClearLocked() { block_index_ = 0; } +static std::atomic is_streaming_timeline{false}; + +bool Timeline::StreamTo(const char* recorder_kind, + const char* file, + const char* streams, + const char** error) { + Timeline::Cleanup(); + + TimelineEventRecorder* recorder = nullptr; + if (strcmp(recorder_kind, "perfettofile") == 0) { +#if defined(SUPPORT_PERFETTO) + recorder = + CreateTimelineEventPerfettoFileRecorder(file, /*intern_strings=*/true); +#else + *error = "Support for Perfetto recorder is not included into this build"; + return false; +#endif // defined(SUPPORT_PERFETTO) + } else if (strcmp(recorder_kind, "file") == 0) { + recorder = new TimelineEventFileRecorder(file); + } else if (strcmp(recorder_kind, "systrace") == 0) { + recorder = CreateSystraceTimelineRecorder(); + if (recorder == nullptr) { + *error = "Support for systrace recorder is not included into this build"; + return false; + } + } + + Timeline::InitWithRecorder(recorder, streams); + is_streaming_timeline.store(true); + +#if defined(DART_INCLUDE_PROFILER) && defined(SUPPORT_PERFETTO) + if (FLAG_profiler && (strcmp(recorder_kind, "perfettofile") == 0)) { + Profiler::SetProfileProcessorCallback([](auto& profile) { + RecorderSynchronizationLockScope ls; + if (recorder_ != nullptr && ls.IsActive() && + is_streaming_timeline.load()) { + static_cast(recorder_) + ->WriteProfile(profile); + } + }); + } +#endif + return true; +} + +void Timeline::StopStreaming() { + is_streaming_timeline.store(false); +#if defined(DART_INCLUDE_PROFILER) + Profiler::SetProfileProcessorCallback(nullptr); +#endif + Timeline::Cleanup(); + Timeline::Init(); +} + TimelineEventBlock::TimelineEventBlock(intptr_t block_index) : next_(nullptr), length_(0), diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h index 4c9031f6156..9f3a5958288 100644 --- a/runtime/vm/timeline.h +++ b/runtime/vm/timeline.h @@ -20,7 +20,7 @@ #include "vm/os.h" #include "vm/os_thread.h" -#if defined(SUPPORT_TIMELINE) && defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_TIMELINE) && defined(SUPPORT_PERFETTO) #include "perfetto/protozero/scattered_heap_buffer.h" #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" #endif // defined(SUPPORT_TIMELINE) && defined(SUPPORT_PERFETTO) && \ @@ -75,6 +75,8 @@ class Zone; #define STARTUP_RECORDER_NAME "Startup" #define SYSTRACE_RECORDER_NAME "Systrace" +// Note: when updating this list consider updating TimelineStream enum in +// sdk/lib/developer.dart. // (name, fuchsia_name, has_static_labels). #define TIMELINE_STREAM_LIST(V) \ V(API, "dart:api", true) \ @@ -226,6 +228,18 @@ class Timeline : public AllStatic { // Cleanup timeline system. Not thread safe. static void Cleanup(); + // Start streaming timeline using given |recorder| (perfettofile, file or + // systrace). + // + // Returns true on success, otherwise returns false and stores error message + // into |error|. + static bool StreamTo(const char* recorder, + const char* file, + const char* streams, + const char** error); + + static void StopStreaming(); + // Access the global recorder. Not thread safe. static TimelineEventRecorder* recorder() { return recorder_; } @@ -267,6 +281,10 @@ class Timeline : public AllStatic { #undef TIMELINE_STREAM_FLAGS private: + // Initialize timeline system. Not thread safe. + static void InitWithRecorder(TimelineEventRecorder* recorder, + const char* streams); + static TimelineEventRecorder* recorder_; static Dart_TimelineRecorderCallback callback_; static MallocGrowableArray* enabled_streams_; @@ -475,13 +493,13 @@ class TimelineEvent { void PrintJSON(JSONStream* stream) const; #endif void PrintJSON(JSONWriter* writer) const; -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) bool CanBeRepresentedByPerfettoTracePacket() const; /* * Populates the fields of |packet| with this event's data. */ void PopulateTracePacket(perfetto::protos::pbzero::TracePacket* packet) const; -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) ThreadId thread() const { return thread_; } @@ -626,9 +644,9 @@ class TimelineEvent { friend class TimelineEventPlatformRecorder; friend class TimelineEventFuchsiaRecorder; friend class TimelineEventMacosRecorder; -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#if defined(SUPPORT_PERFETTO) friend class TimelineEventPerfettoFileRecorder; -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) +#endif // defined(SUPPORT_PERFETTO) friend class TimelineStream; friend class TimelineTestHelper; DISALLOW_COPY_AND_ASSIGN(TimelineEvent); @@ -649,6 +667,8 @@ class TimelineTrackMetadata { * object into |jsarr_events|. */ void PrintJSON(const JSONArray& jsarr_events) const; +#endif // !defined(PRODUCT) + #if defined(SUPPORT_PERFETTO) /* * Populates the fields of |track_descriptor_packet| with the metadata stored @@ -657,7 +677,6 @@ class TimelineTrackMetadata { void PopulateTracePacket( perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const; #endif // defined(SUPPORT_PERFETTO) -#endif // !defined(PRODUCT) private: // The ID of the process that this track is associated with. @@ -668,19 +687,19 @@ class TimelineTrackMetadata { CStringUniquePtr track_name_; }; +#if defined(SUPPORT_PERFETTO) class AsyncTimelineTrackMetadata { public: AsyncTimelineTrackMetadata(intptr_t pid, intptr_t async_id); intptr_t pid() const { return pid_; } intptr_t async_id() const { return async_id_; } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + /* * Populates the fields of |track_descriptor_packet| with the metadata stored * by this object. */ void PopulateTracePacket( perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const; -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) private: // The ID of the process that this track is associated with. @@ -688,6 +707,7 @@ class AsyncTimelineTrackMetadata { // The async ID that this track is associated with. intptr_t async_id_; }; +#endif // defined(SUPPORT_PERFETTO) #define TIMELINE_DURATION(thread, stream, name) \ TimelineBeginEndScope tbes(thread, Timeline::Get##stream##Stream(), name); @@ -912,20 +932,18 @@ class TimelineEventRecorder : public MallocAllocated { virtual void AddTrackMetadataBasedOnThread(const intptr_t process_id, const intptr_t trace_id, const char* thread_name); - void AddAsyncTrackMetadataBasedOnEvent(const TimelineEvent& event); + virtual void AddAsyncTrackMetadataBasedOnEvent(const TimelineEvent& event); protected: + static constexpr intptr_t kTrackUuidToTrackMetadataInitialCapacity = 1 << 4; + SimpleHashMap& track_uuid_to_track_metadata() { return track_uuid_to_track_metadata_; } - SimpleHashMap& async_track_uuid_to_track_metadata() { - return async_track_uuid_to_track_metadata_; + + Mutex& track_uuid_to_track_metadata_lock() { + return track_uuid_to_track_metadata_lock_; } -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) - protozero::HeapBuffered& packet() { - return packet_; - } -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) #ifndef PRODUCT void WriteTo(const char* directory); @@ -968,23 +986,52 @@ class TimelineEventRecorder : public MallocAllocated { friend class OSThread; private: - static constexpr intptr_t kTrackUuidToTrackMetadataInitialCapacity = 1 << 4; Mutex track_uuid_to_track_metadata_lock_; SimpleHashMap track_uuid_to_track_metadata_; - Mutex async_track_uuid_to_track_metadata_lock_; - SimpleHashMap async_track_uuid_to_track_metadata_; -#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) - // We allocate one heap-buffered packet as a class member, because it lets us - // continuously follow a cycle of resetting the buffer and writing its - // contents. - protozero::HeapBuffered packet_; -#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) DISALLOW_COPY_AND_ASSIGN(TimelineEventRecorder); }; +#if defined(SUPPORT_PERFETTO) +template +class TimelineEventRecorderWithPerfettoSupport : public Base { + public: + template + explicit TimelineEventRecorderWithPerfettoSupport(Args&&... args); + + ~TimelineEventRecorderWithPerfettoSupport(); + + void PrintPerfettoMeta(JSONBase64String* jsonBase64String); + + void AddAsyncTrackMetadataBasedOnEvent(const TimelineEvent& event); + + protected: + protozero::HeapBuffered& packet() { + return packet_; + } + + SimpleHashMap& async_track_uuid_to_track_metadata() { + return async_track_uuid_to_track_metadata_; + } + + private: + // We allocate one heap-buffered packet as a class member, because it lets us + // continuously follow a cycle of resetting the buffer and writing its + // contents. + protozero::HeapBuffered packet_; + + static constexpr intptr_t kTrackUuidToTrackMetadataInitialCapacity = 1 << 4; + Mutex async_track_uuid_to_track_metadata_lock_; + SimpleHashMap async_track_uuid_to_track_metadata_; +}; +#else +template +using TimelineEventRecorderWithPerfettoSupport = Base; +#endif + // An abstract recorder that buffers recorded events. -class TimelineEventBufferedRecorder : public TimelineEventRecorder { +class TimelineEventBufferedRecorder + : public TimelineEventRecorderWithPerfettoSupport { public: #ifndef PRODUCT void PrintJSON(JSONStream* js, TimelineEventFilter* filter) final; diff --git a/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart index 04514d1b497..6fa57f3a1a0 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart @@ -390,4 +390,20 @@ abstract final class NativeRuntime { throw UnsupportedError( "Generating heap snapshots is not supported on the web.", ); + + @patch + static void streamTimelineTo( + TimelineRecorder recorder, { + String? path, + String streams = "Dart,GC,Compiler", + bool enableProfiler = false, + Duration samplingInterval = const Duration(microseconds: 1000), + }) => throw UnsupportedError( + "Streaming timelines is not supported on the web.", + ); + + @patch + static void stopStreamingTimeline() => throw UnsupportedError( + "Streaming timelines is not supported on the web.", + ); } diff --git a/sdk/lib/_internal/js_runtime/lib/developer_patch.dart b/sdk/lib/_internal/js_runtime/lib/developer_patch.dart index eeceb62acbd..06f34c76dd5 100644 --- a/sdk/lib/_internal/js_runtime/lib/developer_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/developer_patch.dart @@ -359,4 +359,20 @@ abstract final class NativeRuntime { throw UnsupportedError( "Generating heap snapshots is not supported on the web.", ); + + @patch + static void streamTimelineTo( + TimelineRecorder recorder, { + String? path, + String streams = "Dart,GC,Compiler", + bool enableProfiler = false, + Duration samplingInterval = const Duration(microseconds: 1000), + }) => throw UnsupportedError( + "Streaming timelines is not supported on the web.", + ); + + @patch + static void stopStreamingTimeline() => throw UnsupportedError( + "Streaming timelines is not supported on the web.", + ); } diff --git a/sdk/lib/_internal/vm/lib/developer.dart b/sdk/lib/_internal/vm/lib/developer.dart index 2f6063f4745..d27df33f159 100644 --- a/sdk/lib/_internal/vm/lib/developer.dart +++ b/sdk/lib/_internal/vm/lib/developer.dart @@ -11,6 +11,8 @@ import "dart:_internal" show patch; import "dart:async" show Future, Zone; +import "dart:io" show Platform; + import "dart:isolate" show SendPort; /// These are the additional parts of this patch library: @@ -228,4 +230,101 @@ abstract final class NativeRuntime { @patch @pragma("vm:external-name", "Developer_NativeRuntime_writeHeapSnapshotToFile") external static void writeHeapSnapshotToFile(String filepath); + + @patch + static void streamTimelineTo( + TimelineRecorder recorder, { + String? path, + List streams = const [.dart, .gc], + bool enableProfiler = false, + Duration samplingInterval = const Duration(microseconds: 1000), + }) { + if (samplingInterval.inMicroseconds < 50) { + throw ArgumentError.value( + samplingInterval, + 'samplingInterval', + 'should be at least 50 us', + ); + } + + if (recorder == .systrace) { + if (path != null) { + throw ArgumentError.value( + path, + 'path', + '$recorder writes output to the global ftrace buffer and ' + 'can not redirect output to a specific file', + ); + } + + if (Platform.isWindows) { + throw ArgumentError.value( + recorder, + 'recorder', + '$recorder not supported on Windows', + ); + } + } + + if (recorder != .systrace && path == null) { + throw ArgumentError.value( + path, + 'path', + '$recorder needs an output file to write timeline data to', + ); + } + + if (recorder != .perfetto && enableProfiler) { + throw ArgumentError.value( + enableProfiler, + 'enableProfiler', + '$recorder does not support encoding profiling data, ' + 'disable profiler or switch to TimelineRecorder.perfetto', + ); + } + + final recorderName = switch (recorder) { + .perfetto => 'perfettofile', + .chrome => 'file', + .systrace => 'systrace', + }; + + // Convert list of TimelineStream into a comma-separated list of values. + final streamsString = [ + for (var str in streams) + switch (str) { + .api => 'API', + .compiler => 'Compiler', + .compilerVerbose => 'CompilerVerbose', + .dart => 'Dart', + .debugger => 'Debugger', + .embedder => 'Embedder', + .gc => 'GC', + .isolate => 'Isolate', + .microtask => 'Microtask', + .vm => 'vm', + }, + ].join(','); + + _streamTimelineToImpl( + recorderName, + path, + streamsString, + enableProfiler, + samplingInterval.inMicroseconds, + ); + } + + @pragma("vm:external-name", "Developer_NativeRuntime_streamTimelineTo") + external static void _streamTimelineToImpl( + String recorder, + String? path, + String streams, + bool enableProfiler, + int samplingInterval, + ); + + @patch + @pragma("vm:external-name", "Developer_NativeRuntime_stopStreamingTimeline") + external static void stopStreamingTimeline(); } diff --git a/sdk/lib/developer/developer.dart b/sdk/lib/developer/developer.dart index eb4e542fad6..dda14d93c49 100644 --- a/sdk/lib/developer/developer.dart +++ b/sdk/lib/developer/developer.dart @@ -144,6 +144,69 @@ external void log( @Since('2.19') external int get reachabilityBarrier; +/// Types of timeline recorders supported by the VM. +@Since('3.11') +enum TimelineRecorder { + /// [Perfetto](https://ui.perfetto.dev)'s protobuf based format. + /// + /// Supports both profiling and tracing data. + /// + /// Scheme is available in [Perfetto docs](https://perfetto.dev/docs/reference/trace-packet-proto). + perfetto, + + /// Chrome's JSON based format viewable by [Catapult](chrome://tracing). + /// + /// Supports only tracing data. + /// + /// Scheme is described in [here](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0). + chrome, + + /// Emits platform specific timeline events. + /// + /// * On Linux and Android this means writing events into + /// [ftrace](https://docs.kernel.org/trace/ftrace.html) buffer. + /// * On Mac OS X this uses [signposts](https://developer.apple.com/documentation/os/recording-performance-data). + /// * On Fuchsia it uses [Fuchsia tracing system](https://fuchsia.dev/fuchsia-src/concepts/kernel/tracing-system). + /// * Not supported on Windows. + /// + systrace, +} + +/// Specific sets of events whose recording can be enabled separately. +@Since('3.11') +enum TimelineStream { + /// Calls to `Dart_*` VM C API functions. + api, + + /// Events related to compilation to machine code. + compiler, + + /// Detailed timing information about compiler phases. + compilerVerbose, + + /// Events created via [Timeline] APIs. + dart, + + /// Events related to debugger. + debugger, + + /// Events created by `Dart_RecordTimelineEvent`. + embedder, + + /// Events related to garbage collection and/or heap iteration. + gc, + + /// Isolate and isolate group lifecycle events such as startup and shutdown. + isolate, + + /// Events representing `dart:async` microtasks. VM will only populate this + /// stream with events if it is started with `--profile-microtasks`. + microtask, + + /// VM lifecycle events such as startup and shutdown. + vm, +} + /// Functionality available on the native runtime. @Since('3.0') abstract final class NativeRuntime { @@ -173,4 +236,43 @@ abstract final class NativeRuntime { /// NOTE: This is an experimental function. We reserve the right to change /// or remove it in the future. external static void writeHeapSnapshotToFile(String filepath); + + /// Tells runtime to write timeline data using [recorder]. + /// + /// Timeline recording is enabled for the whole runtime and not for any + /// specific isolate or isolate group. + /// + /// Once started timeline recording will continue until it is stopped by + /// [stopStreamingTimeline]. + /// + /// Some recorders write into a specific file (specified by [path]), while + /// others write to system wide recording buffer. + /// + /// The [streams] specifies which timeline streams to enable. Only + /// [TimelineStream.dart] and [TimelineStream.gc] are enabled by default. + /// + /// If [recorder] supports profiling data then setting [enableProfiler] to + /// `true` will turn on sampling profiler, which will collect profiling + /// samples with frequency specified by [samplingInterval]. These samples + /// will then written into the timeline. + /// + /// Throws [ArgumentError] iff: + /// + /// * [path] is specified but [recorder] writes to a fixed location. + /// * [path] is not specified and [recorder] requires it. + /// * [enableProfiler] is `true` and [recorder] does not support writing out + /// profiling data. + /// * [samplingInterval] is too small. + @Since('3.11') + external static void streamTimelineTo( + TimelineRecorder recorder, { + String? path, + List streams = const [.dart, .gc], + bool enableProfiler = false, + Duration samplingInterval = const Duration(microseconds: 1000), + }); + + /// Finishes capturing of timeline data started by [streamTimelineTo]. + @Since('3.11') + external static void stopStreamingTimeline(); } diff --git a/tests/lib/mirrors/invocation_fuzz_test.dart b/tests/lib/mirrors/invocation_fuzz_test.dart index d3887488b69..d98604780a7 100644 --- a/tests/lib/mirrors/invocation_fuzz_test.dart +++ b/tests/lib/mirrors/invocation_fuzz_test.dart @@ -53,6 +53,10 @@ var denylist = [ // Don't instantiate callables with random function pointers. 'dart.ffi._NativeCallableIsolateLocal', + + // Don't write heap snapshots or profile to random files. + 'dart.developer.NativeRuntime.writeHeapSnapshotToFile', + 'dart.developer.NativeRuntime.streamTimelineTo', ]; bool isDenylisted(Symbol qualifiedSymbol) {