Feat/add CFS scheduler - #1794
Conversation
Add timing hooks for scheduler wait time, serialization read/write duration, and end-to-end compilation wall-clock. Instrument CpsThread with per-thread execution counters (totalChunksRun, totalNanosExecuting, becameRunnableAt) and CpsThreadGroup with per-pass scheduler metrics. Extend the TimingKind enum with four new categories: - serializationRead — River deserialization time in loadProgramAsync - serializationWrite — River writeObject time in saveProgram - compilationTotal — parseScript end-to-end duration - schedulerWait — thread wait time in the run() loop All new counters are exported via the existing support-bundle framework and visible in thread dump output. Zero behavioral change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
Replace the round-robin "run all threads to exhaustion" loop in CpsThreadGroup.run() with a Completely Fair Scheduler that picks the thread with the lowest vruntime each pass. Threads accumulate vruntime proportional to their execution time divided by weight. Key changes: - Add vruntime (long) and weight (int=1024) fields to CpsThread - Update vruntime after each chunk: vruntime += (elapsed*1024)/max(1,weight) - New threads start at min_vruntime for prompt scheduling - Guards against deserialized weight=0 from old program.dat - Rewrite run() to select one thread via priority queue (vruntime min) - Track scheduler-wait time, reset becameRunnableAt to prevent double-count - Dynamic quantum: baseQuantum / max(1, activeBuilds/loadFactor), floor 5ms - Configurable via system properties: org.jenkinsci.plugins.workflow.cps.CFS.baseQuantumMs (default 50ms) org.jenkinsci.plugins.workflow.cps.CFS.loadFactor (default 10) All error handling, completion, and cleanup logic preserved verbatim. Test assertions updated for order-independent FlowNode IDs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
Document the Completely Fair Scheduler algorithm for CPS thread execution, including vruntime tracking, scheduling weight, dynamic quantum computation, configuration via system properties, comparison to the original scheduler, and future enhancement directions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
Add explicit explanation that the quantum is checked after each CPS chunk completes, never mid-execution. Document that 50ms default generates zero additional overhead since nearly all chunks suspend naturally in microseconds. Add Performance Considerations section covering overhead and serialization impact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
67862e7 to
e040b59
Compare
Add hierarchical two-level vruntime tracking modeled after Linux CFS group
scheduling to ensure fair CPU distribution across multiple pipeline builds
sharing the same master thread pool.
Three mechanisms work together:
1. Flow vruntime tracking (CpsFlowExecution):
- Each pipeline build accumulates flowVruntime, representing total CPU
consumption of all its threads.
- flowWeight (default 1024, NICE_0_LOAD) scales accumulation rate.
- getGlobalMinFlowVruntime() scans active builds for the minimum.
2. Thread initialization floor (CpsThread):
- New threads inherit max(groupMinVruntime, flowVruntime) instead of
just groupMinVruntime, preventing CPU-heavy pipelines from getting
unfairly low vruntime on new threads.
3. Flow-level quantum adjustment (CpsThreadGroup):
- computeQuantumNs() now scans FlowExecutionList for global minimum
flowVruntime. Flows exceeding the minimum get proportionally reduced
quantum, bounded at 25% of base.
- Controlled by new system property:
org.jenkinsci.plugins.workflow.cps.CFS.flowFairnessFactor
(int, range 0-100, default 50). 0 disables quantum adjustment
while keeping vruntime tracking active.
Observability: scheduler pass logs and CpsThread.toString() now include
flow vruntime and weight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
9 tests covering: - flowVruntime advances when threads execute - flowVruntime increases with more work - flow weight defaults and scaling behavior - thread toString includes flow vruntime/weight - getGlobalMinFlowVruntime correctness - concurrent builds have independent flow vruntime tracking - quantum adjustment with non-zero flowFairnessFactor - quantum adjustment disabled with flowFairnessFactor=0 - heavy vs light pipeline vruntime accumulation comparison Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
Move pipeline-level fairness from 'Future Enhancements' to a fully documented feature. Add sections covering: - Flow vruntime accumulation mechanism - New thread initialization with flow floor - Flow-level quantum adjustment formula - Flow weight configuration - flowFairnessFactor system property reference - Updated observability section with flowVr in scheduler logs and CpsThread.toString() output Promote cross-pipeline fairness to implemented; remaining future enhancements are thread weight boosting, FlowHead affinity, and CFS time slice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
d0964e6 to
9c1358d
Compare
|
@jglick can someone take a look? I'm testing it but I don't know is somenthing we want to explore or have |
|
If you are referring to
suggests poorly written Pipeline script; Groovy code should be completing on the order of milliseconds. |
Yes, I have started from a TODO, so what you need are metrics that are showing some improvements at different loads. Right now, I'm using in my infrastructure but I can prepare some metrics |
Add CFS (Completely Fair Scheduler) to CPS VM thread execution
Motivation
The CPS VM's
CpsThreadGroup.run()currently executes all runnable threads in insertion (ID) order to exhaustion in a single pass. A standing// TODOat that method acknowledges the problem: a tight Groovy loop that rarely suspends can monopolize the CPS VM thread, delaying all other pipeline branches in the same build. Later-created threads (higher IDs) always execute after earlier ones — there is no fairness guarantee.Changes
Two commits that build on each other:
1. Performance Instrumentation (
CpsFlowExecution)Adds measurement hooks so every subsequent optimization can be benchmarked:
TimingKindvalues:serializationRead,serializationWrite,compilationTotal,schedulerWait— recorded during normal pipeline execution and visible in thread dumps and support bundles.CpsThread:totalChunksRun,totalNanosExecuting,becameRunnableAt— foundation for vruntime tracking.CpsThreadGroup: thread counts, runnable counts, pass duration, queue depth — logged atFINElevel.Zero behavioral change. All new fields are either transient (diagnostic) or primitive
long(transparent to River serialization).2. CFS Scheduler (
CpsThreadGroup.run())Replaces the "run all runnable threads" loop with a fairness algorithm adapted from Linux's Completely Fair Scheduler:
CpsThreadaccumulates virtual runtime (vruntime) proportional to its wall-clock execution time divided by weight:min_vruntimeso they get prompt attention rather than being starved.The quantum is cooperative — checked after each CPS chunk completes, never during execution. Nearly all chunks suspend naturally at step boundaries (
sleep,sh,semaphore) in microseconds, so the 50ms default generates zero additional overhead. The real fairness comes from vruntime ordering: CPU-heavy threads accumulate higher vruntime and naturally lose priority on subsequent passes.Configuration
org.jenkinsci.plugins.workflow.cps.CFS.baseQuantumMslong(ms)org.jenkinsci.plugins.workflow.cps.CFS.loadFactorintExample JVM arguments:
Files Changed
How It Compares to Before
Backward Compatibility
The new
vruntime(long) andweight(int) fields onCpsThreadare Java primitives, handled automatically by the existing River serialization. Oldprogram.datfiles deserialize withvruntime=0andweight=0; theresume()method correctsweight=0to the default1024on first use. No migration is needed.Testing
All 79 CPS tests pass:
Serialization round-trips are verified by the restart tests (
RestartingLoadStepTest,ReplayActionTest).Documentation
Complete documentation in
doc/cfs-scheduler.mdcovering:schedulerWaittiming)Deployment in our infrastructure for more testing
Submitter checklist