├── .eslintrc.cjs
├── .github
└── workflows
│ ├── deploy.yml
│ └── pr-check.yml
├── .gitignore
├── .nojekyll
├── 404.html
├── README.md
├── _config.yml
├── bun.lockb
├── components.json
├── cover.jpeg
├── index.html
├── package.json
├── postcss.config.js
├── public
└── favicon.png
├── samplebaseline.json
├── src
├── App.css
├── App.tsx
├── BenchmarkTable.tsx
├── CompareChart.tsx
├── assets
│ ├── github-mark.svg
│ ├── github-mark.tsx
│ └── react.svg
├── components
│ ├── theme-provider.tsx
│ └── ui
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── chart.tsx
│ │ ├── command.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── popover.tsx
│ │ ├── select.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ └── textarea.tsx
├── index.css
├── lib
│ └── utils.ts
├── main.tsx
├── mainComponent.tsx
├── page.tsx
├── types
│ ├── benchmark.ts
│ └── filters.ts
├── ui
│ ├── Compare.tsx
│ ├── Filters.tsx
│ ├── mode-toggle.tsx
│ ├── shell.tsx
│ ├── theme-toggle.tsx
│ └── upload.tsx
├── utils
│ └── benchmarkCompare.ts
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy static content to Pages
2 |
3 | on:
4 | # Runs on pushes targeting the default branch
5 | push:
6 | branches: ['main']
7 |
8 | # Allows you to run this workflow manually from the Actions tab
9 | workflow_dispatch:
10 |
11 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
12 | permissions:
13 | contents: read
14 | pages: write
15 | id-token: write
16 |
17 | # Allow one concurrent deployment
18 | concurrency:
19 | group: 'pages'
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | # Single deploy job since we're just deploying
24 | deploy:
25 | environment:
26 | name: github-pages
27 | url: ${{ steps.deployment.outputs.page_url }}
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 | - name: Set up Bun
33 | uses: oven-sh/setup-bun@v2
34 | - name: Install dependencies
35 | run: bun install --frozen-lockfile
36 | - name: Build
37 | run: npm run build
38 | - name: Setup Pages
39 | uses: actions/configure-pages@v4
40 | - name: Upload artifact
41 | uses: actions/upload-pages-artifact@v3
42 | with:
43 | # Upload dist folder
44 | path: './dist'
45 | - name: Deploy to GitHub Pages
46 | id: deployment
47 | uses: actions/deploy-pages@v4
48 |
--------------------------------------------------------------------------------
/.github/workflows/pr-check.yml:
--------------------------------------------------------------------------------
1 | name: PR Check
2 |
3 | on:
4 | # Runs on pushes targeting the default branch
5 | pull_request:
6 | branches: ['main']
7 |
8 |
9 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
10 | permissions:
11 | contents: read
12 |
13 | # Allow one concurrent deployment
14 | concurrency:
15 | group: 'pr-check'
16 | cancel-in-progress: false
17 |
18 | jobs:
19 | # Single deploy job since we're just deploying
20 | build:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | - name: Set up Bun
26 | uses: oven-sh/setup-bun@v2
27 | - name: Install dependencies
28 | run: bun install --frozen-lockfile
29 | - name: Build
30 | run: npm run build
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Page Apps for GitHub Pages
6 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # BenchMarkify 📈
4 |
5 |
6 |
7 |
8 | > Benchmarkify is a web app that helps you visualize and analyze Android macrobenchmark results stored in JSON format.
9 |
10 | ### ✨ Demo
11 |
12 | 
13 |
14 |
15 | ## 🧰 Features
16 | 1. Select Select 1 or multiple benchmark results.
17 | 2. Select any metric.
18 | 3. Compare Benchmarks in Charts or tables.
19 | 4. Store Benchmarks locally to use later.
20 | 5. Share the results with team. (we do not store any data).
21 |
22 | ## ⌨️ Usage
23 |
24 | 1. Select the file from `/build/outputs/.json`
25 | 2. Paste in BenchMarkify.
26 | 3. Generate clear and informative charts and tables.
27 |
28 |
29 |
30 |
31 | ## ✍️ Author
32 |
33 | 👤 **Yogesh Choudhary Paliyal**
34 |
35 | * Twitter: @yogeshpaliyal
36 | * Email: yogeshpaliyal.foss@gmail.com
37 |
38 | Feel free to ping me 😉
39 |
40 | ## 🤝 Contributing
41 |
42 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any
43 | contributions you make are **greatly appreciated**.
44 |
45 | 1. Open an issue first to discuss what you would like to change.
46 | 1. Fork the Project
47 | 1. Create your feature branch (`git checkout -b feature/amazing-feature`)
48 | 1. Commit your changes (`git commit -m 'Add some amazing feature'`)
49 | 1. Push to the branch (`git push origin feature/amazing-feature`)
50 | 1. Open a pull request
51 |
52 | Please make sure to update tests as appropriate.
53 |
54 | ## ❤ Show your support
55 |
56 | Give a ⭐️ if this project helped you!
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | ## 📝 License
72 |
73 | ```
74 | Copyright © 2024 - Yogesh Choudhary Paliyal
75 |
76 | Licensed under the Apache License, Version 2.0 (the "License");
77 | you may not use this file except in compliance with the License.
78 | You may obtain a copy of the License at
79 |
80 | http://www.apache.org/licenses/LICENSE-2.0
81 |
82 | Unless required by applicable law or agreed to in writing, software
83 | distributed under the License is distributed on an "AS IS" BASIS,
84 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
85 | See the License for the specific language governing permissions and
86 | limitations under the License.
87 | ```
88 |
89 | _This README was generated by [readgen](https://github.com/theapache64/readgen)_ ❤
90 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: pages-themes/cayman@v0.2.0
2 | plugins:
3 | - jekyll-remote-theme # add this line to the plugins list if you already have one
4 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/benchmarkify/8f25a115d41cb4b5cbc475c0c0ad1cfb399a8a95/bun.lockb
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/App.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/cover.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/benchmarkify/8f25a115d41cb4b5cbc475c0c0ad1cfb399a8a95/cover.jpeg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
28 |
29 |
30 |
37 |
38 |
39 |
40 |
41 | BenchMarkify
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "benchmark-charts",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "homepage": "https://github.yogeshpaliyal.com/benchmarkify/#",
7 | "scripts": {
8 | "dev": "vite",
9 | "build": "tsc -b && vite build",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@hookform/resolvers": "^3.9.0",
15 | "@phosphor-icons/react": "^2.1.7",
16 | "@radix-ui/react-dialog": "^1.1.2",
17 | "@radix-ui/react-dropdown-menu": "^2.1.1",
18 | "@radix-ui/react-label": "^2.1.0",
19 | "@radix-ui/react-popover": "^1.1.2",
20 | "@radix-ui/react-select": "^2.1.1",
21 | "@radix-ui/react-slot": "^1.1.0",
22 | "@radix-ui/react-tabs": "^1.1.0",
23 | "babel-runtime": "^6.26.0",
24 | "class-variance-authority": "^0.7.0",
25 | "clsx": "^2.1.1",
26 | "cmdk": "1.0.0",
27 | "install": "^0.13.0",
28 | "lucide-react": "^0.408.0",
29 | "mathjs": "^14.3.1",
30 | "pandas-js": "^0.2.4",
31 | "react": "^18.3.1",
32 | "react-dom": "^18.3.1",
33 | "react-hook-form": "^7.52.1",
34 | "react-router-dom": "^6.25.1",
35 | "recharts": "^2.12.7",
36 | "tailwind-merge": "^2.4.0",
37 | "tailwindcss-animate": "^1.0.7",
38 | "zod": "^3.23.8"
39 | },
40 | "devDependencies": {
41 | "@types/node": "^20.14.11",
42 | "@types/react": "^18.3.3",
43 | "@types/react-dom": "^18.3.0",
44 | "@typescript-eslint/eslint-plugin": "^7.15.0",
45 | "@typescript-eslint/parser": "^7.15.0",
46 | "@vitejs/plugin-react": "^4.3.1",
47 | "autoprefixer": "^10.4.19",
48 | "eslint": "^8.57.0",
49 | "eslint-plugin-react-hooks": "^4.6.2",
50 | "eslint-plugin-react-refresh": "^0.4.7",
51 | "postcss": "^8.4.39",
52 | "tailwindcss": "^3.4.6",
53 | "typescript": "^5.2.2",
54 | "vite": "^5.3.4"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/benchmarkify/8f25a115d41cb4b5cbc475c0c0ad1cfb399a8a95/public/favicon.png
--------------------------------------------------------------------------------
/samplebaseline.json:
--------------------------------------------------------------------------------
1 | {
2 | "context": {
3 | "build": {
4 | "brand": "Jio",
5 | "device": "TEST123",
6 | "fingerprint": "TEST123",
7 | "model": "LS1542QWN",
8 | "version": {
9 | "sdk": 30
10 | }
11 | },
12 | "cpuCoreCount": 4,
13 | "cpuLocked": false,
14 | "cpuMaxFreqHz": 1305600000,
15 | "memTotalBytes": 1972162560,
16 | "sustainedPerformanceModeEnabled": false
17 | },
18 | "benchmarks": [
19 | {
20 | "name": "startupCompilationBaselineProfiles",
21 | "params": {},
22 | "className": "com.example.benchmark.StartupBenchmarks",
23 | "totalRunTimeNs": 155690886242,
24 | "metrics": {
25 | "timeToInitialDisplayMs": {
26 | "minimum": 1096.115833,
27 | "maximum": 1414.014947,
28 | "median": 1161.0081765,
29 | "runs": [
30 | 1414.014947,
31 | 1133.693229,
32 | 1170.526197,
33 | 1281.624479,
34 | 1101.296875,
35 | 1128.309427,
36 | 1151.490156,
37 | 1172.742812,
38 | 1346.225885,
39 | 1096.115833
40 | ]
41 | }
42 | },
43 | "sampledMetrics": {},
44 | "warmupIterations": 0,
45 | "repeatIterations": 10,
46 | "thermalThrottleSleepSeconds": 0
47 | },
48 | {
49 | "name": "startupCompilationNone",
50 | "params": {},
51 | "className": "com.example.benchmark.StartupBenchmarks",
52 | "totalRunTimeNs": 129921305159,
53 | "metrics": {
54 | "timeToInitialDisplayMs": {
55 | "minimum": 1489.099687,
56 | "maximum": 2170.634582,
57 | "median": 1685.6467699999998,
58 | "runs": [
59 | 2170.634582,
60 | 1556.952812,
61 | 1489.099687,
62 | 1917.608541,
63 | 1519.933229,
64 | 1568.782031,
65 | 1513.596249,
66 | 1877.662291,
67 | 1847.907603,
68 | 1802.511509
69 | ]
70 | }
71 | },
72 | "sampledMetrics": {},
73 | "warmupIterations": 0,
74 | "repeatIterations": 10,
75 | "thermalThrottleSleepSeconds": 0
76 | },
77 | {
78 | "name": "startupCompilationNone2",
79 | "params": {},
80 | "className": "com.example.benchmark.StartupBenchmarks",
81 | "totalRunTimeNs": 129921305159,
82 | "metrics": {
83 | "timeToInitialDisplayMs": {
84 | "minimum": 1489.099687,
85 | "maximum": 2170.634582,
86 | "median": 1685.6467699999998,
87 | "runs": [
88 | 2170.634582,
89 | 1556.952812,
90 | 1489.099687,
91 | 1917.608541,
92 | 1519.933229,
93 | 1568.782031,
94 | 1513.596249,
95 | 1877.662291,
96 | 1847.907603,
97 | 1802.511509
98 | ]
99 | }
100 | },
101 | "sampledMetrics": {},
102 | "warmupIterations": 0,
103 | "repeatIterations": 10,
104 | "thermalThrottleSleepSeconds": 0
105 | }
106 | ]
107 | }
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 72.22% 50.59%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 5% 64.9%;
26 | --radius: 0.5rem;
27 |
28 | --chart-1: 12 76% 61%;
29 | --chart-2: 173 58% 39%;
30 | --chart-3: 197 37% 24%;
31 | --chart-4: 43 74% 66%;
32 | --chart-5: 27 87% 67%;
33 | }
34 |
35 | .dark {
36 | --background: 240 10% 3.9%;
37 | --foreground: 0 0% 98%;
38 | --card: 240 10% 3.9%;
39 | --card-foreground: 0 0% 98%;
40 | --popover: 240 10% 3.9%;
41 | --popover-foreground: 0 0% 98%;
42 | --primary: 0 0% 98%;
43 | --primary-foreground: 240 5.9% 10%;
44 | --secondary: 240 3.7% 15.9%;
45 | --secondary-foreground: 0 0% 98%;
46 | --muted: 240 3.7% 15.9%;
47 | --muted-foreground: 240 5% 64.9%;
48 | --accent: 240 3.7% 15.9%;
49 | --accent-foreground: 0 0% 98%;
50 | --destructive: 0 62.8% 30.6%;
51 | --destructive-foreground: 0 85.7% 97.3%;
52 | --border: 240 3.7% 15.9%;
53 | --input: 240 3.7% 15.9%;
54 | --ring: 240 4.9% 83.9%;
55 |
56 | --chart-1: 220 70% 50%;
57 | --chart-2: 160 60% 45%;
58 | --chart-3: 30 80% 55%;
59 | --chart-4: 280 65% 60%;
60 | --chart-5: 340 75% 55%;
61 | }
62 | }
63 |
64 | @layer base {
65 | * {
66 | @apply border-border;
67 | }
68 | html {
69 | @apply scroll-smooth;
70 | }
71 | body {
72 | @apply bg-background text-foreground;
73 | /* font-feature-settings: "rlig" 1, "calt" 1; */
74 | font-synthesis-weight: none;
75 | text-rendering: optimizeLegibility;
76 | }
77 | }
78 |
79 | @layer utilities {
80 | .step {
81 | counter-increment: step;
82 | }
83 |
84 | .step:before {
85 | @apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
86 | @apply ml-[-50px] mt-[-4px];
87 | content: counter(step);
88 | }
89 |
90 | .chunk-container {
91 | @apply shadow-none;
92 | }
93 |
94 | .chunk-container::after {
95 | content: "";
96 | @apply absolute -inset-4 shadow-xl rounded-xl border;
97 | }
98 | }
99 |
100 | @media (max-width: 640px) {
101 | .container {
102 | @apply px-4;
103 | }
104 | }
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import "./App.css";
3 | import { CompareChart } from "./CompareChart";
4 | import { Textarea } from "@/components/ui/textarea";
5 | import { Benchmark, calculateAverage } from "./types/benchmark";
6 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
7 | import { BenchmarkTable } from "./BenchmarkTable";
8 | import sampleBenchmarks from "../samplebaseline.json";
9 | import { FiltersSelector } from "./ui/Filters";
10 | import { Filters } from "./types/filters";
11 | import { Button } from "./components/ui/button";
12 | import {
13 | Select,
14 | SelectContent,
15 | SelectGroup,
16 | SelectItem,
17 | SelectLabel,
18 | SelectTrigger,
19 | SelectValue,
20 | } from "@/components/ui/select";
21 | import { Compare } from "./ui/Compare";
22 |
23 | function App({ json, resetJson, filters }: { json: string | undefined, resetJson: () => void, filters: Filters | undefined }) {
24 | const [benchmarks, setBenchmarks] = useState([]);
25 |
26 | const [rawInput, setRawInput] = useState(json);
27 | const [filter, setFilter] = useState(filters);
28 | const [selectedTab, setSelectedTab] = useState("charts");
29 |
30 | const [localBenchmarks, setLocalBenchmarks] = useState<
31 | Record | undefined
32 | >();
33 |
34 | useEffect(() => {
35 | const localBenchMarksStr = localStorage.getItem("benchmarks");
36 | if (localBenchMarksStr) {
37 | try {
38 | setLocalBenchmarks(JSON.parse(localBenchMarksStr));
39 | } catch (e) {
40 | console.error(e);
41 | }
42 | }
43 | });
44 |
45 | useEffect(() => {
46 | try {
47 | const value = JSON.parse(rawInput ?? "");
48 | const benchmarksWithAverage = value.benchmarks.map(
49 | (benchmark: Benchmark) => {
50 | const metricsWithAverage = Object.keys(benchmark.metrics).reduce(
51 | (acc, key) => {
52 | const metric = benchmark.metrics[key];
53 | const average = calculateAverage(metric.runs);
54 | acc[key] = { ...metric, average };
55 | return acc;
56 | },
57 | {} as Record
58 | );
59 | return { ...benchmark, metrics: metricsWithAverage };
60 | }
61 | );
62 | setBenchmarks(benchmarksWithAverage);
63 | } catch (e) {
64 | setBenchmarks([]);
65 | console.error(e);
66 | }
67 | }, [rawInput]);
68 |
69 | const [filteredBenchmarks, setFilteredBenchmarks] = useState<
70 | Benchmark[] | undefined
71 | >([]);
72 |
73 | useEffect(() => {
74 | setFilteredBenchmarks(
75 | benchmarks?.filter((benchmark) =>
76 | filter?.benchmarkNames.includes(benchmark.name)
77 | )
78 | );
79 | }, [filter, benchmarks]);
80 |
81 | const validateJson = (json: any) => {
82 | const sampleJson = sampleBenchmarks;
83 | const isValidContext =
84 | JSON.stringify(Object.keys(json.context)) ===
85 | JSON.stringify(Object.keys(sampleJson.context));
86 | const isValidBenchmarks = json.benchmarks.every((benchmark: any) => {
87 | return (
88 | JSON.stringify(Object.keys(benchmark)) ===
89 | JSON.stringify(Object.keys(sampleJson.benchmarks[0]))
90 | );
91 | });
92 | return isValidContext && isValidBenchmarks;
93 | };
94 |
95 | return (
96 |
97 |
98 |
99 |
100 |
122 |
123 |
147 |
154 |
155 |
156 |
163 |
172 |
173 |
174 |
175 |
180 |
181 |
182 |
183 |
203 |
204 |
205 |
206 |
211 |
212 | Charts
213 | Table
214 |
215 | {selectedTab == "charts" && (
216 |
217 |
218 |
222 |
223 |
224 | )}
225 | {selectedTab == "table" && (
226 |
227 |
228 |
232 |
233 |
234 | )}
235 |
236 |
237 |
238 |
239 |
240 | );
241 | }
242 |
243 | export default App;
244 |
--------------------------------------------------------------------------------
/src/BenchmarkTable.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Table,
3 | TableBody,
4 | TableCaption,
5 | TableCell,
6 | TableHead,
7 | TableHeader,
8 | TableRow,
9 | } from "@/components/ui/table";
10 | import { Benchmark } from "./types/benchmark";
11 | import { Filters } from "./types/filters";
12 |
13 | export function BenchmarkTable({
14 | benchmarks,
15 | filters
16 | }: {
17 | benchmarks: Benchmark[] | undefined;
18 | filters: Filters | undefined;
19 | }) {
20 |
21 | const tableData = benchmarks?.map((benchmark) => ({
22 | name: benchmark.name,
23 | minimum: filters ? benchmark.metrics[filters.metrics].minimum : 0,
24 | median: filters ? benchmark.metrics[filters.metrics].median : 0,
25 | maximum: filters ? benchmark.metrics[filters.metrics].maximum : 0,
26 | average: filters ? benchmark.metrics[filters.metrics].average : 0,
27 | })) || []
28 |
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | Name
36 | Minimum
37 | Median
38 | Maximum
39 | Average
40 |
41 |
42 |
43 |
44 | {tableData.map((item) => (
45 |
46 | {item.name}
47 | {item.minimum}
48 | {item.median}
49 | {item.maximum}
50 | {item.average}
51 |
52 | ))}
53 |
54 |
55 |
56 |
);
57 | }
58 |
--------------------------------------------------------------------------------
/src/CompareChart.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { TrendingUp } from "lucide-react";
4 | import {
5 | Bar,
6 | BarChart,
7 | CartesianGrid,
8 | Line,
9 | LineChart,
10 | XAxis,
11 | YAxis,
12 | } from "recharts";
13 |
14 | import {
15 | Card,
16 | CardContent,
17 | CardDescription,
18 | CardFooter,
19 | CardHeader,
20 | CardTitle,
21 | } from "@/components/ui/card";
22 | import {
23 | ChartConfig,
24 | ChartContainer,
25 | ChartLegend,
26 | ChartLegendContent,
27 | ChartTooltip,
28 | ChartTooltipContent,
29 | } from "@/components/ui/chart";
30 | import { Benchmark } from "./types/benchmark";
31 | import { Filters } from "./types/filters";
32 |
33 | const chartConfig = {
34 | minimum: {
35 | label: "minimum",
36 | color: "hsl(var(--chart-1))",
37 | },
38 | median: {
39 | label: "median",
40 | color: "hsl(var(--chart-2))",
41 | },
42 | maximum: {
43 | label: "maximum",
44 | color: "hsl(var(--chart-3))",
45 | },
46 | average: {
47 | label: "average",
48 | color: "hsl(var(--chart-4))",
49 | },
50 | } satisfies ChartConfig;
51 |
52 | export function CompareChart({
53 | benchmarks,
54 | filter,
55 | }: {
56 | benchmarks: Benchmark[] | undefined;
57 | filter: Filters | undefined;
58 | }) {
59 | const chartData =
60 | benchmarks?.map((benchmark) => ({
61 | name: benchmark.name,
62 | minimum: filter ? benchmark.metrics[filter.metrics]?.minimum : 0,
63 | median: filter ? benchmark.metrics[filter.metrics]?.median : 0,
64 | maximum: filter ? benchmark.metrics[filter.metrics]?.maximum : 0,
65 | average: filter ? benchmark.metrics[filter.metrics]?.average : 0,
66 | })) || [];
67 |
68 | return (
69 |
70 |
71 | {filter?.metrics}
72 |
73 |
74 |
75 |
83 |
84 |
91 |
97 | }
100 | />
101 |
106 |
111 |
116 |
121 | } />
122 |
123 |
124 |
125 |
126 | );
127 | }
128 |
--------------------------------------------------------------------------------
/src/assets/github-mark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/github-mark.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "@/components/theme-provider";
2 |
3 | const GithubIcon = () => {
4 | const currentTheme = useTheme();
5 | const fillColor = currentTheme.finalTheme == "dark" ? "#ffffff" : "#000000";
6 |
7 | return (
8 |
16 | );
17 | };
18 |
19 | export default GithubIcon;
20 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react"
2 |
3 | type Theme = "dark" | "light" | "system"
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode
7 | defaultTheme?: Theme
8 | storageKey?: string
9 | }
10 |
11 | type ThemeProviderState = {
12 | theme: Theme
13 | finalTheme : "dark" | "light"
14 | setTheme: (theme: Theme) => void
15 | }
16 |
17 | const initialState: ThemeProviderState = {
18 | theme: "system",
19 | finalTheme: "light",
20 | setTheme: () => null,
21 | }
22 |
23 | const ThemeProviderContext = createContext(initialState)
24 |
25 | export function ThemeProvider({
26 | children,
27 | defaultTheme = "system",
28 | storageKey = "vite-ui-theme",
29 | ...props
30 | }: ThemeProviderProps) {
31 | const [theme, setTheme] = useState(
32 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
33 | )
34 | const finalTheme = theme === "system" ? window.matchMedia("(prefers-color-scheme: dark)")
35 | .matches
36 | ? "dark"
37 | : "light" : theme
38 |
39 | useEffect(() => {
40 | const root = window.document.documentElement
41 |
42 | root.classList.remove("light", "dark")
43 |
44 | if (theme === "system") {
45 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
46 | .matches
47 | ? "dark"
48 | : "light"
49 |
50 | root.classList.add(systemTheme)
51 | return
52 | }
53 |
54 | root.classList.add(theme)
55 | }, [theme])
56 |
57 | const value = {
58 | theme,
59 | finalTheme,
60 | setTheme: (theme: Theme) => {
61 | localStorage.setItem(storageKey, theme)
62 | setTheme(theme)
63 | },
64 | }
65 |
66 | return (
67 |
68 | {children}
69 |
70 | )
71 | }
72 |
73 | export const useTheme = () => {
74 | const context = useContext(ThemeProviderContext)
75 |
76 | if (context === undefined)
77 | throw new Error("useTheme must be used within a ThemeProvider")
78 |
79 | return context
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/chart.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as RechartsPrimitive from "recharts"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | // Format: { THEME_NAME: CSS_SELECTOR }
7 | const THEMES = { light: "", dark: ".dark" } as const
8 |
9 | export type ChartConfig = {
10 | [k in string]: {
11 | label?: React.ReactNode
12 | icon?: React.ComponentType
13 | } & (
14 | | { color?: string; theme?: never }
15 | | { color?: never; theme: Record }
16 | )
17 | }
18 |
19 | type ChartContextProps = {
20 | config: ChartConfig
21 | }
22 |
23 | const ChartContext = React.createContext(null)
24 |
25 | function useChart() {
26 | const context = React.useContext(ChartContext)
27 |
28 | if (!context) {
29 | throw new Error("useChart must be used within a ")
30 | }
31 |
32 | return context
33 | }
34 |
35 | const ChartContainer = React.forwardRef<
36 | HTMLDivElement,
37 | React.ComponentProps<"div"> & {
38 | config: ChartConfig
39 | children: React.ComponentProps<
40 | typeof RechartsPrimitive.ResponsiveContainer
41 | >["children"]
42 | }
43 | >(({ id, className, children, config, ...props }, ref) => {
44 | const uniqueId = React.useId()
45 | const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46 |
47 | return (
48 |
49 |
58 |
59 |
60 | {children}
61 |
62 |
63 |
64 | )
65 | })
66 | ChartContainer.displayName = "Chart"
67 |
68 | const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69 | const colorConfig = Object.entries(config).filter(
70 | ([_, config]) => config.theme || config.color
71 | )
72 |
73 | if (!colorConfig.length) {
74 | return null
75 | }
76 |
77 | return (
78 |