Chris Parmer — home

Latency percentile bands

Azure Functions 2021 invocation trace (real data, no fallback)

Example from the compendium of canonical charts

Latency percentile bands — Azure Functions 2021 invocation trace (real data, no fallback)

Python Code

"""Latency percentile bands — Azure Functions 2021 invocation trace (real data, no fallback)."""


import pandas as pd
import plotly.graph_objects as go
import requests, warnings, subprocess, tempfile, os


# Azure Functions "two weeks of January 2021" per-invocation trace.
# Columns: app, func, end_timestamp (s), duration (s). ~2M rows, RAR5-compressed.
RAR_URL = "https://raw.githubusercontent.com/Azure/AzurePublicDataset/master/data/AzureFunctionsInvocationTraceForTwoWeeksJan2021.rar"


def load_trace():
    """Download + extract the RAR5 trace with `unar`, return the invocation DataFrame."""
    warnings.filterwarnings("ignore")
    requests.packages.urllib3.disable_warnings()

    if not subprocess.run(["which", "unar"], capture_output=True).stdout.strip():
        raise RuntimeError("`unar` not found — install it (brew install unar) to extract the RAR5 trace")

    with tempfile.TemporaryDirectory() as tmpdir:
        rar_path = os.path.join(tmpdir, "trace.rar")
        print("  downloading Azure Functions 2021 trace …")
        r = requests.get(RAR_URL, verify=False, timeout=180, stream=True)
        r.raise_for_status()
        with open(rar_path, "wb") as f:
            for chunk in r.iter_content(1 << 20):
                f.write(chunk)
        print(f"  downloaded {os.path.getsize(rar_path)/1e6:.1f}MB, extracting …")

        result = subprocess.run(["unar", "-o", tmpdir, rar_path], capture_output=True, timeout=180)
        if result.returncode != 0:
            raise RuntimeError(f"unar failed: {result.stderr.decode()[:200]}")

        # The archive holds a single comma-delimited .txt (not .csv).
        data_files = [f for f in os.listdir(tmpdir) if f.endswith((".txt", ".csv"))]
        if not data_files:
            raise RuntimeError(f"no data file in archive: {os.listdir(tmpdir)}")
        return pd.read_csv(os.path.join(tmpdir, data_files[0]))


def generate():
    print("building latency percentile bands …")
    df = load_trace()

    # Bucket invocations into hours of the two-week trace, take per-hour percentiles.
    df["hour"] = (df["end_timestamp"] // 3600).astype(int)
    bands = df.groupby("hour")["duration"].quantile([0.5, 0.9, 0.99]).unstack()
    bands.columns = ["p50", "p90", "p99"]
    bands = bands.reset_index()
    x = bands["hour"]

    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=x, y=bands["p99"], mode="lines", name="p99",
        line=dict(color=PINK, width=1.5),
        hovertemplate="Hour %{x}<br>p99 = %{y:.3f}s<extra></extra>",
    ))
    fig.add_trace(go.Scatter(
        x=x, y=bands["p90"], mode="lines", name="p90",
        line=dict(color=TEAL, width=1.5),
        fill="tonexty", fillcolor="rgba(82,179,208,0.15)",
        hovertemplate="Hour %{x}<br>p90 = %{y:.3f}s<extra></extra>",
    ))
    fig.add_trace(go.Scatter(
        x=x, y=bands["p50"], mode="lines", name="p50",
        line=dict(color=VIOLET, width=2),
        fill="tonexty", fillcolor="rgba(132,94,238,0.15)",
        hovertemplate="Hour %{x}<br>p50 = %{y:.3f}s<extra></extra>",
    ))

    fig.update_layout(
        title=dict(text="Serverless Latency Percentile Bands — Azure Functions 2021", x=0.5),
        xaxis=dict(title="Hour of trace"),
        yaxis=dict(title="Duration (s)", type="log"),
        legend=dict(orientation="h", y=1.08),
        margin=dict(t=60, b=50, l=70, r=40),
        height=460,
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly