From a0b35de326441b462f6fe0fb3a2b18bb10a40390 Mon Sep 17 00:00:00 2001 From: Mark Zhou Date: Fri, 29 Apr 2022 21:48:41 +0000 Subject: [PATCH] [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 Reviewed-by: Stephen Adams Commit-Queue: Mark Zhou --- .../js_emitter/main_call_stub_generator.dart | 16 +++++- .../program_builder/program_builder.dart | 8 ++- .../startup_emitter/fragment_emitter.dart | 24 ++++++--- .../startup_emitter/model_emitter.dart | 26 +++------- pkg/dart2js_runtime_metrics/CHANGELOG.md | 4 ++ .../lib/_runtime_metrics_dart2js.dart | 5 ++ .../lib/_runtime_metrics_dartdevc.dart | 12 +++++ .../lib/_runtime_metrics_unknown.dart | 12 +++++ .../lib/_runtime_metrics_vm.dart | 12 +++++ .../lib/runtime_metrics.dart | 8 +++ pkg/dart2js_runtime_metrics/pubspec.yaml | 2 +- .../test/runtime_metrics_test.dart | 51 +++++++++++++++++++ pkg/js_runtime/lib/shared/embedded_names.dart | 6 +++ .../lib/dart2js_runtime_metrics.dart | 27 +++++++++- .../_internal/js_runtime/lib/js_helper.dart | 20 ++++++++ .../js_runtime/lib/shared/embedded_names.dart | 6 +++ 16 files changed, 208 insertions(+), 31 deletions(-) create mode 100644 pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dart2js.dart create mode 100644 pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dartdevc.dart create mode 100644 pkg/dart2js_runtime_metrics/lib/_runtime_metrics_unknown.dart create mode 100644 pkg/dart2js_runtime_metrics/lib/_runtime_metrics_vm.dart create mode 100644 pkg/dart2js_runtime_metrics/lib/runtime_metrics.dart create mode 100644 pkg/dart2js_runtime_metrics/test/runtime_metrics_test.dart diff --git a/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart index fe6ef0c4893..60984933fcf 100644 --- a/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart +++ b/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart @@ -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, }); diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart index 5068dda9ea2..b9f15350d92 100644 --- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart +++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart @@ -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) { diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart index 66db107af05..fc83adb4152 100644 --- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart +++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart @@ -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); diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart index 242dc31b9a9..b5bf78d3b56 100644 --- a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart +++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart @@ -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 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) ]); diff --git a/pkg/dart2js_runtime_metrics/CHANGELOG.md b/pkg/dart2js_runtime_metrics/CHANGELOG.md index a0712a79e75..b7d9e52abd1 100644 --- a/pkg/dart2js_runtime_metrics/CHANGELOG.md +++ b/pkg/dart2js_runtime_metrics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +- Adding `runtimeMetrics`. + ## 0.1.0 - Initial version. diff --git a/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dart2js.dart b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dart2js.dart new file mode 100644 index 00000000000..3aa7cbbc8b7 --- /dev/null +++ b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dart2js.dart @@ -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; diff --git a/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dartdevc.dart b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dartdevc.dart new file mode 100644 index 00000000000..43382b8c326 --- /dev/null +++ b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_dartdevc.dart @@ -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 get runtimeMetrics { + return {'runtime': 'dartdevc'}; +} diff --git a/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_unknown.dart b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_unknown.dart new file mode 100644 index 00000000000..41659d2b655 --- /dev/null +++ b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_unknown.dart @@ -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 get runtimeMetrics { + return {'runtime': 'unknown'}; +} diff --git a/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_vm.dart b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_vm.dart new file mode 100644 index 00000000000..8c4a7796edb --- /dev/null +++ b/pkg/dart2js_runtime_metrics/lib/_runtime_metrics_vm.dart @@ -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 get runtimeMetrics { + return {'runtime': 'vm'}; +} diff --git a/pkg/dart2js_runtime_metrics/lib/runtime_metrics.dart b/pkg/dart2js_runtime_metrics/lib/runtime_metrics.dart new file mode 100644 index 00000000000..1ae29fdcba6 --- /dev/null +++ b/pkg/dart2js_runtime_metrics/lib/runtime_metrics.dart @@ -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'; diff --git a/pkg/dart2js_runtime_metrics/pubspec.yaml b/pkg/dart2js_runtime_metrics/pubspec.yaml index 7b9b43935a3..31c227f552e 100644 --- a/pkg/dart2js_runtime_metrics/pubspec.yaml +++ b/pkg/dart2js_runtime_metrics/pubspec.yaml @@ -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. diff --git a/pkg/dart2js_runtime_metrics/test/runtime_metrics_test.dart b/pkg/dart2js_runtime_metrics/test/runtime_metrics_test.dart new file mode 100644 index 00000000000..8b0d0b529b3 --- /dev/null +++ b/pkg/dart2js_runtime_metrics/test/runtime_metrics_test.dart @@ -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 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 {} diff --git a/pkg/js_runtime/lib/shared/embedded_names.dart b/pkg/js_runtime/lib/shared/embedded_names.dart index 0334810fc3f..9d48e1adb51 100644 --- a/pkg/js_runtime/lib/shared/embedded_names.dart +++ b/pkg/js_runtime/lib/shared/embedded_names.dart @@ -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'; diff --git a/sdk/lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart b/sdk/lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart index 637d2c73eda..7077d0a7bb5 100644 --- a/sdk/lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart +++ b/sdk/lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart @@ -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 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, +/// 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 get runtimeMetrics { + final Map result = {'runtime': 'dart2js'}; + final raw = rawRuntimeMetrics(); + copyAndStringifyProperties(raw, result); + return result; +} diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart index ae676e0db74..50f9c4f34c8 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart @@ -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(void Function(T)? callback) { // For performance reasons avoid wrapping if we are in the root zone. diff --git a/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart b/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart index 0334810fc3f..9d48e1adb51 100644 --- a/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart +++ b/sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart @@ -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';