[dart2js] Moving runtime allocations to pkg:dart2js_runtime_metrics

* Stores runtime information on `$runtimeMetrics` instead of `$__dart_deferred_initializers__`
* Keys each app's runtime allocations onto self.runtimeMetrics.$currentScript

Change-Id: I6612fd2a5ac792bfcb7580ffe91d5391b80d5965
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242507
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
This commit is contained in:
Mark Zhou 2022-04-29 21:48:41 +00:00 committed by Commit Bot
parent f092184371
commit a0b35de326
16 changed files with 208 additions and 31 deletions

View file

@ -6,6 +6,7 @@
library dart2js.js_emitter.main_call_stub_generator;
import 'package:compiler/src/options.dart';
import 'package:js_runtime/shared/embedded_names.dart' as embeddedNames;
import '../common/elements.dart';
@ -16,8 +17,12 @@ import '../js/js.dart' show js;
import 'code_emitter_task.dart' show Emitter;
class MainCallStubGenerator {
static jsAst.Statement generateInvokeMain(CommonElements commonElements,
Emitter emitter, FunctionEntity main, bool requiresStartupMetrics) {
static jsAst.Statement generateInvokeMain(
CommonElements commonElements,
Emitter emitter,
FunctionEntity main,
bool requiresStartupMetrics,
CompilerOptions options) {
jsAst.Expression mainAccess = emitter.staticFunctionAccess(main);
jsAst.Expression currentScriptAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.CURRENT_SCRIPT);
@ -96,6 +101,10 @@ class MainCallStubGenerator {
if (#startupMetrics) {
init.#startupMetricsEmbeddedGlobal.add('callMainMs');
}
if (#isCollectingRuntimeMetrics) {
self.#runtimeMetricsContainer = self.#runtimeMetricsContainer || Object.create(null);
self.#runtimeMetricsContainer[currentScript.src] = init.#runtimeMetricsEmbeddedGlobal;
}
var callMain = #mainCallClosure;
if (typeof dartMainRunner === "function") {
dartMainRunner(callMain, []);
@ -105,6 +114,9 @@ class MainCallStubGenerator {
})''', {
'currentScript': currentScriptAccess,
'mainCallClosure': mainCallClosure,
'isCollectingRuntimeMetrics': options.experimentalTrackAllocations,
'runtimeMetricsContainer': embeddedNames.RUNTIME_METRICS_CONTAINER,
'runtimeMetricsEmbeddedGlobal': embeddedNames.RUNTIME_METRICS,
'startupMetrics': requiresStartupMetrics,
'startupMetricsEmbeddedGlobal': embeddedNames.STARTUP_METRICS,
});

View file

@ -296,8 +296,12 @@ class ProgramBuilder {
}
js.Statement _buildInvokeMain() {
return MainCallStubGenerator.generateInvokeMain(_commonElements,
_task.emitter, _mainFunction, _backendUsage.requiresStartupMetrics);
return MainCallStubGenerator.generateInvokeMain(
_commonElements,
_task.emitter,
_mainFunction,
_backendUsage.requiresStartupMetrics,
_options);
}
DeferredFragment _buildDeferredFragment(LibrariesMap librariesMap) {

View file

@ -46,6 +46,11 @@ if (#startupMetrics) {
.add("dartProgramMs");
}
if (#isCollectingRuntimeMetrics) {
dartProgram.$RUNTIME_METRICS = Object.create(null);
var allocations = dartProgram.$RUNTIME_METRICS['allocations'] = Object.create(null);
}
// Copies the own properties from [from] to [to].
function copyProperties(from, to) {
var keys = Object.keys(from);
@ -420,10 +425,6 @@ function initializeDeferredHunk(hunk) {
hunk(hunkHelpers, #embeddedGlobalsObject, holders, #staticState);
}
if (#isTrackingAllocations) {
var allocations = #deferredGlobal['allocations'] = {};
}
// Creates the holders.
#holders;
@ -523,7 +524,9 @@ const String _directAccessTestExpression = r'''
/// This template is used for Dart 2.
const String _deferredBoilerplate = '''
function(hunkHelpers, #embeddedGlobalsObject, holdersList, #staticState) {
if (#isCollectingRuntimeMetrics) {
var allocations = #embeddedGlobalsObject.#runtimeMetrics['allocations'];
}
// Builds the holders. They only contain the data for new holders.
// If names are not set on functions, we do it now. Finally, updates the
// holders of the main-fragment. Uses the provided holdersList to access the
@ -751,8 +754,7 @@ class FragmentEmitter {
//'stubName': js.string(_namer.stubNameField),
//'argumentCount': js.string(_namer.fixedNames.requiredParameterField),
//'defaultArgumentValues': js.string(_namer.fixedNames.defaultValuesField),
'deferredGlobal': ModelEmitter.deferredInitializersGlobal,
'isTrackingAllocations': _options.experimentalTrackAllocations,
'isCollectingRuntimeMetrics': _options.experimentalTrackAllocations,
'prototypes': emitPrototypes(fragment),
'inheritance': emitInheritance(fragment),
'aliases': emitInstanceMethodAliases(fragment),
@ -808,7 +810,9 @@ class FragmentEmitter {
js.Expression code = js.js(_deferredBoilerplate, {
// TODO(floitsch): don't just reference 'init'.
'embeddedGlobalsObject': js.Parameter('init'),
'isCollectingRuntimeMetrics': _options.experimentalTrackAllocations,
'staticState': DeferredHolderParameter(),
'runtimeMetrics': RUNTIME_METRICS,
'updateHolders': updateHolders,
'prototypes': fragment.classPrototypes,
'closures': fragment.closurePrototypes,
@ -1875,6 +1879,12 @@ class FragmentEmitter {
js.string(STARTUP_METRICS), js.js('dartProgram.$STARTUP_METRICS')));
}
if (_options.experimentalTrackAllocations) {
// Copy the metrics object that was stored on the main unit IIFE.
globals.add(js.Property(
js.string(RUNTIME_METRICS), js.js('dartProgram.$RUNTIME_METRICS')));
}
js.ObjectInitializer globalsObject =
js.ObjectInitializer(globals, isOneLiner: false);

View file

@ -27,6 +27,7 @@ import 'package:js_runtime/shared/embedded_names.dart'
NATIVE_SUPERCLASS_TAG_NAME,
RTI_UNIVERSE,
RtiUniverseFieldNames,
RUNTIME_METRICS,
STARTUP_METRICS,
TearOffParametersPropertyNames,
TYPE_TO_INTERCEPTOR_MAP,
@ -368,19 +369,11 @@ class ModelEmitter {
return js.Comment(generatedBy(_options, flavor: '$flavor'));
}
List<js.Statement> buildDeferredInitializerGlobal() {
return [
js.js.statement(
'self.#deferredInitializers = '
'self.#deferredInitializers || Object.create(null);',
{'deferredInitializers': deferredInitializersGlobal}),
if (_options.experimentalTrackAllocations)
js.js.statement(
'self.#deferredInitializers["allocations"] = '
'self.#deferredInitializers["allocations"] '
'|| Object.create(null)',
{'deferredInitializers': deferredInitializersGlobal})
];
js.Statement buildDeferredInitializerGlobal() {
return js.js.statement(
'self.#deferredInitializers = '
'self.#deferredInitializers || Object.create(null);',
{'deferredInitializers': deferredInitializersGlobal});
}
js.Statement buildStartupMetrics() {
@ -431,7 +424,7 @@ var ${startupMetricsGlobal} =
js.Program program = js.Program([
buildGeneratedBy(),
js.Comment(HOOKS_API_USAGE),
if (isSplit) ...buildDeferredInitializerGlobal(),
if (isSplit) buildDeferredInitializerGlobal(),
if (_closedWorld.backendUsage.requiresStartupMetrics)
buildStartupMetrics(),
code
@ -566,10 +559,7 @@ var ${startupMetricsGlobal} =
js.Program program = js.Program([
if (isFirst) buildGeneratedBy(),
if (isFirst) ...buildDeferredInitializerGlobal(),
if (_options.experimentalTrackAllocations)
js.js.statement("var allocations = #deferredGlobal['allocations']",
{'deferredGlobal': deferredInitializersGlobal}),
if (isFirst) buildDeferredInitializerGlobal(),
js.js.statement('$deferredInitializersGlobal.current = #', code)
]);

View file

@ -1,3 +1,7 @@
## 0.2.0
- Adding `runtimeMetrics`.
## 0.1.0
- Initial version.

View file

@ -0,0 +1,5 @@
// Copyright (c) 2022, 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.
export 'dart:_dart2js_runtime_metrics' show runtimeMetrics;

View file

@ -0,0 +1,12 @@
// Copyright (c) 2022, 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.
/// A collection of metrics collected during the runtime of a Dart app.
///
/// The contents of the map depend on the platform. The map values are simple
/// objects (strings, numbers, Booleans). There is always an entry for the key
/// `'runtime'` with a [String] value.
Map<String, Object> get runtimeMetrics {
return {'runtime': 'dartdevc'};
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2022, 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.
/// A collection of metrics collected during the runtime of a Dart app.
///
/// The contents of the map depend on the platform. The map values are simple
/// objects (strings, numbers, Booleans). There is always an entry for the key
/// `'runtime'` with a [String] value.
Map<String, Object> get runtimeMetrics {
return {'runtime': 'unknown'};
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2022, 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.
/// A collection of metrics collected during the runtime of a Dart app.
///
/// The contents of the map depend on the platform. The map values are simple
/// objects (strings, numbers, Booleans). There is always an entry for the key
/// `'runtime'` with a [String] value.
Map<String, Object> get runtimeMetrics {
return {'runtime': 'vm'};
}

View file

@ -0,0 +1,8 @@
// Copyright (c) 2022, 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.
export '_runtime_metrics_unknown.dart'
if (dart.library._dart2js_runtime_metrics) '_runtime_metrics_dart2js.dart'
if (dart.library.ffi) '_runtime_metrics_vm.dart'
if (dart.library.js) '_runtime_metrics_dartdevc.dart';

View file

@ -1,7 +1,7 @@
name: dart2js_runtime_metrics
# This package is not intended for consumption on pub.dev. DO NOT publish.
publish_to: none
version: 0.1.0
version: 0.2.0
description: >-
`dart2js` can generate extra code to measure certain activities.
This library provides access to the measurements at runtime.

View file

@ -0,0 +1,51 @@
// Copyright (c) 2022, 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.
// dart2jsOptions=--experimental-track-allocations
import 'package:dart2js_runtime_metrics/runtime_metrics.dart';
import 'package:expect/expect.dart';
void main() {
Map<String, Object> metrics = runtimeMetrics;
print('metrics: $metrics');
String expectedRuntime;
if (1.0 is! int) {
expectedRuntime = 'vm';
} else if (ClassWithLongName().toString().contains('minified:')) {
// dart2js minified: "Instance of 'minified:xy'".
expectedRuntime = 'dart2js';
} else if ('$main' == "Closure 'main'") {
// dart2js non-minified.
expectedRuntime = 'dart2js';
} else if ('$main'.startsWith('Closure: () => void from: function main()')) {
expectedRuntime = 'dartdevc';
} else {
throw 'Cannot feature-test current runtime:'
'\nmetrics = $metrics\n main = $main';
}
Expect.isTrue(metrics.containsKey('runtime'), "Has 'runtime' key: $metrics");
Expect.equals(expectedRuntime, metrics['runtime'],
"Expected 'runtime: $expectedRuntime': $metrics");
if (expectedRuntime == 'dart2js') {
Expect.isTrue(metrics.containsKey('allocations'));
return;
}
if (expectedRuntime == 'dartdevc') {
return;
}
if (expectedRuntime == 'vm') {
return;
}
throw 'Should not get here.';
}
class ClassWithLongName {}

View file

@ -179,6 +179,12 @@ const DEFERRED_INITIALIZED = 'deferredInitialized';
/// This embedded global is used for --experiment-new-rti.
const RTI_UNIVERSE = 'typeUniverse';
/// An embedded global used to collect and access runtime metrics.
const RUNTIME_METRICS = 'rm';
/// Global name that holds runtime metrics Dart2JS apps.
const RUNTIME_METRICS_CONTAINER = 'runtimeMetrics';
/// An embedded global used to collect and access startup metrics.
const STARTUP_METRICS = 'sm';

View file

@ -4,7 +4,12 @@
library dart2js_runtime_metrics;
import 'dart:_js_helper' show fillLiteralMap, rawStartupMetrics;
import 'dart:_js_helper'
show
copyAndStringifyProperties,
fillLiteralMap,
rawRuntimeMetrics,
rawStartupMetrics;
/// A collection of metrics for events that happen before `main()` is entered.
///
@ -35,3 +40,23 @@ Map<String, Object> get startupMetrics {
fillLiteralMap(raw, result);
return result;
}
/// A collection of metrics collected during the runtime of a Dart app.
///
/// The contents of the map depend on the platform. The map values are simple
/// objects (strings, numbers, Booleans). There is always an entry for the key
/// `'runtime'` with a [String] value.
///
/// This implementation for dart2js has the content (subject to change):
///
/// - `runtime`: `'dart2js'`
///
/// - `allocations`: A string representation of a Json Map<String, Object>,
/// which holds every class or closure created at runtime. The key contains
/// a resolved path of the class or closure. The value is currently unused.
Map<String, Object> get runtimeMetrics {
final Map<String, Object> result = {'runtime': 'dart2js'};
final raw = rawRuntimeMetrics();
copyAndStringifyProperties(raw, result);
return result;
}

View file

@ -21,6 +21,7 @@ import 'dart:_js_embedded_names'
JsGetName,
LEAF_TAGS,
NATIVE_SUPERCLASS_TAG_NAME,
RUNTIME_METRICS,
STARTUP_METRICS,
STATIC_FUNCTION_NAME_PROPERTY_NAME,
TearOffParametersPropertyNames;
@ -1801,6 +1802,21 @@ fillLiteralSet(values, Set result) {
return result;
}
/// Called by generated code to move and stringify properties from an object
/// to a map literal.
copyAndStringifyProperties(from, Map to) {
if (JS('bool', '!#', from)) return to;
List keys = JS('JSArray', r'Object.keys(#)', from);
int index = 0;
int length = getLength(keys);
while (index < length) {
var key = getIndex(keys, index++);
var value = JS('String', r'JSON.stringify(#[#])', from, key);
to[key] = value;
}
return to;
}
/// Returns the property [index] of the JavaScript array [array].
getIndex(var array, int index) {
return JS('var', r'#[#]', array, index);
@ -3064,6 +3080,10 @@ Object? rawStartupMetrics() {
return JS('JSArray', '#.a', JS_EMBEDDED_GLOBAL('', STARTUP_METRICS));
}
Object? rawRuntimeMetrics() {
return JS('', '#', JS_EMBEDDED_GLOBAL('', RUNTIME_METRICS));
}
/// Wraps the given [callback] within the current Zone.
void Function(T)? wrapZoneUnaryCallback<T>(void Function(T)? callback) {
// For performance reasons avoid wrapping if we are in the root zone.

View file

@ -179,6 +179,12 @@ const DEFERRED_INITIALIZED = 'deferredInitialized';
/// This embedded global is used for --experiment-new-rti.
const RTI_UNIVERSE = 'typeUniverse';
/// An embedded global used to collect and access runtime metrics.
const RUNTIME_METRICS = 'rm';
/// Global name that holds runtime metrics Dart2JS apps.
const RUNTIME_METRICS_CONTAINER = 'runtimeMetrics';
/// An embedded global used to collect and access startup metrics.
const STARTUP_METRICS = 'sm';