polysearch/benchmark.js

200 lines
7.5 KiB
JavaScript

#!/usr/bin/env node
import { loadConfig } from "./src/config.js";
import { HttpClient } from "./src/http/client.js";
import { ProxyPool } from "./src/http/proxy.js";
import { SearchRunner } from "./src/run.js";
import { setUserAgents } from "./src/utils/ua.js";
import { childLogger } from "./src/utils/logger.js";
const WEB_QUERIES = [
"quantum computing", "machine learning", "renaissance art", "solar system",
"ancient rome", "climate change", "python programming", "space exploration",
"world war 2", "ocean depth", "artificial intelligence", "mount everest",
"greek mythology", "industrial revolution", "human genome", "black holes",
"coral reef", "buddhism history", "cold war", "mars colonization",
"electric vehicles", "great barrier reef", "dark matter", "dinosaur fossils",
"ancient egypt pyramids", "big bang theory", "amazon rainforest",
"vitamin deficiency", "stock market crash 1929", "northern lights"
];
const IMAGE_QUERIES = [
"vintage radio", "mars rover", "aurora borealis", "mountain landscape",
"classic cars", "modern architecture", "street photography", "wild animals",
"space nebula", "underwater coral", "sunset beach", "city skyline",
"butterfly macro", "starry night sky", "tropical forest", "medieval castle",
"abstract art", "vintage motorcycles", "taj mahal", "rainforest waterfall",
"northern lights norway", "japanese garden", "safari animals", "galaxy cluster",
"old steam train", "desert dunes", "cherry blossom", "iceberg antarctica",
"neon city night", "autumn forest path"
];
function generateReport(results) {
const total = results.length;
const success = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
const successRate = (success / total * 100).toFixed(1);
const byType = {};
for (const r of results) {
byType[r.type] = byType[r.type] || [];
byType[r.type].push(r);
}
console.log("\n" + "=".repeat(80));
console.log(" BENCHMARK REPORT");
console.log("=".repeat(80));
console.log(`\n Total requests: ${total}`);
console.log(` Successful: ${success} (${successRate}%)`);
console.log(` Failed: ${failed} (${(100 - parseFloat(successRate)).toFixed(1)}%)`);
console.log(` Date: ${new Date().toISOString()}`);
console.log(` Proxy pool: ${results[0]?.proxyCount || "N/A"} proxies`);
for (const [type, items] of Object.entries(byType)) {
const tSuccess = items.filter(r => r.success).length;
const tFailed = items.filter(r => !r.success).length;
const times = items.filter(r => r.success).map(r => r.durationMs).sort((a, b) => a - b);
const dataSizes = items.filter(r => r.dataSizeKb != null).map(r => r.dataSizeKb).sort((a, b) => a - b);
console.log(`\n ── ${type.toUpperCase()} (${items.length} requests, ${tSuccess} ok / ${tFailed} fail) ──`);
if (times.length > 0) {
const avg = times.reduce((s, v) => s + v, 0) / times.length;
const p50 = times[Math.floor(times.length * 0.5)];
const p95 = times[Math.floor(times.length * 0.95)];
const p99 = times[Math.floor(times.length * 0.99)];
const min = times[0];
const max = times[times.length - 1];
console.log(` Response time (ms): avg=${avg.toFixed(0)} p50=${p50} p95=${p95} p99=${p99} min=${min} max=${max}`);
}
if (dataSizes.length > 0) {
const avg = dataSizes.reduce((s, v) => s + v, 0) / dataSizes.length;
const p50 = dataSizes[Math.floor(dataSizes.length * 0.5)];
const p95 = dataSizes[Math.floor(dataSizes.length * 0.95)];
console.log(` Data size (KB): avg=${avg.toFixed(2)} p50=${p50.toFixed(1)} p95=${p95.toFixed(1)}`);
}
const engineCounts = {};
items.filter(r => r.success).forEach(r => {
const key = r.engine || "unknown";
engineCounts[key] = (engineCounts[key] || 0) + 1;
});
if (Object.keys(engineCounts).length > 0) {
console.log(` Engines used: ${Object.entries(engineCounts).map(([k, v]) => `${k}=${v}`).join(", ")}`);
}
const proxiesUsed = new Set(items.filter(r => r.success).map(r => r.proxyHost).filter(Boolean));
const proxyFails = items.filter(r => !r.success).map(r => r.proxyHost).filter(Boolean);
if (proxiesUsed.size > 0) {
console.log(` Distinct proxies used: ${proxiesUsed.size}`);
console.log(` Proxy failures: ${proxyFails.length}`);
}
}
const errorTypes = {};
for (const r of results) {
if (!r.success && r.error) {
const key = r.error.substring(0, 60);
errorTypes[key] = (errorTypes[key] || 0) + 1;
}
}
if (Object.keys(errorTypes).length > 0) {
console.log(`\n ── Error Breakdown ──`);
for (const [err, count] of Object.entries(errorTypes).sort((a, b) => b[1] - a[1])) {
console.log(` [${count}x] ${err}`);
}
}
console.log("=".repeat(80) + "\n");
}
async function runSingleTest(runner, query, type, index) {
const start = Date.now();
try {
const data = await runner.run({ query, type, limit: 3 });
const durationMs = Date.now() - start;
const results = data[type]?.results || [];
return {
index,
query,
type,
success: true,
durationMs,
dataSizeKb: JSON.stringify(data).length / 1024,
resultCount: results.length,
engine: data[type]?.engine || "none",
proxyHost: null,
errors: data.errors?.length || 0
};
} catch (err) {
return {
index,
query,
type,
success: false,
durationMs: Date.now() - start,
error: err.message,
resultCount: 0,
engine: "none",
proxyHost: null
};
}
}
async function main() {
const log = childLogger({ component: "benchmark" });
log.info({ webCount: WEB_QUERIES.length, imageCount: IMAGE_QUERIES.length }, "starting benchmark");
const config = await loadConfig();
if (config.http.user_agents) setUserAgents(config.http.user_agents);
const proxyPool = new ProxyPool(config.proxies, config.proxy);
const httpClient = new HttpClient(config.http);
if (config.proxy.enabled) {
httpClient.setProxyPool(proxyPool);
log.info({ count: config.proxies.length }, "proxy pool attached");
proxyPool.logState();
}
const runner = new SearchRunner({ httpClient, config });
const allResults = [];
const queries = [
...WEB_QUERIES.map(q => ({ query: q, type: "web" })),
...IMAGE_QUERIES.map(q => ({ query: q, type: "image" }))
];
const batchSize = 5;
for (let i = 0; i < queries.length; i += batchSize) {
const batch = queries.slice(i, i + batchSize);
log.info({ batch: Math.floor(i / batchSize) + 1, total: queries.length }, `running batch`);
const batchResults = await Promise.all(
batch.map((item, j) => {
const idx = i + j + 1;
return runSingleTest(runner, item.query, item.type, idx);
})
);
allResults.push(...batchResults);
for (const r of batchResults) {
const icon = r.success ? "✓" : "✗";
console.log(`${icon} [${r.type.padEnd(5)}] #${String(r.index).padStart(2)} "${r.query.slice(0, 30).padEnd(30)}" ${r.success ? `${r.durationMs}ms` : `FAIL: ${r.error?.slice(0, 40)}`}`);
}
}
const totalDuration = allResults.reduce((s, r) => s + r.durationMs, 0);
log.info({
totalRequests: allResults.length,
totalDurationMs: totalDuration,
avgDurationMs: Math.round(totalDuration / allResults.length),
successRate: `${(allResults.filter(r => r.success).length / allResults.length * 100).toFixed(1)}%`
}, "benchmark complete");
generateReport(allResults);
}
main();