How the Jenkins Build Timeline Works: BuildTimelineWidget and MultiStageTimeSeries Explained
Jenkins visualizes build events and system metrics on a timeline by pairing the deprecated BuildTimelineWidget—which streams JSON event data—with MultiStageTimeSeries, a tiered store that keeps 10‑second, 1‑minute, and 1‑hour resolution histories for long‑term trend charts.
The Jenkins build timeline implementation lives in the jenkinsci/jenkins core repository and powers both the historical build list and the performance graphs shown on controller and agent pages. By combining a thin UI widget wrapper with a multi‑resolution numerical backend, Jenkins can render recent build activity with high precision while retaining months of coarse‑grained system metrics. The following sections break down the source‑level mechanics of both components and how they exchange data.
BuildTimelineWidget and Event Streaming
The hudson.model.BuildTimelineWidget class, located in core/src/main/java/hudson/model/BuildTimelineWidget.java, acts as a server‑side adapter for the client‑side timeline. It is constructed with a RunList<?> representing the latest builds:
new BuildTimelineWidget(runList);
When the browser requests the timeline data endpoint, the widget’s doData method builds and streams a JSONObject back to the client. As implemented in jenkinsci/jenkins, this JSON payload historically contained an "events" array that fed the SIMILE timeline control. However, since Jenkins 2.431 the widget has been deprecated and is no longer rendered in the default UI; it remains in the codebase solely for backward compatibility while the newer Build History view and Pipeline stage graphs have taken its place. A related low‑level scheduling structure, Timeline, is defined in core/src/main/java/hudson/model/queue/Timeline.java and was used internally by the widget.
MultiStageTimeSeries and Tiered Storage
Numerical data for the Jenkins build timeline—such as executor load, queue length, or node health—is managed by hudson.model.MultiStageTimeSeries in core/src/main/java/hudson/model/MultiStageTimeSeries.java. Rather than storing every sample at a single resolution, the class maintains three independent TimeSeries instances, each defined in core/src/main/java/hudson/model/TimeSeries.java:
sec10– Captures rapid changes and retains roughly six hours of data.min– Stores one sample every minute, retained for about two days.hour– Stores one sample every hour, retained for up to eight weeks.
This tiered design, documented in the class Javadoc, achieves three goals simultaneously: long‑term retention, a low memory footprint, and high accuracy for the most recent data.
Update Mechanics and Decay Filtering
A background thread calls update(float f) every ten seconds. Inside that method, a modulo counter cycles from 0 through 359, which maps to one hour of 10‑second ticks:
public void update(float f) {
counter = (counter + 1) % 360; // 360 ticks = 1 hour
sec10.update(f); // always update the 10-sec series
if (counter % 6 == 0) min.update(f); // every minute
if (counter == 0) hour.update(f); // every hour
}
Each underlying TimeSeries applies an exponential moving average (EMA) to smooth noise. When TimeSeries.update(float newData) is invoked, the stored value becomes:
data = previous * decay + newData * (1 - decay)
The history array is then shifted so the newest point sits at index 0, preserving a fixed‑size circular buffer whose capacity depends on the series resolution. The EMA behavior is verified by unit tests in core/src/test/java/hudson/model/TimeSeriesTest.java.
Rendering Trend Charts from TimeSeries Data
The nested MultiStageTimeSeries.TrendChart class generates a JFreeChart line chart from the selected series. When the UI requests a graph, the pick(TimeScale) method selects the appropriate resolution—SEC10, MIN, or HOUR—and applies a matching DateFormat to the X‑axis labels (for example, HH:mm:ss for the 10‑second scale). The resulting PNG is served by the doGraph endpoint, which is exposed automatically through Stapler.
How BuildTimelineWidget and MultiStageTimeSeries Interact
Historically, BuildTimelineWidget embedded the output of one or more MultiStageTimeSeries objects directly into its JSON payload. This allowed the SIMILE timeline to overlay discrete build events and continuous metric series on a single axis. With the widget’s deprecation, the Jenkins build timeline UI now consumes the data model through alternative paths: the REST API exposed by the Api object on each series, or the newer analytics charts. Nevertheless, the underlying storage mechanism in MultiStageTimeSeries and the EMA logic in TimeSeries remain unchanged.
Practical Code Examples
The following patterns show how core and plugin code instantiate and expose timeline data.
Creating a MultiStageTimeSeries for executor load:
// In a Jenkins extension (e.g., ComputerListener)
private final MultiStageTimeSeries executorLoad =
new MultiStageTimeSeries(
Messages._MultiStageTimeSeries_ExecutorLoad(), // title (localizable)
Color.GREEN, // chart colour
0f, // initial value
0.9f); // decay (90% weight to history)
@Override
public void onCompleted(Computer c, TaskListener listener) {
float currentLoad = c.getExecutors().size(); // simplistic example
executorLoad.update(currentLoad);
}
Exposing the series via a Stapler URL:
@Extension
public class ExecutorLoadAction implements Action {
public MultiStageTimeSeries getExecutorLoad() { return executorLoad; }
// URL: /computer/…/executorLoad/api/json
public Api getApi() { return executorLoad.getApi(); }
}
Rendering a trend chart in a Jelly view:
<jelly:stapler>
<jelly:invokeMethod name="doGraph" class="${it.executorLoad}" oncomplete="..."/>
</jelly:stapler>
The doGraph method is provided by MultiStageTimeSeries.TrendChart and returns a PNG image of the line chart.
Summary
BuildTimelineWidgetincore/src/main/java/hudson/model/BuildTimelineWidget.javais a deprecated wrapper that streams build event JSON; it is retained only for backward compatibility.MultiStageTimeSeriesincore/src/main/java/hudson/model/MultiStageTimeSeries.javaaggregates threeTimeSeriesinstances at 10‑second, 1‑minute, and 1‑hour resolutions.- A modulo counter inside
update(float)drives the tiered update logic, promoting samples from fine to coarse series at one‑minute and one‑hour boundaries. - Each
TimeSeries, defined incore/src/main/java/hudson/model/TimeSeries.java, stores an exponential moving average to smooth short‑term fluctuations while preserving trend. - Modern Jenkins visualizations consume the same underlying data model through REST APIs and
TrendChartPNG endpoints rather than the old widget JSON feed.
Frequently Asked Questions
What replaced BuildTimelineWidget in modern Jenkins versions?
Starting with Jenkins 2.431, the default UI dropped the SIMILE timeline in favor of the standard Build History view on job pages and Pipeline stage graphs generated by Pipeline plugins. The BuildTimelineWidget class still exists, but its doData method no longer drives the primary interface.
How does MultiStageTimeSeries prevent memory bloat when storing weeks of data?
Instead of keeping every 10‑second sample for eight weeks, MultiStageTimeSeries stores only the most recent six hours at 10‑second resolution, two days at one‑minute resolution, and eight weeks at one‑hour resolution. The fixed‑size circular buffers inside TimeSeries ensure memory usage is bounded regardless of uptime.
Why does TimeSeries use an exponential moving average instead of raw values?
The update(float) method in core/src/main/java/hudson/model/TimeSeries.java computes data = previous * decay + newData * (1 - decay). This EMA filter smooths transient spikes—such as a brief spike in executor count—while still reflecting the overall trend, which makes the resulting charts easier to read.
How can a plugin expose its own time series on the Jenkins UI?
Instantiate MultiStageTimeSeries with a localized title, color, and decay factor, then call update(float) periodically from a background thread or listener. Expose the instance through a getter on a Stapler‑bound object, and the built‑in Api and TrendChart classes will automatically provide /api/json and /doGraph endpoints.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →