{
  "id": "P172",
  "slug": "error-budget",
  "key": "P172-error-budget",
  "title": "Error budget",
  "summary": "A 99.9% monthly target: two months that spend the same budget reach 42 users and 1,000, an hour with no requests reads as fully met, and one endpoint spends 82% of a budget the service never exceeds.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P172 - Error budget\n\nA service with a 99.9% monthly target, a budget of 43.2 minutes of outage\na month, and what the budget does not count.\n\n- `main.eml` - the menu, the service, two months, a day with an empty\n  hour, one quarter's budget over six endpoints, and outages of your own\n- `budget.eml` - the budget, the users and their hours, the outages of\n  each month, the day's hourly requests, and the endpoints\n\nHow each part works:\n\n- The 1,000 users are each active one hour a day, the same hour every day,\n  spread evenly over the 24 hours. An outage interrupts the users of its\n  hour.\n- As in the corpus case `the-error-budget-was-monthly-and-the-outage-was-not`,\n  month A has one outage of 42.0 minutes and month B thirty of 1.4, a day\n  apart at different hours: the budget reads 1.2 minutes left in both.\n  Month A interrupted the 42 users of one hour; month B all 1,000, and 251\n  of them twice.\n- As in `the-slo-was-met-over-a-window-with-no-requests`, a latency\n  objective met hour by hour reads 100.00% for 03:00, when the load\n  balancer sent the service nothing: 0 misses of 0 requests. Counted as no\n  answer, with a check that fires on an empty hour, it pages.\n- As in `the-error-budget-was-spent-evenly-and-one-endpoint-ate-it`, one\n  budget of 1.00% over all six endpoints reads 0.78%, inside it. /export\n  fails 43.44% of its requests on 1.48% of the traffic and spends 82.18%\n  of the budget used; with a budget per endpoint it is the one frozen.\n\nWhat is checked: menu choices 1 to 6; 1 to 60 outages; minutes each, as a\nwhole number or to one decimal place, from 0.1 to 43.2. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- the service, the two months, the day's hours and the endpoints;\n- your own: 30 outages of 1.4 minutes, 1 of 42, and 60 of 1 (over the\n  budget).\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- outages: a cancel, 0, 61 and x; minutes: a cancel, 0, 0.0, 43.3, 1.45,\n  .5 and 1,5;\n- 24 outages of 0.1 minutes, and 2 of 43.2.\n\nBuilt on the verified corpus cases `the-error-budget-was-monthly-and-the-outage-was-not`\n(two months that spend the same budget), `the-slo-was-met-over-a-window-with-no-requests`\n(an hour with no requests read as fully met) and\n`the-error-budget-was-spent-evenly-and-one-endpoint-ate-it` (one quarterly\nbudget over 214 endpoints).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P172 error budget: a 99.9% monthly target - two months that spend the\n# same budget, a day's latency objective with an empty hour, one quarter's\n# budget over six endpoints, and outages of your own.\nimport budget\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    str(n) => s\n    \"\" => out\n    0 => k\n    len(s) => i\n    while i > 0:\n        if k == 3:\n            \",\" + out => out\n            0 => k\n        s[i - 1] + out => out\n        k + 1 => k\n        i - 1 => i\n    return out\n\ndef tenths(t):\n    return commas(int((t - t % 10) / 10)) + \".\" + str(t % 10)\n\ndef per(a, b):\n    # a of b as a percent to two places, half-up\n    a * 20000 + b => y\n    int((y - y % (2 * b)) / (2 * b)) => h\n    h % 100 => r\n    if r < 10:\n        return str(int((h - r) / 100)) + \".0\" + str(r) + \"%\"\n    return str(int((h - r) / 100)) + \".\" + str(r) + \"%\"\n\ndef clock(h, m):\n    \"\" => s\n    if h < 10:\n        \"0\" => s\n    s + str(h) + \":\" => s\n    if m < 10:\n        s + \"0\" => s\n    return s + str(m)\n\ndef twice(outs):\n    # users active in the hours of two or more outages\n    [0] * 24 => n\n    for o in outs:\n        n[o[1]] + 1 => n[o[1]]\n    0 => c\n    0 => u\n    while u < budget.users():\n        if n[u % 24] >= 2:\n            c + 1 => c\n        u + 1 => u\n    return c\n\ndef show_service():\n    (\"A service with a 99.9% monthly target: 0.1% of a 30-day month is a budget of \" + tenths(budget.budget_tenths()) + \" minutes of outage.\") ^0\n    (\"Its \" + commas(budget.users()) + \" users are each active one hour a day, the same hour every day, spread evenly over the 24 hours.\") ^0\n\ndef month_row(name, outs):\n    budget.spent(outs) => s\n    (\"  \" + pad(name, 7) + lpad(str(len(outs)), 7) + lpad(tenths(s), 9) + lpad(tenths(budget.budget_tenths() - s), 13) + lpad(commas(budget.hit(outs)), 11)) ^0\n\ndef show_months():\n    budget.month_a() => a\n    budget.spread(30, 14) => b\n    \"Two months, each inside the budget:\" ^0\n    (\"  month A: 1 outage, on day \" + str(a[0][0]) + \" at \" + clock(a[0][1], a[0][2]) + \", \" + tenths(a[0][3]) + \" minutes\") ^0\n    \"  month B: 30 outages of 1.4 minutes, one a day, at 00:10, 07:10, 14:10, 21:10, 04:10 and so on\" ^0\n    (\"  \" + pad(\"month\", 7) + lpad(\"outages\", 7) + lpad(\"minutes\", 9) + lpad(\"budget left\", 13) + lpad(\"users hit\", 11)) ^0\n    month_row(\"A\", a)\n    month_row(\"B\", b)\n    (\"The budget reads the same for both. Month A interrupted the \" + str(budget.hit(a)) + \" users of one hour; month B interrupted all \" + commas(budget.hit(b)) + \", and \" + str(twice(b)) + \" of them twice.\") ^0\n\ndef show_hours():\n    budget.hourly() => hs\n    \"A day's latency objective, hour by hour: a request over the threshold is a miss, and an hour meets the objective at 99.50% or better.\" ^0\n    (\"  \" + pad(\"hour\", 7) + lpad(\"requests\", 9) + lpad(\"misses\", 8) + lpad(\"met\", 10)) ^0\n    0 => met\n    for h in hs:\n        \"100.00%\" => shown\n        if h[1] > 0:\n            per(h[1] - h[2], h[1]) => shown\n        if h[1] == 0 or (h[1] - h[2]) * 10000 >= 9950 * h[1]:\n            met + 1 => met\n        (\"  \" + pad(clock(h[0], 0), 7) + lpad(commas(h[1]), 9) + lpad(str(h[2]), 8) + lpad(shown, 10)) ^0\n    (str(met) + \" of 24 hours meet it. 03:00 reads 100.00% from 0 requests: the load balancer sent nothing to the service that hour, and nobody could reach it.\") ^0\n    \"With an hour of no requests counted as no answer, and an availability check that fires on an empty hour, 03:00 pages instead.\" ^0\n\ndef show_endpoints():\n    budget.endpoints() => es\n    0 => req\n    0 => err\n    for e in es:\n        req + e[1] => req\n        err + e[2] => err\n    (\"One budget for the quarter over every endpoint: at most \" + per(budget.budget_rate(), 10000) + \" of requests may fail.\") ^0\n    (\"  \" + pad(\"endpoint\", 10) + lpad(\"requests\", 12) + lpad(\"errors\", 9) + lpad(\"error rate\", 12) + lpad(\"of the errors\", 15)) ^0\n    [] => over\n    for e in es:\n        (\"  \" + pad(e[0], 10) + lpad(commas(e[1]), 12) + lpad(commas(e[2]), 9) + lpad(per(e[2], e[1]), 12) + lpad(per(e[2], err), 15)) ^0\n        if e[2] * 10000 > budget.budget_rate() * e[1]:\n            over + [e[0]] => over\n    (\"All endpoints: \" + commas(req) + \" requests, \" + commas(err) + \" errors, \" + per(err, req) + \": inside the budget, and no freeze.\") ^0\n    es[4] => x\n    (x[0] + \" fails \" + per(x[2], x[1]) + \" of its requests on \" + per(x[1], req) + \" of the traffic, and spends \" + per(x[2], err) + \" of the budget used.\") ^0\n    if len(over) == 1:\n        (\"With a budget per endpoint, in proportion to its traffic, \" + over[0] + \" is the one endpoint over its own and the one frozen.\") ^0\n\ndef own():\n    plain(input(\"outages (1 to 60)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 60:\n        \"Type a whole number from 1 to 60.\" ^0\n        return\n    int(t) => n\n    plain(input(\"minutes each, to a tenth (0.1 to 43.2)> \")) => m\n    if m == \"\":\n        \"Cancelled.\" ^0\n        return\n    0 - 1 => tt\n    if digits(m) and len(m) <= 2:\n        int(m) * 10 => tt\n    elif len(m) >= 3 and len(m) <= 4 and m[len(m) - 2] == \".\" and digits(m[0:len(m) - 2]) and digits(m[len(m) - 1:]):\n        int(m[0:len(m) - 2]) * 10 + int(m[len(m) - 1:]) => tt\n    if tt < 1 or tt > 432:\n        \"Type minutes such as 1.4, from 0.1 to 43.2.\" ^0\n        return\n    budget.spread(n, tt) => outs\n    budget.spent(outs) => s\n    str(n) + \" outages of \" + tenths(tt) + \" minutes spend \" => lead\n    if n == 1:\n        \"1 outage of \" + tenths(tt) + \" minutes spends \" => lead\n    if s <= budget.budget_tenths():\n        (lead + tenths(s) + \" of the \" + tenths(budget.budget_tenths()) + \" minutes, \" + tenths(budget.budget_tenths() - s) + \" left.\") ^0\n    else:\n        (lead + tenths(s) + \" minutes, \" + tenths(s - budget.budget_tenths()) + \" over the budget.\") ^0\n    \"They interrupt \" => verb\n    if n == 1:\n        \"It interrupts \" => verb\n    (verb + commas(budget.hit(outs)) + \" of the \" + commas(budget.users()) + \" users, \" + commas(twice(outs)) + \" of them more than once.\") ^0\n\n\"== Error budget ==\" ^0\n\"A monthly budget of outage minutes, and what it does not count.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_service()\n    elif choice == \"2\":\n        show_months()\n    elif choice == \"3\":\n        show_hours()\n    elif choice == \"4\":\n        show_endpoints()\n    elif choice == \"5\":\n        own()\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import budget\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    s = str(n)\n    out = \"\"\n    k = 0\n    i = len(s)\n    while i > 0:\n        if k == 3:\n            out = \",\" + out\n            k = 0\n        out = s[i - 1] + out\n        k = k + 1\n        i = i - 1\n    return out\n\ndef tenths(t):\n    return commas(int((t - t % 10) / 10)) + \".\" + str(t % 10)\n\ndef per(a, b):\n    y = a * 20000 + b\n    h = int((y - y % (2 * b)) / (2 * b))\n    r = h % 100\n    if r < 10:\n        return str(int((h - r) / 100)) + \".0\" + str(r) + \"%\"\n    return str(int((h - r) / 100)) + \".\" + str(r) + \"%\"\n\ndef clock(h, m):\n    s = \"\"\n    if h < 10:\n        s = \"0\"\n    s = s + str(h) + \":\"\n    if m < 10:\n        s = s + \"0\"\n    return s + str(m)\n\ndef twice(outs):\n    n = [0] * 24\n    for o in outs:\n        n[o[1]] = n[o[1]] + 1\n    c = 0\n    u = 0\n    while u < budget.users():\n        if n[u % 24] >= 2:\n            c = c + 1\n        u = u + 1\n    return c\n\ndef show_service():\n    print(\"A service with a 99.9% monthly target: 0.1% of a 30-day month is a budget of \" + tenths(budget.budget_tenths()) + \" minutes of outage.\")\n    print(\"Its \" + commas(budget.users()) + \" users are each active one hour a day, the same hour every day, spread evenly over the 24 hours.\")\n\ndef month_row(name, outs):\n    s = budget.spent(outs)\n    print(\"  \" + pad(name, 7) + lpad(str(len(outs)), 7) + lpad(tenths(s), 9) + lpad(tenths(budget.budget_tenths() - s), 13) + lpad(commas(budget.hit(outs)), 11))\n\ndef show_months():\n    a = budget.month_a()\n    b = budget.spread(30, 14)\n    print(\"Two months, each inside the budget:\")\n    print(\"  month A: 1 outage, on day \" + str(a[0][0]) + \" at \" + clock(a[0][1], a[0][2]) + \", \" + tenths(a[0][3]) + \" minutes\")\n    print(\"  month B: 30 outages of 1.4 minutes, one a day, at 00:10, 07:10, 14:10, 21:10, 04:10 and so on\")\n    print(\"  \" + pad(\"month\", 7) + lpad(\"outages\", 7) + lpad(\"minutes\", 9) + lpad(\"budget left\", 13) + lpad(\"users hit\", 11))\n    month_row(\"A\", a)\n    month_row(\"B\", b)\n    print(\"The budget reads the same for both. Month A interrupted the \" + str(budget.hit(a)) + \" users of one hour; month B interrupted all \" + commas(budget.hit(b)) + \", and \" + str(twice(b)) + \" of them twice.\")\n\ndef show_hours():\n    hs = budget.hourly()\n    print(\"A day's latency objective, hour by hour: a request over the threshold is a miss, and an hour meets the objective at 99.50% or better.\")\n    print(\"  \" + pad(\"hour\", 7) + lpad(\"requests\", 9) + lpad(\"misses\", 8) + lpad(\"met\", 10))\n    met = 0\n    for h in hs:\n        shown = \"100.00%\"\n        if h[1] > 0:\n            shown = per(h[1] - h[2], h[1])\n        if h[1] == 0 or (h[1] - h[2]) * 10000 >= 9950 * h[1]:\n            met = met + 1\n        print(\"  \" + pad(clock(h[0], 0), 7) + lpad(commas(h[1]), 9) + lpad(str(h[2]), 8) + lpad(shown, 10))\n    print(str(met) + \" of 24 hours meet it. 03:00 reads 100.00% from 0 requests: the load balancer sent nothing to the service that hour, and nobody could reach it.\")\n    print(\"With an hour of no requests counted as no answer, and an availability check that fires on an empty hour, 03:00 pages instead.\")\n\ndef show_endpoints():\n    es = budget.endpoints()\n    req = 0\n    err = 0\n    for e in es:\n        req = req + e[1]\n        err = err + e[2]\n    print(\"One budget for the quarter over every endpoint: at most \" + per(budget.budget_rate(), 10000) + \" of requests may fail.\")\n    print(\"  \" + pad(\"endpoint\", 10) + lpad(\"requests\", 12) + lpad(\"errors\", 9) + lpad(\"error rate\", 12) + lpad(\"of the errors\", 15))\n    over = []\n    for e in es:\n        print(\"  \" + pad(e[0], 10) + lpad(commas(e[1]), 12) + lpad(commas(e[2]), 9) + lpad(per(e[2], e[1]), 12) + lpad(per(e[2], err), 15))\n        if e[2] * 10000 > budget.budget_rate() * e[1]:\n            over = over + [e[0]]\n    print(\"All endpoints: \" + commas(req) + \" requests, \" + commas(err) + \" errors, \" + per(err, req) + \": inside the budget, and no freeze.\")\n    x = es[4]\n    print(x[0] + \" fails \" + per(x[2], x[1]) + \" of its requests on \" + per(x[1], req) + \" of the traffic, and spends \" + per(x[2], err) + \" of the budget used.\")\n    if len(over) == 1:\n        print(\"With a budget per endpoint, in proportion to its traffic, \" + over[0] + \" is the one endpoint over its own and the one frozen.\")\n\ndef own():\n    t = plain(input(\"outages (1 to 60)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 60:\n        print(\"Type a whole number from 1 to 60.\")\n        return\n    n = int(t)\n    m = plain(input(\"minutes each, to a tenth (0.1 to 43.2)> \"))\n    if m == \"\":\n        print(\"Cancelled.\")\n        return\n    tt = 0 - 1\n    if digits(m) and len(m) <= 2:\n        tt = int(m) * 10\n    elif len(m) >= 3 and len(m) <= 4 and m[len(m) - 2] == \".\" and digits(m[0:len(m) - 2]) and digits(m[len(m) - 1:]):\n        tt = int(m[0:len(m) - 2]) * 10 + int(m[len(m) - 1:])\n    if tt < 1 or tt > 432:\n        print(\"Type minutes such as 1.4, from 0.1 to 43.2.\")\n        return\n    outs = budget.spread(n, tt)\n    s = budget.spent(outs)\n    lead = str(n) + \" outages of \" + tenths(tt) + \" minutes spend \"\n    if n == 1:\n        lead = \"1 outage of \" + tenths(tt) + \" minutes spends \"\n    if s <= budget.budget_tenths():\n        print(lead + tenths(s) + \" of the \" + tenths(budget.budget_tenths()) + \" minutes, \" + tenths(budget.budget_tenths() - s) + \" left.\")\n    else:\n        print(lead + tenths(s) + \" minutes, \" + tenths(s - budget.budget_tenths()) + \" over the budget.\")\n    verb = \"They interrupt \"\n    if n == 1:\n        verb = \"It interrupts \"\n    print(verb + commas(budget.hit(outs)) + \" of the \" + commas(budget.users()) + \" users, \" + commas(twice(outs)) + \" of them more than once.\")\n\nprint(\"== Error budget ==\")\nprint(\"A monthly budget of outage minutes, and what it does not count.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_service()\n    elif choice == \"2\":\n        show_months()\n    elif choice == \"3\":\n        show_hours()\n    elif choice == \"4\":\n        show_endpoints()\n    elif choice == \"5\":\n        own()\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "budget.eml",
      "eml": "# P172 error budget - a service with a 99.9% monthly target, 43.2 minutes\n# of budget a month. As in the corpus case\n# the-error-budget-was-monthly-and-the-outage-was-not, a budget sums the\n# minutes and forgets when they fell and whom they reached. As in\n# the-slo-was-met-over-a-window-with-no-requests, a window with no requests\n# reads as fully met. And as in\n# the-error-budget-was-spent-evenly-and-one-endpoint-ate-it, one budget\n# over every endpoint can be almost all one endpoint's.\n\ndef budget_tenths():\n    # 0.1% of a 30-day month, in tenths of a minute\n    return 432\n\ndef users():\n    # each of the 1,000 users is active one hour a day, the hour u % 24\n    return 1000\n\ndef month_a():\n    # [day, hour, minute it starts, tenths of a minute] - one outage\n    return [[10, 14, 10, 420]]\n\ndef spread(n, tenths):\n    # n outages of the same length, one a day, at the hour 7 * i % 24\n    [] => out\n    0 => i\n    while i < n:\n        out + [[i % 30 + 1, (7 * i) % 24, 10, tenths]] => out\n        i + 1 => i\n    return out\n\ndef spent(outs):\n    0 => t\n    for o in outs:\n        t + o[3] => t\n    return t\n\ndef hit(outs):\n    # users active in the hour of at least one outage\n    [False] * 24 => hours\n    for o in outs:\n        True => hours[o[1]]\n    0 => n\n    0 => u\n    while u < users():\n        if hours[u % 24]:\n            n + 1 => n\n        u + 1 => u\n    return n\n\ndef hourly():\n    # [hour, requests, requests over the latency threshold] for one day;\n    # from 03:00 to 04:00 the load balancer sent nothing to the service\n    [] => out\n    0 => h\n    while h < 24:\n        800 + 150 * ((h * 5) % 7) => r\n        if h == 3:\n            0 => r\n        out + [[h, r, int(r / 400)]] => out\n        h + 1 => h\n    return out\n\ndef endpoints():\n    # [endpoint, requests a quarter, errors]\n    return [[\"/search\", 24000000, 31000], [\"/orders\", 18000000, 26000], [\"/cart\", 11000000, 14000],\n            [\"/login\", 6100000, 13800], [\"/export\", 900000, 391000], [\"/health\", 1000000, 0]]\n\ndef budget_rate():\n    # errors per 10,000 requests the quarter's budget allows\n    return 100\n",
      "python": "def budget_tenths():\n    return 432\n\ndef users():\n    return 1000\n\ndef month_a():\n    return [[10, 14, 10, 420]]\n\ndef spread(n, tenths):\n    out = []\n    i = 0\n    while i < n:\n        out = out + [[i % 30 + 1, 7 * i % 24, 10, tenths]]\n        i = i + 1\n    return out\n\ndef spent(outs):\n    t = 0\n    for o in outs:\n        t = t + o[3]\n    return t\n\ndef hit(outs):\n    hours = [False] * 24\n    for o in outs:\n        hours[o[1]] = True\n    n = 0\n    u = 0\n    while u < users():\n        if hours[u % 24]:\n            n = n + 1\n        u = u + 1\n    return n\n\ndef hourly():\n    out = []\n    h = 0\n    while h < 24:\n        r = 800 + 150 * (h * 5 % 7)\n        if h == 3:\n            r = 0\n        out = out + [[h, r, int(r / 400)]]\n        h = h + 1\n    return out\n\ndef endpoints():\n    return [[\"/search\", 24000000, 31000], [\"/orders\", 18000000, 26000], [\"/cart\", 11000000, 14000], [\"/login\", 6100000, 13800], [\"/export\", 900000, 391000], [\"/health\", 1000000, 0]]\n\ndef budget_rate():\n    return 100\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n5\n\n5\n0\n5\n61\n5\nx\n5\n24\n\n5\n24\n0\n5\n24\n0.0\n5\n24\n43.3\n5\n24\n1.45\n5\n24\n.5\n5\n24\n1,5\n5\n24\n0.1\n5\n2\n43.2\n6\n",
      "screen": "== Error budget ==\nA monthly budget of outage minutes, and what it does not count.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> \nCancelled.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 0\nType a whole number from 1 to 60.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 61\nType a whole number from 1 to 60.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> x\nType a whole number from 1 to 60.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> \nCancelled.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 0\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 0.0\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 43.3\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 1.45\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> .5\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 1,5\nType minutes such as 1.4, from 0.1 to 43.2.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 24\nminutes each, to a tenth (0.1 to 43.2)> 0.1\n24 outages of 0.1 minutes spend 2.4 of the 43.2 minutes, 40.8 left.\nThey interrupt 1,000 of the 1,000 users, 0 of them more than once.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 2\nminutes each, to a tenth (0.1 to 43.2)> 43.2\n2 outages of 43.2 minutes spend 86.4 minutes, 43.2 over the budget.\nThey interrupt 84 of the 1,000 users, 0 of them more than once.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\n5\n30\n1.4\n5\n1\n42\n5\n60\n1\n6\n",
      "screen": "== Error budget ==\nA monthly budget of outage minutes, and what it does not count.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 1\nA service with a 99.9% monthly target: 0.1% of a 30-day month is a budget of 43.2 minutes of outage.\nIts 1,000 users are each active one hour a day, the same hour every day, spread evenly over the 24 hours.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 2\nTwo months, each inside the budget:\n  month A: 1 outage, on day 10 at 14:10, 42.0 minutes\n  month B: 30 outages of 1.4 minutes, one a day, at 00:10, 07:10, 14:10, 21:10, 04:10 and so on\n  month  outages  minutes  budget left  users hit\n  A            1     42.0          1.2         42\n  B           30     42.0          1.2      1,000\nThe budget reads the same for both. Month A interrupted the 42 users of one hour; month B interrupted all 1,000, and 251 of them twice.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 3\nA day's latency objective, hour by hour: a request over the threshold is a miss, and an hour meets the objective at 99.50% or better.\n  hour    requests  misses       met\n  00:00        800       2    99.75%\n  01:00      1,550       3    99.81%\n  02:00      1,250       3    99.76%\n  03:00          0       0   100.00%\n  04:00      1,700       4    99.76%\n  05:00      1,400       3    99.79%\n  06:00      1,100       2    99.82%\n  07:00        800       2    99.75%\n  08:00      1,550       3    99.81%\n  09:00      1,250       3    99.76%\n  10:00        950       2    99.79%\n  11:00      1,700       4    99.76%\n  12:00      1,400       3    99.79%\n  13:00      1,100       2    99.82%\n  14:00        800       2    99.75%\n  15:00      1,550       3    99.81%\n  16:00      1,250       3    99.76%\n  17:00        950       2    99.79%\n  18:00      1,700       4    99.76%\n  19:00      1,400       3    99.79%\n  20:00      1,100       2    99.82%\n  21:00        800       2    99.75%\n  22:00      1,550       3    99.81%\n  23:00      1,250       3    99.76%\n24 of 24 hours meet it. 03:00 reads 100.00% from 0 requests: the load balancer sent nothing to the service that hour, and nobody could reach it.\nWith an hour of no requests counted as no answer, and an availability check that fires on an empty hour, 03:00 pages instead.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 4\nOne budget for the quarter over every endpoint: at most 1.00% of requests may fail.\n  endpoint      requests   errors  error rate  of the errors\n  /search     24,000,000   31,000       0.13%          6.52%\n  /orders     18,000,000   26,000       0.14%          5.46%\n  /cart       11,000,000   14,000       0.13%          2.94%\n  /login       6,100,000   13,800       0.23%          2.90%\n  /export        900,000  391,000      43.44%         82.18%\n  /health      1,000,000        0       0.00%          0.00%\nAll endpoints: 61,000,000 requests, 475,800 errors, 0.78%: inside the budget, and no freeze.\n/export fails 43.44% of its requests on 1.48% of the traffic, and spends 82.18% of the budget used.\nWith a budget per endpoint, in proportion to its traffic, /export is the one endpoint over its own and the one frozen.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 30\nminutes each, to a tenth (0.1 to 43.2)> 1.4\n30 outages of 1.4 minutes spend 42.0 of the 43.2 minutes, 1.2 left.\nThey interrupt 1,000 of the 1,000 users, 251 of them more than once.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 1\nminutes each, to a tenth (0.1 to 43.2)> 42\n1 outage of 42.0 minutes spends 42.0 of the 43.2 minutes, 1.2 left.\nIt interrupts 42 of the 1,000 users, 0 of them more than once.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 5\noutages (1 to 60)> 60\nminutes each, to a tenth (0.1 to 43.2)> 1\n60 outages of 1.0 minutes spend 60.0 minutes, 16.8 over the budget.\nThey interrupt 1,000 of the 1,000 users, 1,000 of them more than once.\n\n1) the service  2) two months  3) an empty hour  4) one budget, six endpoints  5) outages of your own  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-error-budget-was-monthly-and-the-outage-was-not",
      "caseId": "614-the-error-budget-was-monthly-and-the-outage-was-not",
      "title": "The error budget was monthly and the outage was not"
    },
    {
      "slug": "the-slo-was-met-over-a-window-with-no-requests",
      "caseId": "848-the-slo-was-met-over-a-window-with-no-requests",
      "title": "The slo was met over a window with no requests"
    },
    {
      "slug": "the-error-budget-was-spent-evenly-and-one-endpoint-ate-it",
      "caseId": "784-the-error-budget-was-spent-evenly-and-one-endpoint-ate-it",
      "title": "The error budget was spent evenly and one endpoint ate it"
    }
  ],
  "updated": "2026-10-19"
}
