{
  "id": "P171",
  "slug": "alert-rules",
  "key": "P171-alert-rules",
  "title": "Alert rules",
  "summary": "Three monitors on one service: an alert on the crossing pages once while the disk fills for three days, a spike falls between two reads of a gauge, and minutes with no sample are recorded as zero.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P171 - Alert rules\n\nThree monitors on one service, each right about what it measures, and\nwhat each one does not report.\n\n- `main.eml` - the menu, the monitors, the disk alert, a spike between\n  reads, the minutes an exporter was down, and a read interval of your own\n- `alerts.eml` - free space over three days, the pages each rule sends,\n  the load second by second, which reads land in a spike, and the load\n  minute by minute with the exporter's outage\n\nHow each part works:\n\n- As in the corpus case `the-alert-fired-once-and-the-condition-held-for-days`,\n  the disk alert holds below 15.0% free, and free space falls from 14.0%\n  to 3.0% over 72 hours, 864 evaluations. On the crossing only: 1 page.\n  On every evaluation: 864. On the crossing and every 4 hours after: 18.\n  The edge rule did what it was written to do, and nothing it sends says\n  the disk went on filling.\n- As in `the-spike-fit-between-two-samples`, the load is read once a minute,\n  and a 20-second spike to 2,000 requests a second starts at 1815 s,\n  between the reads at 1800 and 1860: every read shows 300, and the\n  service drops 20,000 requests over its capacity of 1,000. A read of the\n  highest value since the last read reports the 2,000.\n- As in `the-missing-metric-was-read-as-zero`, the exporter is down for\n  minutes 30 to 47 while the real load is 1,400, over the threshold of\n  500; the 18 minutes are recorded as 0, and the alert stays quiet.\n- For a read every n seconds, a spike of n seconds or more always holds a\n  read, and a shorter one can fall between two.\n\nWhat is checked: menu choices 1 to 6; a read every 1 to 600 seconds, a\nspike starting at second 0 to 3599 and lasting 1 to 600 seconds. An empty\nanswer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the monitors, the disk alert, the spike and the missing minutes;\n- your own: reads every 60 s and every 15 s around the spike, and a\n  60-second spike from 1790 s.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- intervals: a cancel, 0, 601 and x; starts: a cancel, 3600 and x;\n  lengths: a cancel, 0, 601 and x;\n- a read every second, and every 300 s with a 299-second spike.\n\nBuilt on the verified corpus cases `the-alert-fired-once-and-the-condition-held-for-days`\n(a disk alert that fires on the crossing), `the-spike-fit-between-two-samples`\n(a gauge sampled every 60 seconds and a 20-second spike) and\n`the-missing-metric-was-read-as-zero` (an exporter outage recorded as zero).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P171 alert rules: three monitors on one service - the disk alert over\n# three days, a spike between two reads of a gauge, the minutes an exporter\n# was down, and a read interval of your own.\nimport alerts\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 pct(t):\n    # tenths of a percent\n    return str(int((t - t % 10) / 10)) + \".\" + str(t % 10) + \"%\"\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return commas(n) + \" \" + word + \"s\"\n\ndef show_monitors():\n    \"Three monitors on one service:\" ^0\n    (\"  \" + pad(\"disk\", 10) + \"free space, read every 5 minutes; the alert holds below \" + pct(alerts.disk_threshold())) ^0\n    (\"  \" + pad(\"load\", 10) + \"requests a second, read once a minute; the alert fires over \" + commas(alerts.load_threshold()) + \", and the service drops what is over \" + commas(alerts.capacity())) ^0\n    (\"  \" + pad(\"exporter\", 10) + \"the load reaches the monitor through an exporter; a minute without a sample is recorded as 0\") ^0\n\ndef show_disk():\n    \"Free space after it crossed below 15.0%, while a cleanup fails to keep up:\" ^0\n    (\"  \" + pad(\"hour\", 7) + \"free\") ^0\n    0 => h\n    while h <= 72:\n        (\"  \" + pad(str(h), 7) + pct(alerts.free(h))) ^0\n        h + 12 => h\n    (\"The condition held for all 72 hours, \" + commas(alerts.evaluations()) + \" evaluations. Pages sent:\") ^0\n    (\"  \" + pad(\"on the crossing only\", 38) + lpad(plural(alerts.pages(\"edge\", 4), \"page\"), 10)) ^0\n    (\"  \" + pad(\"on every evaluation\", 38) + lpad(plural(alerts.pages(\"level\", 4), \"page\"), 10)) ^0\n    (\"  \" + pad(\"on the crossing, then every 4 hours\", 38) + lpad(plural(alerts.pages(\"reminder\", 4), \"page\"), 10)) ^0\n    \"The edge rule did what it was written to do: one page. Nothing it sends says that the disk went on filling for three days.\" ^0\n\ndef show_spike():\n    \"The hour's load, read once a minute, around the half hour:\" ^0\n    (\"  \" + pad(\"read at\", 10) + \"requests a second\") ^0\n    0 => most\n    0 => s\n    while s < 3600:\n        if alerts.load(s) > most:\n            alerts.load(s) => most\n        if s >= 1740 and s <= 1920:\n            (\"  \" + pad(str(s) + \" s\", 10) + commas(alerts.load(s))) ^0\n        s + 60 => s\n    0 => peak\n    0 => dropped\n    0 => t\n    while t < 3600:\n        if alerts.load(t) > peak:\n            alerts.load(t) => peak\n        if alerts.load(t) > alerts.capacity():\n            dropped + alerts.load(t) - alerts.capacity() => dropped\n        t + 1 => t\n    (\"The highest of the 60 reads: \" + commas(most) + \". The highest second of the hour: \" + commas(peak) + \", from 1815 s for 20 seconds, between the reads at 1800 and 1860.\") ^0\n    (\"Over capacity for those 20 seconds, the service dropped \" + commas(dropped) + \" requests, and no read was over the threshold.\") ^0\n    (\"Read as the highest value since the last read, the 1860 s read reports \" + commas(peak) + \".\") ^0\n\ndef show_missing():\n    0 => up\n    0 => recorded_max\n    0 => m\n    while m < 60:\n        if alerts.exporter_up(m):\n            up + 1 => up\n            if alerts.minute_load(m) > recorded_max:\n                alerts.minute_load(m) => recorded_max\n        m + 1 => m\n    (\"The last hour, minute by minute: \" + str(up) + \" minutes with a sample and \" + str(60 - up) + \" without, minutes 30 to 47, recorded as 0.\") ^0\n    (\"  \" + pad(\"minutes\", 10) + lpad(\"real load\", 10) + lpad(\"recorded\", 10)) ^0\n    (\"  \" + pad(\"0-29\", 10) + lpad(commas(alerts.minute_load(0)), 10) + lpad(commas(alerts.minute_load(0)), 10)) ^0\n    (\"  \" + pad(\"30-47\", 10) + lpad(commas(alerts.minute_load(30)), 10) + lpad(\"0\", 10)) ^0\n    (\"  \" + pad(\"48-59\", 10) + lpad(commas(alerts.minute_load(48)), 10) + lpad(commas(alerts.minute_load(48)), 10)) ^0\n    (\"The highest recorded minute: \" + commas(recorded_max) + \", under the threshold of \" + commas(alerts.load_threshold()) + \", so the alert stayed quiet. The real load was \" + commas(alerts.minute_load(30)) + \" for those 18 minutes, \" + commas(alerts.minute_load(30) - alerts.load_threshold()) + \" over it.\") ^0\n    \"Recorded as missing rather than as 0, the same minutes raise a no-data alert at minute 30.\" ^0\n\ndef number(prompt, lo, hi):\n    # the whole number typed, or -1 after a cancel or a message\n    plain(input(prompt + \" (\" + str(lo) + \" to \" + str(hi) + \")> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return 0 - 1\n    if len(t) > len(str(hi)) or not digits(t) or int(t) < lo or int(t) > hi:\n        (\"Type a whole number from \" + str(lo) + \" to \" + str(hi) + \".\") ^0\n        return 0 - 1\n    return int(t)\n\ndef own():\n    number(\"read every how many seconds\", 1, 600) => every\n    if every < 0:\n        return\n    number(\"spike starts at second\", 0, 3599) => start\n    if start < 0:\n        return\n    number(\"spike lasts seconds\", 1, 600) => length\n    if length < 0:\n        return\n    alerts.samples_in(start, length, every) => got\n    if len(got) == 0:\n        (\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds no read, and the gauge never sees it.\") ^0\n    elif len(got) == 1:\n        (\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds 1 read, at \" + str(got[0]) + \" s.\") ^0\n    else:\n        (\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds \" + str(len(got)) + \" reads, from \" + str(got[0]) + \" s.\") ^0\n    if every == 1:\n        \"With a read every second, every spike holds a read.\" ^0\n    else:\n        (\"A spike of \" + str(every) + \" seconds or more always holds a read; a shorter one can fall between two.\") ^0\n\n\"== Alert rules ==\" ^0\n\"Three monitors, each right about what it measures, and what each one does not report.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_monitors()\n    elif choice == \"2\":\n        show_disk()\n    elif choice == \"3\":\n        show_spike()\n    elif choice == \"4\":\n        show_missing()\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 alerts\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 pct(t):\n    return str(int((t - t % 10) / 10)) + \".\" + str(t % 10) + \"%\"\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return commas(n) + \" \" + word + \"s\"\n\ndef show_monitors():\n    print(\"Three monitors on one service:\")\n    print(\"  \" + pad(\"disk\", 10) + \"free space, read every 5 minutes; the alert holds below \" + pct(alerts.disk_threshold()))\n    print(\"  \" + pad(\"load\", 10) + \"requests a second, read once a minute; the alert fires over \" + commas(alerts.load_threshold()) + \", and the service drops what is over \" + commas(alerts.capacity()))\n    print(\"  \" + pad(\"exporter\", 10) + \"the load reaches the monitor through an exporter; a minute without a sample is recorded as 0\")\n\ndef show_disk():\n    print(\"Free space after it crossed below 15.0%, while a cleanup fails to keep up:\")\n    print(\"  \" + pad(\"hour\", 7) + \"free\")\n    h = 0\n    while h <= 72:\n        print(\"  \" + pad(str(h), 7) + pct(alerts.free(h)))\n        h = h + 12\n    print(\"The condition held for all 72 hours, \" + commas(alerts.evaluations()) + \" evaluations. Pages sent:\")\n    print(\"  \" + pad(\"on the crossing only\", 38) + lpad(plural(alerts.pages(\"edge\", 4), \"page\"), 10))\n    print(\"  \" + pad(\"on every evaluation\", 38) + lpad(plural(alerts.pages(\"level\", 4), \"page\"), 10))\n    print(\"  \" + pad(\"on the crossing, then every 4 hours\", 38) + lpad(plural(alerts.pages(\"reminder\", 4), \"page\"), 10))\n    print(\"The edge rule did what it was written to do: one page. Nothing it sends says that the disk went on filling for three days.\")\n\ndef show_spike():\n    print(\"The hour's load, read once a minute, around the half hour:\")\n    print(\"  \" + pad(\"read at\", 10) + \"requests a second\")\n    most = 0\n    s = 0\n    while s < 3600:\n        if alerts.load(s) > most:\n            most = alerts.load(s)\n        if s >= 1740 and s <= 1920:\n            print(\"  \" + pad(str(s) + \" s\", 10) + commas(alerts.load(s)))\n        s = s + 60\n    peak = 0\n    dropped = 0\n    t = 0\n    while t < 3600:\n        if alerts.load(t) > peak:\n            peak = alerts.load(t)\n        if alerts.load(t) > alerts.capacity():\n            dropped = dropped + alerts.load(t) - alerts.capacity()\n        t = t + 1\n    print(\"The highest of the 60 reads: \" + commas(most) + \". The highest second of the hour: \" + commas(peak) + \", from 1815 s for 20 seconds, between the reads at 1800 and 1860.\")\n    print(\"Over capacity for those 20 seconds, the service dropped \" + commas(dropped) + \" requests, and no read was over the threshold.\")\n    print(\"Read as the highest value since the last read, the 1860 s read reports \" + commas(peak) + \".\")\n\ndef show_missing():\n    up = 0\n    recorded_max = 0\n    m = 0\n    while m < 60:\n        if alerts.exporter_up(m):\n            up = up + 1\n            if alerts.minute_load(m) > recorded_max:\n                recorded_max = alerts.minute_load(m)\n        m = m + 1\n    print(\"The last hour, minute by minute: \" + str(up) + \" minutes with a sample and \" + str(60 - up) + \" without, minutes 30 to 47, recorded as 0.\")\n    print(\"  \" + pad(\"minutes\", 10) + lpad(\"real load\", 10) + lpad(\"recorded\", 10))\n    print(\"  \" + pad(\"0-29\", 10) + lpad(commas(alerts.minute_load(0)), 10) + lpad(commas(alerts.minute_load(0)), 10))\n    print(\"  \" + pad(\"30-47\", 10) + lpad(commas(alerts.minute_load(30)), 10) + lpad(\"0\", 10))\n    print(\"  \" + pad(\"48-59\", 10) + lpad(commas(alerts.minute_load(48)), 10) + lpad(commas(alerts.minute_load(48)), 10))\n    print(\"The highest recorded minute: \" + commas(recorded_max) + \", under the threshold of \" + commas(alerts.load_threshold()) + \", so the alert stayed quiet. The real load was \" + commas(alerts.minute_load(30)) + \" for those 18 minutes, \" + commas(alerts.minute_load(30) - alerts.load_threshold()) + \" over it.\")\n    print(\"Recorded as missing rather than as 0, the same minutes raise a no-data alert at minute 30.\")\n\ndef number(prompt, lo, hi):\n    t = plain(input(prompt + \" (\" + str(lo) + \" to \" + str(hi) + \")> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return 0 - 1\n    if len(t) > len(str(hi)) or not digits(t) or int(t) < lo or int(t) > hi:\n        print(\"Type a whole number from \" + str(lo) + \" to \" + str(hi) + \".\")\n        return 0 - 1\n    return int(t)\n\ndef own():\n    every = number(\"read every how many seconds\", 1, 600)\n    if every < 0:\n        return\n    start = number(\"spike starts at second\", 0, 3599)\n    if start < 0:\n        return\n    length = number(\"spike lasts seconds\", 1, 600)\n    if length < 0:\n        return\n    got = alerts.samples_in(start, length, every)\n    if len(got) == 0:\n        print(\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds no read, and the gauge never sees it.\")\n    elif len(got) == 1:\n        print(\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds 1 read, at \" + str(got[0]) + \" s.\")\n    else:\n        print(\"Reads every \" + str(every) + \" s: the spike from \" + str(start) + \" s for \" + str(length) + \" s holds \" + str(len(got)) + \" reads, from \" + str(got[0]) + \" s.\")\n    if every == 1:\n        print(\"With a read every second, every spike holds a read.\")\n    else:\n        print(\"A spike of \" + str(every) + \" seconds or more always holds a read; a shorter one can fall between two.\")\n\nprint(\"== Alert rules ==\")\nprint(\"Three monitors, each right about what it measures, and what each one does not report.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_monitors()\n    elif choice == \"2\":\n        show_disk()\n    elif choice == \"3\":\n        show_spike()\n    elif choice == \"4\":\n        show_missing()\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": "alerts.eml",
      "eml": "# P171 alert rules - three monitors, each correct on its own terms. As in\n# the corpus case the-alert-fired-once-and-the-condition-held-for-days, an\n# alert on the crossing pages once, and the condition it reports goes on\n# for three days. As in the-spike-fit-between-two-samples, a gauge read once\n# a minute never sees a spike that starts and ends between two reads. And as\n# in the-missing-metric-was-read-as-zero, a minute with no sample recorded\n# as zero is a minute under every threshold.\n\ndef disk_threshold():\n    # free space, in tenths of a percent, below which the disk alert holds\n    return 150\n\ndef free(h):\n    # free space at hour h of the three days, in tenths of a percent\n    return 140 - int(110 * h / 72)\n\ndef evaluations():\n    # the rule is evaluated every 5 minutes for the 72 hours\n    return 72 * 12\n\ndef pages(rule, every):\n    # pages sent over the 72 hours: \"edge\" on the crossing only, \"level\" on\n    # every evaluation that finds the condition, \"reminder\" on the crossing\n    # and again every `every` hours while it holds\n    0 => n\n    0 => k\n    while k < evaluations():\n        int((k - k % 12) / 12) => h\n        if free(h) < disk_threshold():\n            if rule == \"level\":\n                n + 1 => n\n            elif k == 0:\n                n + 1 => n\n            elif rule == \"reminder\" and k % (12 * every) == 0:\n                n + 1 => n\n        k + 1 => k\n    return n\n\ndef load(s):\n    # requests a second at second s of the hour: a 20-second spike at 1815\n    if s >= 1815 and s < 1835:\n        return 2000\n    return 300\n\ndef capacity():\n    return 1000\n\ndef samples_in(start, length, every):\n    # the reads, one every `every` seconds from second 0, that land in the\n    # seconds start .. start + length - 1\n    [] => out\n    start + every - 1 => first\n    first - first % every => s\n    while s < start + length:\n        out + [s] => out\n        s + every => s\n    return out\n\ndef load_threshold():\n    return 500\n\ndef minute_load(m):\n    # the real requests a second in minute m; the exporter is down 30 - 47\n    if m >= 30 and m < 48:\n        return 1400\n    return 300\n\ndef exporter_up(m):\n    return m < 30 or m >= 48\n",
      "python": "def disk_threshold():\n    return 150\n\ndef free(h):\n    return 140 - int(110 * h / 72)\n\ndef evaluations():\n    return 72 * 12\n\ndef pages(rule, every):\n    n = 0\n    k = 0\n    while k < evaluations():\n        h = int((k - k % 12) / 12)\n        if free(h) < disk_threshold():\n            if rule == \"level\":\n                n = n + 1\n            elif k == 0:\n                n = n + 1\n            elif rule == \"reminder\" and k % (12 * every) == 0:\n                n = n + 1\n        k = k + 1\n    return n\n\ndef load(s):\n    if s >= 1815 and s < 1835:\n        return 2000\n    return 300\n\ndef capacity():\n    return 1000\n\ndef samples_in(start, length, every):\n    out = []\n    first = start + every - 1\n    s = first - first % every\n    while s < start + length:\n        out = out + [s]\n        s = s + every\n    return out\n\ndef load_threshold():\n    return 500\n\ndef minute_load(m):\n    if m >= 30 and m < 48:\n        return 1400\n    return 300\n\ndef exporter_up(m):\n    return m < 30 or m >= 48\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n5\n\n5\n0\n5\n601\n5\nx\n5\n30\n\n5\n30\n3600\n5\n30\nx\n5\n30\n100\n\n5\n30\n100\n0\n5\n30\n100\n601\n5\n30\n100\nx\n5\n1\n100\n3\n5\n300\n3550\n299\n6\n",
      "screen": "== Alert rules ==\nThree monitors, each right about what it measures, and what each one does not report.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> \nCancelled.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 0\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 601\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> x\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> \nCancelled.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> 3600\nType a whole number from 0 to 3599.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> x\nType a whole number from 0 to 3599.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> 100\nspike lasts seconds (1 to 600)> \nCancelled.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> 100\nspike lasts seconds (1 to 600)> 0\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> 100\nspike lasts seconds (1 to 600)> 601\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 30\nspike starts at second (0 to 3599)> 100\nspike lasts seconds (1 to 600)> x\nType a whole number from 1 to 600.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 1\nspike starts at second (0 to 3599)> 100\nspike lasts seconds (1 to 600)> 3\nReads every 1 s: the spike from 100 s for 3 s holds 3 reads, from 100 s.\nWith a read every second, every spike holds a read.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 300\nspike starts at second (0 to 3599)> 3550\nspike lasts seconds (1 to 600)> 299\nReads every 300 s: the spike from 3550 s for 299 s holds 1 read, at 3600 s.\nA spike of 300 seconds or more always holds a read; a shorter one can fall between two.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\n5\n60\n1815\n20\n5\n15\n1815\n20\n5\n60\n1790\n60\n6\n",
      "screen": "== Alert rules ==\nThree monitors, each right about what it measures, and what each one does not report.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 1\nThree monitors on one service:\n  disk      free space, read every 5 minutes; the alert holds below 15.0%\n  load      requests a second, read once a minute; the alert fires over 500, and the service drops what is over 1,000\n  exporter  the load reaches the monitor through an exporter; a minute without a sample is recorded as 0\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 2\nFree space after it crossed below 15.0%, while a cleanup fails to keep up:\n  hour   free\n  0      14.0%\n  12     12.2%\n  24     10.4%\n  36     8.5%\n  48     6.7%\n  60     4.9%\n  72     3.0%\nThe condition held for all 72 hours, 864 evaluations. Pages sent:\n  on the crossing only                      1 page\n  on every evaluation                    864 pages\n  on the crossing, then every 4 hours     18 pages\nThe edge rule did what it was written to do: one page. Nothing it sends says that the disk went on filling for three days.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 3\nThe hour's load, read once a minute, around the half hour:\n  read at   requests a second\n  1740 s    300\n  1800 s    300\n  1860 s    300\n  1920 s    300\nThe highest of the 60 reads: 300. The highest second of the hour: 2,000, from 1815 s for 20 seconds, between the reads at 1800 and 1860.\nOver capacity for those 20 seconds, the service dropped 20,000 requests, and no read was over the threshold.\nRead as the highest value since the last read, the 1860 s read reports 2,000.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 4\nThe last hour, minute by minute: 42 minutes with a sample and 18 without, minutes 30 to 47, recorded as 0.\n  minutes    real load  recorded\n  0-29             300       300\n  30-47          1,400         0\n  48-59            300       300\nThe highest recorded minute: 300, under the threshold of 500, so the alert stayed quiet. The real load was 1,400 for those 18 minutes, 900 over it.\nRecorded as missing rather than as 0, the same minutes raise a no-data alert at minute 30.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 60\nspike starts at second (0 to 3599)> 1815\nspike lasts seconds (1 to 600)> 20\nReads every 60 s: the spike from 1815 s for 20 s holds no read, and the gauge never sees it.\nA spike of 60 seconds or more always holds a read; a shorter one can fall between two.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 15\nspike starts at second (0 to 3599)> 1815\nspike lasts seconds (1 to 600)> 20\nReads every 15 s: the spike from 1815 s for 20 s holds 2 reads, from 1815 s.\nA spike of 15 seconds or more always holds a read; a shorter one can fall between two.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 5\nread every how many seconds (1 to 600)> 60\nspike starts at second (0 to 3599)> 1790\nspike lasts seconds (1 to 600)> 60\nReads every 60 s: the spike from 1790 s for 60 s holds 1 read, at 1800 s.\nA spike of 60 seconds or more always holds a read; a shorter one can fall between two.\n\n1) the monitors  2) the disk alert  3) a spike between reads  4) missing minutes  5) a read interval of your own  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-alert-fired-once-and-the-condition-held-for-days",
      "caseId": "597-the-alert-fired-once-and-the-condition-held-for-days",
      "title": "The alert fired once and the condition held for days"
    },
    {
      "slug": "the-spike-fit-between-two-samples",
      "caseId": "832-the-spike-fit-between-two-samples",
      "title": "The spike fit between two samples"
    },
    {
      "slug": "the-missing-metric-was-read-as-zero",
      "caseId": "827-the-missing-metric-was-read-as-zero",
      "title": "The missing metric was read as zero"
    }
  ],
  "updated": "2026-10-19"
}
