Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,10 @@ Chart data adds optional `<command>_partial`, `<command>_attempted_runs`,
per-package datasets. Tables and chart notices label partial results with their
success counts. Synthetic averages retain that warning and omit the affected
command's value. Commands with any
partial result in the selected comparisons are excluded from rankings; history
omits that command's affected daily variation and category average.
partial result in the selected comparisons are excluded from timing rankings;
registry win rankings retain the common denominator and award no wins for
incomplete fixtures. History omits that command's affected daily variation and
category average.

This is an additive schema change. Historical JSON without these fields remains
readable, but its completeness is unknown: failed attempts discarded by older
Expand Down Expand Up @@ -249,7 +251,7 @@ same median multiplied by 1000 and divided by package count (ms/package).
Failed attempts are excluded from timing statistics but retained in completeness
metadata. Partial survivor samples are labeled and excluded from averages,
history, leaderboard timing/win calculations, and registry speed alerts.
All-failed results remain DNF; the existing leaderboard DNF penalty uses the
All-failed results remain DNF; timing leaderboards' DNF penalty uses the
slowest successful median for that fixture.

Average views and leaderboard timing values are arithmetic averages of
Expand Down Expand Up @@ -296,6 +298,21 @@ Registry history uses total installation time in seconds from `registryChartData

### View Results Online

Registry leaderboard cards rank registries by **fixture wins**, not an average
installation time. Each selected fixture/variation pair counts once, so a large
fixture has the same influence as a small fixture. The `average` tab counts pairs
from both registry variations. Cards show wins out of the number of selected
pairs; filtering fixtures recomputes both values. Tool toggles only hide cards:
wins are always measured against all registries in the data.

The fastest complete positive timing earns a win; exact ties each earn a win
and share a rank (ordered alphabetically). Failed (DNF), partial, missing, and
invalid results earn no wins and receive no invented timing penalty. They remain
in the common denominator, including pairs where every registry failed. A card
with incomplete coverage also shows its number of complete results. This summary
describes how often a registry wins, not the magnitude of its speed advantage;
the fixture charts provide the underlying timings.

Results are automatically deployed to GitHub Pages when running on the main branch:

<https://vltpkg.github.io/benchmarks/>
Expand Down
82 changes: 60 additions & 22 deletions app/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,17 @@ HeaderSection.displayName = "HeaderSection";

interface LeaderBoardItemProps {
packageManager: PackageManager;
averageTime: number;
averageTime?: number;
registryStats?: { wins: number; totalTests: number; completedTests: number };
idx: number;
unit?: string;
isBaseline?: boolean;
}

const LeaderBoardItem = ({
packageManager,
averageTime,
averageTime = 0,
registryStats,
idx,
unit = "ms/pkg",
isBaseline = false,
Expand Down Expand Up @@ -362,7 +364,18 @@ const LeaderBoardItem = ({
</span>
)}
</p>
<p className="text-[10px] text-muted-foreground">{formattedTime}</p>
<p className="text-[10px] text-muted-foreground">
{registryStats
? `${registryStats.wins}/${registryStats.totalTests} wins`
: formattedTime}
</p>
{registryStats &&
registryStats.completedTests < registryStats.totalTests && (
<p className="text-[10px] text-muted-foreground">
{registryStats.completedTests}/{registryStats.totalTests}{" "}
complete
</p>
)}
</div>
</div>
</div>
Expand Down Expand Up @@ -404,25 +417,50 @@ const HeaderLeaderboard = forwardRef<HTMLDivElement, ComponentProps<"div">>(
if (leaderboard && leaderboard.length === 0) return null;

return (
<ScrollArea className="relative max-w-7xl">
<div ref={ref} className={cn("flex gap-2", className)} {...props}>
{leaderboard &&
leaderboard.map((item, idx) => (
<LeaderBoardItem
key={`${item.packageManager}-${idx}`}
idx={idx}
averageTime={item.averageTime}
packageManager={item.packageManager}
unit={unit}
isBaseline={isBaselinePackageManager(
item.packageManager,
isRegistryRoute,
)}
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
<div className="space-y-2">
{isRegistryRoute && (
<p className="text-xs text-muted-foreground">
Fixture wins: each selected fixture and variation counts equally.
Fastest complete result wins; tied fastest results each earn a win.
Failed, partial, or missing results earn no wins. Compared across
all registries.
</p>
)}
<ScrollArea className="relative max-w-7xl">
<div ref={ref} className={cn("flex gap-2", className)} {...props}>
{leaderboard &&
leaderboard.map((item, idx) => (
<LeaderBoardItem
key={`${item.packageManager}-${idx}`}
idx={
isRegistryRoute
? leaderboard.findIndex(
(entry) => entry.wins === item.wins,
)
: idx
}
averageTime={item.averageTime}
registryStats={
isRegistryRoute
? {
wins: item.wins,
totalTests: item.totalTests,
completedTests: item.completedTests ?? 0,
}
: undefined
}
packageManager={item.packageManager}
unit={unit}
isBaseline={isBaselinePackageManager(
item.packageManager,
isRegistryRoute,
)}
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
);
},
);
Expand Down
5 changes: 4 additions & 1 deletion app/src/components/variation/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@ export const VariationChart = ({
<p>
Partial results include successful runs only and may appear faster
because failed or timed-out runs were dropped. Commands with partial
results are excluded from rankings and history averages.
results are excluded from history averages.{" "}
{isRegistry
? "Incomplete fixture results earn no leaderboard wins."
: "They are also excluded from timing rankings."}
</p>
<ul className="mt-2 list-disc pl-5">
{partialResults.map((label) => <li key={label}>{label}</li>)}
Expand Down
68 changes: 68 additions & 0 deletions app/src/lib/registry-leaderboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type {
BenchmarkChartData,
Fixture,
PackageManager,
Variation,
} from "@/types/chart-data";

export interface RegistryRanking {
packageManager: PackageManager;
wins: number;
totalTests: number;
completedTests: number;
}

/** Each selected fixture/variation is one contest, independent of its duration. */
export function calculateRegistryLeaderboard(
chartData: BenchmarkChartData,
specificVariation?: Variation,
enabledFixtures?: Set<Fixture>,
): RegistryRanking[] {
const source = chartData.registryChartData;
if (!source) return [];
const variations =
specificVariation && specificVariation !== "average"
? [specificVariation]
: source.variations.filter((variation) => variation !== "average");
const rankings = source.packageManagers.map((packageManager) => ({
packageManager,
wins: 0,
totalTests: 0,
completedTests: 0,
}));

for (const variation of variations) {
for (const fixture of source.data[variation] ?? []) {
if (enabledFixtures && !enabledFixtures.has(fixture.fixture)) continue;
// Missing, DNF and partial samples never earn wins. Preserve a common
// denominator even when a registry failed or the whole contest failed.
const metadata = fixture as typeof fixture & Record<string, unknown>;
const times = rankings.map(({ packageManager: pm }) => {
const value = fixture[pm];
return metadata[`${pm}_dnf`] !== true &&
metadata[`${pm}_partial`] !== true &&
typeof value === "number" &&
Number.isFinite(value) &&
value > 0
? value
: undefined;
});
const best = Math.min(...times.filter((time) => time !== undefined));
rankings.forEach((ranking, index) => {
ranking.totalTests++;
const time = times[index];
if (time === undefined) return;
ranking.completedTests++;
// Exact ties each receive a win; no registry gets arbitrary priority.
if (time === best) ranking.wins++;
});
}
}

return rankings
.filter((ranking) => ranking.totalTests > 0)
.sort(
(a, b) =>
b.wins - a.wins || a.packageManager.localeCompare(b.packageManager),
);
}
57 changes: 32 additions & 25 deletions app/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { calculateRegistryLeaderboard } from "./registry-leaderboard.ts";
import type {
Variation,
Fixture,
Expand Down Expand Up @@ -220,21 +221,27 @@ export const getVariationCategories = (
interface RankingData {
packageManager: PackageManager;
wins: number;
averageTime: number;
averageTime?: number;
totalTests: number;
completedTests?: number;
}

export type LeaderboardRoute =
| "package-managers"
| "task-runners"
| "registries";
"package-managers" | "task-runners" | "registries";

export const calculateLeaderboard = (
chartData: BenchmarkChartData,
specificVariation?: Variation,
route?: LeaderboardRoute,
enabledFixtures?: Set<Fixture>,
): RankingData[] => {
if ((route ?? "registries") === "registries") {
return calculateRegistryLeaderboard(
chartData,
specificVariation,
enabledFixtures,
);
}
const categories = getVariationCategories(chartData.chartData.variations);
const effectiveRoute = route ?? "registries";

Expand Down Expand Up @@ -382,30 +389,30 @@ export const calculateLeaderboard = (
});

// Calculate final rankings
const leaderboard: RankingData[] = (
availablePackageManagers as PackageManager[]
).map((pm) => {
const stats = packageManagerStats[pm];
if (!stats) {
const leaderboard = (availablePackageManagers as PackageManager[]).map(
(pm) => {
const stats = packageManagerStats[pm];
if (!stats) {
return {
packageManager: pm,
wins: 0,
averageTime: Number.MAX_SAFE_INTEGER,
totalTests: 0,
};
}
const averageTime =
stats.testCount > 0
? stats.totalTime / stats.testCount
: Number.MAX_SAFE_INTEGER;

return {
packageManager: pm,
wins: 0,
averageTime: Number.MAX_SAFE_INTEGER,
totalTests: 0,
wins: stats.wins,
averageTime,
totalTests: stats.testCount,
};
}
const averageTime =
stats.testCount > 0
? stats.totalTime / stats.testCount
: Number.MAX_SAFE_INTEGER;

return {
packageManager: pm,
wins: stats.wins,
averageTime,
totalTests: stats.testCount,
};
});
},
);

// Filter out PMs with no data, then sort by average time (lower is
// better) so card order matches the displayed values, wins as tiebreaker
Expand Down
Loading