part of dart._engine; // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(mdebbar): Deprecate this and remove it. // https://github.com/flutter/flutter/issues/127395 @JS('window._flutter_internal_on_benchmark') external JSExportedDartFunction? get jsBenchmarkValueCallback; ui_web.BenchmarkValueCallback? engineBenchmarkValueCallback; /// A function that computes a value of type [R]. /// /// Functions of this signature can be passed to [timeAction] for performance /// profiling. typedef Action = R Function(); /// Uses the [Profiler] to time a synchronous [action] function and reports the /// result under the give metric [name]. /// /// If profiling is disabled, simply calls [action] and returns the result. /// /// Use this for situations when the cost of an extra closure is negligible. /// This function reduces the boilerplate associated with checking if profiling /// is enabled and exercising the stopwatch. /// /// Example: /// /// ```dart /// final result = timeAction('expensive_operation', () { /// // ... expensive work ... /// return someValue; /// }); /// ``` R timeAction(String name, Action action) { if (!Profiler.isBenchmarkMode) { return action(); } else { final Stopwatch stopwatch = Stopwatch()..start(); final R result = action(); stopwatch.stop(); Profiler.instance.benchmark(name, stopwatch.elapsedMicroseconds.toDouble()); return result; } } /// The purpose of this class is to facilitate communication of /// profiling/benchmark data to the outside world (e.g. a macrobenchmark that's /// running a flutter app). /// /// To use the [Profiler]: /// /// 1. Set the environment variable `FLUTTER_WEB_ENABLE_PROFILING` to true. /// /// 2. Using JS interop, assign a listener function to /// `window._flutter_internal_on_benchmark` in the browser. /// /// The listener function will be called every time a new benchmark number is /// calculated. The signature is `Function(String name, num value)`. class Profiler { Profiler._() { _checkBenchmarkMode(); } static bool isBenchmarkMode = const bool.fromEnvironment('FLUTTER_WEB_ENABLE_PROFILING'); static Profiler ensureInitialized() { _checkBenchmarkMode(); return Profiler._instance ??= Profiler._(); } static Profiler get instance { _checkBenchmarkMode(); final Profiler? profiler = _instance; if (profiler == null) { throw Exception( 'Profiler has not been properly initialized. ' 'Make sure Profiler.ensureInitialized() is being called before you ' 'access Profiler.instance', ); } return profiler; } static Profiler? _instance; static void _checkBenchmarkMode() { if (!isBenchmarkMode) { throw Exception( 'Cannot use Profiler unless benchmark mode is enabled. ' 'You can enable it by setting the `FLUTTER_WEB_ENABLE_PROFILING` ' 'environment variable to true.', ); } } /// Used to send benchmark data to whoever is listening to them. void benchmark(String name, double value) { _checkBenchmarkMode(); final ui_web.BenchmarkValueCallback? callback = jsBenchmarkValueCallback?.toDart as ui_web.BenchmarkValueCallback?; if (callback != null) { printWarning( 'The JavaScript benchmarking API (i.e. `window._flutter_internal_on_benchmark`) ' 'is deprecated and will be removed in a future release. Please use ' '`benchmarkValueCallback` from `dart:ui_web` instead.', ); callback(name, value); } if (engineBenchmarkValueCallback != null) { engineBenchmarkValueCallback!(name, value); } } } /// Counts various events that take place while the app is running. /// /// This class will slow down the app, and therefore should be disabled while /// benchmarking. For example, avoid using it in conjunction with [Profiler]. class Instrumentation { Instrumentation._() { _checkInstrumentationEnabled(); } /// Whether instrumentation is enabled. /// /// Check this value before calling any other methods in this class. static bool get enabled => _enabled; static set enabled(bool value) { if (_enabled == value) { return; } if (!value) { _instance._counters.clear(); _instance._printTimer = null; } _enabled = value; } static bool _enabled = const bool.fromEnvironment('FLUTTER_WEB_ENABLE_INSTRUMENTATION'); /// Returns the singleton that provides instrumentation API. static Instrumentation get instance { _checkInstrumentationEnabled(); return _instance; } static final Instrumentation _instance = Instrumentation._(); static void _checkInstrumentationEnabled() { if (!enabled) { throw StateError( 'Cannot use Instrumentation unless it is enabled. ' 'You can enable it by setting the `FLUTTER_WEB_ENABLE_INSTRUMENTATION` ' 'environment variable to true, or by passing ' '--dart-define=FLUTTER_WEB_ENABLE_INSTRUMENTATION=true to the flutter ' 'tool.', ); } } Map get debugCounters => _counters; final Map _counters = {}; Timer? get debugPrintTimer => _printTimer; Timer? _printTimer; /// Increments the count of a particular event by one. void incrementCounter(String event) { _checkInstrumentationEnabled(); final int currentCount = _counters[event] ?? 0; _counters[event] = currentCount + 1; _printTimer ??= Timer(const Duration(seconds: 2), () { if (_printTimer == null || !_enabled) { return; } final StringBuffer message = StringBuffer('Engine counters:\n'); // Entries are sorted for readability and testability. final List> entries = _counters.entries.toList()..sort((MapEntry a, MapEntry b) { return a.key.compareTo(b.key); }); for (final MapEntry entry in entries) { message.writeln(' ${entry.key}: ${entry.value}'); } print(message); _printTimer = null; }); } }