blob: 24a5daf9de5a807c5e9ab326d0ac1a97943d5f77 [file] [log] [blame]
// Copyright (c) 2019, 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:async';
import 'package:logging/logging.dart';
import 'package:observatory/sample_profile.dart';
import 'package:observatory/models.dart' as M;
import 'package:observatory/service.dart' as S;
class TimelineRepositoryBase {
static const _kStackFrames = 'stackFrames';
static const _kTraceEvents = 'traceEvents';
static const kTimeOriginMicros = 'timeOriginMicros';
static const kTimeExtentMicros = 'timeExtentMicros';
Future<void> _formatSamples(M.Isolate isolate, Map traceObject,
Future<S.ServiceObject> cpuSamples) async {
const kRootFrameId = 0;
final profile = SampleProfile();
await profile.load(
isolate as S.ServiceObjectOwner, await cpuSamples as S.ServiceMap);
final trie = profile.loadFunctionTree(M.ProfileTreeDirection.inclusive);
final root = trie.root;
int nextId = kRootFrameId;
processFrame(FunctionCallTreeNode current, FunctionCallTreeNode? parent) {
int id = nextId;
++nextId;
current.frameId = id;
// Skip the root.
if (id != kRootFrameId) {
final function = current.profileFunction.function;
final key = '${isolate.id}-$id';
traceObject[_kStackFrames][key] = {
'category': 'Dart',
'name': function.qualifiedName,
'resolvedUrl': current.profileFunction.resolvedUrl,
if (parent != null && parent.frameId != kRootFrameId)
'parent': '${isolate.id}-${parent.frameId}',
};
}
for (final child in current.children) {
processFrame(child, current);
}
}
processFrame(root, null);
for (final sample in profile.samples) {
FunctionCallTreeNode trie = sample[SampleProfile.kTimelineFunctionTrie];
if (trie.frameId != kRootFrameId) {
traceObject[_kTraceEvents].add({
'ph': 'P', // kind = sample event
'name': '', // Blank to keep about:tracing happy
'pid': profile.pid,
'tid': sample['tid'],
'ts': sample['timestamp'],
'cat': 'Dart',
'sf': '${isolate.id}-${trie.frameId}',
});
}
}
}
Future<Map> getCpuProfileTimeline(M.VMRef ref,
{int? timeOriginMicros, int? timeExtentMicros}) async {
final S.VM vm = ref as S.VM;
final traceObject = <String, dynamic>{
_kStackFrames: {},
_kTraceEvents: [],
};
await Future.wait(vm.isolates.map((isolate) {
final samples = vm.invokeRpc('getCpuSamples', {
'isolateId': isolate.id,
if (timeOriginMicros != null) kTimeOriginMicros: timeOriginMicros,
if (timeExtentMicros != null) kTimeExtentMicros: timeExtentMicros,
});
return _formatSamples(isolate, traceObject, samples);
}));
return traceObject;
}
Future<Map> getTimeline(M.VMRef ref) async {
final S.VM vm = ref as S.VM;
final S.ServiceMap vmTimelineResponse =
await vm.invokeRpc('getVMTimeline', {}) as S.ServiceMap;
final timeOriginMicros = vmTimelineResponse[kTimeOriginMicros];
final timeExtentMicros = vmTimelineResponse[kTimeExtentMicros];
var traceObject = <dynamic, dynamic>{
_kStackFrames: {},
_kTraceEvents: [],
};
try {
final cpuProfile = await getCpuProfileTimeline(
vm,
timeOriginMicros: timeOriginMicros,
timeExtentMicros: timeExtentMicros,
);
traceObject = cpuProfile;
} on S.ServerRpcException catch (e) {
if (e.code != S.ServerRpcException.kFeatureDisabled) {
rethrow;
}
Logger.root.info(
"CPU profiler is disabled. Creating timeline without CPU profile.");
}
traceObject[_kTraceEvents].addAll(vmTimelineResponse[_kTraceEvents]);
return traceObject;
}
}