diff --git a/.gitignore b/.gitignore index cf5124ac1..56781d669 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ src/main/webapp/js/bundles # macOS .DS_Store + +results.txt + diff --git a/docs/examples/300k-nodes.jenkinsfile b/docs/examples/300k-nodes.jenkinsfile new file mode 100644 index 000000000..d965a355f --- /dev/null +++ b/docs/examples/300k-nodes.jenkinsfile @@ -0,0 +1,57 @@ +// Stress-test pipeline that produces roughly 300,000 FlowNodes across three +// bulk parallel phases, with short sleep windows between them so you can poll +// the REST API and watch latency as the graph grows. +// +// Give Jenkins at least 2 GB heap before running — each FlowNode retains a few +// KB, and GC thrash will muddy any measurement you take from it. +// +// Knobs (multiplicative): +// BRANCHES - parallel branches per phase (default 50) +// STAGES_PER_SECTION - sequential stages inside each branch per phase (default 20) +// STEPS_PER_STAGE - echo steps per stage (default 100) +// Defaults yield 3 * 50 * 20 * 100 = 300,000 echo FlowNodes, plus ~6,000 +// stage/parallel wrapping nodes. +// +// Runtime is dominated by CPS step-evaluation (a few ms per echo), so the +// defaults take 30–45 minutes on a typical controller. Drop STEPS_PER_STAGE +// for faster iteration while keeping the structural shape. + +BRANCHES = 50 +STAGES_PER_SECTION = 20 +STEPS_PER_STAGE = 100 + +def bulkSection(int branches, int stagesPerBranch, int stepsPerStage, String label) { + def work = [:] + for (int i = 0; i < branches; i++) { + def idx = i + work["${label}-b${idx}"] = { + for (int j = 0; j < stagesPerBranch; j++) { + stage("${label}-b${idx}-s${j}") { + for (int k = 0; k < stepsPerStage; k++) { + echo "." + } + } + } + } + } + parallel work +} + +node { + stage('Warmup') { + echo "BRANCHES=${BRANCHES} STAGES_PER_SECTION=${STAGES_PER_SECTION} STEPS_PER_STAGE=${STEPS_PER_STAGE}" + echo "Expected echo nodes: ~${3 * BRANCHES * STAGES_PER_SECTION * STEPS_PER_STAGE}" + } + + stage('Settle (tiny graph)') { sleep 30 } + stage('Bulk 1/3') { bulkSection(BRANCHES, STAGES_PER_SECTION, STEPS_PER_STAGE, 's1') } + + stage('Settle (~100k nodes)') { sleep 30 } + stage('Bulk 2/3') { bulkSection(BRANCHES, STAGES_PER_SECTION, STEPS_PER_STAGE, 's2') } + + stage('Settle (~200k nodes)') { sleep 30 } + stage('Bulk 3/3') { bulkSection(BRANCHES, STAGES_PER_SECTION, STEPS_PER_STAGE, 's3') } + + stage('Settle (~300k nodes, still building)') { sleep 30 } + stage('Settle (completed)') { sleep 60 } +} diff --git a/docs/examples/medium-pipeline.jenkinsfile b/docs/examples/medium-pipeline.jenkinsfile new file mode 100644 index 000000000..95cecbeb5 --- /dev/null +++ b/docs/examples/medium-pipeline.jenkinsfile @@ -0,0 +1,86 @@ +// A medium-sized pipeline exercising sequential stages, flat parallel, nested +// parallel, and wide branch counts. Useful when you want to see the Pipeline +// Overview render a non-trivial graph without waiting on a stress test. +// +// Shape: ~70 user-visible stages, a few hundred FlowNodes. Runs in a minute or +// two. Tune the sleep values and branch counts for bigger or smaller graphs. + +def runStage(String name, int sleepSeconds) { + stage(name) { + echo "enter: ${name}" + sleep sleepSeconds + writeFile file: "out/${name.replaceAll('[^A-Za-z0-9]', '_')}.txt", text: name + echo "exit: ${name}" + } +} + +node { + runStage('Checkout', 2) + runStage('Install dependencies', 3) + runStage('Lint', 2) + runStage('Format check', 2) + runStage('Static analysis', 2) + + stage('Build matrix') { + def matrix = [:] + ['linux', 'macos', 'windows', 'freebsd', 'illumos', 'aix'].each { os -> + matrix["Build ${os}"] = { + runStage("${os}: fetch", 1) + runStage("${os}: compile", 3) + runStage("${os}: unit tests", 2) + runStage("${os}: package", 1) + } + } + parallel matrix + } + + stage('Integration') { + parallel( + 'api': { + parallel( + 'api: smoke': { runStage('api smoke', 2) }, + 'api: contract': { runStage('api contract', 3) }, + 'api: regression': { runStage('api regression', 4) }, + ) + }, + 'browser': { + parallel( + 'browser: chrome': { runStage('chrome', 3) }, + 'browser: firefox': { runStage('firefox', 3) }, + 'browser: safari': { runStage('safari', 3) }, + 'browser: edge': { runStage('edge', 3) }, + ) + }, + 'perf': { + runStage('perf: baseline', 2) + runStage('perf: load', 4) + runStage('perf: stress', 5) + }, + 'security': { + runStage('dep audit', 2) + runStage('sast', 3) + runStage('dast', 4) + }, + ) + } + + stage('Publish artifacts') { + def publishers = [:] + // C-style loop: (1..8).each would push an IntRange through a parallel + // closure, which is not CPS-serialisable and fails at checkpoint. + for (int i = 1; i <= 8; i++) { + def idx = i + publishers["Publish region ${idx}"] = { + runStage("upload region ${idx}", 1) + runStage("verify region ${idx}", 1) + } + } + parallel publishers + } + + runStage('Release notes', 1) + runStage('Changelog', 2) + runStage('Tag', 2) + runStage('Announce', 1) + runStage('Cleanup', 1) +} diff --git a/docs/examples/perf-observer.sh b/docs/examples/perf-observer.sh new file mode 100755 index 000000000..9a824a0af --- /dev/null +++ b/docs/examples/perf-observer.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# perf-observer.sh — polls /tree and /allSteps for a running build and prints a +# latency summary at the end. Use it to compare two configurations (e.g. before +# vs. after a change) by running it once per build and diffing the two summaries. +# +# Usage: +# ./perf-observer.sh JENKINS_URL JOB_NAME BUILD_NUMBER [LABEL] +# +# Optional env vars: +# JENKINS_AUTH=user:token pass-through to curl --user +# POLL_INTERVAL=3 seconds between poll rounds (default 3) +# +# Example: +# +# ./perf-observer.sh http://localhost:8080/jenkins perf 1 baseline +# ./perf-observer.sh http://localhost:8080/jenkins perf 2 after-change +# +# Raw samples are written to /tmp/pgv-perf-