diff --git a/app/bin/build_and_test.sh b/app/bin/build_and_test.sh
index 6247d72edc..05afeace9b 100755
--- a/app/bin/build_and_test.sh
+++ b/app/bin/build_and_test.sh
@@ -15,8 +15,6 @@ dartanalyzer --strong bin/*.dart web/*.dart test/*.dart
pub run test -p vm
pub run test -p dartium
pub build
-cp web/*.dart build/web/
-cp -RL packages build/web/
echo
echo "Build succeeded! To deploy to App Engine run the following command after replacing {VERSION}:"
diff --git a/app/lib/components/benchmark_grid.dart b/app/lib/components/benchmark_grid.dart
index 8ca391e5a5..fdc52b7e9f 100644
--- a/app/lib/components/benchmark_grid.dart
+++ b/app/lib/components/benchmark_grid.dart
@@ -61,12 +61,17 @@ class BenchmarkGrid implements OnInit, OnDestroy {
{{unit}}
{{label}}
-
+
''',
directives: const [NgIf, NgFor, NgStyle],
)
-class BenchmarkCard implements AfterViewInit {
+class BenchmarkCard implements AfterViewInit, OnDestroy {
+ /// The total height of the chart. This value must be in sync with the height
+ /// specified for benchmark-card in benchmarks.css.
+ static const int _kChartHeight = 100;
+
BenchmarkData _data;
+ DivElement _tooltip;
@ViewChild('chartContainer') ElementRef chartContainer;
@@ -75,6 +80,7 @@ class BenchmarkCard implements AfterViewInit {
_data = newData;
}
+ double get goal => _data.timeseries.timeseries.goal;
String get id => _data.timeseries.timeseries.id;
String get taskName => _data.timeseries.timeseries.taskName;
String get label => _data.timeseries.timeseries.label;
@@ -99,31 +105,49 @@ class BenchmarkCard implements AfterViewInit {
if (_data.values.isEmpty) return;
double maxValue = _data.values
.map((TimeseriesValue v) => v.value)
- .reduce(math.max);
+ .fold(goal, math.max);
+
+ // Leave a bit of room to bars don't fill the height of the card
+ maxValue *= 1.1;
+
+ chartContainer.nativeElement.children.add(
+ new DivElement()
+ ..classes.add('metric-goal')
+ ..style.height = '${_kChartHeight * goal / maxValue}px'
+ );
for (TimeseriesValue value in _data.values.reversed) {
DivElement bar = new DivElement()
..classes.add('metric-value-bar')
- ..style.height = '${100 * value.value / maxValue}px';
+ ..style.height = '${_kChartHeight * value.value / maxValue}px';
+
+ if (value.value > goal) {
+ bar.classes.add('metric-value-bar-underperformed');
+ }
- DivElement tooltip;
bar.onMouseOver.listen((_) {
- tooltip = new DivElement()
+ _tooltip = new DivElement()
..text = '${value.value}$unit\n'
'Flutter revision: ${value.revision}\n'
- 'Recorded on: ${new DateTime.fromMillisecondsSinceEpoch(value.createTimestamp)}'
+ 'Recorded on: ${new DateTime.fromMillisecondsSinceEpoch(value.createTimestamp)}\n'
+ 'Goal: $goal$unit'
..classes.add('metric-value-tooltip')
..style.top = '${bar.getBoundingClientRect().top}px'
..style.left = '${bar.getBoundingClientRect().right + 5}px';
bar.style.backgroundColor = '#11CC11';
- document.body.append(tooltip);
+ document.body.append(_tooltip);
});
bar.onMouseOut.listen((_) {
- bar.style.backgroundColor = '#AAFFAA';
- tooltip?.remove();
+ bar.style.backgroundColor = '';
+ _tooltip?.remove();
});
chartContainer.nativeElement.children.add(bar);
}
}
+
+ @override
+ void ngOnDestroy() {
+ _tooltip?.remove();
+ }
}
diff --git a/app/lib/model.dart b/app/lib/model.dart
index 6c390ae052..f72305625e 100644
--- a/app/lib/model.dart
+++ b/app/lib/model.dart
@@ -256,6 +256,7 @@ class Timeseries extends Entity {
'TaskName': string(),
'Label': string(),
'Unit': string(),
+ 'Goal': number(),
}
);
@@ -265,6 +266,7 @@ class Timeseries extends Entity {
String get taskName => this['TaskName'];
String get label => this['Label'];
String get unit => this['Unit'];
+ double get goal => this['Goal'];
}
class TimeseriesValue extends Entity {
diff --git a/app/test/benchmark_grid_test.dart b/app/test/benchmark_grid_test.dart
index 1b9683b5d3..ce952f0ca7 100644
--- a/app/test/benchmark_grid_test.dart
+++ b/app/test/benchmark_grid_test.dart
@@ -61,6 +61,7 @@ final _testData = {
'ID': 'series$i',
'Label': 'Series $i',
'Unit': 'ms',
+ 'Goal': i.toDouble(),
},
},
'Values': new List.generate(_rnd.nextInt(5), (int v) => {
diff --git a/app/web/benchmarks.css b/app/web/benchmarks.css
index e9bcb62672..3c07ab1d63 100644
--- a/app/web/benchmarks.css
+++ b/app/web/benchmarks.css
@@ -47,6 +47,21 @@ benchmark-card {
pointer-events: none;
}
+.metric-chart-container {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: flex-end;
+}
+
+.metric-goal {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-top: 1px dashed #81C784;
+ pointer-events: none;
+}
+
.metric-value {
font-size: 42px;
font-weight: bold;
@@ -58,6 +73,10 @@ benchmark-card {
display: inline-block;
}
+.metric-value-bar-underperformed {
+ background-color: #FFAAAA;
+}
+
.metric-value-tooltip {
background-color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
diff --git a/db/schema.go b/db/schema.go
index a3a0eadb15..55c33b7d49 100644
--- a/db/schema.go
+++ b/db/schema.go
@@ -91,6 +91,9 @@ type Timeseries struct {
Label string
// The unit used for the values, e.g. "ms", "kg", "pumpkins".
Unit string
+ // The current goal we want to reach for this metric. As of today, all our metrics are smaller
+ // is better.
+ Goal float64
}
// TimeseriesEntity contains storage data on a Timeseries.