Scheduling Performance Metrics

Imagine a data center that must decide, ten thousand times a second, which of many ready processes gets the CPU next. Every such decision is invisible to the user. Yet its cumulative effect determines whether a web server feels snappy or sluggish. It also decides whether a batch job finishes before the nightly deadline. And it determines whether a shared machine feels fair to every tenant. Without a precise vocabulary for "good scheduling," engineers would be arguing about vibes instead of numbers. This article builds that vocabulary. It defines the metrics that every scheduling algorithm is judged against. These include throughput, turnaround time, waiting time, response time, CPU utilization, and fairness. It shows exactly how to compute each one from a timeline of process arrivals and CPU bursts. These are not abstract definitions. They are the scoreboard used to compare First-Come First-Served Scheduling, Shortest-Job-First Scheduling, Round-Robin Scheduling, and every scheduler built after them. The big picture is that scheduling is a multi-objective optimization problem. Different metrics pull in different directions. Minimizing average waiting time often means starving long jobs. Maximizing throughput can hurt response time. Maximizing fairness can hurt everything else simultaneously. A scheduler is really a policy choice about which tradeoff a system will make. That choice cannot be made intelligently without first measuring the tradeoff. By the end of this article you will be able to compute each metric by hand from a Gantt chart. You will be able to explain why no scheduler can maximize all metrics at once. You will also recognize which metric a given real-world requirement actually cares about. This includes interactive UI, batch throughput, and real-time deadline needs. This groundwork is the yardstick used in every later comparison of scheduling algorithms.

Key takeaways
  • Six core metrics: CPU utilization, throughput, turnaround time, waiting time, response time, and fairness.
  • Turnaround time = completion time − arrival time; it includes waiting and execution.
  • Waiting time = turnaround time − burst time; it isolates time spent doing nothing useful.
  • Response time = time of first CPU access − arrival time; it matters most for interactive systems.
  • These metrics conflict: minimizing average waiting time (SJF-style) can starve long jobs and hurt fairness.
  • All metrics are computed directly from a schedule's Gantt chart using simple arithmetic, no simulation needed.
The core insight

A scheduler cannot be judged by one number — throughput, turnaround, waiting, response time, and fairness each measure a different, often conflicting, notion of "good," and every scheduling policy is a tradeoff among them.

The idea

A CPU scheduler decides, among ready processes, which one runs next and for how long. To compare two scheduling policies objectively, we need quantities computable from the resulting execution timeline — the sequence of intervals in which each process holds the CPU, commonly drawn as a Gantt chart. Six metrics dominate the literature and practice.

CPU utilization is the fraction of wall-clock time the CPU is doing useful work rather than sitting idle. Throughput is the number of processes completed per unit time. Turnaround time for a process is the total time from its arrival in the system to its completion — it bundles waiting, I/O, and execution together. Waiting time isolates just the portion of turnaround spent in the ready queue, not running and not blocked on I/O. Response time measures how long an interactive user waits before seeing the first sign of progress, i.e., time until the process is first scheduled, not until it finishes. Fairness measures how evenly the CPU is shared among competing processes, often quantified by the variance or spread of waiting times, or by formal notions like max-min fairness. These six metrics are not independent choices a system architect can freely maximize together. They form genuine tradeoffs: a scheduler minimizing mean waiting time (like Shortest-Job-First Scheduling) systematically favors short jobs, which increases the variance of waiting times among long jobs and thus reduces fairness. A scheduler maximizing fairness (equal CPU shares, as in Round-Robin Scheduling) sacrifices some throughput to context-switch overhead. Understanding precisely how each metric is computed is the prerequisite for understanding why CPU Scheduling Fundamentals frames scheduling as a policy decision rather than a single "correct" algorithm.

How it works

The mechanism here is not an algorithm that produces a schedule — that is the job of FCFS, SJF, Round-Robin, etc. Rather, it is the algorithm for measuring any given schedule once it exists. Given a schedule (a Gantt chart) plus each process's arrival time and total CPU burst, we compute every metric in one linear pass.

Definitions, precisely, for process PiP_i:

Turnaroundi=Completioni−Arrivali\text{Turnaround}_i = \text{Completion}_i - \text{Arrival}_i
Waitingi=Turnaroundi−Bursti\text{Waiting}_i = \text{Turnaround}_i - \text{Burst}_i
Responsei=FirstRuni−Arrivali\text{Response}_i = \text{FirstRun}_i - \text{Arrival}_i

where FirstRuni\text{FirstRun}_i is the timestamp of the first CPU slice ever given to PiP_i (relevant when preemption lets a process run in several disjoint slices).

System-wide aggregates:

Throughput=nmakespan,Utilization=total busy timemakespan\text{Throughput} = \frac{n}{\text{makespan}}, \qquad \text{Utilization} = \frac{\text{total busy time}}{\text{makespan}}

where nn is the number of completed processes and makespan is the total elapsed wall-clock time from the first arrival to the last completion.

Fairness (one common quantitative form, Jain's fairness index) applied to waiting times w1,…,wnw_1, \dots, w_n:

J(w)=(∑iwi)2n∑iwi2,1n≤J(w)≤1J(w) = \frac{\left(\sum_i w_i\right)^2}{n \sum_i w_i^2}, \qquad \frac{1}{n} \le J(w) \le 1

J=1J = 1 means every process waited exactly the same amount; J→1/nJ \to 1/n means one process absorbed almost all the waiting.

Step-by-step algorithm

``` ALGORITHM ComputeSchedulingMetrics INPUT: processes[1..n] // each with arrival_time, burst_time schedule // list of (pid, start_time, end_time) slices, // in chronological order, possibly several // slices per pid if preemptive OUTPUT: utilization, throughput, avg_turnaround, avg_waiting, avg_response, fairness // Pass 1: scan the schedule once to find, per process, // first_run time and completion (last end) time, and total busy time. first_run[1..n] <- +infinity completion[1..n] <- -infinity total_busy <- 0 earliest_arrival <- min(arrival_time[i] for all i) last_completion <- -infinity for each slice (pid, start, end) in schedule: first_run[pid] <- min(first_run[pid], start) completion[pid] <- max(completion[pid], end) total_busy <- total_busy + (end - start) last_completion <- max(last_completion, end) // Pass 2: derive per-process metrics sum_turnaround <- 0 sum_waiting <- 0 sum_response <- 0 for i in 1..n: turnaround[i] <- completion[i] - arrival_time[i] waiting[i] <- turnaround[i] - burst_time[i] response[i] <- first_run[i] - arrival_time[i] sum_turnaround <- sum_turnaround + turnaround[i] sum_waiting <- sum_waiting + waiting[i] sum_response <- sum_response + response[i] makespan <- last_completion - earliest_arrival avg_turnaround <- sum_turnaround / n avg_waiting <- sum_waiting / n avg_response <- sum_response / n throughput <- n / makespan utilization <- total_busy / makespan // Fairness: Jain's index over the waiting-time vector sum_w <- sum_waiting sum_w2 <- sum(waiting[i]^2 for i in 1..n) fairness <- (sum_w sum_w) / (n sum_w2) // guard sum_w2 = 0 -> fairness = 1 return (utilization, throughput, avg_turnaround, avg_waiting, avg_response, fairness) ``` Two passes over a schedule of mm slices and nn processes: the first pass is O(m)O(m), aggregating per-process extremes and busy time; the second is O(n)O(n), deriving the per-process metrics and sums. This is exactly why the metrics are cheap to instrument in a real kernel or simulator — they require only counters updated at each context switch, not any re-derivation of the schedule itself.

Figure
Gantt chart and the three time intervals it encodes for one process
Gantt chart: P1 arrives at t=0, runs in two slices (preempted) time 0 3 6 9 12 15 wait P1 runs wait P2 runs P1 runs response time = 3 turnaround time = 12 (completion 12 − arrival 0) waiting = turnaround − burst = 12 − 6 = 6

Complexity & correctness

Time complexity. Given a schedule already expressed as mm context-switch slices over nn processes, computing all six metrics takes O(m+n)O(m + n) time: one linear scan of the slice list to find each process's first-run time, completion time, and busy total, followed by one linear scan over the nn processes to derive turnaround, waiting, and response, plus O(n)O(n) work for the fairness index. There is no hidden superlinear step — every quantity is a running min, max, or sum. Space complexity is O(n)O(n) for the per-process accumulator arrays; the slice list itself can be streamed rather than stored, so working memory need not grow with the length of the timeline.

Correctness argument. The definitions are not heuristics; they are direct bookkeeping identities that follow from what "waiting" and "turnaround" mean. Fix a process PiP_i with arrival time aia_i and total CPU demand (burst) bib_i. Over its lifetime it is always in exactly one of three states: waiting in the ready queue, running on the CPU, or blocked on I/O (ignored here since classic burst models assume CPU-only accounting between arrival and completion, or I/O is folded into separate bursts). Hence the time from arrival to completion decomposes as

Turnaroundi=time spent waiting⏟Waitingi+time spent running⏟bi\text{Turnaround}_i = \underbrace{\text{time spent waiting}}_{\text{Waiting}_i} + \underbrace{\text{time spent running}}_{b_i}

which is the definition Waitingi=Turnaroundi−bi\text{Waiting}_i = \text{Turnaround}_i - b_i — it is a conservation law over disjoint states, so the algorithm's Pass 2 formula is exact by construction, not an approximation. Similarly, response time is exact because FirstRuni\text{FirstRun}_i is tracked as the minimum start time over all of PiP_i's slices in Pass 1, and by definition no CPU time can be delivered to PiP_i before that timestamp. Throughput and utilization are ratios over the makespan, itself computed as max⁡icompletioni−min⁡iarrivali\max_i \text{completion}_i - \min_i \text{arrival}_i; both are well-defined as long as at least one process completes, and the busy-time sum in the numerator of utilization can never exceed the makespan because slices are non-overlapping on a single CPU, guaranteeing 0≤Utilization≤10 \le \text{Utilization} \le 1 as a sanity invariant. Jain's fairness index is bounded in [1/n,1][1/n, 1] by the Cauchy–Schwarz inequality applied to the vector of waiting times: (∑wi)2≤n∑wi2(\sum w_i)^2 \le n \sum w_i^2 gives the upper bound J≤1J \le 1 (equality iff all wiw_i equal), and the lower bound follows because a single dominant outlier maximizes ∑wi2\sum w_i^2 relative to (∑wi)2(\sum w_i)^2.

Honest tradeoffs. These metrics are descriptive, not prescriptive — computing them tells you how a given schedule performed, but optimizing one metric in isolation is a distinct, harder combinatorial problem (e.g., minimizing average waiting time non-preemptively is solved optimally by SJF, but minimizing maximum waiting time, or achieving fairness, requires different objectives entirely, and no single-queue scheduler can simultaneously minimize mean waiting time, maximize fairness, and maximize utilization under bursty arrivals — these are provably competing objectives, as later algorithms illustrate).

Worked example

Consider four processes arriving over time, non-preemptively scheduled in arrival order (FCFS), to make the arithmetic concrete and traceable.

ProcessArrivalBurst
P105
P213
P328
P432

FCFS runs each process to completion in arrival order without preemption. The resulting Gantt chart:

SliceStartEnd
P105
P258
P3816
P41618

Now apply the algorithm's formulas directly.

ProcessCompletionTurnaround = Compl − ArrWaiting = TAT − BurstResponse = FirstRun − Arr
P155 − 0 = 55 − 5 = 00 − 0 = 0
P288 − 1 = 77 − 3 = 45 − 1 = 4
P31616 − 2 = 1414 − 8 = 68 − 2 = 6
P41818 − 3 = 1515 − 2 = 1316 − 3 = 13

Averages:

avg turnaround=5+7+14+154=414=10.25\text{avg turnaround} = \frac{5+7+14+15}{4} = \frac{41}{4} = 10.25
avg waiting=0+4+6+134=234=5.75\text{avg waiting} = \frac{0+4+6+13}{4} = \frac{23}{4} = 5.75
avg response=0+4+6+134=5.75\text{avg response} = \frac{0+4+6+13}{4} = 5.75

(Response equals waiting here only because FCFS is non-preemptive and every process runs exactly once — once it starts, it never waits again; this equality vanishes under preemptive schedulers.)

Makespan =18−0=18= 18 - 0 = 18. Utilization =5+3+8+218=1818=1.0= \frac{5+3+8+2}{18} = \frac{18}{18} = 1.0 (the CPU is never idle here because arrivals overlap the running process). Throughput =4/18≈0.222= 4/18 \approx 0.222 processes per unit time.

Fairness (Jain's index) on waiting times {0,4,6,13}\{0, 4, 6, 13\}:

∑wi=23,∑wi2=0+16+36+169=221\sum w_i = 23, \quad \sum w_i^2 = 0 + 16 + 36 + 169 = 221
J=2324×221=529884≈0.599J = \frac{23^2}{4 \times 221} = \frac{529}{884} \approx 0.599

A value noticeably below 1 confirms the intuition visible in the table: P4, unlucky enough to arrive after the long P3, absorbs a disproportionate share of waiting (13 units) compared to P1 (0 units) — precisely the "convoy effect" that motivates Shortest-Job-First Scheduling as an alternative policy, which would reorder execution to reduce average waiting at the cost of even more unfairness to P3.

Common pitfalls & traps

PitfallWhy it happensFix
Confusing turnaround with waiting timeBoth feel like "time until done"; turnaround includes execution, waiting does notAlways compute waiting as turnaround minus burst, never independently
Using completion time instead of first-run time for responseNatural if only thinking non-preemptivelyTrack the timestamp of the process's first CPU slice explicitly, especially under preemption
Computing utilization over total simulated time instead of makespan from first arrivalOff-by-a-margin if the CPU sits idle before the first arrivalAnchor makespan at the earliest arrival, not at time zero of the trace
Averaging metrics across processes with wildly different burst lengths and calling it "fair"Mean waiting time hides varianceReport a fairness index (e.g., Jain's) or the max waiting time alongside the mean
Assuming a lower average waiting time always means a "better" schedulerIgnores who bears the waitingReport per-process waiting distribution, not just the mean, before declaring a winner

Pioneers & history

The formal study of scheduling metrics emerged from operations research and early time-sharing systems, where measuring "goodness" of a schedule was as urgent as designing algorithms to improve it.

Origins · 1 minEdsger W. Dijkstra and how this was discovered
EW
Edsger W. Dijkstra
1930–2002 · Formalized cooperating sequential processes and articulated principles of correctness for concurrent and scheduled systems

History
1954
— Job-shop scheduling formalized in operations research, introducing makespan as an objective.
1962
— Time-sharing systems (CTSS) make response time a first-class concern distinct from turnaround.
1968
— Dijkstra's work on cooperating sequential processes shapes rigorous reasoning about scheduling correctness.
1973
— Round-Robin scheduling analysis popularizes fairness as an explicit, measurable design goal.
1991
— Linux and modern multitasking kernels adopt explicit waiting/response-time accounting in scheduler design.

Where this matters

Every modern operating system scheduler — the Linux Completely Fair Scheduler, Windows' priority-based scheduler, and real-time schedulers in embedded systems — is tuned and benchmarked using exactly these metrics. Cloud platforms report throughput and utilization as service-level objectives; interactive systems (desktop UIs, mobile OSes, game engines) are engineered around response time because users perceive delay in milliseconds, not job completion. Data-center batch schedulers (Hadoop's YARN, Kubernetes' scheduler) optimize throughput and fair-share allocation across tenants, directly applying Jain's fairness index or similar measures to enforce multi-tenant SLAs. Understanding these metrics is the prerequisite for reading any scheduling research paper or systems benchmark, and for evaluating the tradeoffs made by First-Come First-Served Scheduling, Shortest-Job-First Scheduling, and Round-Robin Scheduling on their own terms.

Ask the tutor: Can you show how these same metrics change if P3 is preempted partway through by a shorter arriving process?

Why do we need six different metrics instead of just one overall "performance score"?

Different metrics capture fundamentally different stakeholder concerns. A batch-processing data center cares about throughput and utilization — how many jobs finish per hour and whether expensive hardware sits idle. An interactive user cares about response time — how quickly the system acknowledges a keystroke. A fairness-sensitive multi-tenant cloud cares about the spread of waiting times across tenants, not just the average. Collapsing these into one number would hide the very tradeoffs schedulers are designed to navigate; a single "score" could rank a starvation-prone but fast-average scheduler above a slower but fair one, silently encoding a value judgment that should be explicit. Reporting metrics separately keeps the tradeoffs visible and lets each system choose the policy matching its actual workload.

Is turnaround time always larger than waiting time?

Yes, as long as burst time is strictly positive, which it always is for any process that actually executes. Turnaround time is by definition waiting time plus burst time, so turnaround exceeds waiting time by exactly the burst time, never less. The only edge case is a hypothetical zero-burst process, which is not physically meaningful since every process consumes at least some CPU. In practice this identity is a useful sanity check: if computed waiting time ever exceeds turnaround time in a simulation or trace, there is a bug in how completion or burst times were recorded, not a real scheduling phenomenon.

Can CPU utilization exceed 100%?

No, not on a single-CPU system, and this is a useful invariant to check computed metrics against. Utilization is defined as total busy time divided by the makespan (elapsed wall-clock time), and because CPU slices in a Gantt chart are disjoint, non-overlapping intervals on one CPU, their sum can never exceed the total elapsed time. If a computed utilization value is reported above 1.0, either slices were double-counted, the makespan was computed incorrectly, or the trace involves multiple CPUs being summed against a single-CPU denominator. On multiprocessor systems utilization is typically reported per-core or as an average across cores, still bounded in [0, 1] per core.

How does preemption change these metrics compared to non-preemptive scheduling?

Preemption can lower average waiting time by letting short jobs interleave with long ones, but it introduces genuine complexity into response and turnaround accounting because a process may run in several disjoint slices rather than one. Response time becomes strictly meaningful only under preemption or multi-slice execution, since in strictly non-preemptive FCFS, response time collapses to equal waiting time (as seen in the worked example). Preemption also adds real context-switch overhead, which slightly reduces effective utilization and throughput compared to an idealized zero-cost model — a cost the simple formulas here assume away unless explicitly added as extra busy-time slices in the schedule.

What is the difference between fairness and low average waiting time?

Average waiting time is a single aggregate number that can be minimized by favoring some processes heavily at the expense of others, as long as the total sum stays low. Fairness measures how evenly that waiting time is distributed across processes, independent of the aggregate's size. A scheduler can have an excellent (low) average waiting time while being deeply unfair — for example, always running short jobs first while a handful of long jobs wait almost forever, dragging fairness index toward its minimum even as the mean looks great. This is why serious scheduling evaluations report both a central tendency metric and a spread or fairness metric side by side.

Does maximizing CPU utilization automatically mean the system performs well for users?

Not necessarily — utilization only measures whether the CPU is busy, not whether it is doing work users actually care about right now. A CPU can be at 100% utilization while every interactive process suffers terrible response time because the scheduler is busy running long batch jobs. Utilization is a necessary but insufficient condition for good perceived performance; it must be read alongside response time and waiting time to know whether that busy CPU time is serving the workload's actual priorities. This is precisely why real schedulers use multiple metrics and priority classes rather than optimizing utilization alone.

Where does the "makespan" used in throughput and utilization actually start and end?

Makespan is defined as the time from the earliest arrival among all processes in the trace to the latest completion time among them, capturing the full wall-clock span the workload occupied the system. Starting the clock at time zero of the simulation instead of the earliest arrival is a common bug, since it can include CPU-idle time before any process exists, artificially deflating utilization. Ending the measurement at anything other than the last completion similarly biases throughput, since unfinished processes should not count in the numerator (n completed) while their partial time still stretches the denominator.

Before this

Where this leads