Support the dart:developer timeline APIs in dart2js and DDC.

Exposes timeline events in the Chrome DevTools performance panel using the  Web APIs performance.mark() and performance.measure(): https://developer.mozilla.org/en-US/docs/Web/API/Performance

CoreLibraryReviewExempt: Only change in sdk/lib is updating a comment.
Bug: https://github.com/flutter/devtools/issues/4652
Change-Id: I4f934bcffeb2920ffaf9b7b3a67fc5fc3b814294
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/310974
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Elliott Brooks <elliottbrooks@google.com>
This commit is contained in:
Elliott Brooks 2023-06-30 22:38:11 +00:00 committed by Commit Queue
parent 73160bc40d
commit af8fb2cd73
4 changed files with 202 additions and 13 deletions

View file

@ -12,6 +12,15 @@ import 'dart:async';
import 'dart:convert' show json;
import 'dart:isolate';
// These values must be kept in sync with developer/timeline.dart.
const int _beginPatch = 1;
const int _endPatch = 2;
const int _asyncBeginPatch = 5;
const int _asyncEndPatch = 7;
const int _flowBeginPatch = 9;
const int _flowStepPatch = 10;
const int _flowEndPatch = 11;
var _issuedRegisterExtensionWarning = false;
var _issuedPostEventWarning = false;
final _developerSupportWarning = 'from dart:developer is only supported in '
@ -140,26 +149,110 @@ void _postEvent(String eventKind, String eventData) {
@patch
bool _isDartStreamEnabled() {
return false;
return true;
}
@patch
int _getTraceClock() {
// TODO.
return _clockValue++;
// Note: Use `millisecondsSinceEpoch` instead of `microsecondsSinceEpoch`
// because JS isn't able to hold the value of `microsecondsSinceEpoch` without
// rounding errors.
return DateTime.now().millisecondsSinceEpoch;
}
int _clockValue = 0;
int _taskId = 1;
@patch
int _getNextTaskId() {
return 0;
return _taskId++;
}
bool _isBeginEvent(int type) => type == _beginPatch || type == _asyncBeginPatch;
bool _isEndEvent(int type) => type == _endPatch || type == _asyncEndPatch;
bool _isUnsupportedEvent(int type) =>
type == _flowBeginPatch || type == _flowEndPatch || type == _flowStepPatch;
String _createEventName({
required int taskId,
required String name,
required bool isBeginEvent,
required bool isEndEvent,
}) {
if (isBeginEvent) {
return '$taskId-$name-begin';
}
if (isEndEvent) {
return '$taskId-$name-end';
}
// Return only the name for events that don't need measurements:
return name;
}
Map<String, int> _eventNameToCount = {};
String _postfixWithCount(String eventName) {
final count = _eventNameToCount[eventName];
if (count == null) return eventName;
return '$eventName-$count';
}
void _incrementEventCount(String eventName) {
final currentCount = _eventNameToCount[eventName] ?? 0;
_eventNameToCount[eventName] = currentCount + 1;
}
void _decrementEventCount(String eventName) {
if (!_eventNameToCount.containsKey(eventName)) return;
final newCount = _eventNameToCount[eventName]! - 1;
if (newCount <= 0) {
_eventNameToCount.remove(eventName);
} else {
_eventNameToCount[eventName] = newCount;
}
}
@patch
void _reportTaskEvent(
int taskId, int flowId, int type, String name, String argumentsAsJson) {
// TODO.
// Ignore any unsupported events.
if (_isUnsupportedEvent(type)) return;
final isBeginEvent = _isBeginEvent(type);
final isEndEvent = _isEndEvent(type);
var currentEventName = _createEventName(
taskId: taskId,
name: name,
isBeginEvent: isBeginEvent,
isEndEvent: isEndEvent,
);
// Postfix the event name with the current count of events with that name. This
// guarantees that we are always measuring from the most recent begin event.
if (isBeginEvent) {
_incrementEventCount(currentEventName);
currentEventName = _postfixWithCount(currentEventName);
}
final markOptions = JS('', '{detail: JSON.parse(#)}', argumentsAsJson);
// Start by creating a mark event.
JS('', 'performance.mark(#, #)', currentEventName, markOptions);
// If it's an end event, then create a measurement from the most recent begin
// event with the same name.
if (isEndEvent) {
final beginEventName = _createEventName(
taskId: taskId, name: name, isBeginEvent: true, isEndEvent: false);
JS(
'',
'performance.measure(#, #, #)',
name,
_postfixWithCount(beginEventName),
currentEventName,
);
_decrementEventCount(beginEventName);
}
}
@patch

View file

@ -9,6 +9,15 @@ import 'dart:_foreign_helper' show JS;
import 'dart:async' show Zone;
import 'dart:isolate';
// These values must be kept in sync with developer/timeline.dart.
const int _beginPatch = 1;
const int _endPatch = 2;
const int _asyncBeginPatch = 5;
const int _asyncEndPatch = 7;
const int _flowBeginPatch = 9;
const int _flowStepPatch = 10;
const int _flowEndPatch = 11;
@patch
@pragma('dart2js:tryInline')
bool debugger({bool when = true, String? message}) {
@ -60,26 +69,110 @@ void _postEvent(String eventKind, String eventData) {
@patch
bool _isDartStreamEnabled() {
return false;
return true;
}
@patch
int _getTraceClock() {
// TODO.
return _clockValue++;
// Note: Use `millisecondsSinceEpoch` instead of `microsecondsSinceEpoch`
// because JS isn't able to hold the value of `microsecondsSinceEpoch` without
// rounding errors.
return DateTime.now().millisecondsSinceEpoch;
}
int _clockValue = 0;
int _taskId = 1;
@patch
int _getNextTaskId() {
return 0;
return _taskId++;
}
bool _isBeginEvent(int type) => type == _beginPatch || type == _asyncBeginPatch;
bool _isEndEvent(int type) => type == _endPatch || type == _asyncEndPatch;
bool _isUnsupportedEvent(int type) =>
type == _flowBeginPatch || type == _flowEndPatch || type == _flowStepPatch;
String _createEventName({
required int taskId,
required String name,
required bool isBeginEvent,
required bool isEndEvent,
}) {
if (isBeginEvent) {
return '$taskId-$name-begin';
}
if (isEndEvent) {
return '$taskId-$name-end';
}
// Return only the name for events that don't need measurements:
return name;
}
Map<String, int> _eventNameToCount = {};
String _postfixWithCount(String eventName) {
final count = _eventNameToCount[eventName];
if (count == null) return eventName;
return '$eventName-$count';
}
void _incrementEventCount(String eventName) {
final currentCount = _eventNameToCount[eventName] ?? 0;
_eventNameToCount[eventName] = currentCount + 1;
}
void _decrementEventCount(String eventName) {
if (!_eventNameToCount.containsKey(eventName)) return;
final newCount = _eventNameToCount[eventName]! - 1;
if (newCount <= 0) {
_eventNameToCount.remove(eventName);
} else {
_eventNameToCount[eventName] = newCount;
}
}
@patch
void _reportTaskEvent(
int taskId, int flowId, int type, String name, String argumentsAsJson) {
// TODO.
// Ignore any unsupported events.
if (_isUnsupportedEvent(type)) return;
final isBeginEvent = _isBeginEvent(type);
final isEndEvent = _isEndEvent(type);
var currentEventName = _createEventName(
taskId: taskId,
name: name,
isBeginEvent: isBeginEvent,
isEndEvent: isEndEvent,
);
// Postfix the event name with the current count of events with that name. This
// guarantees that we are always measuring from the most recent begin event.
if (isBeginEvent) {
_incrementEventCount(currentEventName);
currentEventName = _postfixWithCount(currentEventName);
}
final markOptions = JS('', '{detail: JSON.parse(#)}', argumentsAsJson);
// Start by creating a mark event.
JS('', 'performance.mark(#, #)', currentEventName, markOptions);
// If it's an end event, then create a measurement from the most recent begin
// event with the same name.
if (isEndEvent) {
final beginEventName = _createEventName(
taskId: taskId, name: name, isBeginEvent: true, isEndEvent: false);
JS(
'',
'performance.measure(#, #, #)',
name,
_postfixWithCount(beginEventName),
currentEventName,
);
_decrementEventCount(beginEventName);
}
}
@patch

View file

@ -17,7 +17,9 @@ typedef TimelineSyncFunction<T> = T Function();
typedef Future TimelineAsyncFunction();
// These values must be kept in sync with the enum "EventType" in
// runtime/vm/timeline.h.
// runtime/vm/timeline.h, along with the JS-specific implementations in:
// - _internal/js_runtime/lib/developer_patch.dart
// - _internal/js_dev_runtime/patch/developer_patch.dart
const int _begin = 1;
const int _end = 2;
const int _instant = 4;

View file

@ -26,6 +26,7 @@ developer/timeline_test: Skip # Not supported
isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
[ $runtime == d8 ]
developer/timeline_recorders_test: SkipByDesign # https://bugs.chromium.org/p/v8/issues/detail?id=14129
js/export/static_interop_mock/proto_test: SkipByDesign # Uses dart:html.
js/js_util/javascriptobject_extensions_test: SkipByDesign # Uses dart:html.
js/static_interop_test/constants_test: SkipByDesign # Uses dart:html.