From 097bf87e418e0f4242a6d0a8f90c09104d02fadf Mon Sep 17 00:00:00 2001 From: Pierre-Yves B <PyvesDev@gmail.com> Date: Sun, 22 Mar 2020 20:45:53 +0100 Subject: [PATCH] Make it easier to benchmark and profile the code (#4780) * Make it easier to benchmark and profile the code * Remove unnecessary escape * Clarify that the backend server is started without the frontend * Add missing NODE_CONFIG_ENV environment variable * Add error message when user has not included console.time statements * Fix lint issue * Handle multiple console.time statements * Switch NODE_CONFIG_ENV to test * Switch to const as variable never re-assigned --- .gitignore | 3 ++ config/custom-environment-variables.yml | 3 -- config/default.yml | 3 -- core/base-service/base-static.js | 12 +---- core/server/server.js | 4 -- doc/flamegraph.png | Bin 0 -> 30746 bytes doc/performance-testing.md | 45 +++++++++++++++++ package.json | 2 + scripts/benchmark-performance.js | 26 ++++++++++ scripts/benchmark-performance.sh | 11 ----- scripts/capture-timings.js | 61 ++++++++++++++++++++++++ 11 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 doc/flamegraph.png create mode 100644 doc/performance-testing.md create mode 100644 scripts/benchmark-performance.js delete mode 100755 scripts/benchmark-performance.sh create mode 100644 scripts/capture-timings.js diff --git a/.gitignore b/.gitignore index fbed1f5ac3..e398cad1c8 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,6 @@ service-definitions.yml # Rendered API docs /api-docs/ + +# Flamebearer +flamegraph.html diff --git a/config/custom-environment-variables.yml b/config/custom-environment-variables.yml index 468c4bf5de..effec83c27 100644 --- a/config/custom-environment-variables.yml +++ b/config/custom-environment-variables.yml @@ -48,9 +48,6 @@ public: authorizedOrigins: 'TEAMCITY_ORIGINS' trace: 'TRACE_SERVICES' - profiling: - makeBadge: 'PROFILE_MAKE_BADGE' - cacheHeaders: defaultCacheLengthSeconds: 'BADGE_MAX_AGE_SECONDS' diff --git a/config/default.yml b/config/default.yml index 7adbce874e..405670561c 100644 --- a/config/default.yml +++ b/config/default.yml @@ -23,9 +23,6 @@ public: intervalSeconds: 200 trace: false - profiling: - makeBadge: false - cacheHeaders: defaultCacheLengthSeconds: 120 diff --git a/core/base-service/base-static.js b/core/base-service/base-static.js index ae1ab70229..c932b317bc 100644 --- a/core/base-service/base-static.js +++ b/core/base-service/base-static.js @@ -13,9 +13,6 @@ const { prepareRoute, namedParamsForMatch } = require('./route') module.exports = class BaseStaticService extends BaseService { static register({ camp, metricInstance }, serviceConfig) { - const { - profiling: { makeBadge: shouldProfileMakeBadge }, - } = serviceConfig const { regex, captureNames } = prepareRoute(this.route) const metricHelper = MetricHelper.create({ @@ -52,16 +49,9 @@ module.exports = class BaseStaticService extends BaseService { const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '') badgeData.format = format - if (shouldProfileMakeBadge) { - console.time('makeBadge total') - } - const svg = makeBadge(badgeData) - if (shouldProfileMakeBadge) { - console.timeEnd('makeBadge total') - } - setCacheHeadersForStaticResource(ask.res) + const svg = makeBadge(badgeData) makeSend(format, ask.res, end)(svg) metricHandle.noteResponseSent() diff --git a/core/server/server.js b/core/server/server.js index b4843e420b..3f4f5a4238 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -123,9 +123,6 @@ const publicConfigSchema = Joi.object({ teamcity: defaultService, trace: Joi.boolean().required(), }).required(), - profiling: { - makeBadge: Joi.boolean().required(), - }, cacheHeaders: { defaultCacheLengthSeconds: Joi.number() .integer() @@ -341,7 +338,6 @@ class Server { { handleInternalErrors: config.public.handleInternalErrors, cacheHeaders: config.public.cacheHeaders, - profiling: config.public.profiling, fetchLimitBytes: bytes(config.public.fetchLimit), rasterUrl: config.public.rasterUrl, private: config.private, diff --git a/doc/flamegraph.png b/doc/flamegraph.png new file mode 100644 index 0000000000000000000000000000000000000000..e7611aedb5057ff37991487a22804b65ed0b9ec5 GIT binary patch literal 30746 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 R*$fN}44$rjF6*2UngFZre`f#y literal 0 HcmV?d00001 diff --git a/doc/performance-testing.md b/doc/performance-testing.md new file mode 100644 index 0000000000..8e48fd0610 --- /dev/null +++ b/doc/performance-testing.md @@ -0,0 +1,45 @@ +# Performance testing + +Shields has some basic tooling available to help you get started with +performance testing. + +## Benchmarking the badge generation + +Want to micro-benchmark a section of the code responsible for generating the +static badges? Follow these two simple steps: + +1. Surround the code you want to time with `console.time` and `console.timeEnd` + statements. For example: + +``` +console.time('makeBadge') +const svg = makeBadge(badgeData) +console.timeEnd('makeBadge') +``` + +2. Run `npm run benchmark:badge` in your terminal. An average timing will + be displayed! + +If you want to change the number of iterations in the benchmark, you can modify +the values specified by the `benchmark:badge` script in _package.json_. If +you want to benchmark a specific code path not covered by the static badge, you +can modify the badge URL in _scripts/benchmark-performance.js_. + +## Profiling the full code + +Want to have an overview of how the entire application is performing? Simply +run `npm run profile:server` in your terminal. This will start the +backend server (i.e. without the frontend) in profiling mode and any requests +you make on `localhost:8080` will generate data in a file with a name +similar to _isolate-00000244AB6ED3B0-11920-v8.log_. + +You can then make use of this profiling data in various tools, for example +[flamebearer](https://github.com/mapbox/flamebearer): + +``` +npm install -g flamebearer +node --prof-process --preprocess -j isolate-00000244AB6ED3B0-11920-v8.log | flamebearer +``` + +An example output is the following: + diff --git a/package.json b/package.json index e1bb13324a..0b0b61b6eb 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,8 @@ "start:server:e2e-on-build": "node server 8080", "start:server": "cross-env NODE_CONFIG_ENV=development nodemon server 8080", "debug:server": "cross-env NODE_CONFIG_ENV=development nodemon --inspect server.js 8080", + "profile:server": "cross-env NODE_CONFIG_ENV=development node --prof server 8080", + "benchmark:badge": "cross-env NODE_CONFIG_ENV=test node scripts/benchmark-performance.js --iterations 10100 | node scripts/capture-timings.js --warmup-iterations 100", "prestart": "run-s --silent depcheck defs features", "start": "concurrently --names server,frontend \"npm run start:server\" \"cross-env GATSBY_BASE_URL=http://localhost:8080 gatsby develop --port 3000\"", "e2e": "start-server-and-test start http://localhost:3000 test:e2e", diff --git a/scripts/benchmark-performance.js b/scripts/benchmark-performance.js new file mode 100644 index 0000000000..7686138f7c --- /dev/null +++ b/scripts/benchmark-performance.js @@ -0,0 +1,26 @@ +'use strict' + +const config = require('config').util.toObject() +const got = require('got') +const minimist = require('minimist') +const Server = require('../core/server/server') + +async function main() { + const server = new Server(config) + await server.start() + const args = minimist(process.argv) + const iterations = parseInt(args.iterations) || 10000 + for (let i = 0; i < iterations; ++i) { + await got(`${server.baseUrl}badge/coverage-${i}-green.svg`) + } + await server.stop() +} + +;(async () => { + try { + await main() + } catch (e) { + console.error(e) + process.exit(1) + } +})() diff --git a/scripts/benchmark-performance.sh b/scripts/benchmark-performance.sh deleted file mode 100755 index ea77308c62..0000000000 --- a/scripts/benchmark-performance.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -PROFILE_MAKE_BADGE=1 node server 1111 >perftest.log & -sleep 2 -for ((i=0;i<10000;i++)); do - curl -s http://localhost:1111/badge/coverage-"$i"%-green.svg >/dev/null -done -kill $(jobs -p) -<perftest.log grep 'makeBadge total' | \ - grep -Eo '[0-9\.]+' | \ - awk '{s+=$1;n++} END {print s/n}' diff --git a/scripts/capture-timings.js b/scripts/capture-timings.js new file mode 100644 index 0000000000..fee8bf24a2 --- /dev/null +++ b/scripts/capture-timings.js @@ -0,0 +1,61 @@ +'use strict' + +const readline = require('readline') +const minimist = require('minimist') + +async function captureTimings(warmupIterations) { + const rl = readline.createInterface({ + input: process.stdin, + }) + + const times = {} + let timingsCount = 0 + let labelsCount = 0 + const timing = /^(.+): ([0-9.]+)ms$/i + + for await (const line of rl) { + const match = timing.exec(line) + if (match) { + labelsCount = Object.keys(times).length + if (timingsCount > warmupIterations * labelsCount) { + const label = match[1] + const time = parseFloat(match[2]) + times[label] = time + (times[label] || 0) + } + ++timingsCount + } + } + return { times, iterations: timingsCount / labelsCount } +} + +function logResults({ times, iterations, warmupIterations }) { + if (isNaN(iterations)) { + console.log( + `No timings captured. Have you included console.time statements in the badge creation code path?` + ) + } else { + const timedIterations = iterations - warmupIterations + for (const [label, time] of Object.entries(times)) { + const averageTime = time / timedIterations + console.log( + `Average '${label}' time over ${timedIterations} iterations: ${averageTime}ms` + ) + } + } +} + +async function main() { + const args = minimist(process.argv) + const warmupIterations = parseInt(args['warmup-iterations']) || 100 + const { times, iterations } = await captureTimings(warmupIterations) + logResults({ times, iterations, warmupIterations }) +} + +;(async () => { + try { + await main() + } catch (e) { + console.error(e) + process.exit(1) + } +})() -- GitLab