Index: benchmark/lib/dashboard_view.dart |
diff --git a/benchmark/lib/dashboard_view.dart b/benchmark/lib/dashboard_view.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8c20fa6a282b9048fbdc2858a236c8e7e2bfc209 |
--- /dev/null |
+++ b/benchmark/lib/dashboard_view.dart |
@@ -0,0 +1,272 @@ |
+// Copyright (c) 2015, 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. |
+ |
+library protoc.benchmark.html_view; |
Søren Gjesse
2015/09/08 06:43:54
Might have been easier to put more of this into th
skybrian
2015/09/08 17:25:11
It's because I wanted to keep each view self-conta
|
+ |
+import 'dart:async' show Stream, StreamController; |
+import 'dart:html'; |
+ |
+import 'generated/benchmark.pb.dart' as pb; |
+import 'dashboard_model.dart'; |
+import 'report.dart' show encodeReport; |
+ |
+/// A dashboard allowing the user to run a benchmark suite and compare the |
+/// results to any saved report. |
+class DashboardView { |
+ final elt = new DivElement(); |
+ |
+ final _runButton = new _Button(); |
+ final _status = new _Label(new SpanElement()..style.height = "1em"); |
+ final _envElt = new PreElement(); |
+ final _menu = new _Menu(); |
+ final _responseTable = new TableElement(); |
+ final _jsonView = new _JsonView(); |
+ |
+ String _renderedPlatform; |
+ final rows = <_ResponseView>[]; |
+ |
+ DashboardView() { |
+ // Fill in "template" elements that never change. |
+ elt.children.addAll([ |
+ new DivElement() |
+ ..children.addAll([_runButton.elt, new Text(" "), _status.elt,]), |
+ _envElt, |
+ new Text("Choose baseline: "), |
+ _menu.elt, |
+ _responseTable |
+ ..children.addAll([ |
+ new TableRowElement() |
+ ..children.addAll([ |
+ _th("Benchmark"), |
+ _th("Params"), |
+ _th("1000 * int32 reads / second")..colSpan = 4 |
+ ]), |
+ new TableRowElement() |
+ ..children.addAll([ |
+ _th(""), |
+ _th(""), |
+ _thRight("Baseline"), |
+ _thRight("Median"), |
+ _thRight("Max"), |
+ _thRight("Count") |
+ ]) |
+ ]), |
+ _jsonView.elt |
+ ]); |
+ } |
+ |
+ Stream get onRunButtonClick => _runButton.onClick; |
+ Stream<String> get onMenuChange => _menu.onChange; |
+ |
+ void render(DashboardModel model) { |
+ _runButton.render("Run", model.canRun); |
+ if (!model.latest.hasStatus() || model.latest.status == pb.Status.DONE) { |
+ _status.render(""); |
+ } else { |
+ _status.render(model.latest.status.name); |
+ } |
+ |
+ _renderEnv(model.latest); |
+ _menu.render(model.savedReports.keys, model.baselineName); |
+ _renderResponses(model.getBaselineSamples(), model.latest); |
+ _jsonView.render(model.latest); |
+ } |
+ |
+ void _renderEnv(pb.Report r) { |
+ String newPlatform = r.env.platform.toString(); |
+ if (newPlatform == _renderedPlatform) return; |
+ _envElt.text = newPlatform; |
+ _renderedPlatform = newPlatform; |
+ } |
+ |
+ /// Renders a table with one row for each benchmark. |
+ void _renderResponses(BaselineSamples baseline, pb.Report r) { |
+ var it = r.responses.iterator; |
+ |
+ // Update existing rows |
+ for (var row in rows) { |
+ var hasNext = it.moveNext(); |
+ assert(hasNext); // assume that the table only grows |
+ var left = baseline.getSample(it.current.request); |
+ row.render(left, it.current); |
+ } |
+ |
+ // Add any new rows |
+ while (it.moveNext()) { |
+ var left = baseline.getSample(it.current.request); |
+ var row = new _ResponseView()..render(left, it.current); |
+ _responseTable.append(row.elt); |
+ rows.add(row); |
+ } |
+ } |
+ |
+ static _th(String columnName) => new Element.th() |
+ ..style.textAlign = "left" |
+ ..text = columnName; |
+ |
+ static _thRight(String columnName) => new Element.th() |
+ ..style.textAlign = "right" |
+ ..text = columnName; |
+} |
+ |
+/// A single row in the benchmark table. |
+/// |
+/// Displays how many samples were collected and the median and max samples. |
+/// Also displays a baseline sample for comparison. |
+class _ResponseView { |
+ final elt = new TableRowElement(); |
+ final _name = new _Label(new TableCellElement()); |
+ final _params = new _Label(new TableCellElement()); |
+ final _baseline = new _SampleView(); |
+ final _median = new _SampleView(); |
+ final _max = new _SampleView(); |
+ final _count = new _Label(new TableCellElement()..style.textAlign = "right"); |
+ |
+ _ResponseView() { |
+ elt.children.addAll([ |
+ _name.elt, |
+ _params.elt, |
+ _baseline.elt, |
+ _median.elt, |
+ _max.elt, |
+ _count.elt |
+ ]); |
+ } |
+ |
+ void render(pb.Sample baseline, pb.Response response) { |
+ _name.render(response.request.id.name); |
+ _params.render(response.request.params.toString()); |
+ _baseline.render(baseline); |
+ _median.render(medianSample(response)); |
+ _max.render(maxSample(response)); |
+ _count.render("${response.samples.length}"); |
+ } |
+} |
+ |
+/// A table cell holding one sample. |
+class _SampleView { |
+ final elt = new TableCellElement()..style.textAlign = "right"; |
+ pb.Sample _rendered; |
+ |
+ void render(pb.Sample s) { |
+ if (_rendered == s) return; |
+ elt.text = _renderInt32Reads(s); |
+ _rendered = s; |
+ } |
+ |
+ static String _renderInt32Reads(pb.Sample s) { |
+ if (s == null) return "*"; |
+ double kIntsPerSecond = s.counts.int32Reads * 1000 / s.duration; |
+ return kIntsPerSecond.toStringAsFixed(1); |
+ } |
+} |
+ |
+/// Renders the benchmark report as JSON so it can be copied to a file. |
+class _JsonView { |
+ final DivElement elt = new DivElement(); |
+ String _rendered; |
+ |
+ void render(pb.Report r) { |
+ // Don't show JSON while benchmarks are in progress. |
+ String json = ""; |
+ if (r.status == pb.Status.DONE) { |
+ json = encodeReport(r); |
+ } |
+ |
+ if (json == _rendered) return; |
+ |
+ elt.children.clear(); |
+ if (json == "") return; |
+ elt.children.addAll([ |
+ new HeadingElement.h2()..text = "Report data as JSON:", |
+ new PreElement()..text = json |
+ ]); |
+ _rendered = json; |
+ } |
+} |
+ |
+/// A menu of selectable text items. |
+class _Menu { |
+ final elt = new SelectElement(); |
+ final _changes = new StreamController.broadcast(); |
+ final _options = new List<_MenuOption>(); |
+ |
+ _Menu() { |
+ elt.onChange.listen((e) => _changes.add(elt.value)); |
+ } |
+ |
+ Stream<String> get onChange => _changes.stream; |
+ |
+ void render(List<String> items, String selected) { |
+ var it = items.iterator; |
+ |
+ // Update existing items |
+ for (var opt in _options) { |
+ var hasNext = it.moveNext(); |
+ assert(hasNext); // assume menu never shrinks |
+ opt.render(it.current, it.current == selected); |
+ } |
+ |
+ // Add any new items |
+ while (it.moveNext()) { |
+ var opt = new _MenuOption(); |
+ opt.render(it.current, it.current == selected); |
+ elt.append(opt.elt); |
+ _options.add(opt); |
+ } |
+ } |
+} |
+ |
+class _MenuOption { |
+ final elt = new OptionElement(); |
+ String _renderedItem; |
+ bool _renderedSelected; |
+ |
+ void render(String item, selected) { |
+ if (_renderedItem != item) { |
+ elt.text = item; |
+ elt.value = item; |
+ _renderedItem = item; |
+ } |
+ if (_renderedSelected != selected) { |
+ elt.selected = selected; |
+ _renderedSelected = selected; |
+ } |
+ } |
+} |
+ |
+class _Label { |
+ final HtmlElement elt; |
+ String _rendered; |
+ _Label(this.elt); |
+ |
+ void render(String text) { |
+ if (_rendered == text) return; |
+ elt.text = text; |
+ _rendered = text; |
+ } |
+} |
+ |
+class _Button { |
+ final elt = new ButtonElement(); |
+ final _clicks = new StreamController.broadcast(); |
+ String _renderedLabel; |
+ bool _renderedEnabled; |
+ _Button() { |
+ elt.onClick.listen((e) => _clicks.add(true)); |
+ } |
+ |
+ Stream get onClick => _clicks.stream; |
+ |
+ void render(String label, bool enabled) { |
+ if (label != _renderedLabel) { |
+ elt.text = label; |
+ _renderedLabel = label; |
+ } |
+ if (_renderedEnabled != enabled) { |
+ elt.disabled = !enabled; |
+ _renderedEnabled = enabled; |
+ } |
+ } |
+} |