feat: 대역폭 추정 및 DAU 표시 기능 추가
- 동시접속자 기반 월간 대역폭 자동 추정 - DAU(일일활성사용자) 추정치 표시 (동접 × 10-14) - 대역폭 기반 Linode/Vultr 자동 선택 로직 - 비용 분석에 대역폭 비용 포함 - 지역 미선택시 서울/도쿄/오사카/싱가포르 기본 표시 - 지역별 서버 분리 표시 (GROUP BY instance + region) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
170
scripts/parse-benchmarks.ts
Normal file
170
scripts/parse-benchmarks.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
interface BenchmarkRow {
|
||||
idproc: string;
|
||||
descproc: string;
|
||||
percentile: string;
|
||||
nbproc: string;
|
||||
perf: string;
|
||||
}
|
||||
|
||||
const BENCHMARK_FILES = [
|
||||
'pts-nginx.csv',
|
||||
'pts-apache.csv',
|
||||
'pts-apache-siege.csv',
|
||||
'pts-node-octane.csv',
|
||||
'pts-node-express-loadtest.csv',
|
||||
'pts-phpbench.csv',
|
||||
'pts-redis.csv',
|
||||
'pts-mysqlslap.csv',
|
||||
'pts-compress-7zip.csv',
|
||||
'pts-compress-pbzip2.csv',
|
||||
'pts-compress-gzip.csv',
|
||||
'pts-postmark.csv',
|
||||
'pts-compilebench.csv',
|
||||
'pts-c-ray.csv',
|
||||
'pts-coremark.csv',
|
||||
'pts-byte.csv',
|
||||
'pts-encode-mp3.csv',
|
||||
'pts-encode-flac.csv',
|
||||
'pts-x264.csv',
|
||||
'pts-build-imagemagick.csv',
|
||||
'pts-build-nodejs.csv',
|
||||
'pts-build-php.csv',
|
||||
];
|
||||
|
||||
function parseCSV(content: string): BenchmarkRow[] {
|
||||
const lines = content.trim().split('\n');
|
||||
const header = lines[0].split(',');
|
||||
const rows: BenchmarkRow[] = [];
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const values = lines[i].split(',');
|
||||
if (values.length >= 6) {
|
||||
rows.push({
|
||||
idproc: values[1],
|
||||
descproc: values[2],
|
||||
percentile: values[3],
|
||||
nbproc: values[4],
|
||||
perf: values[5],
|
||||
});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function parsePerf(perf: string): { score: number; deviation: number | null } {
|
||||
// Format: "496096 +/- 4671" or "496096"
|
||||
const match = perf.match(/^([\d.]+)\s*(?:\+\/-\s*([\d.]+))?$/);
|
||||
if (match) {
|
||||
return {
|
||||
score: parseFloat(match[1]),
|
||||
deviation: match[2] ? parseFloat(match[2]) : null,
|
||||
};
|
||||
}
|
||||
return { score: 0, deviation: null };
|
||||
}
|
||||
|
||||
function parsePercentile(percentile: string): number {
|
||||
// Format: "100th", "98th", "95th", etc.
|
||||
const match = percentile.match(/^(\d+)/);
|
||||
return match ? parseInt(match[1]) : 0;
|
||||
}
|
||||
|
||||
function extractVendor(name: string): string {
|
||||
if (name.toLowerCase().includes('amd')) return 'AMD';
|
||||
if (name.toLowerCase().includes('intel')) return 'Intel';
|
||||
if (name.toLowerCase().includes('arm')) return 'ARM';
|
||||
if (name.toLowerCase().includes('apple')) return 'Apple';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
function extractCores(name: string): number | null {
|
||||
// Try to extract core count from name
|
||||
const match = name.match(/(\d+)-Core/i);
|
||||
return match ? parseInt(match[1]) : null;
|
||||
}
|
||||
|
||||
function escapeSQL(str: string): string {
|
||||
return str.replace(/'/g, "''");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const dataDir = path.join(__dirname, '../benchmark-data/data/suite');
|
||||
const processors = new Map<string, { name: string; vendor: string; cores: number | null }>();
|
||||
const benchmarks: Array<{
|
||||
procId: string;
|
||||
benchmarkType: string;
|
||||
score: number;
|
||||
deviation: number | null;
|
||||
percentile: number;
|
||||
sampleCount: number;
|
||||
}> = [];
|
||||
|
||||
for (const file of BENCHMARK_FILES) {
|
||||
const filePath = path.join(dataDir, file);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`File not found: ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const rows = parseCSV(content);
|
||||
const benchmarkType = file.replace('.csv', '');
|
||||
|
||||
for (const row of rows) {
|
||||
// Store processor info
|
||||
if (!processors.has(row.idproc)) {
|
||||
processors.set(row.idproc, {
|
||||
name: row.descproc,
|
||||
vendor: extractVendor(row.descproc),
|
||||
cores: extractCores(row.descproc),
|
||||
});
|
||||
}
|
||||
|
||||
// Parse performance
|
||||
const { score, deviation } = parsePerf(row.perf);
|
||||
if (score > 0) {
|
||||
benchmarks.push({
|
||||
procId: row.idproc,
|
||||
benchmarkType,
|
||||
score,
|
||||
deviation,
|
||||
percentile: parsePercentile(row.percentile),
|
||||
sampleCount: parseInt(row.nbproc) || 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SQL
|
||||
let sql = '-- Generated benchmark data\n\n';
|
||||
|
||||
// Insert processors
|
||||
sql += '-- Insert processors\n';
|
||||
const procArray = Array.from(processors.entries());
|
||||
for (const [procId, info] of procArray) {
|
||||
const cores = info.cores !== null ? info.cores : 'NULL';
|
||||
sql += `INSERT OR IGNORE INTO processors (proc_id, name, vendor, cores) VALUES ('${escapeSQL(procId)}', '${escapeSQL(info.name)}', '${info.vendor}', ${cores});\n`;
|
||||
}
|
||||
|
||||
sql += '\n-- Insert benchmark results\n';
|
||||
for (const b of benchmarks) {
|
||||
const deviation = b.deviation !== null ? b.deviation : 'NULL';
|
||||
sql += `INSERT OR IGNORE INTO benchmark_results (processor_id, benchmark_type_id, score, score_deviation, percentile, sample_count)
|
||||
SELECT p.id, bt.id, ${b.score}, ${deviation}, ${b.percentile}, ${b.sampleCount}
|
||||
FROM processors p, benchmark_types bt
|
||||
WHERE p.proc_id = '${escapeSQL(b.procId)}' AND bt.name = '${b.benchmarkType}';\n`;
|
||||
}
|
||||
|
||||
// Output summary
|
||||
console.log(`Processed ${processors.size} processors`);
|
||||
console.log(`Processed ${benchmarks.length} benchmark results`);
|
||||
|
||||
// Write SQL file
|
||||
fs.writeFileSync(path.join(__dirname, '../benchmark-seed.sql'), sql);
|
||||
console.log('Generated benchmark-seed.sql');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user