Skip to content

Commit

Permalink
Port analyze.sh to analyze.js
Browse files Browse the repository at this point in the history
- 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
daveads committed Jul 18, 2024
1 parent 7f43d85 commit f12abde
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 167 deletions.
198 changes: 198 additions & 0 deletions analyze.js
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);
});
164 changes: 0 additions & 164 deletions analyze.sh

This file was deleted.

6 changes: 3 additions & 3 deletions run_benchmarks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,6 @@ for service in "apollo_server" "caliban" "netflix_dgs" "gqlgen" "tailcall" "asyn
fi
done

bash analyze.sh "${bench1Results[@]}"
bash analyze.sh "${bench2Results[@]}"
bash analyze.sh "${bench3Results[@]}"
node analyze.js "${bench1Results[@]}"
node analyze.js "${bench2Results[@]}"
node analyze.js "${bench3Results[@]}"

1 comment on commit f12abde

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Server Requests/sec Latency (ms) Relative
1 { posts { id userId title user { id name email }}}
[Tailcall] 29,973.73 3.32 113.06x
[async-graphql] 1,841.30 54.34 6.95x
[Hasura] 1,590.02 63.98 6.00x
[Caliban] 1,578.06 63.00 5.95x
[GraphQL JIT] 1,330.00 74.89 5.02x
[Gqlgen] 764.34 129.81 2.88x
[Netflix DGS] 358.17 192.43 1.35x
[Apollo GraphQL] 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 |

Please sign in to comment.