Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit f12abde

Browse files
committed
Port analyze.sh to analyze.js
- Converted Bash script to JavaScript for Node.js environment - Maintained core functionality of metric extraction and analysis - Utilized child_process to execute shell commands for file parsing - Implemented Gnuplot execution via Node.js - Ensured compatibility with existing benchmark result files
1 parent 7f43d85 commit f12abde

File tree

3 files changed

+201
-167
lines changed

3 files changed

+201
-167
lines changed

analyze.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require('child_process');
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
function extractMetric(file, metric) {
8+
try {
9+
const command = `grep "${metric}" "${file}" | awk '{print $2}' | sed 's/ms//'`;
10+
const result = execSync(command, { encoding: 'utf-8' }).trim();
11+
return result;
12+
} catch (error) {
13+
console.error(`Error extracting metric from ${file}: ${error.message}`);
14+
return null;
15+
}
16+
}
17+
18+
function average(values) {
19+
const sum = values.reduce((a, b) => parseFloat(a) + parseFloat(b), 0);
20+
return sum / values.length;
21+
}
22+
23+
const formattedServerNames = {
24+
tailcall: "Tailcall",
25+
gqlgen: "Gqlgen",
26+
apollo: "Apollo GraphQL",
27+
netflixdgs: "Netflix DGS",
28+
caliban: "Caliban",
29+
async_graphql: "async-graphql",
30+
hasura: "Hasura",
31+
graphql_jit: "GraphQL JIT",
32+
};
33+
34+
const servers = ["apollo", "caliban", "netflixdgs", "gqlgen", "tailcall", "async_graphql", "hasura", "graphql_jit"];
35+
const resultFiles = process.argv.slice(2);
36+
const avgReqSecs = {};
37+
const avgLatencies = {};
38+
39+
servers.forEach((server, idx) => {
40+
const startIdx = idx * 3;
41+
const reqSecVals = [];
42+
const latencyVals = [];
43+
for (let j = 0; j < 3; j++) {
44+
const fileIdx = startIdx + j;
45+
const reqSec = extractMetric(resultFiles[fileIdx], "Requests/sec");
46+
const latency = extractMetric(resultFiles[fileIdx], "Latency");
47+
if (reqSec !== null) reqSecVals.push(reqSec);
48+
if (latency !== null) latencyVals.push(latency);
49+
}
50+
avgReqSecs[server] = average(reqSecVals);
51+
avgLatencies[server] = average(latencyVals);
52+
});
53+
54+
const reqSecData = "/tmp/reqSec.dat";
55+
const latencyData = "/tmp/latency.dat";
56+
57+
fs.writeFileSync(reqSecData, "Server Value\n" + servers.map(server => `${server} ${avgReqSecs[server]}`).join('\n'));
58+
fs.writeFileSync(latencyData, "Server Value\n" + servers.map(server => `${server} ${avgLatencies[server]}`).join('\n'));
59+
60+
let whichBench = 1;
61+
if (resultFiles[0].startsWith("bench2")) {
62+
whichBench = 2;
63+
} else if (resultFiles[0].startsWith("bench3")) {
64+
whichBench = 3;
65+
}
66+
67+
const reqSecHistogramFile = `req_sec_histogram${whichBench}.png`;
68+
const latencyHistogramFile = `latency_histogram${whichBench}.png`;
69+
70+
function getMaxValue(data) {
71+
return Math.max(...data.split('\n').slice(1).map(line => parseFloat(line.split(' ')[1])));
72+
}
73+
74+
const reqSecMax = getMaxValue(fs.readFileSync(reqSecData, 'utf-8')) * 1.2;
75+
const latencyMax = getMaxValue(fs.readFileSync(latencyData, 'utf-8')) * 1.2;
76+
77+
const gnuplotScript = `
78+
set term pngcairo size 1280,720 enhanced font 'Courier,12'
79+
set output '${reqSecHistogramFile}'
80+
set style data histograms
81+
set style histogram cluster gap 1
82+
set style fill solid border -1
83+
set xtics rotate by -45
84+
set boxwidth 0.9
85+
set title 'Requests/Sec'
86+
set yrange [0:${reqSecMax}]
87+
set key outside right top
88+
plot '${reqSecData}' using 2:xtic(1) title 'Req/Sec'
89+
90+
set output '${latencyHistogramFile}'
91+
set title 'Latency (in ms)'
92+
set yrange [0:${latencyMax}]
93+
plot '${latencyData}' using 2:xtic(1) title 'Latency'
94+
`;
95+
96+
const gnuplotScriptFile = '/tmp/gnuplot_script.gp';
97+
fs.writeFileSync(gnuplotScriptFile, gnuplotScript);
98+
99+
try {
100+
execSync(`gnuplot ${gnuplotScriptFile}`, { stdio: 'inherit' });
101+
console.log('Gnuplot executed successfully');
102+
} catch (error) {
103+
console.error('Error executing gnuplot:', error.message);
104+
process.exit(1);
105+
}
106+
107+
const assetsDir = path.join(__dirname, "assets");
108+
if (!fs.existsSync(assetsDir)) {
109+
fs.mkdirSync(assetsDir);
110+
}
111+
112+
function moveFile(source, destination) {
113+
try {
114+
if (fs.existsSync(source)) {
115+
fs.renameSync(source, destination);
116+
console.log(`Moved ${source} to ${destination}`);
117+
} else {
118+
console.log(`Source file ${source} does not exist`);
119+
}
120+
} catch (error) {
121+
console.error(`Error moving file ${source}: ${error.message}`);
122+
}
123+
}
124+
125+
moveFile(reqSecHistogramFile, path.join(assetsDir, reqSecHistogramFile));
126+
moveFile(latencyHistogramFile, path.join(assetsDir, latencyHistogramFile));
127+
128+
const serverRPS = {};
129+
servers.forEach((server) => {
130+
serverRPS[server] = avgReqSecs[server];
131+
});
132+
133+
const sortedServers = Object.keys(serverRPS).sort(
134+
(a, b) => serverRPS[b] - serverRPS[a]
135+
);
136+
const lastServer = sortedServers[sortedServers.length - 1];
137+
const lastServerReqSecs = avgReqSecs[lastServer];
138+
139+
const resultsFile = "results.md";
140+
141+
if (!fs.existsSync(resultsFile) || fs.readFileSync(resultsFile, 'utf8').trim() === '') {
142+
fs.writeFileSync(resultsFile, `<!-- PERFORMANCE_RESULTS_START -->
143+
144+
| Query | Server | Requests/sec | Latency (ms) | Relative |
145+
|-------:|--------:|--------------:|--------------:|---------:|`);
146+
}
147+
148+
let resultsTable = "";
149+
150+
if (whichBench === 1) {
151+
resultsTable += `\n| ${whichBench} | \`{ posts { id userId title user { id name email }}}\` |`;
152+
} else if (whichBench === 2) {
153+
resultsTable += `\n| ${whichBench} | \`{ posts { title }}\` |`;
154+
} else if (whichBench === 3) {
155+
resultsTable += `\n| ${whichBench} | \`{ greet }\` |`;
156+
}
157+
158+
sortedServers.forEach((server) => {
159+
const formattedReqSecs = avgReqSecs[server].toLocaleString(undefined, {
160+
minimumFractionDigits: 2,
161+
maximumFractionDigits: 2,
162+
});
163+
const formattedLatencies = avgLatencies[server].toLocaleString(undefined, {
164+
minimumFractionDigits: 2,
165+
maximumFractionDigits: 2,
166+
});
167+
const relativePerformance = (avgReqSecs[server] / lastServerReqSecs).toFixed(2);
168+
169+
resultsTable += `\n|| [${formattedServerNames[server]}] | \`${formattedReqSecs}\` | \`${formattedLatencies}\` | \`${relativePerformance}x\` |`;
170+
});
171+
172+
fs.appendFileSync(resultsFile, resultsTable + "\n");
173+
174+
if (whichBench === 3) {
175+
fs.appendFileSync(resultsFile, "\n<!-- PERFORMANCE_RESULTS_END -->");
176+
177+
const finalResults = fs
178+
.readFileSync(resultsFile, "utf-8")
179+
.replace(/(\r\n|\n|\r)/gm, "\\n");
180+
181+
const readmePath = "README.md";
182+
let readmeContent = fs.readFileSync(readmePath, "utf-8");
183+
const performanceResultsRegex =
184+
/<!-- PERFORMANCE_RESULTS_START -->[\s\S]*<!-- PERFORMANCE_RESULTS_END -->/;
185+
if (performanceResultsRegex.test(readmeContent)) {
186+
readmeContent = readmeContent.replace(
187+
performanceResultsRegex,
188+
finalResults
189+
);
190+
} else {
191+
readmeContent += `\n${finalResults}`;
192+
}
193+
fs.writeFileSync(readmePath, readmeContent);
194+
}
195+
196+
resultFiles.forEach((file) => {
197+
fs.unlinkSync(file);
198+
});

analyze.sh

Lines changed: 0 additions & 164 deletions
This file was deleted.

run_benchmarks.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@ for service in "apollo_server" "caliban" "netflix_dgs" "gqlgen" "tailcall" "asyn
8383
fi
8484
done
8585

86-
bash analyze.sh "${bench1Results[@]}"
87-
bash analyze.sh "${bench2Results[@]}"
88-
bash analyze.sh "${bench3Results[@]}"
86+
node analyze.js "${bench1Results[@]}"
87+
node analyze.js "${bench2Results[@]}"
88+
node analyze.js "${bench3Results[@]}"

0 commit comments

Comments
 (0)