-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 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
- Loading branch information
Showing
3 changed files
with
201 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
#!/usr/bin/env node | ||
|
||
const { execSync } = require('child_process'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
function extractMetric(file, metric) { | ||
try { | ||
const command = `grep "${metric}" "${file}" | awk '{print $2}' | sed 's/ms//'`; | ||
const result = execSync(command, { encoding: 'utf-8' }).trim(); | ||
return result; | ||
} catch (error) { | ||
console.error(`Error extracting metric from ${file}: ${error.message}`); | ||
return null; | ||
} | ||
} | ||
|
||
function average(values) { | ||
const sum = values.reduce((a, b) => parseFloat(a) + parseFloat(b), 0); | ||
return sum / values.length; | ||
} | ||
|
||
const formattedServerNames = { | ||
tailcall: "Tailcall", | ||
gqlgen: "Gqlgen", | ||
apollo: "Apollo GraphQL", | ||
netflixdgs: "Netflix DGS", | ||
caliban: "Caliban", | ||
async_graphql: "async-graphql", | ||
hasura: "Hasura", | ||
graphql_jit: "GraphQL JIT", | ||
}; | ||
|
||
const servers = ["apollo", "caliban", "netflixdgs", "gqlgen", "tailcall", "async_graphql", "hasura", "graphql_jit"]; | ||
const resultFiles = process.argv.slice(2); | ||
const avgReqSecs = {}; | ||
const avgLatencies = {}; | ||
|
||
servers.forEach((server, idx) => { | ||
const startIdx = idx * 3; | ||
const reqSecVals = []; | ||
const latencyVals = []; | ||
for (let j = 0; j < 3; j++) { | ||
const fileIdx = startIdx + j; | ||
const reqSec = extractMetric(resultFiles[fileIdx], "Requests/sec"); | ||
const latency = extractMetric(resultFiles[fileIdx], "Latency"); | ||
if (reqSec !== null) reqSecVals.push(reqSec); | ||
if (latency !== null) latencyVals.push(latency); | ||
} | ||
avgReqSecs[server] = average(reqSecVals); | ||
avgLatencies[server] = average(latencyVals); | ||
}); | ||
|
||
const reqSecData = "/tmp/reqSec.dat"; | ||
const latencyData = "/tmp/latency.dat"; | ||
|
||
fs.writeFileSync(reqSecData, "Server Value\n" + servers.map(server => `${server} ${avgReqSecs[server]}`).join('\n')); | ||
fs.writeFileSync(latencyData, "Server Value\n" + servers.map(server => `${server} ${avgLatencies[server]}`).join('\n')); | ||
|
||
let whichBench = 1; | ||
if (resultFiles[0].startsWith("bench2")) { | ||
whichBench = 2; | ||
} else if (resultFiles[0].startsWith("bench3")) { | ||
whichBench = 3; | ||
} | ||
|
||
const reqSecHistogramFile = `req_sec_histogram${whichBench}.png`; | ||
const latencyHistogramFile = `latency_histogram${whichBench}.png`; | ||
|
||
function getMaxValue(data) { | ||
return Math.max(...data.split('\n').slice(1).map(line => parseFloat(line.split(' ')[1]))); | ||
} | ||
|
||
const reqSecMax = getMaxValue(fs.readFileSync(reqSecData, 'utf-8')) * 1.2; | ||
const latencyMax = getMaxValue(fs.readFileSync(latencyData, 'utf-8')) * 1.2; | ||
|
||
const gnuplotScript = ` | ||
set term pngcairo size 1280,720 enhanced font 'Courier,12' | ||
set output '${reqSecHistogramFile}' | ||
set style data histograms | ||
set style histogram cluster gap 1 | ||
set style fill solid border -1 | ||
set xtics rotate by -45 | ||
set boxwidth 0.9 | ||
set title 'Requests/Sec' | ||
set yrange [0:${reqSecMax}] | ||
set key outside right top | ||
plot '${reqSecData}' using 2:xtic(1) title 'Req/Sec' | ||
set output '${latencyHistogramFile}' | ||
set title 'Latency (in ms)' | ||
set yrange [0:${latencyMax}] | ||
plot '${latencyData}' using 2:xtic(1) title 'Latency' | ||
`; | ||
|
||
const gnuplotScriptFile = '/tmp/gnuplot_script.gp'; | ||
fs.writeFileSync(gnuplotScriptFile, gnuplotScript); | ||
|
||
try { | ||
execSync(`gnuplot ${gnuplotScriptFile}`, { stdio: 'inherit' }); | ||
console.log('Gnuplot executed successfully'); | ||
} catch (error) { | ||
console.error('Error executing gnuplot:', error.message); | ||
process.exit(1); | ||
} | ||
|
||
const assetsDir = path.join(__dirname, "assets"); | ||
if (!fs.existsSync(assetsDir)) { | ||
fs.mkdirSync(assetsDir); | ||
} | ||
|
||
function moveFile(source, destination) { | ||
try { | ||
if (fs.existsSync(source)) { | ||
fs.renameSync(source, destination); | ||
console.log(`Moved ${source} to ${destination}`); | ||
} else { | ||
console.log(`Source file ${source} does not exist`); | ||
} | ||
} catch (error) { | ||
console.error(`Error moving file ${source}: ${error.message}`); | ||
} | ||
} | ||
|
||
moveFile(reqSecHistogramFile, path.join(assetsDir, reqSecHistogramFile)); | ||
moveFile(latencyHistogramFile, path.join(assetsDir, latencyHistogramFile)); | ||
|
||
const serverRPS = {}; | ||
servers.forEach((server) => { | ||
serverRPS[server] = avgReqSecs[server]; | ||
}); | ||
|
||
const sortedServers = Object.keys(serverRPS).sort( | ||
(a, b) => serverRPS[b] - serverRPS[a] | ||
); | ||
const lastServer = sortedServers[sortedServers.length - 1]; | ||
const lastServerReqSecs = avgReqSecs[lastServer]; | ||
|
||
const resultsFile = "results.md"; | ||
|
||
if (!fs.existsSync(resultsFile) || fs.readFileSync(resultsFile, 'utf8').trim() === '') { | ||
fs.writeFileSync(resultsFile, `<!-- PERFORMANCE_RESULTS_START --> | ||
| Query | Server | Requests/sec | Latency (ms) | Relative | | ||
|-------:|--------:|--------------:|--------------:|---------:|`); | ||
} | ||
|
||
let resultsTable = ""; | ||
|
||
if (whichBench === 1) { | ||
resultsTable += `\n| ${whichBench} | \`{ posts { id userId title user { id name email }}}\` |`; | ||
} else if (whichBench === 2) { | ||
resultsTable += `\n| ${whichBench} | \`{ posts { title }}\` |`; | ||
} else if (whichBench === 3) { | ||
resultsTable += `\n| ${whichBench} | \`{ greet }\` |`; | ||
} | ||
|
||
sortedServers.forEach((server) => { | ||
const formattedReqSecs = avgReqSecs[server].toLocaleString(undefined, { | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}); | ||
const formattedLatencies = avgLatencies[server].toLocaleString(undefined, { | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}); | ||
const relativePerformance = (avgReqSecs[server] / lastServerReqSecs).toFixed(2); | ||
|
||
resultsTable += `\n|| [${formattedServerNames[server]}] | \`${formattedReqSecs}\` | \`${formattedLatencies}\` | \`${relativePerformance}x\` |`; | ||
}); | ||
|
||
fs.appendFileSync(resultsFile, resultsTable + "\n"); | ||
|
||
if (whichBench === 3) { | ||
fs.appendFileSync(resultsFile, "\n<!-- PERFORMANCE_RESULTS_END -->"); | ||
|
||
const finalResults = fs | ||
.readFileSync(resultsFile, "utf-8") | ||
.replace(/(\r\n|\n|\r)/gm, "\\n"); | ||
|
||
const readmePath = "README.md"; | ||
let readmeContent = fs.readFileSync(readmePath, "utf-8"); | ||
const performanceResultsRegex = | ||
/<!-- PERFORMANCE_RESULTS_START -->[\s\S]*<!-- PERFORMANCE_RESULTS_END -->/; | ||
if (performanceResultsRegex.test(readmeContent)) { | ||
readmeContent = readmeContent.replace( | ||
performanceResultsRegex, | ||
finalResults | ||
); | ||
} else { | ||
readmeContent += `\n${finalResults}`; | ||
} | ||
fs.writeFileSync(readmePath, readmeContent); | ||
} | ||
|
||
resultFiles.forEach((file) => { | ||
fs.unlinkSync(file); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
f12abde
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ posts { id userId title user { id name email }}}
29,973.73
3.32
113.06x
1,841.30
54.34
6.95x
1,590.02
63.98
6.00x
1,578.06
63.00
5.95x
1,330.00
74.89
5.02x
764.34
129.81
2.88x
358.17
192.43
1.35x
265.12
370.11
1.00x
| 2 |
{ posts { title }}
||| [Tailcall] |
61,712.06
|1.61
|45.35x
||| [Caliban] |
9,324.07
|11.09
|6.85x
||| [async-graphql] |
9,314.54
|10.92
|6.84x
||| [Hasura] |
2,438.37
|40.98
|1.79x
||| [Gqlgen] |
2,163.08
|47.96
|1.59x
||| [Apollo GraphQL] |
1,711.26
|58.33
|1.26x
||| [Netflix DGS] |
1,590.88
|70.30
|1.17x
||| [GraphQL JIT] |
1,360.85
|73.38
|1.00x
|| 3 |
{ greet }
||| [Caliban] |
67,363.47
|1.13
|26.36x
||| [Tailcall] |
63,718.73
|1.58
|24.93x
||| [async-graphql] |
50,091.19
|2.03
|19.60x
||| [Gqlgen] |
47,550.68
|5.04
|18.61x
||| [Netflix DGS] |
8,119.35
|15.15
|3.18x
||| [Apollo GraphQL] |
7,857.50
|12.95
|3.07x
||| [GraphQL JIT] |
5,112.15
|19.52
|2.00x
||| [Hasura] |
2,555.76
|39.07
|1.00x
|