| // 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. |
| |
| import 'dart:async'; |
| import 'dart:js_interop'; |
| |
| import 'package:ui/ui_web/src/ui_web.dart' as ui_web; |
| |
| import 'util.dart'; |
| |
| // 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> = 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: |
| /// |
| /// ``` |
| /// final result = timeAction('expensive_operation', () { |
| /// ... expensive work ... |
| /// return someValue; |
| /// }); |
| /// ``` |
| R timeAction<R>(String name, Action<R> 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<String, int> get debugCounters => _counters; |
| final Map<String, int> _counters = <String, int>{}; |
| |
| 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<MapEntry<String, int>> entries = _counters.entries.toList() |
| ..sort((MapEntry<String, int> a, MapEntry<String, int> b) { |
| return a.key.compareTo(b.key); |
| }); |
| for (final MapEntry<String, int> entry in entries) { |
| message.writeln(' ${entry.key}: ${entry.value}'); |
| } |
| print(message); |
| _printTimer = null; |
| }, |
| ); |
| } |
| } |