├── .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 | ![](cover.jpeg) 2 | 3 | # BenchMarkify 📈 4 | 5 | Twitter: yogeshpaliyal 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 | ![image](https://github.com/user-attachments/assets/2bff6b8c-e5c5-43e3-85b8-1dbfe7560023) 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 | Patron Link 60 | 61 | 62 | 63 | Buy Me A Coffee 64 | 65 | 66 | 67 | Donation 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 |