blob: 656d02314ebf1130b04761730a07b3129ca1f621 [file] [log] [blame]
// Copyright (c) 2024, 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:developer' as dev;
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'common/test_helper.dart';
void primeTimeline() {
dev.Timeline.startSync('apple');
dev.Timeline.instantSync('ISYNC', arguments: {'fruit': 'banana'});
dev.Timeline.finishSync();
final parentTask = dev.TimelineTask.withTaskId(42);
final task = dev.TimelineTask(parent: parentTask, filterKey: 'testFilter');
task.start('TASK1', arguments: {'task1-start-key': 'task1-start-value'});
task.instant(
'ITASK',
arguments: {'task1-instant-key': 'task1-instant-value'},
);
task.finish(arguments: {'task1-finish-key': 'task1-finish-value'});
final flow = dev.Flow.begin(id: 123);
dev.Timeline.startSync('peach', flow: flow);
dev.Timeline.finishSync();
dev.Timeline.startSync('watermelon', flow: dev.Flow.step(flow.id));
dev.Timeline.finishSync();
dev.Timeline.startSync('pear', flow: dev.Flow.end(flow.id));
dev.Timeline.finishSync();
}
List<TimelineEvent> filterForDartEvents(List<TimelineEvent> events) {
return events.where((event) => event.json!['cat'] == 'Dart').toList();
}
bool mapContains(Map<String, dynamic> map, Map submap) {
for (final key in submap.keys) {
if (map[key] != submap[key]) {
return false;
}
}
return true;
}
bool eventsContains(
List<TimelineEvent> events,
String phase,
String name, [
Map? arguments,
]) {
for (final timelineEvent in events) {
final event = timelineEvent.json!;
if ((event['ph'] == phase) && (event['name'] == name)) {
if (arguments == null) {
return true;
} else if (mapContains(event['args'], arguments)) {
return true;
}
}
}
return false;
}
int timeOrigin(List<TimelineEvent> events) {
if (events.isEmpty) {
return 0;
}
int smallest = events.first.json!['ts'];
for (final timelineEvent in events) {
final event = timelineEvent.json!;
if (event['ts'] < smallest) {
smallest = event['ts'];
}
}
return smallest;
}
int timeDuration(List<TimelineEvent> events, int timeOrigin) {
if (events.isEmpty) {
return 0;
}
int biggestDuration = events.first.json!['ts'] - timeOrigin;
for (final timelineEvent in events) {
final event = timelineEvent.json!;
final duration = event['ts'] - timeOrigin;
if (duration > biggestDuration) {
biggestDuration = duration;
}
}
return biggestDuration;
}
void allEventsHaveIsolateNumber(List<TimelineEvent> events) {
for (final timelineEvent in events) {
final event = timelineEvent.json!;
if (event['ph'] == 'M') {
// Skip meta-data events.
continue;
}
if (event['name'] == 'Runnable' && event['ph'] == 'i') {
// Skip Runnable events which don't have an isolate.
continue;
}
if (event['cat'] == 'VM') {
// Skip VM category events which don't have an isolate.
continue;
}
if (event['cat'] == 'API') {
// Skip API category events which sometimes don't have an isolate.
continue;
}
if (event['cat'] == 'Embedder' &&
(event['name'] == 'DFE::ReadScript' ||
event['name'] == 'CreateIsolateGroupAndSetupHelper' ||
event['name'] == 'CreateAndSetupDartDevIsolate')) {
continue;
}
final arguments = event['args'];
expect(arguments, isA<Map>());
expect(arguments['isolateGroupId'], isA<String>());
if (!const ['GC', 'Compiler', 'CompilerVerbose'].contains(event['cat']) &&
!const [
'FinishTopLevelClassLoading',
'FinishClassLoading',
'ProcessPendingClasses',
].contains(event['name'])) {
expect(arguments['isolateId'], isA<String>());
}
}
}
final tests = <VMTest>[
(VmService service) async {
var timeline = await service.getVMTimeline();
var traceEvents = timeline.traceEvents!;
final int numEvents = traceEvents.length;
final dartEvents = filterForDartEvents(traceEvents);
expect(dartEvents.length, greaterThanOrEqualTo(11));
allEventsHaveIsolateNumber(dartEvents);
allEventsHaveIsolateNumber(traceEvents);
expect(
eventsContains(dartEvents, 'i', 'ISYNC', {'fruit': 'banana'}),
isTrue,
);
expect(eventsContains(dartEvents, 'B', 'apple'), isTrue);
expect(eventsContains(dartEvents, 'E', 'apple'), isTrue);
expect(
eventsContains(
dartEvents,
'b',
'TASK1',
{
'filterKey': 'testFilter',
'task1-start-key': 'task1-start-value',
'parentId': 42.toRadixString(16),
},
),
isTrue,
);
expect(
eventsContains(
dartEvents,
'e',
'TASK1',
{
'filterKey': 'testFilter',
'task1-finish-key': 'task1-finish-value',
},
),
isTrue,
);
expect(
eventsContains(
dartEvents,
'n',
'ITASK',
{
'filterKey': 'testFilter',
'task1-instant-key': 'task1-instant-value',
},
),
isTrue,
);
expect(eventsContains(dartEvents, 'q', 'ITASK'), isFalse);
expect(eventsContains(dartEvents, 'B', 'peach'), isTrue);
expect(eventsContains(dartEvents, 'E', 'peach'), isTrue);
expect(eventsContains(dartEvents, 'B', 'watermelon'), isTrue);
expect(eventsContains(dartEvents, 'E', 'watermelon'), isTrue);
expect(eventsContains(dartEvents, 'B', 'pear'), isTrue);
expect(eventsContains(dartEvents, 'E', 'pear'), isTrue);
expect(eventsContains(dartEvents, 's', '123'), isTrue);
expect(eventsContains(dartEvents, 't', '123'), isTrue);
expect(eventsContains(dartEvents, 'f', '123'), isTrue);
// Calculate the time Window of Dart events.
final origin = timeOrigin(dartEvents);
final extent = timeDuration(dartEvents, origin);
// Query for the timeline with the time window for Dart events.
timeline = await service.getVMTimeline(
timeOriginMicros: origin,
timeExtentMicros: extent,
);
traceEvents = timeline.traceEvents!;
// Verify that we received fewer events than before.
expect(traceEvents.length, lessThan(numEvents));
// Verify that we have the same number of Dart events.
final dartEvents2 = filterForDartEvents(traceEvents);
expect(dartEvents2.length, dartEvents.length);
},
];
void main([args = const <String>[]]) {
// NOTE: the original service test compiled the target program to kernel
// first, with the following comment providing an explanation:
//
// We first compile the testee to kernel and run the subprocess on the kernel
// file. That avoids cases where the testee has to run a lot of code in the
// kernel-isolate (e.g. due to ia32's kernel-service not being app-jit
// trained). We do that because otherwise the --complete-timeline will
// collect a lot of data, possibly leading to OOMs or timeouts.
//
// If this test times out on ia32, we may need to do the same.
runVMTests(
args,
tests,
'get_vm_timeline_rpc_test.dart',
testeeBefore: primeTimeline,
extraArgs: ['--complete-timeline'],
);
}