in build_runner/lib/src/server/server.dart [398:637]
String _renderPerformance(
BuildPerformance performance,
bool hideSkipped,
bool detailedSlices,
int slicesResolution,
PerfSortOrder sortOrder,
String filter) {
try {
var rows = StringBuffer();
final resolution = Duration(milliseconds: slicesResolution);
var count = 0,
maxSlices = 1,
max = 0,
min = performance.stopTime.millisecondsSinceEpoch -
performance.startTime.millisecondsSinceEpoch;
void writeRow(BuilderActionPerformance action,
BuilderActionStagePerformance stage, TimeSlice slice) {
var actionKey = '${action.builderKey}:${action.primaryInput}';
var tooltip = '<div class=perf-tooltip>'
'<p><b>Builder:</b> ${action.builderKey}</p>'
'<p><b>Input:</b> ${action.primaryInput}</p>'
'<p><b>Stage:</b> ${stage.label}</p>'
'<p><b>Stage time:</b> '
'${stage.startTime.difference(performance.startTime).inMilliseconds / 1000}s - '
'${stage.stopTime.difference(performance.startTime).inMilliseconds / 1000}s</p>'
'<p><b>Stage real duration:</b> ${stage.duration.inMilliseconds / 1000} seconds</p>'
'<p><b>Stage user duration:</b> ${stage.innerDuration.inMilliseconds / 1000} seconds</p>';
if (slice != stage) {
tooltip += '<p><b>Slice time:</b> '
'${slice.startTime.difference(performance.startTime).inMilliseconds / 1000}s - '
'${slice.stopTime.difference(performance.startTime).inMilliseconds / 1000}s</p>'
'<p><b>Slice duration:</b> ${slice.duration.inMilliseconds / 1000} seconds</p>';
}
tooltip += '</div>';
var start = slice.startTime.millisecondsSinceEpoch -
performance.startTime.millisecondsSinceEpoch;
var end = slice.stopTime.millisecondsSinceEpoch -
performance.startTime.millisecondsSinceEpoch;
if (min > start) min = start;
if (max < end) max = end;
rows.writeln(
' ["$actionKey", "${stage.label}", "$tooltip", $start, $end],');
++count;
}
final filterRegex = filter.isNotEmpty ? RegExp(filter) : null;
final actions = performance.actions
.where((action) =>
!hideSkipped ||
action.stages.any((stage) => stage.label == 'Build'))
.where((action) =>
filterRegex == null ||
filterRegex.hasMatch('${action.builderKey}:${action.primaryInput}'))
.toList();
int Function(BuilderActionPerformance, BuilderActionPerformance) comparator;
switch (sortOrder) {
case PerfSortOrder.startTimeAsc:
comparator = (a1, a2) => a1.startTime.compareTo(a2.startTime);
break;
case PerfSortOrder.startTimeDesc:
comparator = (a1, a2) => a2.startTime.compareTo(a1.startTime);
break;
case PerfSortOrder.stopTimeAsc:
comparator = (a1, a2) => a1.stopTime.compareTo(a2.stopTime);
break;
case PerfSortOrder.stopTimeDesc:
comparator = (a1, a2) => a2.stopTime.compareTo(a1.stopTime);
break;
case PerfSortOrder.durationAsc:
comparator = (a1, a2) => a1.duration.compareTo(a2.duration);
break;
case PerfSortOrder.durationDesc:
comparator = (a1, a2) => a2.duration.compareTo(a1.duration);
break;
case PerfSortOrder.innerDurationAsc:
comparator = (a1, a2) => a1.innerDuration.compareTo(a2.innerDuration);
break;
case PerfSortOrder.innerDurationDesc:
comparator = (a1, a2) => a2.innerDuration.compareTo(a1.innerDuration);
break;
}
actions.sort(comparator);
for (var action in actions) {
if (hideSkipped &&
!action.stages.any((stage) => stage.label == 'Build')) {
continue;
}
for (var stage in action.stages) {
if (!detailedSlices) {
writeRow(action, stage, stage);
continue;
}
var slices = stage.slices.fold<List<TimeSlice>>([], (list, slice) {
if (list.isNotEmpty &&
slice.startTime.difference(list.last.stopTime) < resolution) {
// concat with previous if gap less than resolution
list.last = TimeSlice(list.last.startTime, slice.stopTime);
} else {
if (list.length > 1 && list.last.duration < resolution) {
// remove previous if its duration less than resolution
list.last = slice;
} else {
list.add(slice);
}
}
return list;
});
if (slices.isNotEmpty) {
for (var slice in slices) {
writeRow(action, stage, slice);
}
} else {
writeRow(action, stage, stage);
}
if (maxSlices < slices.length) maxSlices = slices.length;
}
}
if (max - min < 1000) {
rows.writeln(' ['
'"https://github.com/google/google-visualization-issues/issues/2269"'
', "", "", $min, ${min + 1000}]');
}
return '''
<html>
<head>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script>
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('timeline');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'string', id: 'ActionKey' });
dataTable.addColumn({ type: 'string', id: 'Stage' });
dataTable.addColumn({ type: 'string', role: 'tooltip', p: { html: true } });
dataTable.addColumn({ type: 'number', id: 'Start' });
dataTable.addColumn({ type: 'number', id: 'End' });
dataTable.addRows([
$rows
]);
console.log('rendering', $count, 'blocks, max', $maxSlices,
'slices in stage, resolution', $slicesResolution, 'ms');
var options = {
tooltip: { isHtml: true }
};
var statusText = document.getElementById('status');
var timeoutId;
var updateFunc = function () {
if (timeoutId) {
// don't schedule more than one at a time
return;
}
statusText.innerText = 'Drawing table...';
console.time('draw-time');
timeoutId = setTimeout(function () {
chart.draw(dataTable, options);
console.timeEnd('draw-time');
statusText.innerText = '';
timeoutId = null;
});
};
updateFunc();
window.addEventListener('resize', updateFunc);
}
</script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
}
body {
display: flex;
flex-direction: column;
}
#timeline {
display: flex;
flex-direction: row;
flex: 1;
}
.controls-header p {
display: inline-block;
margin: 0.5em;
}
.perf-tooltip {
margin: 0.5em;
}
</style>
</head>
<body>
<form class="controls-header" action="/$_performancePath" onchange="this.submit()">
<p><label><input type="checkbox" name="hideSkipped" value="true" ${hideSkipped ? 'checked' : ''}> Hide Skipped Actions</label></p>
<p><label><input type="checkbox" name="detailedSlices" value="true" ${detailedSlices ? 'checked' : ''}> Show Async Slices</label></p>
<p>Sort by: <select name="sortOrder">
<option value="0" ${sortOrder.index == 0 ? 'selected' : ''}>Start Time Asc</option>
<option value="1" ${sortOrder.index == 1 ? 'selected' : ''}>Start Time Desc</option>
<option value="2" ${sortOrder.index == 2 ? 'selected' : ''}>Stop Time Asc</option>
<option value="3" ${sortOrder.index == 3 ? 'selected' : ''}>Stop Time Desc</option>
<option value="5" ${sortOrder.index == 4 ? 'selected' : ''}>Real Duration Asc</option>
<option value="5" ${sortOrder.index == 5 ? 'selected' : ''}>Real Duration Desc</option>
<option value="6" ${sortOrder.index == 6 ? 'selected' : ''}>User Duration Asc</option>
<option value="7" ${sortOrder.index == 7 ? 'selected' : ''}>User Duration Desc</option>
</select></p>
<p>Slices Resolution: <select name="slicesResolution">
<option value="0" ${slicesResolution == 0 ? 'selected' : ''}>0</option>
<option value="1" ${slicesResolution == 1 ? 'selected' : ''}>1</option>
<option value="3" ${slicesResolution == 3 ? 'selected' : ''}>3</option>
<option value="5" ${slicesResolution == 5 ? 'selected' : ''}>5</option>
<option value="10" ${slicesResolution == 10 ? 'selected' : ''}>10</option>
<option value="15" ${slicesResolution == 15 ? 'selected' : ''}>15</option>
<option value="20" ${slicesResolution == 20 ? 'selected' : ''}>20</option>
<option value="25" ${slicesResolution == 25 ? 'selected' : ''}>25</option>
</select></p>
<p>Filter (RegExp): <input type="text" name="filter" value="$filter"></p>
<p id="status"></p>
</form>
<div id="timeline"></div>
</body>
</html>
''';
} on UnimplementedError catch (_) {
return _enablePerformanceTracking;
} on UnsupportedError catch (_) {
return _enablePerformanceTracking;
}
}