diff --git a/frameworks/vue/notes.json b/frameworks/vue/notes.json
new file mode 100644
index 0000000..4182a7c
--- /dev/null
+++ b/frameworks/vue/notes.json
@@ -0,0 +1,3 @@
+{
+ "variant": "Vapor"
+}
diff --git a/results/app/components/variant.gts b/results/app/components/variant.gts
new file mode 100644
index 0000000..7825342
--- /dev/null
+++ b/results/app/components/variant.gts
@@ -0,0 +1,14 @@
+import type { TOC } from "@ember/component/template-only";
+
+/**
+ * A framework's variant label (e.g. "Vapor"), shown under the framework
+ * name and above its version. Renders nothing when the run recorded no
+ * variant for the framework.
+ */
+export const Variant =
+ {{#if @variant}}
+ {{@variant}}
+ {{/if}}
+ satisfies TOC<{
+ variant: string | undefined;
+}>;
diff --git a/results/app/routes/compare.ts b/results/app/routes/compare.ts
index 8610b88..ecf01df 100644
--- a/results/app/routes/compare.ts
+++ b/results/app/routes/compare.ts
@@ -1,7 +1,7 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
-import { results } from "virtual:result-sets";
+import { experiments, runs } from "virtual:result-sets";
import { warnOnVersionDivergence } from "#utils";
@@ -75,20 +75,20 @@ export default class Compare extends Route {
}
/**
- * The run next to `name`, preferring `direction` -- `results` is
+ * The run next to `name`, preferring `direction` -- `runs` is
* newest-first, so older runs are later in the list. Falls back to the
* other side for the first and last run, and to `name` itself when it's
* the only run there is.
*/
function neighborOf(name: string, direction: "older" | "newer") {
- const index = results.indexOf(name);
+ const index = runs.indexOf(name);
- if (index === -1) return results[0];
+ if (index === -1) return runs[0];
const [preferred, fallback] =
direction === "older" ? [index + 1, index - 1] : [index - 1, index + 1];
- return results[preferred] ?? results[fallback] ?? name;
+ return runs[preferred] ?? runs[fallback] ?? name;
}
/**
@@ -103,11 +103,13 @@ function runsFor(a: string | undefined, b: string | undefined) {
if (a) return { a, b: neighborOf(a, "newer") };
// no runs named at all -- the two most recent
- return { a: results[1] ?? results[0], b: results[0] };
+ return { a: runs[1] ?? runs[0], b: runs[0] };
}
async function fetchResultSet(name: string): Promise {
- const response = await fetch(`/results/${name}.json`);
+ // experiments live in a separate directory from the official runs
+ const dir = experiments.includes(name) ? "experiments" : "results";
+ const response = await fetch(`/${dir}/${name}.json`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const json = await response.json();
diff --git a/results/app/routes/index.ts b/results/app/routes/index.ts
index d0bb42d..c068b47 100644
--- a/results/app/routes/index.ts
+++ b/results/app/routes/index.ts
@@ -1,7 +1,7 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
-import { results } from "virtual:result-sets";
+import { runs } from "virtual:result-sets";
import type RouterService from "@ember/routing/router-service";
@@ -11,7 +11,7 @@ export default class Index extends Route {
beforeModel() {
this.router.transitionTo("results", {
queryParams: {
- q: results[0],
+ q: runs[0],
},
});
}
diff --git a/results/app/routes/results.ts b/results/app/routes/results.ts
index 59ebd58..6b453cc 100644
--- a/results/app/routes/results.ts
+++ b/results/app/routes/results.ts
@@ -1,6 +1,8 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
+import { experiments } from "virtual:result-sets";
+
import { warnOnVersionDivergence } from "#utils";
import type RouterService from "@ember/routing/router-service";
@@ -49,7 +51,9 @@ export default class Results extends Route {
const { q } = params as unknown as Params;
try {
- const response = await fetch(`/results/${q}.json`);
+ // experiments live in a separate directory from the official runs
+ const dir = experiments.includes(q) ? "experiments" : "results";
+ const response = await fetch(`/${dir}/${q}.json`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const json = await response.json();
diff --git a/results/app/styles/app.css b/results/app/styles/app.css
index ed7ae83..9d7d4cb 100644
--- a/results/app/styles/app.css
+++ b/results/app/styles/app.css
@@ -65,6 +65,16 @@ h1 {
font-size: 0.8rem;
}
+/* a framework's build flavor (e.g. "Vapor"), sitting between the name and
+ the version -- a block so it lands on its own line under the name */
+.variant {
+ display: block;
+ text-align: center;
+ font-size: 0.8rem;
+ font-weight: 600;
+ opacity: 0.75;
+}
+
body {
display: grid;
gap: 0.5rem;
@@ -114,6 +124,7 @@ body {
}
main {
+ min-width: 500px;
display: grid;
justify-items: center;
}
diff --git a/results/app/templates/compare.gts b/results/app/templates/compare.gts
index 6d1616b..1ad065a 100644
--- a/results/app/templates/compare.gts
+++ b/results/app/templates/compare.gts
@@ -4,10 +4,11 @@ import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { pageTitle } from "ember-page-title";
-import { results } from "virtual:result-sets";
+import { experiments, runs } from "virtual:result-sets";
import { BenchmarkName } from "#components/benchmark-name.gts";
import { FrameworkInfo } from "#components/framework-info.gts";
+import { Variant } from "#components/variant.gts";
import { Version } from "#components/version.gts";
import { nameOf } from "#frameworks";
import {
@@ -21,9 +22,11 @@ import {
round,
throttleLabel,
timeFor,
+ variantOf,
versionOf,
} from "#utils";
+import type { TOC } from "@ember/component/template-only";
import type RouterService from "@ember/routing/router-service";
import type { Model, NamedRun } from "#routes/compare.ts";
import type { BenchmarkInfo, ResultSet } from "#types";
@@ -43,6 +46,29 @@ function qp(runName: string) {
return { q: runName };
}
+/**
+ * The options for one of the A/B run selectors: the official runs, plus
+ * the experiments in their own group when there are any. Either side can
+ * point at either category, so a run can be compared against an experiment.
+ */
+const RunOptions =
+
+ {{#if experiments.length}}
+
+ {{/if}}
+ satisfies TOC<{
+ which: "a" | "b";
+ isRun: (which: "a" | "b", name: string) => boolean;
+}>;
+
/**
* Both runs state their throttle outright rather than leaving it to be
* inferred from the warning that only shows when they disagree.
@@ -303,6 +329,16 @@ export default class Compare extends Component<{ model: Model }> {
return lowerIsBetterBenches(this.benchmarkInfo);
}
+ /**
+ * The variant either run recorded for the compared framework, preferring
+ * the candidate (B). Both runs are usually the same build, so this reads
+ * "what flavor of the framework am I looking at" rather than a per-run
+ * difference.
+ */
+ get variant() {
+ return variantOf(this.b.data, this.framework) ?? variantOf(this.a.data, this.framework);
+ }
+
isFramework = (name: string) => this.framework === name;
isRun = (which: "a" | "b", name: string) => this[which].name === name;
@@ -337,17 +373,13 @@ export default class Compare extends Component<{ model: Model }> {