From 32c595ea894a52b17cfc42557bfc3fc9f265c001 Mon Sep 17 00:00:00 2001 From: Derek Xu Date: Thu, 20 Apr 2023 16:42:23 +0000 Subject: [PATCH] [VM/Timeline] Add getPerfettoVMTimeline Service RPC TEST=Loaded the response from a getPerfettoVMTimeline request in the Perfetto trace viewer and made sure everything looked correct. Change-Id: I8d4bc35fb36601701976c28653db92b2161e4724 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/288066 Commit-Queue: Derek Xu Reviewed-by: Ben Konyi --- pkg/vm_service/java/.gitignore | 2 + pkg/vm_service/java/version.properties | 2 +- pkg/vm_service/lib/src/vm_service.dart | 104 +++++++++++++++- runtime/vm/service.cc | 116 ++++++++++++------ runtime/vm/service/service.md | 67 +++++++++- runtime/vm/timeline.cc | 162 +++++++++++++++++++++++-- runtime/vm/timeline.h | 55 +++++++-- 7 files changed, 442 insertions(+), 66 deletions(-) diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore index a2b00be477b..ce412072bb4 100644 --- a/pkg/vm_service/java/.gitignore +++ b/pkg/vm_service/java/.gitignore @@ -28,6 +28,7 @@ src/org/dartlang/vm/service/consumer/GetStackConsumer.java src/org/dartlang/vm/service/consumer/InvokeConsumer.java src/org/dartlang/vm/service/consumer/KillConsumer.java src/org/dartlang/vm/service/consumer/PauseConsumer.java +src/org/dartlang/vm/service/consumer/PerfettoTimelineConsumer.java src/org/dartlang/vm/service/consumer/PortListConsumer.java src/org/dartlang/vm/service/consumer/ProcessMemoryUsageConsumer.java src/org/dartlang/vm/service/consumer/ProtocolListConsumer.java @@ -104,6 +105,7 @@ src/org/dartlang/vm/service/element/NullRef.java src/org/dartlang/vm/service/element/Obj.java src/org/dartlang/vm/service/element/ObjRef.java src/org/dartlang/vm/service/element/Parameter.java +src/org/dartlang/vm/service/element/PerfettoTimeline.java src/org/dartlang/vm/service/element/PortList.java src/org/dartlang/vm/service/element/ProcessMemoryItem.java src/org/dartlang/vm/service/element/ProcessMemoryUsage.java diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties index c3507907b7a..b490dfee02d 100644 --- a/pkg/vm_service/java/version.properties +++ b/pkg/vm_service/java/version.properties @@ -1 +1 @@ -version=4.4 +version=4.5 diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart index f82621a89f3..3edbe23dbd9 100644 --- a/pkg/vm_service/lib/src/vm_service.dart +++ b/pkg/vm_service/lib/src/vm_service.dart @@ -28,7 +28,7 @@ export 'snapshot_graph.dart' HeapSnapshotObjectNoData, HeapSnapshotObjectNullData; -const String vmServiceVersion = '4.4.0'; +const String vmServiceVersion = '4.5.0'; /// @optional const String optional = 'optional'; @@ -161,6 +161,7 @@ Map _typeFactories = { '@Object': ObjRef.parse, 'Object': Obj.parse, 'Parameter': Parameter.parse, + 'PerfettoTimeline': PerfettoTimeline.parse, 'PortList': PortList.parse, 'ProfileFunction': ProfileFunction.parse, 'ProtocolList': ProtocolList.parse, @@ -218,6 +219,7 @@ Map> _methodReturnTypes = { 'getIsolateGroupMemoryUsage': const ['MemoryUsage'], 'getScripts': const ['ScriptList'], 'getObject': const ['Obj'], + 'getPerfettoVMTimeline': const ['PerfettoTimeline'], 'getPorts': const ['PortList'], 'getRetainingPath': const ['RetainingPath'], 'getProcessMemoryUsage': const ['ProcessMemoryUsage'], @@ -774,6 +776,40 @@ abstract class VmServiceInterface { int? count, }); + /// The `getPerfettoVMTimeline` RPC is used to retrieve an object which + /// contains a VM timeline trace represented in Perfetto's proto format. See + /// [PerfettoTimeline] for a detailed description of the response. + /// + /// The `timeOriginMicros` parameter is the beginning of the time range used + /// to filter timeline events. It uses the same monotonic clock as + /// dart:developer's `Timeline.now` and the VM embedding API's + /// `Dart_TimelineGetMicros`. See [VmServiceInterface.getVMTimelineMicros] for + /// access to this clock through the service protocol. + /// + /// The `timeExtentMicros` parameter specifies how large the time range used + /// to filter timeline events should be. + /// + /// For example, given `timeOriginMicros` and `timeExtentMicros`, only + /// timeline events from the following time range will be returned: + /// `(timeOriginMicros, timeOriginMicros + timeExtentMicros)`. + /// + /// If `getPerfettoVMTimeline` is invoked while the current recorder is + /// Callback, an [RPCError] with error code `114`, `invalid timeline request`, + /// will be returned as timeline events are handled by the embedder in this + /// mode. + /// + /// If `getPerfettoVMTimeline` is invoked while the current recorder is one of + /// Fuchsia or Macos or Systrace, an [RPCError] with error code `114`, + /// `invalid timeline request`, will be returned as timeline events are + /// handled by the OS in these modes. + /// + /// If `getPerfettoVMTimeline` is invoked while the current recorder is File + /// or Perfettofile, an [RPCError] with error code `114`, `invalid timeline + /// request`, will be returned as timeline events are written directly to a + /// file, and thus cannot be retrieved through the VM Service, in these modes. + Future getPerfettoVMTimeline( + {int? timeOriginMicros, int? timeExtentMicros}); + /// The `getPorts` RPC is used to retrieve the list of `ReceivePort` instances /// for a given isolate. /// @@ -917,7 +953,8 @@ abstract class VmServiceInterface { Future getVM(); /// The `getVMTimeline` RPC is used to retrieve an object which contains VM - /// timeline events. + /// timeline events. See [Timeline] for a detailed description of the + /// response. /// /// The `timeOriginMicros` parameter is the beginning of the time range used /// to filter timeline events. It uses the same monotonic clock as @@ -1557,6 +1594,12 @@ class VmServerConnection { count: params['count'], ); break; + case 'getPerfettoVMTimeline': + response = await _serviceImplementation.getPerfettoVMTimeline( + timeOriginMicros: params!['timeOriginMicros'], + timeExtentMicros: params['timeExtentMicros'], + ); + break; case 'getPorts': response = await _serviceImplementation.getPorts( params!['isolateId'], @@ -2119,6 +2162,14 @@ class VmService implements VmServiceInterface { if (count != null) 'count': count, }); + @override + Future getPerfettoVMTimeline( + {int? timeOriginMicros, int? timeExtentMicros}) => + _call('getPerfettoVMTimeline', { + if (timeOriginMicros != null) 'timeOriginMicros': timeOriginMicros, + if (timeExtentMicros != null) 'timeExtentMicros': timeExtentMicros, + }); + @override Future getPorts(String isolateId) => _call('getPorts', {'isolateId': isolateId}); @@ -7189,6 +7240,54 @@ class Parameter { '[Parameter parameterType: $parameterType, fixed: $fixed]'; } +/// See [VmServiceInterface.getPerfettoVMTimeline]; +class PerfettoTimeline extends Response { + static PerfettoTimeline? parse(Map? json) => + json == null ? null : PerfettoTimeline._fromJson(json); + + /// A Base64 string representing the requested timeline trace in Perfetto's + /// proto format. + String? trace; + + /// The start of the period of time covered by the trace. + int? timeOriginMicros; + + /// The duration of time covered by the trace. + int? timeExtentMicros; + + PerfettoTimeline({ + this.trace, + this.timeOriginMicros, + this.timeExtentMicros, + }); + + PerfettoTimeline._fromJson(Map json) + : super._fromJson(json) { + trace = json['trace'] ?? ''; + timeOriginMicros = json['timeOriginMicros'] ?? -1; + timeExtentMicros = json['timeExtentMicros'] ?? -1; + } + + @override + String get type => 'PerfettoTimeline'; + + @override + Map toJson() { + final json = {}; + json['type'] = type; + json.addAll({ + 'trace': trace ?? '', + 'timeOriginMicros': timeOriginMicros ?? -1, + 'timeExtentMicros': timeExtentMicros ?? -1, + }); + return json; + } + + @override + String toString() => '[PerfettoTimeline ' // + 'trace: $trace, timeOriginMicros: $timeOriginMicros, timeExtentMicros: $timeExtentMicros]'; +} + /// A `PortList` contains a list of ports associated with some isolate. /// /// See [VmServiceInterface.getPorts]. @@ -8226,6 +8325,7 @@ class Success extends Response { String toString() => '[Success]'; } +/// See [VmServiceInterface.getVMTimeline]; class Timeline extends Response { static Timeline? parse(Map? json) => json == null ? null : Timeline._fromJson(json); diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 62718f230c6..d13f02aec56 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -4094,6 +4094,77 @@ static void GetIsolateMetric(Thread* thread, JSONStream* js) { HandleNativeMetric(thread, js, id); } +enum GetVMTimelineResponseFormat { JSON = 0, Perfetto = 1 }; + +inline void GetVMTimelineCommon(GetVMTimelineResponseFormat format, + Thread* thread, + JSONStream* js) { + Isolate* isolate = thread->isolate(); + ASSERT(isolate != nullptr); + StackZone zone(thread); + Timeline::ReclaimCachedBlocksFromThreads(); + TimelineEventRecorder* timeline_recorder = Timeline::recorder(); + ASSERT(timeline_recorder != nullptr); + const char* name = timeline_recorder->name(); + if (strcmp(name, CALLBACK_RECORDER_NAME) == 0) { + js->PrintError(kInvalidTimelineRequest, + "A recorder of type \"%s\" is currently in use. As a " + "result, timeline events are handled by the embedder rather " + "than the VM.", + timeline_recorder->name()); + return; + } else if (strcmp(name, FUCHSIA_RECORDER_NAME) == 0 || + strcmp(name, SYSTRACE_RECORDER_NAME) == 0 || + strcmp(name, MACOS_RECORDER_NAME) == 0) { + js->PrintError( + kInvalidTimelineRequest, + "A recorder of type \"%s\" is currently in use. As a result, timeline " + "events are handled by the OS rather than the VM. See the VM service " + "documentation for more details on where timeline events can be found " + "for this recorder type.", + timeline_recorder->name()); + return; + } else if (strcmp(name, FILE_RECORDER_NAME) == 0 || + strcmp(name, PERFETTO_FILE_RECORDER_NAME) == 0) { + js->PrintError(kInvalidTimelineRequest, + "A recorder of type \"%s\" is currently in use. As a " + "result, timeline events are written directly to a file and " + "thus cannot be retrieved through the VM Service.", + timeline_recorder->name()); + return; + } + int64_t time_origin_micros = + Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); + int64_t time_extent_micros = + Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); + TimelineEventFilter filter(time_origin_micros, time_extent_micros); + if (format == GetVMTimelineResponseFormat::JSON) { + timeline_recorder->PrintJSON(js, &filter); + } else if (format == GetVMTimelineResponseFormat::Perfetto) { +#if defined(SUPPORT_PERFETTO) + // This branch will never be reached when SUPPORT_PERFETTO is not defined, + // because |GetPerfettoVMTimeline| is not defined when SUPPORT_PERFETTO is + // not defined. + timeline_recorder->PrintPerfettoTimeline(js, filter); +#else + UNREACHABLE(); +#endif // defined(SUPPORT_PERFETTO) + } +} + +#if defined(SUPPORT_PERFETTO) +static const MethodParameter* const get_perfetto_vm_timeline_params[] = { + NO_ISOLATE_PARAMETER, + new Int64Parameter("timeOriginMicros", /*required=*/false), + new Int64Parameter("timeExtentMicros", /*required=*/false), + nullptr, +}; + +static void GetPerfettoVMTimeline(Thread* thread, JSONStream* js) { + GetVMTimelineCommon(GetVMTimelineResponseFormat::Perfetto, thread, js); +} +#endif // defined(SUPPORT_PERFETTO) + static void SetVMTimelineFlags(Thread* thread, JSONStream* js) { #if !defined(SUPPORT_TIMELINE) PrintSuccess(js); @@ -4161,46 +4232,7 @@ static const MethodParameter* const get_vm_timeline_params[] = { }; static void GetVMTimeline(Thread* thread, JSONStream* js) { - Isolate* isolate = thread->isolate(); - ASSERT(isolate != nullptr); - StackZone zone(thread); - Timeline::ReclaimCachedBlocksFromThreads(); - TimelineEventRecorder* timeline_recorder = Timeline::recorder(); - ASSERT(timeline_recorder != nullptr); - const char* name = timeline_recorder->name(); - if (strcmp(name, CALLBACK_RECORDER_NAME) == 0) { - js->PrintError(kInvalidTimelineRequest, - "A recorder of type \"%s\" is currently in use. As a " - "result, timeline events are handled by the embedder rather " - "than the VM.", - timeline_recorder->name()); - return; - } else if (strcmp(name, FUCHSIA_RECORDER_NAME) == 0 || - strcmp(name, SYSTRACE_RECORDER_NAME) == 0 || - strcmp(name, MACOS_RECORDER_NAME) == 0) { - js->PrintError( - kInvalidTimelineRequest, - "A recorder of type \"%s\" is currently in use. As a result, timeline " - "events are handled by the OS rather than the VM. See the VM service " - "documentation for more details on where timeline events can be found " - "for this recorder type.", - timeline_recorder->name()); - return; - } else if (strcmp(name, FILE_RECORDER_NAME) == 0 || - strcmp(name, PERFETTO_FILE_RECORDER_NAME) == 0) { - js->PrintError(kInvalidTimelineRequest, - "A recorder of type \"%s\" is currently in use. As a " - "result, timeline events are written directly to a file and " - "thus cannot be retrieved through the VM Service.", - timeline_recorder->name()); - return; - } - int64_t time_origin_micros = - Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); - int64_t time_extent_micros = - Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); - TimelineEventFilter filter(time_origin_micros, time_extent_micros); - timeline_recorder->PrintJSON(js, &filter); + GetVMTimelineCommon(GetVMTimelineResponseFormat::JSON, thread, js); } static const char* const step_enum_names[] = { @@ -5771,6 +5803,10 @@ static const ServiceMethodDescriptor service_methods_[] = { get_instances_params }, { "getInstancesAsList", GetInstancesAsList, get_instances_as_list_params }, +#if defined(SUPPORT_PERFETTO) + { "getPerfettoVMTimeline", GetPerfettoVMTimeline, + get_perfetto_vm_timeline_params }, +#endif // defined(SUPPORT_PERFETTO) { "getPorts", GetPorts, get_ports_params }, { "getIsolate", GetIsolate, diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md index 48f3322c4e7..e290adbf1f3 100644 --- a/runtime/vm/service/service.md +++ b/runtime/vm/service/service.md @@ -1,8 +1,8 @@ -# Dart VM Service Protocol 4.4 +# Dart VM Service Protocol 4.5 > Please post feedback to the [observatory-discuss group][discuss-list] -This document describes of _version 4.4_ of the Dart VM Service Protocol. This +This document describes of _version 4.5_ of the Dart VM Service Protocol. This protocol is used to communicate with a running Dart Virtual Machine. To use the Service Protocol, start the VM with the *--observe* flag. @@ -1013,6 +1013,46 @@ Int32List, Int64List, Float32List, Float64List, Inst32x3List, Float32x4List, and Float64x2List. These parameters are otherwise ignored. +### getPerfettoVMTimeline + +``` +PerfettoTimeline getPerfettoVMTimeline(int timeOriginMicros [optional], + int timeExtentMicros [optional]) +``` + +The _getPerfettoVMTimeline_ RPC is used to retrieve an object which contains a +VM timeline trace represented in Perfetto's proto format. See +[PerfettoTimeline](#perfettotimeline) for a detailed description of the +response. + +The _timeOriginMicros_ parameter is the beginning of the time range used to +filter timeline events. It uses the same monotonic clock as dart:developer's +`Timeline.now` and the VM embedding API's `Dart_TimelineGetMicros`. See +[getVMTimelineMicros](#getvmtimelinemicros) for access to this clock through the +service protocol. + +The _timeExtentMicros_ parameter specifies how large the time range used to +filter timeline events should be. + +For example, given _timeOriginMicros_ and _timeExtentMicros_, only timeline +events from the following time range will be returned: +`(timeOriginMicros, timeOriginMicros + timeExtentMicros)`. + +If _getPerfettoVMTimeline_ is invoked while the current recorder is Callback, an +[RPC error](#rpc-error) with error code _114_, `invalid timeline request`, will +be returned as timeline events are handled by the embedder in this mode. + +If _getPerfettoVMTimeline_ is invoked while the current recorder is one of +Fuchsia or Macos or Systrace, an [RPC error](#rpc-error) with error code _114_, +`invalid timeline request`, will be returned as timeline events are handled by +the OS in these modes. + +If _getPerfettoVMTimeline_ is invoked while the current recorder is File or +Perfettofile, an [RPC error](#rpc-error) with error code _114_, +`invalid timeline request`, will be returned as timeline events are written +directly to a file, and thus cannot be retrieved through the VM Service, in +these modes. + ### getPorts ``` @@ -1192,7 +1232,7 @@ Timeline getVMTimeline(int timeOriginMicros [optional], ``` The _getVMTimeline_ RPC is used to retrieve an object which contains VM timeline -events. +events. See [Timeline](#timeline) for a detailed description of the response. The _timeOriginMicros_ parameter is the beginning of the time range used to filter timeline events. It uses the same monotonic clock as dart:developer's `Timeline.now` @@ -3746,6 +3786,24 @@ A _Parameter_ is a representation of a function parameter. See [Instance](#instance). +### PerfettoTimeline + +``` +class PerfettoTimeline extends Response { + // A Base64 string representing the requested timeline trace in Perfetto's + // proto format. + string trace; + + // The start of the period of time covered by the trace. + int timeOriginMicros; + + // The duration of time covered by the trace. + int timeExtentMicros; +} +``` + +See [getPerfettoVMTimeline](#getperfettovmtimeline); + ### PortList ``` @@ -4254,6 +4312,8 @@ class Timeline extends Response { } ``` +See [getVMTimeline](#getvmtimeline); + ### TimelineEvent ``` @@ -4524,5 +4584,6 @@ version | comments 4.2 | Added `getInstancesAsList` RPC. 4.3 | Added `isSealed`, `isMixinClass`, `isBaseClass`, `isInterfaceClass`, and `isFinal` properties to `Class`. 4.4 | Added `label` property to `@Instance`. Added `UserTag` to `InstanceKind`. +4.5 | Added `getPerfettoVMTimeline` RPC. [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc index 2fbd8435572..d884ef71184 100644 --- a/runtime/vm/timeline.cc +++ b/runtime/vm/timeline.cc @@ -84,7 +84,16 @@ inline void PopulateProcessDescriptorPacket( } inline const std::tuple, intptr_t> -GetProtoPreamble(const intptr_t size) { +GetProtoPreamble( + protozero::HeapBuffered* packet) { + ASSERT(packet != nullptr); + + intptr_t size = 0; + for (const protozero::ScatteredHeapBuffer::Slice& slice : + packet->GetSlices()) { + size += slice.size() - slice.unused_bytes(); + } + std::unique_ptr preamble = std::make_unique(perfetto::TracePacket::kMaxPreambleBytes); uint8_t* ptr = &preamble[0]; @@ -100,6 +109,23 @@ GetProtoPreamble(const intptr_t size) { return std::make_tuple(std::move(preamble), preamble_size); } +inline void AppendPacketToJSONBase64String( + JSONBase64String* jsonBase64String, + protozero::HeapBuffered* packet) { + ASSERT(jsonBase64String != nullptr); + ASSERT(packet != nullptr); + + auto& 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); + for (const protozero::ScatteredHeapBuffer::Slice& slice : + packet->GetSlices()) { + jsonBase64String->AppendBytes(slice.start(), + slice.size() - slice.unused_bytes()); + } +} + } // namespace perfetto_utils #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) @@ -1350,7 +1376,42 @@ void TimelineEventRecorder::PrintJSONMeta(const JSONArray& jsarr_events) { value->PrintJSON(jsarr_events); } } -#endif + +#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(); + + 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() { // Grab the current thread. @@ -1432,6 +1493,15 @@ void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) { if (event == nullptr) { return; } +#if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) + // 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. + if (event->event_type() == TimelineEvent::kAsyncBegin || + event->event_type() == TimelineEvent::kAsyncInstant) { + AddAsyncTrackMetadataBasedOnEvent(*event); + } +#endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) // Grab the current thread. OSThread* thread = OSThread::Current(); ASSERT(thread != nullptr); @@ -1625,6 +1695,19 @@ void TimelineEventFixedBufferRecorder::PrintJSONEvents( }); } +#if defined(SUPPORT_PERFETTO) +void TimelineEventFixedBufferRecorder::PrintPerfettoEvents( + JSONBase64String* jsonBase64String, + const TimelineEventFilter& filter) { + PrintEventsCommon(filter, [this, + &jsonBase64String](const TimelineEvent& event) { + event.PopulateTracePacket(packet().get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet()); + packet().Reset(); + }); +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventFixedBufferRecorder::PrintJSON(JSONStream* js, TimelineEventFilter* filter) { JSONObject topLevel(js); @@ -1638,6 +1721,29 @@ void TimelineEventFixedBufferRecorder::PrintJSON(JSONStream* js, topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); } +#define PRINT_PERFETTO_TIMELINE_BODY \ + JSONObject jsobj_topLevel(js); \ + jsobj_topLevel.AddProperty("type", "PerfettoTimeline"); \ + \ + js->AppendSerializedObject("\"trace\":"); \ + { \ + JSONBase64String jsonBase64String(js); \ + PrintPerfettoMeta(&jsonBase64String); \ + PrintPerfettoEvents(&jsonBase64String, filter); \ + } \ + \ + jsobj_topLevel.AddPropertyTimeMicros("timeOriginMicros", \ + TimeOriginMicros()); \ + jsobj_topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); + +#if defined(SUPPORT_PERFETTO) +void TimelineEventFixedBufferRecorder::PrintPerfettoTimeline( + JSONStream* js, + const TimelineEventFilter& filter) { + PRINT_PERFETTO_TIMELINE_BODY +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventFixedBufferRecorder::PrintTraceEvent( JSONStream* js, TimelineEventFilter* filter) { @@ -1645,7 +1751,7 @@ void TimelineEventFixedBufferRecorder::PrintTraceEvent( PrintJSONMeta(events); PrintJSONEvents(events, *filter); } -#endif +#endif // !defined(PRODUCT) TimelineEventBlock* TimelineEventFixedBufferRecorder::GetHeadBlockLocked() { return &blocks_[0]; @@ -1719,12 +1825,20 @@ void TimelineEventCallbackRecorder::PrintJSON(JSONStream* js, UNREACHABLE(); } +#if defined(SUPPORT_PERFETTO) +void TimelineEventCallbackRecorder::PrintPerfettoTimeline( + JSONStream* js, + const TimelineEventFilter& filter) { + UNREACHABLE(); +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventCallbackRecorder::PrintTraceEvent( JSONStream* js, TimelineEventFilter* filter) { JSONArray events(js); } -#endif +#endif // !defined(PRODUCT) TimelineEvent* TimelineEventCallbackRecorder::StartEvent() { TimelineEvent* event = new TimelineEvent(); @@ -1811,12 +1925,20 @@ void TimelineEventPlatformRecorder::PrintJSON(JSONStream* js, UNREACHABLE(); } +#if defined(SUPPORT_PERFETTO) +void TimelineEventPlatformRecorder::PrintPerfettoTimeline( + JSONStream* js, + const TimelineEventFilter& filter) { + UNREACHABLE(); +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventPlatformRecorder::PrintTraceEvent( JSONStream* js, TimelineEventFilter* filter) { JSONArray events(js); } -#endif +#endif // !defined(PRODUCT) TimelineEvent* TimelineEventPlatformRecorder::StartEvent() { TimelineEvent* event = new TimelineEvent(); @@ -2028,13 +2150,8 @@ TimelineEventPerfettoFileRecorder::~TimelineEventPerfettoFileRecorder() { void TimelineEventPerfettoFileRecorder::WritePacket( protozero::HeapBuffered* packet) const { - intptr_t size = 0; - for (const protozero::ScatteredHeapBuffer::Slice& slice : - packet->GetSlices()) { - size += slice.size() - slice.unused_bytes(); - } const std::tuple, intptr_t>& response = - perfetto_utils::GetProtoPreamble(size); + perfetto_utils::GetProtoPreamble(packet); Write(reinterpret_cast(std::get<0>(response).get()), std::get<1>(response)); for (const protozero::ScatteredHeapBuffer::Slice& slice : @@ -2093,6 +2210,19 @@ void TimelineEventEndlessRecorder::PrintJSONEvents( }); } +#if defined(SUPPORT_PERFETTO) +void TimelineEventEndlessRecorder::PrintPerfettoEvents( + JSONBase64String* jsonBase64String, + const TimelineEventFilter& filter) { + PrintEventsCommon(filter, [this, + &jsonBase64String](const TimelineEvent& event) { + event.PopulateTracePacket(packet().get()); + perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet()); + packet().Reset(); + }); +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js, TimelineEventFilter* filter) { JSONObject topLevel(js); @@ -2106,6 +2236,14 @@ void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js, topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); } +#if defined(SUPPORT_PERFETTO) +void TimelineEventEndlessRecorder::PrintPerfettoTimeline( + JSONStream* js, + const TimelineEventFilter& filter) { + PRINT_PERFETTO_TIMELINE_BODY +} +#endif // defined(SUPPORT_PERFETTO) + void TimelineEventEndlessRecorder::PrintTraceEvent( JSONStream* js, TimelineEventFilter* filter) { @@ -2113,7 +2251,7 @@ void TimelineEventEndlessRecorder::PrintTraceEvent( PrintJSONMeta(events); PrintJSONEvents(events, *filter); } -#endif +#endif // !defined(PRODUCT) TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() { return head_; diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h index 2cabab139f4..01aa27d41d7 100644 --- a/runtime/vm/timeline.h +++ b/runtime/vm/timeline.h @@ -49,6 +49,7 @@ namespace dart { #endif // !defined(SUPPORT_TIMELINE) class JSONArray; +class JSONBase64String; class JSONObject; class JSONStream; class JSONWriter; @@ -889,8 +890,15 @@ class TimelineEventRecorder : public MallocAllocated { // Interface method(s) which must be implemented. #ifndef PRODUCT virtual void PrintJSON(JSONStream* js, TimelineEventFilter* filter) = 0; +#if defined(SUPPORT_PERFETTO) + /* + * Prints a PerfettoTimeline service response into |js|. + */ + virtual void PrintPerfettoTimeline(JSONStream* js, + const TimelineEventFilter& filter) = 0; +#endif // defined(SUPPORT_PERFETTO) virtual void PrintTraceEvent(JSONStream* js, TimelineEventFilter* filter) = 0; -#endif +#endif // !defined(PRODUCT) virtual const char* name() const = 0; virtual intptr_t Size() = 0; TimelineEventBlock* GetNewBlock(); @@ -929,7 +937,14 @@ class TimelineEventRecorder : public MallocAllocated { // Utility method(s). #ifndef PRODUCT void PrintJSONMeta(const JSONArray& jsarr_events); -#endif +#if defined(SUPPORT_PERFETTO) + /* + * Appends metadata about the timeline in Perfetto's proto format to + * |jsonBase64String|. + */ + void PrintPerfettoMeta(JSONBase64String* jsonBase64String); +#endif // defined(SUPPORT_PERFETTO) +#endif // !defined(PRODUCT) TimelineEvent* ThreadBlockStartEvent(); void ThreadBlockCompleteEvent(TimelineEvent* event); @@ -972,8 +987,12 @@ class TimelineEventFixedBufferRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSON(JSONStream* js, TimelineEventFilter* filter) final; +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoTimeline(JSONStream* js, + const TimelineEventFilter& filter) final; +#endif // defined(SUPPORT_PERFETTO) void PrintTraceEvent(JSONStream* js, TimelineEventFilter* filter) final; -#endif +#endif // !defined(PRODUCT) intptr_t Size(); @@ -987,7 +1006,11 @@ class TimelineEventFixedBufferRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSONEvents(const JSONArray& array, const TimelineEventFilter& filter); -#endif +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoEvents(JSONBase64String* jsonBase64String, + const TimelineEventFilter& filter); +#endif // defined(SUPPORT_PERFETTO) +#endif // !defined(PRODUCT) VirtualMemory* memory_; TimelineEventBlock* blocks_; @@ -1040,8 +1063,12 @@ class TimelineEventCallbackRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSON(JSONStream* js, TimelineEventFilter* filter) final; +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoTimeline(JSONStream* js, + const TimelineEventFilter& filter) final; +#endif // defined(SUPPORT_PERFETTO) void PrintTraceEvent(JSONStream* js, TimelineEventFilter* filter) final; -#endif +#endif // !defined(PRODUCT) // Called when |event| is completed. It is unsafe to keep a reference to // |event| as it may be freed as soon as this function returns. @@ -1090,8 +1117,12 @@ class TimelineEventEndlessRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSON(JSONStream* js, TimelineEventFilter* filter) final; +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoTimeline(JSONStream* js, + const TimelineEventFilter& filter) final; +#endif // defined(SUPPORT_PERFETTO) void PrintTraceEvent(JSONStream* js, TimelineEventFilter* filter) final; -#endif +#endif // !defined(PRODUCT) const char* name() const { return ENDLESS_RECORDER_NAME; } intptr_t Size() { return block_index_ * sizeof(TimelineEventBlock); } @@ -1106,7 +1137,11 @@ class TimelineEventEndlessRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSONEvents(const JSONArray& array, const TimelineEventFilter& filter); -#endif +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoEvents(JSONBase64String* jsonBase64String, + const TimelineEventFilter& filter); +#endif // defined(SUPPORT_PERFETTO) +#endif // !defined(PRODUCT) TimelineEventBlock* head_; TimelineEventBlock* tail_; @@ -1132,8 +1167,12 @@ class TimelineEventPlatformRecorder : public TimelineEventRecorder { #ifndef PRODUCT void PrintJSON(JSONStream* js, TimelineEventFilter* filter) final; +#if defined(SUPPORT_PERFETTO) + void PrintPerfettoTimeline(JSONStream* js, + const TimelineEventFilter& filter) final; +#endif // defined(SUPPORT_PERFETTO) void PrintTraceEvent(JSONStream* js, TimelineEventFilter* filter) final; -#endif +#endif // !defined(PRODUCT) // Called when |event| is completed. It is unsafe to keep a reference to // |event| as it may be freed as soon as this function returns.