# How the Jenkins Build Timeline Works: BuildTimelineWidget and MultiStageTimeSeries Explained

> Understand the Jenkins build timeline. Learn how BuildTimelineWidget and MultiStageTimeSeries visualize build events and metrics for trend analysis.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: internals
- Published: 2026-07-28

---

**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`](https://github.com/jenkinsci/jenkins/blob/main/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:

```java
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`](https://github.com/jenkinsci/jenkins/blob/main/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`](https://github.com/jenkinsci/jenkins/blob/main/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`](https://github.com/jenkinsci/jenkins/blob/main/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:

```java
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:

```text
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`](https://github.com/jenkinsci/jenkins/blob/main/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:**

```java
// 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:**

```java
@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:**

```xml
<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

- `BuildTimelineWidget` in [`core/src/main/java/hudson/model/BuildTimelineWidget.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/BuildTimelineWidget.java) is a deprecated wrapper that streams build event JSON; it is retained only for backward compatibility.
- `MultiStageTimeSeries` in [`core/src/main/java/hudson/model/MultiStageTimeSeries.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/MultiStageTimeSeries.java) aggregates three `TimeSeries` instances 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 in [`core/src/main/java/hudson/model/TimeSeries.java`](https://github.com/jenkinsci/jenkins/blob/main/core/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 `TrendChart` PNG 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`](https://github.com/jenkinsci/jenkins/blob/main/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.