Timeline service protocol support with Observatory UI

Service Protocol:
- _clearVMTimeline (clear timeline)
- _getVMTimeline (fetch timeline)
- _setVMTimelineFlag (control recording)
- _getVMTimelineFlag (...)
- _getVMTimeline service unit test

Observatory:
- timeline page with record on/off, clear timeline, and refresh buttons.
- trace-viewer based timeline view that runs in an iframe driven by a small javascript program

Misc:
- Fix default enable logic bugs
- Add 'Debugger Pause' timeline event
- Threads can have a name and that name will be displayed in Observatory
- Tighten up locking when printing JSON

R=rmacnak@google.com

Review URL: https://codereview.chromium.org/1406413006 .
This commit is contained in:
John McCutchan 2015-11-10 08:02:22 -08:00
parent 686faebf70
commit 43b0ae00e7
29 changed files with 7593 additions and 226 deletions

View file

@ -894,20 +894,23 @@ DART_EXPORT int64_t Dart_TimelineGetMicros();
#define DART_TIMELINE_STREAM_COMPILER (1 << 1)
/** Timeline stream for Dart provided events */
#define DART_TIMELINE_STREAM_DART (1 << 2)
/** Timeline stream for debugger provided events */
#define DART_TIMELINE_STREAM_DEBUGGER (1 << 3)
/** Timeline stream for embedder provided events */
#define DART_TIMELINE_STREAM_EMBEDDER (1 << 3)
#define DART_TIMELINE_STREAM_EMBEDDER (1 << 4)
/** Timeline stream for GC events */
#define DART_TIMELINE_STREAM_GC (1 << 4)
#define DART_TIMELINE_STREAM_GC (1 << 5)
/** Timeline stream for isolate events */
#define DART_TIMELINE_STREAM_ISOLATE (1 << 5)
#define DART_TIMELINE_STREAM_ISOLATE (1 << 6)
/** Timeline stream for VM events */
#define DART_TIMELINE_STREAM_VM (1 << 6)
#define DART_TIMELINE_STREAM_VM (1 << 7)
/** Enable all timeline stream recording for an isolate */
#define DART_TIMELINE_STREAM_ALL (DART_TIMELINE_STREAM_API | \
DART_TIMELINE_STREAM_COMPILER | \
DART_TIMELINE_STREAM_DART | \
DART_TIMELINE_STREAM_DEBUGGER | \
DART_TIMELINE_STREAM_EMBEDDER | \
DART_TIMELINE_STREAM_GC | \
DART_TIMELINE_STREAM_ISOLATE)

View file

@ -1,20 +0,0 @@
Running Observatory against content_shell:
0) Open your mobile web application in Dart Editor and launch it on mobile.
1) Forward localhost:9222 to the content_shell's remote debugging protocol:
$ adb forward tcp:9222 localabstract:content_shell_devtools_remote
2) Start the Observatory servers:
$ ./run.sh
By default Observatory will be available on localhost:9090
3) Release the content_shell's remote debugging protocol by clicking the 'Stop'
button in the Dart Editor's debugger. By releasing the debugging protocol,
Observatory can communicate with the content_shell.
4) On Observatory's connect to VM page you should see the name of your app
on the right hand side of the page.

View file

@ -1,159 +0,0 @@
// Copyright (c) 2014, 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.
library observatory_server;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:logging/logging.dart';
final Logger logger = new Logger('ObsServe');
class ObservatoryServer {
static const _CHROME_PREFIX = '/crdptargets/';
// Is logging enabled?
bool log;
// Host to listen on.
String host;
// Port to listen on.
int port;
// Host that pub is listening on.
String pubHost;
// Port that pub is listening on.
int pubPort;
HttpServer _server;
final HttpClient _client = new HttpClient();
ObservatoryServer(List<String> args) {
var parser = new ArgParser();
parser.addFlag('log', help: 'Log activity.', defaultsTo: true);
parser.addOption('port', help: 'Specify port listen on',
defaultsTo: '9090');
parser.addOption('host',
help: 'Specify host to listen on',
defaultsTo: '127.0.0.1');
parser.addOption('pub-port', help: 'Specify port that pub is listening on',
defaultsTo: '9191');
parser.addOption('pub-host', help: 'Specify host that pub is listening on',
defaultsTo: '127.0.0.1');
var results = parser.parse(args);
host = results['host'];
port = int.parse(results['port']);
log = results['log'];
pubHost = results['pub-host'];
pubPort = int.parse(results['pub-port']);
}
List<Map> _makeTargetList(List<Map> tabs) {
var r = <Map>[];
tabs.forEach((tab) {
var uri = Uri.parse(tab['url']);
if (uri.host == 'devtools') {
// Ignore.
return;
}
var target = {
'lastConnectionTime': 0,
'chrome': true,
'name': tab['title'],
'networkAddress': tab['webSocketDebuggerUrl'],
};
r.add(target);
});
return r;
}
void _getChromeTabs(HttpRequest request) {
var path = request.uri.path;
var method = request.method;
if (method != 'GET') {
return;
}
assert(path.startsWith(_CHROME_PREFIX));
var networkAddress = path.substring(_CHROME_PREFIX.length);
if ((networkAddress == '') || (networkAddress == null)) {
request.response.write('[]');
request.response.close();
return;
}
networkAddress = Uri.decodeComponent(networkAddress);
var chunks = networkAddress.split(':');
var chromeAddress = chunks[0];
var chromePort =
(chunks[1] == null) || (chunks[1] == '') ? 9222 : int.parse(chunks[1]);
logger.info('tabs from $chromeAddress:$chromePort');
_client.open(method, chromeAddress, chromePort, 'json')
.then((HttpClientRequest pubRequest) {
// Calling .close() on an HttpClientRequest sends the request to the
// server. The future completes to an HttpClientResponse when the
// server has responded.
return pubRequest.close();
}).then((HttpClientResponse response) {
var respond = (contents) {
var tabs = JSON.decode(contents);
var targets = _makeTargetList(tabs);
request.response.write(JSON.encode(targets));
request.response.close().catchError((e) {
logger.severe('tabs from $chromeAddress:$chromePort failed');
logger.severe(e.toString());
});
};
response.transform(UTF8.decoder).listen(respond);
}).catchError((e) {
logger.severe('tabs from $chromeAddress:$chromePort failed');
logger.severe(e.toString());
});
}
/// Forward [request] to pub.
void _forwardToPub(HttpRequest request) {
var path = request.uri.path;
var method = request.method;
logger.info('pub $method $path');
_client.open(method, pubHost, pubPort, path)
.then((HttpClientRequest pubRequest) {
return pubRequest.close();
}).then((HttpClientResponse response) {
return request.response.addStream(response);
}).then((_) => request.response.close())
.catchError((e) {
logger.severe('pub $method $path failed.');
logger.severe(e.toString());
});
}
void _onHttpRequest(HttpRequest request) {
// Allow cross origin requests.
request.response.headers.add('Access-Control-Allow-Origin', '*');
if (request.uri.path.startsWith(_CHROME_PREFIX)) {
_getChromeTabs(request);
} else {
_forwardToPub(request);
}
}
/// Future completes to [this] on successful startup.
Future start() {
return HttpServer.bind(host, port).then((s) {
_server = s;
_server.listen(_onHttpRequest);
print('ObsServe is running on ${_server.address}:${_server.port}');
});
}
}
main(List<String> args) {
hierarchicalLoggingEnabled = true;
logger.level = Level.ALL;
logger.onRecord.listen(print);
new ObservatoryServer(args)..start();
}

View file

@ -52,6 +52,7 @@ export 'package:observatory/src/elements/script_view.dart';
export 'package:observatory/src/elements/service_ref.dart';
export 'package:observatory/src/elements/service_view.dart';
export 'package:observatory/src/elements/sliding_checkbox.dart';
export 'package:observatory/src/elements/timeline_page.dart';
export 'package:observatory/src/elements/view_footer.dart';
export 'package:observatory/src/elements/vm_connect.dart';
export 'package:observatory/src/elements/vm_ref.dart';

View file

@ -44,6 +44,7 @@
<link rel="import" href="src/elements/script_view.html">
<link rel="import" href="src/elements/service_ref.html">
<link rel="import" href="src/elements/sliding_checkbox.html">
<link rel="import" href="src/elements/timeline_page.html">
<link rel="import" href="src/elements/view_footer.html">
<link rel="import" href="src/elements/vm_connect.html">
<link rel="import" href="src/elements/vm_ref.html">

View file

@ -141,6 +141,7 @@ class ObservatoryApplication extends Observable {
_pageRegistry.add(new MetricsPage(this));
_pageRegistry.add(new PortsPage(this));
_pageRegistry.add(new LoggingPage(this));
_pageRegistry.add(new TimelinePage(this));
// Note that ErrorPage must be the last entry in the list as it is
// the catch all.
_pageRegistry.add(new ErrorPage(this));

View file

@ -425,3 +425,21 @@ class MetricsPage extends Page {
bool canVisit(Uri uri) => uri.path == 'metrics';
}
class TimelinePage extends Page {
TimelinePage(app) : super(app);
void onInstall() {
if (element == null) {
element = new Element.tag('timeline-page');
}
assert(element != null);
}
void _visit(Uri uri) {
assert(element != null);
assert(canVisit(uri));
}
bool canVisit(Uri uri) => uri.path == 'timeline';
}

View file

@ -0,0 +1,78 @@
// Copyright (c) 2015, 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.
library timeline_page_element;
import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'observatory_element.dart';
import 'package:observatory/app.dart';
import 'package:observatory/service_html.dart';
import 'package:polymer/polymer.dart';
@CustomTag('timeline-page')
class TimelinePageElement extends ObservatoryElement {
TimelinePageElement.created() : super.created();
attached() {
super.attached();
_resizeSubscription = window.onResize.listen((_) => _updateSize());
_updateSize();
}
detached() {
super.detached();
if (_resizeSubscription != null) {
_resizeSubscription.cancel();
}
}
Future postMessage(String method) {
IFrameElement e = $['root'];
var message = {
'method': method,
'params': {
'vmAddress': (app.vm as WebSocketVM).target.networkAddress
}
};
e.contentWindow.postMessage(JSON.encode(message), window.location.href);
return null;
}
Future refresh() async {
return postMessage('refresh');
}
Future clear() async {
await app.vm.invokeRpc('_clearVMTimeline', {});
return postMessage('clear');
}
Future recordOn() async {
return app.vm.invokeRpc('_setVMTimelineFlag', {
'_record': 'all',
});
}
Future recordOff() async {
return app.vm.invokeRpc('_setVMTimelineFlag', {
'_record': 'none',
});
}
_updateSize() {
IFrameElement e = $['root'];
final totalHeight = window.innerHeight;
final top = e.offset.top;
final bottomMargin = 32;
final mainHeight = totalHeight - top - bottomMargin;
e.style.setProperty('height', '${mainHeight}px');
e.style.setProperty('width', '100%');
}
StreamSubscription _resizeSubscription;
}

View file

@ -0,0 +1,21 @@
<link rel="import" href="../../../../packages/polymer/polymer.html">
<link rel="import" href="nav_bar.html">
<link rel="import" href="observatory_element.html">
<polymer-element name="timeline-page" extends="observatory-element">
<template>
<link rel="stylesheet" href="css/shared.css">
<style>
</style>
<nav-bar>
<top-nav-menu></top-nav-menu>
<nav-menu link="{{ makeLink('/timeline') }}" anchor="timeline" last="{{ true }}"></nav-menu>
<nav-refresh callback="{{ refresh }}"></nav-refresh>
<nav-refresh callback="{{ clear }}" label="Clear"></nav-refresh>
<nav-refresh callback="{{ recordOn }}" label="Start recording"></nav-refresh>
<nav-refresh callback="{{ recordOff }}" label="Stop recording"></nav-refresh>
</nav-bar>
<iframe id="root" frameborder="0" src="../../../../timeline.html"></iframe>
</template>
</polymer-element>
<script type="application/dart" src="timeline_page.dart"></script>

View file

@ -56,6 +56,9 @@
<div class="memberValue">
See <a on-click="{{ goto }}" _href="{{ gotoLink('/flags') }}">flags</a>
</div>
<div class="memberValue">
View <a on-click="{{ goto }}" _href="{{ gotoLink('/timeline') }}">timeline</a>
</div>
</div>
</div>
<br>

View file

@ -834,8 +834,8 @@ abstract class VM extends ServiceObjectOwner {
await listenEventStream(kIsolateStream, _dispatchEventToIsolate);
await listenEventStream(kDebugStream, _dispatchEventToIsolate);
await listenEventStream(_kGraphStream, _dispatchEventToIsolate);
} on FakeVMRpcException catch (e) {
// ignore FakeVMRpcExceptions here.
} on FakeVMRpcException catch (_) {
// ignore FakeVMRpcExceptions here.
}
}
return await invokeRpcNoUpgrade('getVM', {});

View file

@ -134,6 +134,8 @@
'lib/src/elements/service_view.html',
'lib/src/elements/sliding_checkbox.dart',
'lib/src/elements/sliding_checkbox.html',
'lib/src/elements/timeline_page.dart',
'lib/src/elements/timeline_page.html',
'lib/src/elements/view_footer.dart',
'lib/src/elements/view_footer.html',
'lib/src/elements/vm_connect.dart',
@ -152,5 +154,8 @@
'web/index.html',
'web/main.dart',
'web/favicon.ico',
'web/third_party/trace_viewer_full.html',
'web/timeline.js',
'web/timeline.html',
],
}

View file

@ -8,6 +8,7 @@ transformers:
inline_stylesheets:
lib/src/elements/css/shared.css: false
packages/charted/charts/themes/quantum_theme.css: false
$exclude: [web/third_party/*.html, web/timeline.html]
- $dart2js:
$include: "**/*.polymer.bootstrap.dart"
suppressWarnings: false

View file

@ -1,25 +0,0 @@
#!/bin/sh -e
if [ ! -d "web" ]; then
echo "Error: you must run this script from the client directory."
exit
fi
PUB_PATH=pub
PUB_ARGS="serve --hostname 127.0.0.1 --port 9191"
DART_PATH=dart
DART_ARGS="bin/server.dart --host 127.0.0.1 --port 9090"
DART_ARGS="$DART_ARGS --pub-host 127.0.0.1 --pub-port 9191"
# Kill any child processes on exit.
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
echo "This script assumes that both *pub* and *dart* are in your PATH."
echo "Launching Observatory server."
echo "Launching pub."
echo ""
echo ""
echo ""
$DART_PATH $DART_ARGS &
$PUB_PATH $PUB_ARGS

View file

@ -0,0 +1,33 @@
// Copyright (c) 2015, 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.
// VMOptions=--error_on_bad_type --error_on_bad_override --complete_timeline
import 'dart:developer';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
primeTimeline() {
Timeline.startSync('apple');
Timeline.finishSync();
}
List<Map> filterForDartEvents(List<Map> events) {
return events.where((event) => event['cat'] == 'Dart').toList();
}
var tests = [
(VM vm) async {
Map result = await vm.invokeRpcNoUpgrade('_getVMTimeline', {});
expect(result['type'], equals('_Timeline'));
expect(result['traceEvents'], new isInstanceOf<List>());
List<Map> dartEvents = filterForDartEvents(result['traceEvents']);
expect(dartEvents.length, equals(1));
Map dartEvent = dartEvents[0];
expect(dartEvent['name'], equals('apple'));
},
];
main(args) async => runVMTests(args, tests, testeeBefore: primeTimeline);

View file

@ -0,0 +1,4 @@
This directory contains a vulcanized version of trace-viewer:
https://github.com/catapult-project/catapult/wiki/Embedding-Trace-Viewer

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html style="height: 100%; width: 100%">
<head>
<meta charset="utf-8">
<title>Dart VM Observatory Timeline</title>
<script src="timeline.js"></script>
<link rel="import" href="third_party/trace_viewer_full.html">
<style>
html, body {
box-sizing: border-box;
overflow: hidden;
margin: 0px;
padding: 0;
width: 100%;
height: 100%;
}
#trace-viewer {
width: 100%;
height: 100%;
}
#trace-viewer:focus {
outline: none;
}
</style>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,93 @@
// Copyright (c) 2015, 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.
function onModelLoaded() {
viewer.globalMode = true;
viewer.model = model;
}
function clearTimeline() {
viewer.model = undefined;
}
function onImportFail() {
var overlay = new tr.ui.b.Overlay();
overlay.textContent = tr.b.normalizeException(err).message;
overlay.title = 'Import error';
overlay.visible = true;
console.log('import failed');
}
function updateTimeline(events) {
model = new tr.Model();
var importer = new tr.importer.Import(model);
var p = importer.importTracesWithProgressDialog([events]);
p.then(onModelLoaded, onImportFail);
}
function registerForMessages() {
window.addEventListener("message", onMessage, false);
}
function fetchUri(uri, onLoad, onError) {
var xhr = new XMLHttpRequest();
xhr.open('GET', uri, true);
xhr.responseType = 'text';
xhr.addEventListener("load", onLoad);
xhr.addEventListener("error", onError);
xhr.send();
console.log('GET ' + uri);
}
function fetchTimelineOnLoad(event) {
var xhr = event.target;
var response = JSON.parse(xhr.responseText);
var result = response['result'];
var traceEvents = result['traceEvents'];
updateTimeline(traceEvents);
}
function fetchTimelineOnError(event) {
}
function fetchTimeline(vmAddress) {
var parser = document.createElement('a');
parser.href = vmAddress;
var requestUri = 'http://' +
parser.hostname +
':' +
parser.port +
'/_getVMTimeline';
fetchUri(requestUri, fetchTimelineOnLoad, fetchTimelineOnError);
}
function onMessage(event) {
var request = JSON.parse(event.data);
var method = request['method'];
var params = request['params'];
switch (method) {
case 'refresh':
fetchTimeline(params['vmAddress']);
break;
case 'clear':
clearTimeline();
break;
default:
console.log('Unknown method:' + method + '.');
}
}
document.addEventListener('DOMContentLoaded', function() {
var container = document.createElement('track-view-container');
container.id = 'track_view_container';
viewer = document.createElement('tr-ui-timeline-view');
viewer.track_view_container = container;
viewer.appendChild(container);
viewer.id = 'trace-viewer';
viewer.globalMode = true;
document.body.appendChild(viewer);
registerForMessages();
});
console.log('loaded');

View file

@ -93,6 +93,8 @@ const char* Dart::InitOnce(const uint8_t* vm_isolate_snapshot,
Thread::InitOnceBeforeIsolate();
Thread::EnsureInit();
Timeline::InitOnce();
Thread* thread = Thread::Current();
thread->set_name("Dart_Initialize");
TimelineDurationScope tds(Timeline::GetVMStream(),
"Dart::InitOnce");
Isolate::InitOnce();

View file

@ -5737,6 +5737,8 @@ DART_EXPORT void Dart_TimelineSetRecordedStreams(int64_t stream_mask) {
(stream_mask & DART_TIMELINE_STREAM_COMPILER) != 0);
isolate->GetDartStream()->set_enabled(
(stream_mask & DART_TIMELINE_STREAM_DART) != 0);
isolate->GetDebuggerStream()->set_enabled(
(stream_mask & DART_TIMELINE_STREAM_DEBUGGER) != 0);
isolate->GetEmbedderStream()->set_enabled(
(stream_mask & DART_TIMELINE_STREAM_EMBEDDER) != 0);
isolate->GetGCStream()->set_enabled(
@ -5753,6 +5755,8 @@ DART_EXPORT void Dart_GlobalTimelineSetRecordedStreams(int64_t stream_mask) {
(stream_mask & DART_TIMELINE_STREAM_COMPILER) != 0;
const bool dart_enabled =
(stream_mask & DART_TIMELINE_STREAM_DART) != 0;
const bool debugger_enabled =
(stream_mask & DART_TIMELINE_STREAM_DEBUGGER) != 0;
const bool embedder_enabled =
(stream_mask & DART_TIMELINE_STREAM_EMBEDDER) != 0;
const bool gc_enabled = (stream_mask & DART_TIMELINE_STREAM_GC) != 0;
@ -5761,6 +5765,7 @@ DART_EXPORT void Dart_GlobalTimelineSetRecordedStreams(int64_t stream_mask) {
Timeline::SetStreamAPIEnabled(api_enabled);
Timeline::SetStreamCompilerEnabled(compiler_enabled);
Timeline::SetStreamDartEnabled(dart_enabled);
Timeline::SetStreamDebuggerEnabled(debugger_enabled);
Timeline::SetStreamEmbedderEnabled(embedder_enabled);
Timeline::SetStreamGCEnabled(gc_enabled);
Timeline::SetStreamIsolateEnabled(isolate_enabled);

View file

@ -2522,7 +2522,11 @@ void Debugger::Pause(DebuggerEvent* event) {
// We are about to invoke the debuggers event handler. Disable interrupts
// for this thread while waiting for debug commands over the service protocol.
{
DisableThreadInterruptsScope dtis(Thread::Current());
Thread* thread = Thread::Current();
DisableThreadInterruptsScope dtis(thread);
TimelineDurationScope tds(thread,
isolate_->GetDebuggerStream(),
"Debugger Pause");
InvokeEventHandler(event);
}

View file

@ -864,7 +864,6 @@ Isolate* Isolate::Init(const char* name_prefix,
// Initialize Timeline streams.
#define ISOLATE_TIMELINE_STREAM_INIT(name, enabled_by_default) \
result->stream_##name##_.Init(#name, \
Timeline::EnableStreamByDefault(#name) || \
enabled_by_default, \
Timeline::Stream##name##EnabledFlag());
ISOLATE_TIMELINE_STREAM_LIST(ISOLATE_TIMELINE_STREAM_INIT);

View file

@ -2492,6 +2492,90 @@ static bool GetVMMetric(Thread* thread, JSONStream* js) {
}
static const MethodParameter* set_vm_timeline_flag_params[] = {
NO_ISOLATE_PARAMETER,
new MethodParameter("_record", true),
NULL,
};
static bool SetVMTimelineFlag(Thread* thread, JSONStream* js) {
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
StackZone zone(thread);
bool recording = strcmp(js->LookupParam("_record"), "all") == 0;
Timeline::SetStreamAPIEnabled(recording);
Timeline::SetStreamCompilerEnabled(recording);
Timeline::SetStreamDartEnabled(recording);
Timeline::SetStreamDebuggerEnabled(recording);
Timeline::SetStreamEmbedderEnabled(recording);
Timeline::SetStreamGCEnabled(recording);
Timeline::SetStreamIsolateEnabled(recording);
PrintSuccess(js);
return true;
}
static const MethodParameter* get_vm_timeline_flag_params[] = {
NO_ISOLATE_PARAMETER,
new MethodParameter("_record", false),
NULL,
};
static bool GetVMTimelineFlag(Thread* thread, JSONStream* js) {
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
StackZone zone(thread);
js->PrintError(kFeatureDisabled, "TODO(johnmccutchan)");
return true;
}
static const MethodParameter* clear_vm_timeline_params[] = {
NO_ISOLATE_PARAMETER,
NULL,
};
static bool ClearVMTimeline(Thread* thread, JSONStream* js) {
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
StackZone zone(thread);
Timeline::Clear();
PrintSuccess(js);
return true;
}
static const MethodParameter* get_vm_timeline_params[] = {
NO_ISOLATE_PARAMETER,
NULL,
};
static bool GetVMTimeline(Thread* thread, JSONStream* js) {
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
StackZone zone(thread);
Timeline::ReclaimCachedBlocksFromThreads();
TimelineEventRecorder* timeline_recorder = Timeline::recorder();
// TODO(johnmccutchan): Return an error.
ASSERT(timeline_recorder != NULL);
TimelineEventFilter filter;
timeline_recorder->PrintJSON(js, &filter);
return true;
}
static const MethodParameter* resume_params[] = {
ISOLATE_PARAMETER,
NULL,
@ -3333,6 +3417,8 @@ static ServiceMethodDescriptor service_methods_[] = {
add_breakpoint_at_activation_params },
{ "_clearCpuProfile", ClearCpuProfile,
clear_cpu_profile_params },
{ "_clearVMTimeline", ClearVMTimeline,
clear_vm_timeline_params, },
{ "evaluate", Evaluate,
evaluate_params },
{ "evaluateInFrame", EvaluateInFrame,
@ -3387,6 +3473,10 @@ static ServiceMethodDescriptor service_methods_[] = {
get_vm_metric_params },
{ "_getVMMetricList", GetVMMetricList,
get_vm_metric_list_params },
{ "_getVMTimeline", GetVMTimeline,
get_vm_timeline_params },
{ "_getVMTimelineFlag", GetVMTimelineFlag,
get_vm_timeline_flag_params },
{ "pause", Pause,
pause_params },
{ "removeBreakpoint", RemoveBreakpoint,
@ -3409,6 +3499,8 @@ static ServiceMethodDescriptor service_methods_[] = {
set_trace_class_allocation_params },
{ "setVMName", SetVMName,
set_vm_name_params },
{ "_setVMTimelineFlag", SetVMTimelineFlag,
set_vm_timeline_flag_params },
};

View file

@ -214,6 +214,7 @@ void Thread::EnsureInit() {
Thread::Thread(bool init_vm_constants)
: id_(OSThread::GetCurrentThreadId()),
join_id_(OSThread::GetCurrentThreadJoinId()),
trace_id_(OSThread::GetCurrentThreadTraceId()),
thread_interrupt_disabled_(1), // Thread interrupts disabled by default.
isolate_(NULL),
heap_(NULL),
@ -228,7 +229,8 @@ Thread::Thread(bool init_vm_constants)
vm_tag_(0),
pending_functions_(GrowableObjectArray::null()),
no_callback_scope_depth_(0),
thread_list_next_(NULL) {
thread_list_next_(NULL),
name_(NULL) {
ClearState();
#define DEFAULT_INIT(type_name, member_name, init_expr, default_init_value) \

View file

@ -363,6 +363,21 @@ LEAF_RUNTIME_ENTRY_LIST(DEFINE_OFFSET_METHOD)
return join_id_;
}
ThreadId trace_id() const {
ASSERT(trace_id_ != OSThread::kInvalidThreadJoinId);
return trace_id_;
}
const char* name() const {
return name_;
}
void set_name(const char* name) {
ASSERT(Thread::Current() == this);
ASSERT(name_ == NULL);
name_ = name;
}
// Used to temporarily disable or enable thread interrupts.
void DisableThreadInterrupts();
void EnableThreadInterrupts();
@ -410,6 +425,7 @@ LEAF_RUNTIME_ENTRY_LIST(DEFINE_OFFSET_METHOD)
const ThreadId id_;
const ThreadId join_id_;
const ThreadId trace_id_;
uintptr_t thread_interrupt_disabled_;
Isolate* isolate_;
Heap* heap_;
@ -459,6 +475,9 @@ LEAF_RUNTIME_ENTRY_LIST(DECLARE_MEMBERS)
// All |Thread|s are registered in the thread list.
Thread* thread_list_next_;
// A name for this thread.
const char* name_;
static Thread* thread_list_head_;
static Mutex* thread_list_lock_;

View file

@ -418,6 +418,8 @@ void ThreadPool::Worker::Shutdown() {
// static
void ThreadPool::Worker::Main(uword args) {
Thread::EnsureInit();
Thread* thread = Thread::Current();
thread->set_name("Dart ThreadPool Worker");
Worker* worker = reinterpret_cast<Worker*>(args);
ThreadId id = OSThread::GetCurrentThreadId();
ThreadJoinId join_id = OSThread::GetCurrentThreadJoinId();

View file

@ -84,7 +84,7 @@ void Timeline::InitOnce() {
vm_stream_->Init("VM", EnableStreamByDefault("VM"), NULL);
// Global overrides.
#define ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT(name, not_used) \
stream_##name##_enabled_ = false;
stream_##name##_enabled_ = EnableStreamByDefault(#name);
ISOLATE_TIMELINE_STREAM_LIST(ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT)
#undef ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT
}
@ -109,7 +109,7 @@ TimelineEventRecorder* Timeline::recorder() {
bool Timeline::EnableStreamByDefault(const char* stream_name) {
// TODO(johnmccutchan): Allow for command line control over streams.
return (FLAG_timeline_dir != NULL) || FLAG_timing;
return (FLAG_timeline_dir != NULL) || FLAG_timing || FLAG_complete_timeline;
}
@ -141,11 +141,21 @@ void Timeline::ReclaimCachedBlocksFromThreads() {
}
void Timeline::Clear() {
TimelineEventRecorder* recorder = Timeline::recorder();
if (recorder == NULL) {
return;
}
ReclaimCachedBlocksFromThreads();
recorder->Clear();
}
TimelineEventRecorder* Timeline::recorder_ = NULL;
TimelineStream* Timeline::vm_stream_ = NULL;
#define ISOLATE_TIMELINE_STREAM_DEFINE_FLAG(name, enabled_by_default) \
bool Timeline::stream_##name##_enabled_ = false;
bool Timeline::stream_##name##_enabled_ = enabled_by_default;
ISOLATE_TIMELINE_STREAM_LIST(ISOLATE_TIMELINE_STREAM_DEFINE_FLAG)
#undef ISOLATE_TIMELINE_STREAM_DEFINE_FLAG
@ -631,6 +641,26 @@ TimelineEventRecorder::TimelineEventRecorder()
void TimelineEventRecorder::PrintJSONMeta(JSONArray* events) const {
ThreadIterator it;
while (it.HasNext()) {
Thread* thread = it.Next();
const char* thread_name = thread->name();
if (thread_name == NULL) {
// Only emit a thread name if one was set.
continue;
}
JSONObject obj(events);
int64_t pid = OS::ProcessId();
int64_t tid = OSThread::ThreadIdToIntPtr(thread->trace_id());
obj.AddProperty("name", "thread_name");
obj.AddProperty("ph", "M");
obj.AddProperty64("pid", pid);
obj.AddProperty64("tid", tid);
{
JSONObject args(&obj, "args");
args.AddPropertyF("name", "%s (%" Pd64 ")", thread_name, tid);
}
}
}
@ -783,7 +813,8 @@ TimelineEventRingRecorder::~TimelineEventRingRecorder() {
void TimelineEventRingRecorder::PrintJSONEvents(
JSONArray* events,
TimelineEventFilter* filter) const {
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
intptr_t block_offset = FindOldestBlockIndex();
if (block_offset == -1) {
// All blocks are empty.
@ -807,7 +838,6 @@ void TimelineEventRingRecorder::PrintJSONEvents(
void TimelineEventRingRecorder::PrintJSON(JSONStream* js,
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
JSONObject topLevel(js);
topLevel.AddProperty("type", "_Timeline");
{
@ -843,6 +873,15 @@ TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() {
}
void TimelineEventRingRecorder::Clear() {
MutexLocker ml(&lock_);
for (intptr_t i = 0; i < num_blocks_; i++) {
TimelineEventBlock* block = blocks_[i];
block->Reset();
}
}
intptr_t TimelineEventRingRecorder::FindOldestBlockIndex() const {
int64_t earliest_time = kMaxInt64;
intptr_t earliest_index = -1;
@ -923,7 +962,6 @@ TimelineEventEndlessRecorder::TimelineEventEndlessRecorder()
void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js,
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
JSONObject topLevel(js);
topLevel.AddProperty("type", "_Timeline");
{
@ -977,9 +1015,9 @@ TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() {
void TimelineEventEndlessRecorder::PrintJSONEvents(
JSONArray* events,
TimelineEventFilter* filter) const {
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
TimelineEventBlock* current = head_;
while (current != NULL) {
if (!filter->IncludeBlock(current)) {
current = current->next();

View file

@ -30,6 +30,7 @@ class Zone;
V(API, false) \
V(Compiler, false) \
V(Dart, false) \
V(Debugger, false) \
V(Embedder, false) \
V(GC, false) \
V(Isolate, false) \
@ -52,6 +53,8 @@ class Timeline : public AllStatic {
// Reclaim all |TimelineEventBlocks|s that are cached by threads.
static void ReclaimCachedBlocksFromThreads();
static void Clear();
#define ISOLATE_TIMELINE_STREAM_FLAGS(name, not_used) \
static const bool* Stream##name##EnabledFlag() { \
return &stream_##name##_enabled_; \
@ -523,6 +526,7 @@ class TimelineEventRecorder {
virtual void CompleteEvent(TimelineEvent* event) = 0;
virtual TimelineEventBlock* GetHeadBlockLocked() = 0;
virtual TimelineEventBlock* GetNewBlockLocked() = 0;
virtual void Clear() = 0;
// Utility method(s).
void PrintJSONMeta(JSONArray* array) const;
@ -560,8 +564,9 @@ class TimelineEventRingRecorder : public TimelineEventRecorder {
TimelineEventBlock* GetHeadBlockLocked();
intptr_t FindOldestBlockIndex() const;
TimelineEventBlock* GetNewBlockLocked();
void Clear();
void PrintJSONEvents(JSONArray* array, TimelineEventFilter* filter) const;
void PrintJSONEvents(JSONArray* array, TimelineEventFilter* filter);
TimelineEventBlock** blocks_;
intptr_t capacity_;
@ -590,6 +595,8 @@ class TimelineEventStreamingRecorder : public TimelineEventRecorder {
TimelineEventBlock* GetHeadBlockLocked() {
return NULL;
}
void Clear() {
}
TimelineEvent* StartEvent();
void CompleteEvent(TimelineEvent* event);
};
@ -610,12 +617,10 @@ class TimelineEventEndlessRecorder : public TimelineEventRecorder {
void CompleteEvent(TimelineEvent* event);
TimelineEventBlock* GetNewBlockLocked();
TimelineEventBlock* GetHeadBlockLocked();
void PrintJSONEvents(JSONArray* array, TimelineEventFilter* filter) const;
// Useful only for testing. Only works for one thread.
void Clear();
void PrintJSONEvents(JSONArray* array, TimelineEventFilter* filter);
TimelineEventBlock* head_;
intptr_t block_index_;