Skip to content

Commit 4b2a141

Browse files
authored
Add "Copy as markdown" button (#665)
Adds a "Copy as markdown" button for easier export to LLMs. Example output: ```md **Old:** main (`d0a96f157601484683a809d1a87ee89270dbac96`) **New:** `d360c0d4c9be11184254990b3fa53c4e562b01b4` --- | Metric | main | d360c0d4c9be11184254990b3fa53c4e562b01b4 | P | Delta | |---|---|---|---|---| | QPS Total | 22771.24 (±2.4%) | 22483.63 (±3.7%) | 0.011 | -1.263% | | Reads | 15938.69 (±2.4%) | 15737.51 (±3.7%) | 0.011 | -1.262% | | Writes | 4555.22 (±2.4%) | 4497.49 (±3.7%) | 0.015 | -1.267% | | Other | 2277.34 (±2.4%) | 2248.62 (±3.7%) | 0.009 | -1.261% | | TPS | 1139.02 (±2.4%) | 1124.65 (±3.7%) | 0.010 | -1.261% | | P95 Latency | 43.39ms (±1.8%) | 43.78ms (±2.7%) | 0.252 | 0.899% | | Errors / Second | 0 (±0%) | 0 (±0%) | 1.000 | 0% | | Total CPU / Query | 0μs (±0%) | 1389.18μs (±7.1%) | 0.000 | 0% | | vtgate | 0μs (±0%) | 1016.17μs (±8.5%) | 0.000 | 0% | | vttablet | 0μs (±0%) | 362.89μs (±8.6%) | 0.000 | 0% | | Total Allocated / Query | 0B (±0%) | 39.11KB (±6.6%) | 0.000 | 0% | | vtgate | 0B (±0%) | 29.72KB (±8.4%) | 0.000 | 0% | | vttablet | 0B (±0%) | 9.38KB (±8.6%) | 0.000 | 0% | | Metric | main | d360c0d4c9be11184254990b3fa53c4e562b01b4 | P | Delta | |---|---|---|---|---| | QPS Total | 22474.72 (±2.0%) | 21757.57 (±2.3%) | 0.000 | -3.191% | | Reads | 15731.63 (±2.0%) | 15229.33 (±2.3%) | 0.000 | -3.193% | | Writes | 4495.45 (±2.0%) | 4352.31 (±2.3%) | 0.000 | -3.184% | | Other | 2247.64 (±2.0%) | 2175.93 (±2.3%) | 0.000 | -3.190% | | TPS | 1124.17 (±2.0%) | 1088.32 (±2.3%) | 0.000 | -3.189% | | P95 Latency | 43.78ms (±2.7%) | 44.98ms (±1.8%) | 0.000 | 2.741% | | Errors / Second | 0 (±0%) | 0 (±0%) | 1.000 | 0% | | Total CPU / Query | 1300.78μs (±8.4%) | 1391.36μs (±7.5%) | 0.009 | 6.964% | | vtgate | 921.04μs (±11.1%) | 994.68μs (±7.6%) | 0.009 | 7.996% | | vttablet | 379.67μs (±7.7%) | 392.15μs (±16.8%) | 0.353 | 3.286% | | Total Allocated / Query | 35.83KB (±8.7%) | 39.54KB (±6.0%) | 0.000 | 10.369% | | vtgate | 25.67KB (±11.7%) | 29.4KB (±6.0%) | 0.000 | 14.543% | | vttablet | 10.22KB (±8.5%) | 10.04KB (±17.7%) | 0.436 | -1.758% | | Metric | main | d360c0d4c9be11184254990b3fa53c4e562b01b4 | P | Delta | |---|---|---|---|---| | QPS Total | 4566.71 (±3.4%) | 3263.05 (±9.3%) | 0.000 | -28.547% | | Reads | 2082.17 (±3.4%) | 1503.63 (±9.2%) | 0.000 | -27.786% | | Writes | 2159.06 (±3.3%) | 1494.38 (±9.6%) | 0.000 | -30.786% | | Other | 325.48 (±4.0%) | 265.05 (±10.0%) | 0.000 | -18.566% | | TPS | 163.09 (±4.0%) | 116.00 (±9.8%) | 0.000 | -28.868% | | P95 Latency | 861.95ms (±5.6%) | 969.02ms (±4.6%) | 0.000 | 12.422% | | Errors / Second | 0.66 (±13.0%) | 0.99 (±20.2%) | 0.000 | 51.145% | | Total CPU / Query | 3183.13μs (±5.3%) | 2047.19μs (±10.8%) | 0.000 | -35.686% | | vtgate | 1587.05μs (±3.7%) | 1083.38μs (±3.4%) | 0.000 | -31.736% | | vttablet | 1592.24μs (±13.6%) | 938.23μs (±20.2%) | 0.000 | -41.075% | | Total Allocated / Query | 119.05KB (±3.3%) | 74.65KB (±7.0%) | 0.000 | -37.292% | | vtgate | 83.32KB (±2.4%) | 52.48KB (±5.6%) | 0.000 | -37.015% | | vttablet | 36.24KB (±4.0%) | 23.13KB (±7.3%) | 0.000 | -36.164% | ``` Signed-off-by: Mohamed Hamza <mhamza@fastmail.com>
1 parent 967278d commit 4b2a141

2 files changed

Lines changed: 94 additions & 4 deletions

File tree

website/src/common/MacroBenchmarkTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const getDeltaBadgeVariant = (key: string, delta: number, p: number) => {
105105
return "destructive";
106106
};
107107

108-
const formatCellValue = (key: string, value: number) => {
108+
export const formatCellValue = (key: string, value: number) => {
109109
if (key.includes("CpuTime")) {
110110
return secondToMicrosecond(value);
111111
} else if (key.includes("MemStatsAllocBytes")) {

website/src/pages/ComparePage/ComparePage.tsx

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,24 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import MacroBenchmarkTable from "@/common/MacroBenchmarkTable";
17+
import MacroBenchmarkTable, { formatCellValue, getRange } from "@/common/MacroBenchmarkTable";
1818
import { Button } from "@/components/ui/button";
1919
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2020
import { Skeleton } from "@/components/ui/skeleton";
2121
import useApiCall from "@/hooks/useApiCall";
2222
import { CompareData, MacroBenchmarkTableData, VitessRefs } from "@/types";
2323
import {
2424
errorApi,
25+
fixed,
2526
formatCompareData,
2627
getGitRefFromRefName,
2728
getRefName,
2829
} from "@/utils/Utils";
2930
import { PlusCircledIcon } from "@radix-ui/react-icons";
30-
import { useEffect, useState } from "react";
31+
import { useEffect, useRef, useState } from "react";
3132
import { Link, useNavigate } from "react-router-dom";
3233
import CompareHero from "./components/CompareHero";
34+
import { Copy, Check } from "lucide-react";
3335

3436
export default function Compare() {
3537
const navigate = useNavigate();
@@ -81,11 +83,84 @@ export default function Compare() {
8183
}, [gitRef.old, gitRef.new, vitessRefs]);
8284

8385
let formattedData: MacroBenchmarkTableData[] = [];
84-
8586
if (data !== undefined && data.length > 0) {
8687
formattedData = formatCompareData(data);
8788
}
8889

90+
const [copied, setCopied] = useState(false);
91+
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
92+
93+
// Generates a markdown summary of all workload comparison tables.
94+
const generateCompareMarkdown = (): string => {
95+
if (!data || data.length === 0) return "";
96+
const oldName = vitessRefs
97+
? getRefName(gitRef.old, vitessRefs)
98+
: gitRef.old;
99+
const newName = vitessRefs
100+
? getRefName(gitRef.new, vitessRefs)
101+
: gitRef.new;
102+
const lines: string[] = [];
103+
104+
lines.push("# arewefastyet - Vitess benchmark comparison");
105+
lines.push("");
106+
const formatRef = (name: string, hash: string): string => {
107+
if (name !== hash) {
108+
return `${name} (\`${hash}\`)`;
109+
}
110+
return `\`${hash}\``;
111+
};
112+
lines.push(`**Old:** ${formatRef(oldName, gitOldRef)}`);
113+
lines.push(`**New:** ${formatRef(newName, gitNewRef)}`);
114+
lines.push("");
115+
lines.push("---");
116+
lines.push("");
117+
// Per-workload tables.
118+
data.forEach((macro, index) => {
119+
if (macro.result.missing_results) return;
120+
const tableData = formattedData[index];
121+
if (!tableData) return;
122+
lines.push(`## ${macro.workload}`);
123+
lines.push("");
124+
lines.push(
125+
`| Metric | ${oldName} | ${newName} | P | Delta |`
126+
);
127+
lines.push(`|---|---|---|---|---|`);
128+
const dataKeys = Object.keys(
129+
tableData
130+
) as Array<keyof MacroBenchmarkTableData>;
131+
dataKeys.forEach((key) => {
132+
const row = tableData[key];
133+
const oldVal = `${formatCellValue(key, Number(row.old.center))} (${getRange(row.old.range)})`;
134+
const newVal = `${formatCellValue(key, Number(row.new.center))} (${getRange(row.new.range)})`;
135+
const p = fixed(row.p, 3);
136+
const delta = `${fixed(row.delta, 3)}%`;
137+
lines.push(
138+
`| ${row.title} | ${oldVal} | ${newVal} | ${p} | ${delta} |`
139+
);
140+
});
141+
142+
lines.push("");
143+
});
144+
return lines.join("\n");
145+
};
146+
147+
const handleCopyMarkdown = async () => {
148+
const markdown = generateCompareMarkdown();
149+
150+
try {
151+
await navigator.clipboard.writeText(markdown);
152+
setCopied(true);
153+
154+
if (copiedTimerRef.current) {
155+
clearTimeout(copiedTimerRef.current);
156+
}
157+
158+
copiedTimerRef.current = setTimeout(() => setCopied(false), 2000);
159+
} catch (err) {
160+
console.error("Failed to copy markdown to clipboard:", err);
161+
}
162+
};
163+
89164
return (
90165
<>
91166
<CompareHero
@@ -118,6 +193,21 @@ export default function Compare() {
118193
)}
119194
{!isMacrobenchLoading && data !== undefined && data.length > 0 && (
120195
<>
196+
<div className="flex justify-end w-[80vw] xl:w-[60vw] mt-8">
197+
<Button
198+
variant="outline"
199+
size="sm"
200+
className="h-8 border-dashed"
201+
onClick={handleCopyMarkdown}
202+
>
203+
{copied ? (
204+
<Check className="mr-2 h-4 w-4 text-primary" />
205+
) : (
206+
<Copy className="mr-2 h-4 w-4 text-primary" />
207+
)}
208+
{copied ? "Copied!" : "Copy as markdown"}
209+
</Button>
210+
</div>
121211
{data.map((macro, index) => {
122212
return (
123213
<div className="w-[80vw] xl:w-[60vw] my-12" key={index}>

0 commit comments

Comments
 (0)