blob: 5e22d2be31d3d3508d26205e6f91e816c3895dd4 [file] [log] [blame]
// 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);
}