| // Copyright (c) 2023, 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. |
| |
| import "dart:io"; |
| |
| Stopwatch stopwatch = new Stopwatch(); |
| List<int> data = []; |
| bool _doReportCandidates = false; |
| late List<Sum> _sum; |
| List<int> _activeStack = []; |
| List<DelayedReportData> delayedReportData = []; |
| |
| void initialize(int count, bool reportCandidates) { |
| _sum = new List.generate(count, (_) => new Sum(), growable: false); |
| stopwatch.start(); |
| _doReportCandidates = reportCandidates; |
| } |
| |
| @pragma("vm:prefer-inline") |
| void enter(int i) { |
| data.add(1); // enter |
| data.add(i); // what |
| data.add(stopwatch.elapsedTicks); // time |
| } |
| |
| @pragma("vm:prefer-inline") |
| void exit(int i) { |
| data.add(0); // exit |
| data.add(i); // what |
| data.add(stopwatch.elapsedTicks); // time |
| |
| if (_doReportCandidates && data.length > 100000000) { |
| // We're reporting everything which can use a lot of ram. Try to trim it. |
| _trim(); |
| } |
| } |
| |
| void _trim() { |
| stopwatch.stop(); |
| print("Trimming..."); |
| int factorForMicroSeconds = stopwatch.frequency ~/ 1000000; |
| _processData(null, factorForMicroSeconds); |
| stopwatch.start(); |
| } |
| |
| void report(List<String> names) { |
| int factorForMicroSeconds = stopwatch.frequency ~/ 1000000; |
| |
| File f = new File("cfe_compile_trace.txt"); |
| RandomAccessFile randomAccessFile = f.openSync(mode: FileMode.writeOnly); |
| StringBuffer sb = new StringBuffer(); |
| sb.write("["); |
| |
| WithOutputInfo withOutputInfo = |
| new WithOutputInfo(names, sb, randomAccessFile); |
| |
| // Report previously delayed data. |
| for (DelayedReportData data in delayedReportData) { |
| _writeFlameOutputToBuffer(withOutputInfo, data.procedureNumber, |
| data.enterMicroseconds, data.duration); |
| } |
| |
| // Report "new" data. |
| _processData(withOutputInfo, factorForMicroSeconds); |
| sb.write("\n]"); |
| randomAccessFile.writeStringSync(sb.toString()); |
| sb.clear(); |
| randomAccessFile.closeSync(); |
| print("Write to $f"); |
| |
| _reportCandidates(factorForMicroSeconds, names); |
| } |
| |
| class WithOutputInfo { |
| final List<String> names; |
| final StringBuffer sb; |
| String separator = "\n"; |
| final RandomAccessFile randomAccessFile; |
| |
| WithOutputInfo(this.names, this.sb, this.randomAccessFile); |
| } |
| |
| void _processData(WithOutputInfo? withOutputInfo, int factorForMicroSeconds) { |
| for (int i = 0; i < data.length; i += 3) { |
| int enterOrExit = data[i]; |
| int procedureNumber = data[i + 1]; |
| int ticks = data[i + 2]; |
| if (enterOrExit == 1) { |
| // Enter. |
| _activeStack.add(procedureNumber); |
| _activeStack.add(ticks); |
| } else if (enterOrExit == 0) { |
| // Exit |
| int enterTicks = _activeStack.removeLast(); |
| int enterProcedureNumber = _activeStack.removeLast(); |
| if (enterProcedureNumber != procedureNumber) { |
| if (withOutputInfo != null) { |
| print("DEBUG: Now exiting " |
| "${withOutputInfo.names[procedureNumber]}."); |
| print("DEBUG: Latest entering " |
| "${withOutputInfo.names[enterProcedureNumber]}."); |
| } |
| bool foundMatch = false; |
| int steps = 1; |
| for (int i = _activeStack.length - 2; i >= 0; i -= 2) { |
| steps++; |
| if (_activeStack[i] == procedureNumber) { |
| foundMatch = true; |
| break; |
| } |
| } |
| if (foundMatch) { |
| _activeStack.add(enterProcedureNumber); |
| _activeStack.add(enterTicks); |
| enterProcedureNumber = |
| _activeStack.removeAt(_activeStack.length - steps * 2); |
| enterTicks = _activeStack.removeAt(_activeStack.length - steps * 2); |
| assert(enterProcedureNumber != procedureNumber); |
| } else { |
| throw "Mismatching enter/exit with no matching " |
| "enter found for this exit."; |
| } |
| } |
| |
| double enterMicroseconds = enterTicks / factorForMicroSeconds; |
| double duration = (ticks - enterTicks) / factorForMicroSeconds; |
| _sum[procedureNumber].addTick(ticks - enterTicks); |
| |
| // Avoid outputting too much data. |
| if (_doReportCandidates) { |
| // If collecting all, don't output everything as that will simply be |
| // too much data. |
| if (duration < 1000) continue; |
| } |
| if (withOutputInfo != null) { |
| _writeFlameOutputToBuffer( |
| withOutputInfo, procedureNumber, enterMicroseconds, duration); |
| } else { |
| // Save for later output. |
| delayedReportData.add(new DelayedReportData( |
| procedureNumber, enterMicroseconds, duration)); |
| } |
| } else { |
| throw "Error: $enterOrExit expected to be 0 or 1."; |
| } |
| if (withOutputInfo != null && withOutputInfo.sb.length > 1024 * 1024) { |
| withOutputInfo.randomAccessFile |
| .writeStringSync(withOutputInfo.sb.toString()); |
| withOutputInfo.sb.clear(); |
| } |
| } |
| data.clear(); |
| } |
| |
| void _writeFlameOutputToBuffer(WithOutputInfo withOutputInfo, |
| int procedureNumber, double enterMicroseconds, double duration) { |
| withOutputInfo.sb.write(withOutputInfo.separator); |
| withOutputInfo.separator = ",\n"; |
| String name = withOutputInfo.names[procedureNumber]; |
| |
| String displayName = name.substring(name.indexOf("|") + 1); |
| String file = name.substring(0, name.indexOf("|")); |
| withOutputInfo.sb |
| .write('{"ph": "X", "ts": $enterMicroseconds, "dur": $duration, ' |
| '"name": "$displayName", "cat": "$file", "pid": 1, "tid": 1}'); |
| } |
| |
| void _reportCandidates(int factorForMicroSeconds, List<String> names) { |
| StringBuffer sb = new StringBuffer(); |
| StringBuffer sbDebug = new StringBuffer(); |
| final int leastRuntime = _doReportCandidates ? 500 : 50; |
| for (int i = 0; i < _sum.length; i++) { |
| Sum sum = _sum[i]; |
| double averageMicroseconds = sum.average / factorForMicroSeconds; |
| if (averageMicroseconds >= leastRuntime) { |
| // Average call time is >= 0.5 or 0.05 ms. |
| String name = names[i]; |
| sb.writeln(name); |
| sbDebug.writeln(name); |
| sbDebug.writeln(" => ${sum.count}, ${sum.totalTicks}, " |
| "${sum.average}, $averageMicroseconds"); |
| } |
| } |
| if (sb.length > 0) { |
| String extra = _doReportCandidates ? "" : "_subsequent"; |
| File f = new File("cfe_compile_trace_candidates$extra.txt"); |
| f.writeAsStringSync(sb.toString()); |
| print("Wrote candidates to $f"); |
| f = new File("cfe_compile_trace_candidates${extra}_debug.txt"); |
| f.writeAsStringSync(sbDebug.toString()); |
| print("Wrote candidates debug data to $f"); |
| } |
| } |
| |
| class Sum { |
| int count = 0; |
| int totalTicks = 0; |
| double get average => totalTicks / count; |
| void addTick(int tick) { |
| count++; |
| totalTicks += tick; |
| } |
| } |
| |
| class DelayedReportData { |
| final int procedureNumber; |
| final double enterMicroseconds; |
| final double duration; |
| |
| DelayedReportData( |
| this.procedureNumber, this.enterMicroseconds, this.duration); |
| } |