import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { ImageResponse } from "next/og"; import React from "react"; import { METADATA_URL } from "../src/constants"; import type { IconFile } from "../src/types/icons"; // Standalone cached functions for benchmarking (no Next.js dependencies) let iconsDataCache: IconFile | null = null; const iconFileCache = new Map(); let preloadDone = false; async function getAllIconsStandalone(): Promise { if (iconsDataCache) { return iconsDataCache; } const response = await fetch(METADATA_URL); if (!response.ok) { throw new Error(`Failed to fetch icons: ${response.statusText}`); } iconsDataCache = (await response.json()) as IconFile; return iconsDataCache; } async function preloadAllIconsStandalone(): Promise { if (preloadDone) { return; } const startTime = Date.now(); const iconsData = await getAllIconsStandalone(); const iconNames = Object.keys(iconsData); const pngDir = join(process.cwd(), `../png`); console.log(`[Preload] Loading ${iconNames.length} icons into memory...`); const loadPromises = iconNames.map(async (iconName) => { if (iconFileCache.has(iconName)) { return; } try { const iconPath = join(pngDir, `${iconName}.png`); const buffer = await readFile(iconPath); iconFileCache.set(iconName, buffer); } catch (_error) { iconFileCache.set(iconName, null); } }); await Promise.all(loadPromises); const duration = Date.now() - startTime; const loadedCount = Array.from(iconFileCache.values()).filter( (v) => v !== null, ).length; console.log( `[Preload] Loaded ${loadedCount}/${iconNames.length} icons in ${duration}ms (${(loadedCount / duration).toFixed(2)} icons/ms)\n`, ); preloadDone = true; } async function readIconFileStandalone( iconName: string, ): Promise { if (iconFileCache.has(iconName)) { return iconFileCache.get(iconName)!; } try { const iconPath = join(process.cwd(), `../png/${iconName}.png`); const buffer = await readFile(iconPath); iconFileCache.set(iconName, buffer); return buffer; } catch (_error) { iconFileCache.set(iconName, null); return null; } } const size = { width: 1200, height: 630, }; async function generateOGImage( icon: string, iconsData: Record, totalIcons: number, index: number, profileTimings: Map, ) { const stepTimings: Record = {}; let stepStart: number; stepStart = Date.now(); const formattedIconName = icon .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); stepTimings.formatName = Date.now() - stepStart; stepStart = Date.now(); const iconData = await readIconFileStandalone(icon); stepTimings.readFile = Date.now() - stepStart; stepStart = Date.now(); const iconUrl = iconData ? `data:image/png;base64,${iconData.toString("base64")}` : null; stepTimings.base64 = Date.now() - stepStart; stepStart = Date.now(); const imageResponse = await new ImageResponse(
{iconUrl ? ( {formattedIconName} ) : (
{formattedIconName}
)}
Download {formattedIconName} icon for free
Amongst {totalIcons} other high-quality dashboard icons
{["SVG", "PNG", "WEBP"].map((format) => (
{format}
))}
dashboardicons.com
, { ...size, }, ); stepTimings.imageResponse = Date.now() - stepStart; for (const [step, timing] of Object.entries(stepTimings)) { if (!profileTimings.has(step)) { profileTimings.set(step, []); } profileTimings.get(step)!.push(timing); } return imageResponse; } async function benchmark() { console.log("Starting OG image generation benchmark...\n"); const startTime = Date.now(); console.log("Fetching icons data..."); const iconsData = await getAllIconsStandalone(); const iconNames = Object.keys(iconsData); const totalIcons = iconNames.length; const testIcons = iconNames.slice(0, 100); await preloadAllIconsStandalone(); console.log(`Testing with ${testIcons.length} icons\n`); const times: number[] = []; const profileTimings = new Map(); for (let i = 0; i < testIcons.length; i++) { const icon = testIcons[i]; const iconStartTime = Date.now(); try { await generateOGImage(icon, iconsData, totalIcons, i, profileTimings); const iconEndTime = Date.now(); const duration = iconEndTime - iconStartTime; times.push(duration); if ((i + 1) % 10 === 0) { const avgTime = times.slice(-10).reduce((a, b) => a + b, 0) / 10; console.log( `Generated ${i + 1}/${testIcons.length} images (avg: ${avgTime.toFixed(2)}ms per image)`, ); } } catch (error) { console.error(`Failed to generate image for ${icon}:`, error); } } const endTime = Date.now(); const totalDuration = endTime - startTime; const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); console.log("\n" + "=".repeat(50)); console.log("Benchmark Results"); console.log("=".repeat(50)); console.log(`Total images generated: ${testIcons.length}`); console.log(`Total time: ${(totalDuration / 1000).toFixed(2)}s`); console.log(`Average time per image: ${avgTime.toFixed(2)}ms`); console.log(`Min time: ${minTime.toFixed(2)}ms`); console.log(`Max time: ${maxTime.toFixed(2)}ms`); console.log( `Images per second: ${((testIcons.length / totalDuration) * 1000).toFixed(2)}`, ); console.log("\n" + "-".repeat(50)); console.log("Performance Breakdown (per image):"); console.log("-".repeat(50)); for (const [step, timings] of profileTimings.entries()) { const avg = timings.reduce((a, b) => a + b, 0) / timings.length; const min = Math.min(...timings); const max = Math.max(...timings); const total = timings.reduce((a, b) => a + b, 0); const percentage = ( (total / times.reduce((a, b) => a + b, 0)) * 100 ).toFixed(1); console.log( ` ${step.padEnd(15)}: avg ${avg.toFixed(2)}ms | min ${min.toFixed(2)}ms | max ${max.toFixed(2)}ms | ${percentage}%`, ); } console.log("=".repeat(50)); } benchmark().catch(console.error);