mirror of
https://github.com/dart-lang/sdk
synced 2024-09-04 16:03:44 +00:00
[dart2js] Improve deferred part file logging.
Adds an "eventLog" property to the global "$__dart_deferred_initializers__" object. The event log is just a JS Array that gets initialized alongside $__dart_deferred_initializers__. Each event entry is a String->String JS object. I unified the format of these event objects for easier parsing. Each printed event is now valid JSON (passed through JSON.stringify) so we can more easily post-process the logs if necessary. Fully replacing the existing "_eventLog" field makes the logging logic simpler. Having a single array allows us to not include a timestamp because the array captures the event ordering (actual timing doesn't matter). It also makes it easier to stringify the logs when there's only 1 data source/format. Sample output: ``` {"hunkName":"out.js_1.part.js","event":"loadHunk"} {"uri":"path_to/out.js_1.part.js","hunkName":"out.js_1.part.js","event":"downloadHunk"} {"file":"out.js_1","event":"beginLoadPart"} {"file":"out.js_1","event":"endPartLoad","hash":"aCscY7yv4EHodRPmCqYn5BqMelA="} {"uri":"path_to/out.js_1.part.js","hunkName":"out.js_1.part.js","event":"downloadHunkSuccess"} {"hunkName":"out.js_1.part.js","hash":"aCscY7yv4EHodRPmCqYn5BqMelA=","event":"initialize"} ``` Change-Id: Ifee73be0bf7dc53a0e7710ff033dd2b0fae9fb0e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/307240 Commit-Queue: Nate Biggs <natebiggs@google.com> Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
parent
eefa234d28
commit
ac6150c4c0
|
@ -1691,7 +1691,8 @@ class FragmentEmitter {
|
||||||
/// to the list of generated fragments that must be installed when the
|
/// to the list of generated fragments that must be installed when the
|
||||||
/// deferred library is loaded.
|
/// deferred library is loaded.
|
||||||
Iterable<js.Property> emitEmbeddedGlobalsForDeferredLoading(
|
Iterable<js.Property> emitEmbeddedGlobalsForDeferredLoading(
|
||||||
DeferredLoadingState deferredLoadingState) {
|
DeferredLoadingState deferredLoadingState,
|
||||||
|
{required bool isSplit}) {
|
||||||
List<js.Property> globals = [];
|
List<js.Property> globals = [];
|
||||||
|
|
||||||
globals.add(js.Property(
|
globals.add(js.Property(
|
||||||
|
@ -1723,6 +1724,11 @@ class FragmentEmitter {
|
||||||
'deferredInitialized': generateEmbeddedGlobalAccess(DEFERRED_INITIALIZED)
|
'deferredInitialized': generateEmbeddedGlobalAccess(DEFERRED_INITIALIZED)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isSplit) {
|
||||||
|
js.Expression eventLog = js.js("$deferredGlobal.eventLog");
|
||||||
|
globals.add(js.Property(js.string(INITIALIZATION_EVENT_LOG), eventLog));
|
||||||
|
}
|
||||||
|
|
||||||
globals.add(js.Property(
|
globals.add(js.Property(
|
||||||
js.string(INITIALIZE_LOADED_HUNK), initializeLoadedHunkFunction));
|
js.string(INITIALIZE_LOADED_HUNK), initializeLoadedHunkFunction));
|
||||||
|
|
||||||
|
@ -1842,8 +1848,8 @@ class FragmentEmitter {
|
||||||
List<js.Property> globals = [];
|
List<js.Property> globals = [];
|
||||||
|
|
||||||
if (fragmentsToLoad.isNotEmpty) {
|
if (fragmentsToLoad.isNotEmpty) {
|
||||||
globals
|
globals.addAll(emitEmbeddedGlobalsForDeferredLoading(deferredLoadingState,
|
||||||
.addAll(emitEmbeddedGlobalsForDeferredLoading(deferredLoadingState));
|
isSplit: program.isSplit));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (program.typeToInterceptorMap != null) {
|
if (program.typeToInterceptorMap != null) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:js_runtime/synced/embedded_names.dart'
|
||||||
DEFERRED_LIBRARY_PARTS,
|
DEFERRED_LIBRARY_PARTS,
|
||||||
DEFERRED_PART_URIS,
|
DEFERRED_PART_URIS,
|
||||||
DEFERRED_PART_HASHES,
|
DEFERRED_PART_HASHES,
|
||||||
|
INITIALIZATION_EVENT_LOG,
|
||||||
INITIALIZE_LOADED_HUNK,
|
INITIALIZE_LOADED_HUNK,
|
||||||
INTERCEPTORS_BY_TAG,
|
INTERCEPTORS_BY_TAG,
|
||||||
IS_HUNK_INITIALIZED,
|
IS_HUNK_INITIALIZED,
|
||||||
|
@ -342,8 +343,7 @@ class ModelEmitter {
|
||||||
|
|
||||||
_task.measureSubtask('write fragments', () {
|
_task.measureSubtask('write fragments', () {
|
||||||
writeMainFragment(mainFragment, mainCode,
|
writeMainFragment(mainFragment, mainCode,
|
||||||
isSplit: program.deferredFragments.isNotEmpty ||
|
isSplit: program.isSplit || _options.experimentalTrackAllocations);
|
||||||
_options.experimentalTrackAllocations);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_closedWorld.backendUsage.requiresPreamble &&
|
if (_closedWorld.backendUsage.requiresPreamble &&
|
||||||
|
@ -377,11 +377,20 @@ class ModelEmitter {
|
||||||
return js.Comment(generatedBy(_options, flavor: '$flavor'));
|
return js.Comment(generatedBy(_options, flavor: '$flavor'));
|
||||||
}
|
}
|
||||||
|
|
||||||
js.Statement buildDeferredInitializerGlobal() {
|
List<js.Statement> buildDeferredInitializerGlobal() {
|
||||||
return js.js.statement(
|
return [
|
||||||
'self.#deferredInitializers = '
|
js.js.statement(
|
||||||
'self.#deferredInitializers || Object.create(null);',
|
'self.#deferredInitializers = '
|
||||||
{'deferredInitializers': deferredInitializersGlobal});
|
'self.#deferredInitializers || Object.create(null);',
|
||||||
|
{'deferredInitializers': deferredInitializersGlobal}),
|
||||||
|
js.js.statement(
|
||||||
|
'self.#deferredInitializers.#eventLog = '
|
||||||
|
'self.#deferredInitializers.#eventLog || [];',
|
||||||
|
{
|
||||||
|
'deferredInitializers': deferredInitializersGlobal,
|
||||||
|
'eventLog': INITIALIZATION_EVENT_LOG
|
||||||
|
})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
js.Statement buildStartupMetrics() {
|
js.Statement buildStartupMetrics() {
|
||||||
|
@ -432,7 +441,7 @@ var ${startupMetricsGlobal} =
|
||||||
js.Program program = js.Program([
|
js.Program program = js.Program([
|
||||||
buildGeneratedBy(),
|
buildGeneratedBy(),
|
||||||
js.Comment(HOOKS_API_USAGE),
|
js.Comment(HOOKS_API_USAGE),
|
||||||
if (isSplit) buildDeferredInitializerGlobal(),
|
if (isSplit) ...buildDeferredInitializerGlobal(),
|
||||||
if (_closedWorld.backendUsage.requiresStartupMetrics)
|
if (_closedWorld.backendUsage.requiresStartupMetrics)
|
||||||
buildStartupMetrics(),
|
buildStartupMetrics(),
|
||||||
code
|
code
|
||||||
|
@ -571,10 +580,20 @@ var ${startupMetricsGlobal} =
|
||||||
// deferredInitializer.current = <pretty-printed code>;
|
// deferredInitializer.current = <pretty-printed code>;
|
||||||
// deferredInitializer[<hash>] = deferredInitializer.current;
|
// deferredInitializer[<hash>] = deferredInitializer.current;
|
||||||
|
|
||||||
|
final outputFileJsString = js.string(outputFileName);
|
||||||
js.Program program = js.Program([
|
js.Program program = js.Program([
|
||||||
if (isFirst) buildGeneratedBy(),
|
if (isFirst) buildGeneratedBy(),
|
||||||
if (isFirst) buildDeferredInitializerGlobal(),
|
if (isFirst) ...buildDeferredInitializerGlobal(),
|
||||||
js.js.statement('$deferredInitializersGlobal.current = #', code)
|
js.js.statement(
|
||||||
|
'#deferredInitializers.#eventLog.push({"part": '
|
||||||
|
'#outputFileName, "event": "beginLoadPart"});',
|
||||||
|
{
|
||||||
|
'deferredInitializers': deferredInitializersGlobal,
|
||||||
|
'eventLog': INITIALIZATION_EVENT_LOG,
|
||||||
|
'outputFileName': outputFileJsString
|
||||||
|
}),
|
||||||
|
js.js.statement('#deferredInitializers.current = #code',
|
||||||
|
{'deferredInitializers': deferredInitializersGlobal, 'code': code})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Hasher hasher = Hasher();
|
Hasher hasher = Hasher();
|
||||||
|
@ -591,10 +610,30 @@ var ${startupMetricsGlobal} =
|
||||||
// This will be used to retrieve the initializing function from the global
|
// This will be used to retrieve the initializing function from the global
|
||||||
// variable.
|
// variable.
|
||||||
String hash = hasher.getHash();
|
String hash = hasher.getHash();
|
||||||
|
final jsStringHash = js.string(hash);
|
||||||
|
|
||||||
// Now we copy the deferredInitializer.current into its correct hash.
|
// Now we copy the deferredInitializer.current into its correct hash.
|
||||||
output.add('\n${deferredInitializersGlobal}["$hash"] = '
|
final epilogue = js.Program([
|
||||||
'${deferredInitializersGlobal}.current\n');
|
js.js.statement(
|
||||||
|
'#deferredInitializers[#hash] = #deferredInitializers.current', {
|
||||||
|
'deferredInitializers': deferredInitializersGlobal,
|
||||||
|
'hash': jsStringHash
|
||||||
|
}),
|
||||||
|
js.js.statement(
|
||||||
|
'#deferredInitializers.#eventLog.push({"part": #outputFileName, '
|
||||||
|
'"hash": #hash, "event": "endPartLoad"})',
|
||||||
|
{
|
||||||
|
'deferredInitializers': deferredInitializersGlobal,
|
||||||
|
'hash': jsStringHash,
|
||||||
|
'eventLog': INITIALIZATION_EVENT_LOG,
|
||||||
|
'outputFileName': outputFileJsString,
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
output.add('\n');
|
||||||
|
output.add(js
|
||||||
|
.createCodeBuffer(epilogue, _options,
|
||||||
|
_sourceInformationStrategy as JavaScriptSourceInformationStrategy)
|
||||||
|
.getText());
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
"id": "outputUnit/1",
|
"id": "outputUnit/1",
|
||||||
"kind": "outputUnit",
|
"kind": "outputUnit",
|
||||||
"name": "1",
|
"name": "1",
|
||||||
"size": 1112,
|
"size": 1426,
|
||||||
"filename": "out_1.part.js",
|
"filename": "out_1.part.js",
|
||||||
"imports": [
|
"imports": [
|
||||||
"lib"
|
"lib"
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
"id": "outputUnit/1",
|
"id": "outputUnit/1",
|
||||||
"kind": "outputUnit",
|
"kind": "outputUnit",
|
||||||
"name": "1",
|
"name": "1",
|
||||||
"size": 834,
|
"size": 1148,
|
||||||
"filename": "out_1.part.js",
|
"filename": "out_1.part.js",
|
||||||
"imports": [
|
"imports": [
|
||||||
"lib1"
|
"lib1"
|
||||||
|
|
|
@ -159,6 +159,16 @@ const IS_HUNK_INITIALIZED = 'isHunkInitialized';
|
||||||
/// globals don't clash with it.
|
/// globals don't clash with it.
|
||||||
const DEFERRED_INITIALIZED = 'deferredInitialized';
|
const DEFERRED_INITIALIZED = 'deferredInitialized';
|
||||||
|
|
||||||
|
/// Property name for the reference to the initialization event log which is
|
||||||
|
/// included in exceptions when deferred loading fails.
|
||||||
|
///
|
||||||
|
/// The event log is a JS array where each entry is a plain JS object
|
||||||
|
/// representing event data. Each entry will be passed to JSON.stringify()
|
||||||
|
/// before being appended to the thrown exception.
|
||||||
|
///
|
||||||
|
/// This embedded global is only used for deferred loading.
|
||||||
|
const INITIALIZATION_EVENT_LOG = 'eventLog';
|
||||||
|
|
||||||
/// An embedded global used to collect and access runtime metrics.
|
/// An embedded global used to collect and access runtime metrics.
|
||||||
const RUNTIME_METRICS = 'rm';
|
const RUNTIME_METRICS = 'rm';
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'dart:_js_embedded_names'
|
||||||
DEFERRED_PART_URIS,
|
DEFERRED_PART_URIS,
|
||||||
DEFERRED_PART_HASHES,
|
DEFERRED_PART_HASHES,
|
||||||
GET_ISOLATE_TAG,
|
GET_ISOLATE_TAG,
|
||||||
|
INITIALIZATION_EVENT_LOG,
|
||||||
INITIALIZE_LOADED_HUNK,
|
INITIALIZE_LOADED_HUNK,
|
||||||
INTERCEPTORS_BY_TAG,
|
INTERCEPTORS_BY_TAG,
|
||||||
IS_HUNK_LOADED,
|
IS_HUNK_LOADED,
|
||||||
|
@ -2758,14 +2759,39 @@ String getIsolateAffinityTag(String name) {
|
||||||
final Map<String, Future<Null>?> _loadingLibraries = <String, Future<Null>?>{};
|
final Map<String, Future<Null>?> _loadingLibraries = <String, Future<Null>?>{};
|
||||||
final Set<String> _loadedLibraries = new Set<String>();
|
final Set<String> _loadedLibraries = new Set<String>();
|
||||||
|
|
||||||
/// Events used to diagnose failures from deferred loading requests.
|
|
||||||
final List<String> _eventLog = <String>[];
|
|
||||||
|
|
||||||
typedef void DeferredLoadCallback();
|
typedef void DeferredLoadCallback();
|
||||||
|
|
||||||
// Function that will be called every time a new deferred import is loaded.
|
// Function that will be called every time a new deferred import is loaded.
|
||||||
DeferredLoadCallback? deferredLoadHook;
|
DeferredLoadCallback? deferredLoadHook;
|
||||||
|
|
||||||
|
/// Add events to global initialization event log.
|
||||||
|
void _addEvent(Map<String, String> data) {
|
||||||
|
var initializationEventLog = JS_EMBEDDED_GLOBAL('', INITIALIZATION_EVENT_LOG);
|
||||||
|
var dataObj = JS('=Object', 'Object.create(null)');
|
||||||
|
data.forEach((key, value) {
|
||||||
|
JS('', '#[#] = #', dataObj, key, value);
|
||||||
|
});
|
||||||
|
JS('', '#.push(#)', initializationEventLog, dataObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the event log as a string where each line is a JSON format event.
|
||||||
|
///
|
||||||
|
/// The events are printed in reverse chronological order in case the tail gets
|
||||||
|
/// truncated, the newest events are retained.
|
||||||
|
String _getEventLog() {
|
||||||
|
var initializationEventLog = JS_EMBEDDED_GLOBAL('', INITIALIZATION_EVENT_LOG);
|
||||||
|
final log = JS(
|
||||||
|
'String',
|
||||||
|
'Array.from(#)'
|
||||||
|
'.map(((script) => '
|
||||||
|
'(e, i) => { e["index"] = i; e["script"] = script; return e; })(#))'
|
||||||
|
'.reverse()'
|
||||||
|
'.map((e) => JSON.stringify(e)).join("\\n")',
|
||||||
|
initializationEventLog,
|
||||||
|
thisScript);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads a deferred library. The compiler generates a call to this method to
|
/// Loads a deferred library. The compiler generates a call to this method to
|
||||||
/// implement `import.loadLibrary()`. The [priority] argument is the index of
|
/// implement `import.loadLibrary()`. The [priority] argument is the index of
|
||||||
/// one of the [LoadLibraryPriority] enum's members.
|
/// one of the [LoadLibraryPriority] enum's members.
|
||||||
|
@ -2820,19 +2846,21 @@ Future<Null> loadDeferredLibrary(String loadId, int priority) {
|
||||||
var uri = uris[i];
|
var uri = uris[i];
|
||||||
var hash = hashes[i];
|
var hash = hashes[i];
|
||||||
if (JS('bool', '#(#)', isHunkInitialized, hash)) {
|
if (JS('bool', '#(#)', isHunkInitialized, hash)) {
|
||||||
_eventLog.add(' - already initialized: $uri ($hash)');
|
_addEvent(
|
||||||
|
{'part': '$uri', 'hash': '$hash', 'event': 'alreadyInitialized'});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// On strange scenarios, e.g. if js encounters parse errors, we might get
|
// On strange scenarios, e.g. if js encounters parse errors, we might get
|
||||||
// an "success" callback on the script load but the hunk will be null.
|
// an "success" callback on the script load but the hunk will be null.
|
||||||
if (JS('bool', '#(#)', isHunkLoaded, hash)) {
|
if (JS('bool', '#(#)', isHunkLoaded, hash)) {
|
||||||
_eventLog.add(' - initialize: $uri ($hash)');
|
_addEvent({'part': '$uri', 'hash': '$hash', 'event': 'initialize'});
|
||||||
JS('void', '#(#)', initializer, hash);
|
JS('void', '#(#)', initializer, hash);
|
||||||
} else {
|
} else {
|
||||||
_eventLog.add(' - missing hunk: $uri ($hash)');
|
_addEvent({'part': '$uri', 'hash': '$hash', 'event': 'missing'});
|
||||||
|
|
||||||
throw new DeferredLoadException("Loading ${uris[i]} failed: "
|
throw new DeferredLoadException("Loading ${uris[i]} failed: "
|
||||||
"the code with hash '${hash}' was not loaded.\n"
|
"the code with hash '${hash}' was not loaded.\n"
|
||||||
"event log:\n${_eventLog.join("\n")}\n");
|
"event log:\n${_getEventLog()}\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2999,10 +3027,12 @@ String _computeThisScriptFromTrace() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> _loadHunk(String hunkName, String loadId, int priority) {
|
Future<Null> _loadHunk(String hunkName, String loadId, int priority) {
|
||||||
|
var initializationEventLog = JS_EMBEDDED_GLOBAL('', INITIALIZATION_EVENT_LOG);
|
||||||
|
|
||||||
var future = _loadingLibraries[hunkName];
|
var future = _loadingLibraries[hunkName];
|
||||||
_eventLog.add(' - _loadHunk: $hunkName');
|
_addEvent({'part': hunkName, 'event': 'startLoad'});
|
||||||
if (future != null) {
|
if (future != null) {
|
||||||
_eventLog.add('reuse: $hunkName');
|
_addEvent({'part': hunkName, 'event': 'reuse'});
|
||||||
return future.then((Null _) => null);
|
return future.then((Null _) => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3012,23 +3042,24 @@ Future<Null> _loadHunk(String hunkName, String loadId, int priority) {
|
||||||
// sanitized URL.
|
// sanitized URL.
|
||||||
String uriAsString = JS('', '#.toString()', trustedScriptUri);
|
String uriAsString = JS('', '#.toString()', trustedScriptUri);
|
||||||
|
|
||||||
_eventLog.add(' - download: $hunkName from $uriAsString');
|
_addEvent({'uri': uriAsString, 'part': hunkName, 'event': 'download'});
|
||||||
|
|
||||||
var deferredLibraryLoader = JS('', 'self.dartDeferredLibraryLoader');
|
var deferredLibraryLoader = JS('', 'self.dartDeferredLibraryLoader');
|
||||||
Completer<Null> completer = Completer();
|
Completer<Null> completer = Completer();
|
||||||
|
|
||||||
void success() {
|
void success() {
|
||||||
_eventLog.add(' - download success: $hunkName');
|
_addEvent({'part': hunkName, 'event': 'downloadSuccess'});
|
||||||
completer.complete(null);
|
completer.complete(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void failure(error, String context, StackTrace? stackTrace) {
|
void failure(error, String context, StackTrace? stackTrace) {
|
||||||
_eventLog.add(' - download failed: $hunkName (context: $context)');
|
_addEvent(
|
||||||
|
{'part': hunkName, 'context': context, 'event': 'downloadFailure'});
|
||||||
_loadingLibraries[hunkName] = null;
|
_loadingLibraries[hunkName] = null;
|
||||||
stackTrace ??= StackTrace.current;
|
stackTrace ??= StackTrace.current;
|
||||||
completer.completeError(
|
completer.completeError(
|
||||||
DeferredLoadException('Loading $uriAsString failed: $error\n'
|
DeferredLoadException('Loading $uriAsString failed: $error\n'
|
||||||
'event log:\n${_eventLog.join("\n")}\n'),
|
'event log:\n${_getEventLog()}\n'),
|
||||||
stackTrace);
|
stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,16 @@ const IS_HUNK_INITIALIZED = 'isHunkInitialized';
|
||||||
/// globals don't clash with it.
|
/// globals don't clash with it.
|
||||||
const DEFERRED_INITIALIZED = 'deferredInitialized';
|
const DEFERRED_INITIALIZED = 'deferredInitialized';
|
||||||
|
|
||||||
|
/// Property name for the reference to the initialization event log which is
|
||||||
|
/// included in exceptions when deferred loading fails.
|
||||||
|
///
|
||||||
|
/// The event log is a JS array where each entry is a plain JS object
|
||||||
|
/// representing event data. Each entry will be passed to JSON.stringify()
|
||||||
|
/// before being appended to the thrown exception.
|
||||||
|
///
|
||||||
|
/// This embedded global is only used for deferred loading.
|
||||||
|
const INITIALIZATION_EVENT_LOG = 'eventLog';
|
||||||
|
|
||||||
/// An embedded global used to collect and access runtime metrics.
|
/// An embedded global used to collect and access runtime metrics.
|
||||||
const RUNTIME_METRICS = 'rm';
|
const RUNTIME_METRICS = 'rm';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue