Exercise 1: A 4xx Rate Query, and Why rate() Belongs on a Dashboard — Possible Solution ==================================================================== sum(rate(http_requests_total{status=~"4.."}[10m])) Explanation: status=~"4.." uses PromQL's regex label matcher to catch every status code in the 400-499 range in one pattern, exactly the way the chapter's own example used "5.." for 5xx errors. Wrapping the whole selector in rate(...[10m]) computes the per-second average rate of increase over the trailing 10-minute window, and sum(...) collapses across every remaining label (instance, method, handler, and the specific 4xx code itself) into one combined number. -- Why rate() rather than the raw counter? -- -- -- http_requests_total{status=~"4.."} on its own is a counter -- a -- number that only ever climbs for the life of each process. Graphed -- directly, it would just be a steadily rising line whose SLOPE is -- the actually interesting information, but whose raw height tells a -- viewer almost nothing useful at a glance -- "it's currently at -- 48,213" doesn't answer "is this bad right now." rate() converts -- that raw, ever-climbing total into "how many 4xx responses per -- second is this service currently producing," a number that goes up -- when things get worse and down when they improve -- exactly the -- shape of information a dashboard panel or an alert threshold -- actually needs, and exactly what the chapter's own tip-box warns -- against skipping. WHY THIS WORKS AS AN ANSWER ------------------------------ This builds the regex-matched, rate-wrapped, aggregated query using the chapter's own established pattern, then explains the rate() requirement by contrasting what a raw climbing counter actually communicates against what a per-second rate communicates instead.