200 lines
7.5 KiB
JavaScript
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();
|