{
  "schema_version": 3,
  "app": "pihole",
  "display_name": "Pi-hole v6",
  "upstream_repo": "https://github.com/pi-hole/docker-pi-hole",
  "docs_url": "https://docs.pi-hole.net/docker/",
  "status": "ok",
  "summary": {
    "primary_service": "pihole",
    "app_version": "2026.10.0",
    "app_version_pinned": true,
    "runs": 3,
    "publishable": true,
    "publishable_note": null,
    "idle_total_mem_mib": 9.27,
    "idle_total_cpu_pct": 0.18,
    "peak_total_mem_mib": 18.18,
    "peak_is_under_load": true,
    "startup_seconds": 1.6,
    "startup_peak_total_mem_mib": 11.66,
    "settled": true,
    "settle_seconds_used": 121.0,
    "peak_max_mib": 18.67,
    "sizing_peak_mib": 18.67,
    "idle_mem_mib": {
      "median": 9.27,
      "min": 9.15,
      "max": 9.42,
      "values": [
        9.42,
        9.27,
        9.15
      ]
    },
    "idle_cpu_pct": {
      "median": 0.18,
      "min": 0.16,
      "max": 0.18,
      "values": [
        0.18,
        0.16,
        0.18
      ]
    },
    "peak_mem_mib": {
      "median": 18.18,
      "min": 17.5,
      "max": 18.67,
      "values": [
        18.67,
        18.18,
        17.5
      ]
    },
    "startup_peak_mem_mib": {
      "median": 11.66,
      "min": 10.02,
      "max": 12.67,
      "values": [
        11.66,
        10.02,
        12.67
      ]
    },
    "startup_s": {
      "median": 1.6,
      "min": 1.6,
      "max": 4.2,
      "values": [
        1.6,
        1.6,
        4.2
      ]
    },
    "settled_after_healthy_s": {
      "median": 122.5,
      "min": 122.5,
      "max": 122.5,
      "values": [
        122.5,
        122.5,
        122.5
      ]
    },
    "settled_runs": 3,
    "per_service": {
      "pihole": {
        "idle_mem_mib": {
          "median": 9.27,
          "min": 9.15,
          "max": 9.42,
          "values": [
            9.42,
            9.27,
            9.15
          ]
        },
        "peak_mem_mib": {
          "median": 18.18,
          "min": 17.5,
          "max": 18.67,
          "values": [
            18.67,
            18.18,
            17.5
          ]
        }
      }
    },
    "variance_flag": null,
    "representative_run": 2,
    "cpu_models": [
      "AMD EPYC 9V74 80-Core Processor"
    ],
    "cpu_model_mismatch": false,
    "rule": "idle = median of the run idle means; peak = median of the run peaks (highest simultaneous total, under load when the app has a load step); peak_max_mib = highest run peak; sizing_peak_mib = highest over runs of max(run peak, run startup peak); variance_flag when max/min over runs is above 1.5 for idle or peak."
  },
  "run": {
    "started_at": "2026-10-19T02:52:35Z",
    "date": "2026-10-19",
    "harness_version": "3.0.0",
    "runs_requested": 3,
    "docker_host": null,
    "remote": false,
    "runs_completed": 3
  },
  "host": {
    "label": "github-actions ubuntu-latest (2 vCPU, 8 GB)",
    "ci": {
      "runs_on": "ubuntu-latest",
      "runner_environment": "github-hosted",
      "runner_os": "Linux",
      "runner_arch": "X64",
      "image_os": "ubuntu24",
      "image_version": "20261012.334.1"
    },
    "cpu_model": "AMD EPYC 9V74 80-Core Processor",
    "cpu_model_note": null,
    "cores": 2,
    "mem_total_mib": 7938,
    "kernel": "6.17.0-1022-azure",
    "os": "Ubuntu 24.04.5 LTS",
    "arch": "x86_64",
    "oci_arch": "amd64",
    "docker_server_version": "28.0.4",
    "docker_client_version": "28.0.4",
    "compose_client_version": "2.38.2",
    "storage_driver": "overlay2",
    "containerd_image_store": false,
    "source": "docker info + throwaway container reading /proc/cpuinfo on the daemon host",
    "compose_note": "Compose runs on the client machine; Docker server fields are from the daemon host"
  },
  "helper_images": {
    "curlimages/curl:latest": {
      "pulled_as": "curlimages/curl:latest",
      "digest": "curlimages/curl@sha256:a134550be58ab783810e613ae96fd253c38f7bdd16d9bf9be350700c7a29ac06"
    },
    "python:3.12-slim": {
      "pulled_as": "python:3.12-slim",
      "digest": "python@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1"
    }
  },
  "pull_seconds": 2.9,
  "finished_at": "2026-10-19T03:06:01Z",
  "elapsed_seconds": 806.0,
  "services": {
    "pihole": {
      "image_ref": "pihole/pihole:2026.10.0",
      "image_repo": "pihole/pihole",
      "image_tag": "2026.10.0",
      "digest": "sha256:d34134716fc32038b052a8674c7c6879e0ff21f0e6d8af345166fffa2c27c113",
      "digest_note": null,
      "version": "2026.10.0",
      "version_source": "label:org.opencontainers.image.version",
      "version_pinned": true,
      "version_note": null,
      "resolved_tag": "2026.10.0",
      "resolved_tag_note": null,
      "image_size": {
        "unpacked_mb": 99.5,
        "unpacked_source": "docker image ls Size: unpacked image on the daemon host, rounded by docker to 3 significant figures",
        "compressed_mb": 42.9,
        "compressed_source": "sum of compressed layer sizes in the registry manifest for linux/amd64",
        "compressed_null_reason": null,
        "unit_note": "MB = 10^6 bytes. unpacked = disk used on the host; compressed = what is downloaded"
      },
      "compose_image": "pihole/pihole:2026.10.0",
      "compose_comment": "sha256:d34134716fc32038b052a8674c7c6879e0ff21f0e6d8af345166fffa2c27c113  (version 2026.10.0, from label:org.opencontainers.image.version)",
      "restart_count": 0,
      "oom_killed": false
    }
  },
  "runs": [
    {
      "run": 1,
      "started_at": "2026-10-19T02:52:43Z",
      "host": {
        "label": "github-actions ubuntu-latest (2 vCPU, 8 GB)",
        "ci": {
          "runs_on": "ubuntu-latest",
          "runner_environment": "github-hosted",
          "runner_os": "Linux",
          "runner_arch": "X64",
          "image_os": "ubuntu24",
          "image_version": "20261012.334.1"
        },
        "cpu_model": "AMD EPYC 9V74 80-Core Processor",
        "cpu_model_note": null,
        "cores": 2,
        "mem_total_mib": 7938,
        "kernel": "6.17.0-1022-azure",
        "os": "Ubuntu 24.04.5 LTS",
        "arch": "x86_64",
        "oci_arch": "amd64",
        "docker_server_version": "28.0.4",
        "docker_client_version": "28.0.4",
        "compose_client_version": "2.38.2",
        "storage_driver": "overlay2",
        "containerd_image_store": false,
        "source": "docker info + throwaway container reading /proc/cpuinfo on the daemon host",
        "compose_note": "Compose runs on the client machine; Docker server fields are from the daemon host"
      },
      "date": "2026-10-19",
      "startup": {
        "seconds": 1.6,
        "ok": true,
        "checks": [
          {
            "type": "http",
            "url": "http://pihole:80/api/info/login",
            "expect_status": [
              200
            ],
            "ok": true,
            "ready_after_s": 1.6,
            "detail": "OK 200"
          }
        ],
        "up_command_seconds": 0.3,
        "definition": "wall clock from starting `docker compose up -d` until every health check passed; checks poll every 0.5 s from a throwaway container on the compose network, so resolution is about +/- 1 s"
      },
      "versions": {
        "pihole": "2026.10.0"
      },
      "setup": [
        {
          "exit_code": 0,
          "duration_s": 0.6,
          "reported": {
            "versions_reported": {
              "core": null,
              "web": null,
              "ftl": "v6.8",
              "docker": null
            },
            "blocklist": {
              "url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
              "entries_reported": 71493,
              "list_valid_domains": 71493,
              "list_invalid_domains": 1,
              "reported_by": "GET /api/stats/summary, gravity.domains_being_blocked"
            },
            "dns_config": {
              "upstreams": [
                "1.1.1.1"
              ],
              "listeningMode": "ALL",
              "rateLimit": {
                "count": 0,
                "interval": 60
              },
              "blocking": {
                "active": true,
                "mode": "NULL",
                "edns": "TEXT"
              },
              "cache_size": 10000,
              "dnssec": false,
              "queryLogging": true
            }
          },
          "output_tail": [
            "waiting for the Pi-hole API",
            "gravity: 71493 domains; the list itself: 71493 valid, 1 invalid",
            "versions: {\"core\": null, \"web\": null, \"ftl\": \"v6.8\", \"docker\": null}"
          ],
          "description": "Log in to the API with the lab password, check that the one adlist is the default (StevenBlack unified hosts) and that gravity holds its domains, and set the upstream resolver to plain 1.1.1.1, the query rate limit off and blocking mode NULL (blocked names answer 0.0.0.0). Reports how many domains Pi-hole says it loaded."
        }
      ],
      "idle": {
        "per_service": {
          "pihole": {
            "samples": 16,
            "mem_mean_mib": 9.42,
            "mem_max_mib": 9.71,
            "cpu_mean_pct": 0.18,
            "cpu_max_pct": 0.19
          }
        },
        "total": {
          "samples": 16,
          "dropped_for_total": 0,
          "mem_mean_mib": 9.42,
          "mem_max_mib": 9.71,
          "cpu_mean_pct": 0.18,
          "cpu_max_pct": 0.19
        },
        "window_seconds": 30.3,
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.54,
            "total_cpu_pct": 0.19
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 9.38,
            "total_cpu_pct": 0.19
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.3,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 6.1,
            "total_mem_mib": 9.46,
            "total_cpu_pct": 0.19
          },
          {
            "t_offset_s": 8.1,
            "total_mem_mib": 9.36,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 10.1,
            "total_mem_mib": 9.31,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 12.1,
            "total_mem_mib": 9.47,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 14.1,
            "total_mem_mib": 9.29,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 16.1,
            "total_mem_mib": 9.35,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 18.2,
            "total_mem_mib": 9.25,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 20.2,
            "total_mem_mib": 9.71,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 22.2,
            "total_mem_mib": 9.33,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 24.2,
            "total_mem_mib": 9.33,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 26.2,
            "total_mem_mib": 9.48,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 28.2,
            "total_mem_mib": 9.63,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 30.3,
            "total_mem_mib": 9.54,
            "total_cpu_pct": 0.18
          }
        ],
        "memory_stat": {
          "read_at": "2026-10-19T02:55:18Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 4.93,
              "file_mib": 39.88,
              "active_file_mib": 0.0,
              "inactive_file_mib": 37.97,
              "shmem_mib": 1.9
            }
          },
          "read_lag_s": 0.0,
          "method": "docker exec <container> cat /sys/fs/cgroup/memory.stat (cgroup v1: /sys/fs/cgroup/memory/memory.stat); MiB; splits what docker stats shows as one number into anon (heap) and file cache; a field that is absent was not printed by the kernel or not readable (see reason)",
          "taken": "right after the idle window"
        }
      },
      "settle": {
        "settled": true,
        "mode": "detected",
        "settle_seconds_used": 121.0,
        "settle_started_after_healthy_s": 1.5,
        "params": {
          "window_seconds": 60,
          "threshold_pct": 2.0,
          "threshold_min_mib": 10.0,
          "min_seconds": 120,
          "max_seconds": 600
        },
        "last_window": {
          "n": 30,
          "mean_mib": 9.49,
          "min_mib": 9.27,
          "max_mib": 9.74,
          "spread_mib": 0.47,
          "threshold_mib": 10.0
        },
        "rechecks": 0,
        "rule": "settled = over the last window_seconds the stack total varied (max - min) by less than max(threshold_pct of the window mean, threshold_min_mib); not before min_seconds, at the latest max_seconds, both counted from the end of the setup steps; the idle window must also be flat and agree with it. If max_seconds passes without settling, settled=false and the idle window is measured anyway."
      },
      "startup_peak": {
        "per_service_mem_mib": {
          "pihole": 11.66
        },
        "total_mem_mib": 11.66,
        "samples": 61,
        "note": "highest value across docker stats samples (about one every 2-3 s) from healthy until the stack settled; shorter spikes between samples are not seen"
      },
      "timeline": {
        "step_s": 10,
        "t0": "healthy",
        "offsets_s": [
          10,
          20,
          30,
          40,
          50,
          60,
          70,
          80,
          90,
          100,
          110,
          120,
          130,
          140,
          150,
          160
        ],
        "total_mem_mib": [
          9.9,
          9.5,
          9.5,
          9.4,
          9.3,
          9.5,
          9.5,
          9.5,
          9.5,
          9.4,
          9.5,
          9.5,
          9.4,
          9.4,
          9.4,
          9.6
        ],
        "per_service_mem_mib": {
          "pihole": [
            9.9,
            9.5,
            9.5,
            9.4,
            9.3,
            9.5,
            9.5,
            9.5,
            9.5,
            9.4,
            9.5,
            9.5,
            9.4,
            9.4,
            9.4,
            9.6
          ]
        }
      },
      "load": {
        "per_service": {
          "pihole": {
            "samples": 53,
            "mem_mean_mib": 12.39,
            "mem_max_mib": 18.67,
            "cpu_mean_pct": 4.56,
            "cpu_max_pct": 16.53
          }
        },
        "total": {
          "samples": 53,
          "dropped_for_total": 0,
          "mem_mean_mib": 12.39,
          "mem_max_mib": 18.67,
          "cpu_mean_pct": 4.56,
          "cpu_max_pct": 16.53
        },
        "description": "A Python script (standard library only) in a container on the Compose network sends plain UDP DNS queries (type A) to Pi-hole's port 53. The same script and the same 900 names run against AdGuard Home: 500 real popular domains, 300 names from the StevenBlack blocklist that both apps load (so they are blocked and answered 0.0.0.0) and 100 random .com names that do not exist (NXDOMAIN). Three phases, queries sent on a fixed schedule whether or not earlier ones were answered: every name once at 100 queries/s (first-time latency, 9 s); 200 queries/s for 60 s, names drawn in shuffled rounds so most are repeats; then a burst of 1,000 queries/s for 20 s. A query not answered within 2 s is a timeout. The script records queries sent, answered, timeouts, blocked answers and the p50, p95 and p99 latency of first-time and of repeated (cache-expected) queries, and reads the resolver's own query counters before and after. No real clients, no DoH/DoT, no DHCP, no reverse lookups.",
        "duration_s": 102.1,
        "window": "from LAB_MARK load_start (or step start) until 2 s after the step ended",
        "reported_by_script": {
          "dns_load": {
            "target": "pihole",
            "resolver": "pihole:53",
            "protocol": "udp",
            "query_type": "A",
            "domain_list": {
              "names": 900,
              "allow": 500,
              "block": 300,
              "nx": 100,
              "sha256_16": "459be49b880c5849"
            },
            "preflight": {
              "blocked_probe": "doubleclick.net",
              "allowed_probe": "example.org"
            },
            "seed": 20261013,
            "names_left_out_after_cold_pass": {
              "total": 0,
              "by_category": {},
              "by_class": {}
            },
            "query_timeout_s": 2.0,
            "pause_s": 5,
            "phases": [
              {
                "sent": 900,
                "answered": 900,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 525,
                  "blocked": 262,
                  "nxdomain": 113
                },
                "blocked_answers": 262,
                "block_names_not_blocked": 38,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 0,
                "latency_ms": {
                  "first_allow": {
                    "n": 500,
                    "p50": 6.252,
                    "p95": 195.308,
                    "p99": 364.251,
                    "max": 1212.012,
                    "mean": 36.758
                  },
                  "first_nx": {
                    "n": 100,
                    "p50": 6.441,
                    "p95": 13.507,
                    "p99": 17.118,
                    "max": 18.633,
                    "mean": 7.353
                  },
                  "cached_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "blocked": {
                    "n": 262,
                    "p50": 0.19,
                    "p95": 0.273,
                    "p99": 0.294,
                    "max": 0.3,
                    "mean": 0.183
                  }
                },
                "phase": "cold",
                "target_qps": 100,
                "planned_seconds": 9.0,
                "achieved_qps": 100.0,
                "send_lag_ms": {
                  "p50": 0.118,
                  "p99": 0.309,
                  "max": 0.64
                },
                "names_not_answered": {
                  "total": 0,
                  "by_category": {},
                  "by_class": {}
                }
              },
              {
                "sent": 12000,
                "answered": 12000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 6998,
                  "blocked": 3487,
                  "nxdomain": 1515
                },
                "blocked_answers": 3487,
                "block_names_not_blocked": 509,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 436,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 6228,
                    "p50": 0.112,
                    "p95": 0.154,
                    "p99": 0.237,
                    "max": 21.718,
                    "mean": 0.118
                  },
                  "cached_nx": {
                    "n": 1340,
                    "p50": 0.106,
                    "p95": 0.139,
                    "p99": 0.184,
                    "max": 12.128,
                    "mean": 0.115
                  },
                  "blocked": {
                    "n": 3487,
                    "p50": 0.103,
                    "p95": 0.133,
                    "p99": 0.188,
                    "max": 7.164,
                    "mean": 0.104
                  }
                },
                "phase": "sustained",
                "target_qps": 200,
                "planned_seconds": 60,
                "achieved_qps": 200.0,
                "send_lag_ms": {
                  "p50": 0.107,
                  "p99": 0.24,
                  "max": 1.597
                }
              },
              {
                "sent": 20000,
                "answered": 20000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 11662,
                  "blocked": 5832,
                  "nxdomain": 2506
                },
                "blocked_answers": 5832,
                "block_names_not_blocked": 847,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 144,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 10961,
                    "p50": 0.1,
                    "p95": 0.156,
                    "p99": 0.291,
                    "max": 5.155,
                    "mean": 0.106
                  },
                  "cached_nx": {
                    "n": 2216,
                    "p50": 0.096,
                    "p95": 0.124,
                    "p99": 0.266,
                    "max": 1.17,
                    "mean": 0.098
                  },
                  "blocked": {
                    "n": 5832,
                    "p50": 0.094,
                    "p95": 0.119,
                    "p99": 0.252,
                    "max": 2.152,
                    "mean": 0.096
                  }
                },
                "phase": "burst",
                "target_qps": 1000,
                "planned_seconds": 20,
                "achieved_qps": 1000.0,
                "send_lag_ms": {
                  "p50": 0.095,
                  "p99": 0.264,
                  "max": 2.104
                }
              }
            ],
            "overall": {
              "sent": 32900,
              "answered": 32900,
              "timeouts": 0,
              "answers_by_class": {
                "answer": 19185,
                "blocked": 9581,
                "nxdomain": 4134
              },
              "blocked_answers": 9581,
              "block_names_not_blocked": 1394,
              "other_names_blocked": 0,
              "repeats_not_expected_cached": 580,
              "latency_ms": {
                "first_allow": {
                  "n": 500,
                  "p50": 6.252,
                  "p95": 195.308,
                  "p99": 364.251,
                  "max": 1212.012,
                  "mean": 36.758
                },
                "first_nx": {
                  "n": 100,
                  "p50": 6.441,
                  "p95": 13.507,
                  "p99": 17.118,
                  "max": 18.633,
                  "mean": 7.353
                },
                "cached_allow": {
                  "n": 17189,
                  "p50": 0.104,
                  "p95": 0.155,
                  "p99": 0.262,
                  "max": 21.718,
                  "mean": 0.11
                },
                "cached_nx": {
                  "n": 3556,
                  "p50": 0.099,
                  "p95": 0.134,
                  "p99": 0.217,
                  "max": 12.128,
                  "mean": 0.105
                },
                "blocked": {
                  "n": 9581,
                  "p50": 0.097,
                  "p95": 0.144,
                  "p99": 0.259,
                  "max": 7.164,
                  "mean": 0.102
                }
              }
            },
            "elapsed_s": 99.9,
            "resolver_counters": {
              "before": {
                "queries_total": 2,
                "queries_blocked": 1,
                "queries_forwarded": 1,
                "queries_cached": 0,
                "gravity_domains": 71493
              },
              "after": {
                "queries_total": 32905,
                "queries_blocked": 9582,
                "queries_forwarded": 640,
                "queries_cached": 22683,
                "gravity_domains": 71493
              },
              "delta": {
                "queries_total": 32903,
                "queries_blocked": 9581,
                "queries_forwarded": 639,
                "queries_cached": 22683
              }
            },
            "cache_rule": "a repeat counts as cache-expected when an earlier answer for the name arrived less than half its TTL ago"
          },
          "queries_sent": 32900,
          "queries_answered": 32900,
          "timeouts": 0,
          "blocked_answers": 9581,
          "first_allow_p50_ms": 6.252,
          "first_allow_p95_ms": 195.308,
          "first_allow_p99_ms": 364.251,
          "cached_allow_p50_ms": 0.112,
          "cached_allow_p95_ms": 0.154,
          "cached_allow_p99_ms": 0.237,
          "blocked_p50_ms": 0.103,
          "blocked_p95_ms": 0.133,
          "blocked_p99_ms": 0.188,
          "burst_cached_allow_p50_ms": 0.1,
          "burst_cached_allow_p95_ms": 0.156,
          "burst_cached_allow_p99_ms": 0.291
        },
        "output_tail": [
          "resolver pihole = 172.18.0.2:53; domain list 900 names {'allow': 500, 'block': 300, 'nx': 100}, sha256 459be49b880c5849",
          "cold: sent 900 answered 900 timeouts 0 blocked 262 achieved 100.0 qps; cached allow p50/p95/p99 None/None/None ms; first allow p50 6.252 ms",
          "sustained: sent 12000 answered 12000 timeouts 0 blocked 3487 achieved 200.0 qps; cached allow p50/p95/p99 0.112/0.154/0.237 ms; first allow p50 None ms",
          "burst: sent 20000 answered 20000 timeouts 0 blocked 5832 achieved 1000.0 qps; cached allow p50/p95/p99 0.1/0.156/0.291 ms; first allow p50 None ms"
        ],
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.78,
            "total_cpu_pct": 6.63
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 10.07,
            "total_cpu_pct": 3.27
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.75,
            "total_cpu_pct": 2.57
          },
          {
            "t_offset_s": 6.0,
            "total_mem_mib": 9.93,
            "total_cpu_pct": 2.55
          },
          {
            "t_offset_s": 8.0,
            "total_mem_mib": 9.92,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 10.0,
            "total_mem_mib": 10.13,
            "total_cpu_pct": 0.19
          },
          {
            "t_offset_s": 12.0,
            "total_mem_mib": 10.12,
            "total_cpu_pct": 0.31
          },
          {
            "t_offset_s": 14.0,
            "total_mem_mib": 9.96,
            "total_cpu_pct": 0.39
          },
          {
            "t_offset_s": 16.0,
            "total_mem_mib": 10.03,
            "total_cpu_pct": 2.45
          },
          {
            "t_offset_s": 18.0,
            "total_mem_mib": 10.18,
            "total_cpu_pct": 2.61
          },
          {
            "t_offset_s": 20.0,
            "total_mem_mib": 10.06,
            "total_cpu_pct": 2.65
          },
          {
            "t_offset_s": 22.0,
            "total_mem_mib": 10.22,
            "total_cpu_pct": 2.55
          },
          {
            "t_offset_s": 24.1,
            "total_mem_mib": 10.38,
            "total_cpu_pct": 2.42
          },
          {
            "t_offset_s": 26.1,
            "total_mem_mib": 10.32,
            "total_cpu_pct": 2.73
          },
          {
            "t_offset_s": 28.1,
            "total_mem_mib": 10.29,
            "total_cpu_pct": 2.59
          },
          {
            "t_offset_s": 30.1,
            "total_mem_mib": 10.66,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 32.1,
            "total_mem_mib": 10.75,
            "total_cpu_pct": 2.6
          },
          {
            "t_offset_s": 34.1,
            "total_mem_mib": 10.95,
            "total_cpu_pct": 2.54
          },
          {
            "t_offset_s": 36.1,
            "total_mem_mib": 11.14,
            "total_cpu_pct": 2.77
          },
          {
            "t_offset_s": 38.1,
            "total_mem_mib": 10.93,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 40.1,
            "total_mem_mib": 11.01,
            "total_cpu_pct": 2.48
          },
          {
            "t_offset_s": 42.1,
            "total_mem_mib": 11.5,
            "total_cpu_pct": 2.84
          },
          {
            "t_offset_s": 44.1,
            "total_mem_mib": 11.29,
            "total_cpu_pct": 2.94
          },
          {
            "t_offset_s": 46.1,
            "total_mem_mib": 11.48,
            "total_cpu_pct": 3.18
          },
          {
            "t_offset_s": 48.1,
            "total_mem_mib": 11.65,
            "total_cpu_pct": 2.41
          },
          {
            "t_offset_s": 50.1,
            "total_mem_mib": 11.83,
            "total_cpu_pct": 2.64
          },
          {
            "t_offset_s": 52.1,
            "total_mem_mib": 11.85,
            "total_cpu_pct": 2.57
          },
          {
            "t_offset_s": 54.1,
            "total_mem_mib": 12.21,
            "total_cpu_pct": 2.86
          },
          {
            "t_offset_s": 56.1,
            "total_mem_mib": 12.1,
            "total_cpu_pct": 2.82
          },
          {
            "t_offset_s": 58.1,
            "total_mem_mib": 12.21,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 60.1,
            "total_mem_mib": 12.31,
            "total_cpu_pct": 2.55
          },
          {
            "t_offset_s": 62.1,
            "total_mem_mib": 12.27,
            "total_cpu_pct": 2.74
          },
          {
            "t_offset_s": 64.1,
            "total_mem_mib": 12.41,
            "total_cpu_pct": 3.04
          },
          {
            "t_offset_s": 66.1,
            "total_mem_mib": 12.34,
            "total_cpu_pct": 3.02
          },
          {
            "t_offset_s": 68.2,
            "total_mem_mib": 12.5,
            "total_cpu_pct": 2.95
          },
          {
            "t_offset_s": 70.2,
            "total_mem_mib": 12.68,
            "total_cpu_pct": 2.82
          },
          {
            "t_offset_s": 72.2,
            "total_mem_mib": 12.8,
            "total_cpu_pct": 3.17
          },
          {
            "t_offset_s": 74.2,
            "total_mem_mib": 13.0,
            "total_cpu_pct": 2.21
          },
          {
            "t_offset_s": 76.2,
            "total_mem_mib": 12.79,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 78.2,
            "total_mem_mib": 13.03,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 80.2,
            "total_mem_mib": 13.3,
            "total_cpu_pct": 10.39
          },
          {
            "t_offset_s": 82.2,
            "total_mem_mib": 13.5,
            "total_cpu_pct": 13.52
          },
          {
            "t_offset_s": 84.2,
            "total_mem_mib": 14.0,
            "total_cpu_pct": 14.31
          },
          {
            "t_offset_s": 86.2,
            "total_mem_mib": 14.61,
            "total_cpu_pct": 16.53
          },
          {
            "t_offset_s": 88.2,
            "total_mem_mib": 15.01,
            "total_cpu_pct": 14.3
          },
          {
            "t_offset_s": 90.2,
            "total_mem_mib": 15.4,
            "total_cpu_pct": 13.62
          },
          {
            "t_offset_s": 92.2,
            "total_mem_mib": 15.98,
            "total_cpu_pct": 13.61
          },
          {
            "t_offset_s": 94.2,
            "total_mem_mib": 16.29,
            "total_cpu_pct": 13.86
          },
          {
            "t_offset_s": 96.2,
            "total_mem_mib": 17.98,
            "total_cpu_pct": 14.79
          },
          {
            "t_offset_s": 98.2,
            "total_mem_mib": 17.13,
            "total_cpu_pct": 13.86
          },
          {
            "t_offset_s": 100.2,
            "total_mem_mib": 17.67,
            "total_cpu_pct": 2.5
          },
          {
            "t_offset_s": 102.2,
            "total_mem_mib": 18.67,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 104.2,
            "total_mem_mib": 18.35,
            "total_cpu_pct": 0.17
          }
        ],
        "series_start_offset_s": 1.4,
        "peak_note": "peak = highest value seen across docker stats samples (about one every 2-3 s); shorter spikes between samples are not seen",
        "peak_memory_stat": {
          "read_at": "2026-10-19T02:57:02Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 11.68,
              "file_mib": 48.65,
              "active_file_mib": 0.0,
              "inactive_file_mib": 44.68,
              "shmem_mib": 3.96
            }
          },
          "read_lag_s": 0.0,
          "series_index": 51,
          "sample_t_offset_s": 102.2,
          "total_mem_mib": 18.67
        }
      },
      "container_end_state": {
        "pihole": {
          "restart_count": 0,
          "oom_killed": false
        }
      },
      "teardown": {
        "done": true,
        "volumes_removed": true,
        "images_removed": false,
        "error": null
      },
      "warnings": [],
      "finished_at": "2026-10-19T02:57:08Z",
      "elapsed_seconds": 264.7
    },
    {
      "run": 2,
      "started_at": "2026-10-19T02:57:08Z",
      "host": {
        "label": "github-actions ubuntu-latest (2 vCPU, 8 GB)",
        "ci": {
          "runs_on": "ubuntu-latest",
          "runner_environment": "github-hosted",
          "runner_os": "Linux",
          "runner_arch": "X64",
          "image_os": "ubuntu24",
          "image_version": "20261012.334.1"
        },
        "cpu_model": "AMD EPYC 9V74 80-Core Processor",
        "cpu_model_note": null,
        "cores": 2,
        "mem_total_mib": 7938,
        "kernel": "6.17.0-1022-azure",
        "os": "Ubuntu 24.04.5 LTS",
        "arch": "x86_64",
        "oci_arch": "amd64",
        "docker_server_version": "28.0.4",
        "docker_client_version": "28.0.4",
        "compose_client_version": "2.38.2",
        "storage_driver": "overlay2",
        "containerd_image_store": false,
        "source": "docker info + throwaway container reading /proc/cpuinfo on the daemon host",
        "compose_note": "Compose runs on the client machine; Docker server fields are from the daemon host"
      },
      "date": "2026-10-19",
      "startup": {
        "seconds": 1.6,
        "ok": true,
        "checks": [
          {
            "type": "http",
            "url": "http://pihole:80/api/info/login",
            "expect_status": [
              200
            ],
            "ok": true,
            "ready_after_s": 1.6,
            "detail": "OK 200"
          }
        ],
        "up_command_seconds": 0.2,
        "definition": "wall clock from starting `docker compose up -d` until every health check passed; checks poll every 0.5 s from a throwaway container on the compose network, so resolution is about +/- 1 s"
      },
      "versions": {
        "pihole": "2026.10.0"
      },
      "setup": [
        {
          "exit_code": 0,
          "duration_s": 0.5,
          "reported": {
            "versions_reported": {
              "core": null,
              "web": null,
              "ftl": "v6.8",
              "docker": null
            },
            "blocklist": {
              "url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
              "entries_reported": 71493,
              "list_valid_domains": 71493,
              "list_invalid_domains": 1,
              "reported_by": "GET /api/stats/summary, gravity.domains_being_blocked"
            },
            "dns_config": {
              "upstreams": [
                "1.1.1.1"
              ],
              "listeningMode": "ALL",
              "rateLimit": {
                "count": 0,
                "interval": 60
              },
              "blocking": {
                "active": true,
                "mode": "NULL",
                "edns": "TEXT"
              },
              "cache_size": 10000,
              "dnssec": false,
              "queryLogging": true
            }
          },
          "output_tail": [
            "waiting for the Pi-hole API",
            "gravity: 71493 domains; the list itself: 71493 valid, 1 invalid",
            "versions: {\"core\": null, \"web\": null, \"ftl\": \"v6.8\", \"docker\": null}"
          ],
          "description": "Log in to the API with the lab password, check that the one adlist is the default (StevenBlack unified hosts) and that gravity holds its domains, and set the upstream resolver to plain 1.1.1.1, the query rate limit off and blocking mode NULL (blocked names answer 0.0.0.0). Reports how many domains Pi-hole says it loaded."
        }
      ],
      "idle": {
        "per_service": {
          "pihole": {
            "samples": 16,
            "mem_mean_mib": 9.27,
            "mem_max_mib": 9.36,
            "cpu_mean_pct": 0.16,
            "cpu_max_pct": 0.19
          }
        },
        "total": {
          "samples": 16,
          "dropped_for_total": 0,
          "mem_mean_mib": 9.27,
          "mem_max_mib": 9.36,
          "cpu_mean_pct": 0.16,
          "cpu_max_pct": 0.19
        },
        "window_seconds": 30.3,
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.33,
            "total_cpu_pct": 0.19
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 9.29,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.27,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 6.1,
            "total_mem_mib": 9.34,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 8.1,
            "total_mem_mib": 9.14,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 10.1,
            "total_mem_mib": 9.32,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 12.1,
            "total_mem_mib": 9.36,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 14.1,
            "total_mem_mib": 9.28,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 16.1,
            "total_mem_mib": 9.14,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 18.2,
            "total_mem_mib": 9.31,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 20.2,
            "total_mem_mib": 9.31,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 22.2,
            "total_mem_mib": 9.25,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 24.2,
            "total_mem_mib": 9.16,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 26.2,
            "total_mem_mib": 9.2,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 28.2,
            "total_mem_mib": 9.32,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 30.3,
            "total_mem_mib": 9.25,
            "total_cpu_pct": 0.17
          }
        ],
        "memory_stat": {
          "read_at": "2026-10-19T02:59:43Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 4.95,
              "file_mib": 39.81,
              "active_file_mib": 0.0,
              "inactive_file_mib": 37.91,
              "shmem_mib": 1.9
            }
          },
          "read_lag_s": 0.0,
          "method": "docker exec <container> cat /sys/fs/cgroup/memory.stat (cgroup v1: /sys/fs/cgroup/memory/memory.stat); MiB; splits what docker stats shows as one number into anon (heap) and file cache; a field that is absent was not printed by the kernel or not readable (see reason)",
          "taken": "right after the idle window"
        }
      },
      "settle": {
        "settled": true,
        "mode": "detected",
        "settle_seconds_used": 121.0,
        "settle_started_after_healthy_s": 1.5,
        "params": {
          "window_seconds": 60,
          "threshold_pct": 2.0,
          "threshold_min_mib": 10.0,
          "min_seconds": 120,
          "max_seconds": 600
        },
        "last_window": {
          "n": 30,
          "mean_mib": 9.34,
          "min_mib": 9.13,
          "max_mib": 9.54,
          "spread_mib": 0.41,
          "threshold_mib": 10.0
        },
        "rechecks": 0,
        "rule": "settled = over the last window_seconds the stack total varied (max - min) by less than max(threshold_pct of the window mean, threshold_min_mib); not before min_seconds, at the latest max_seconds, both counted from the end of the setup steps; the idle window must also be flat and agree with it. If max_seconds passes without settling, settled=false and the idle window is measured anyway."
      },
      "startup_peak": {
        "per_service_mem_mib": {
          "pihole": 10.02
        },
        "total_mem_mib": 10.02,
        "samples": 61,
        "note": "highest value across docker stats samples (about one every 2-3 s) from healthy until the stack settled; shorter spikes between samples are not seen"
      },
      "timeline": {
        "step_s": 10,
        "t0": "healthy",
        "offsets_s": [
          10,
          20,
          30,
          40,
          50,
          60,
          70,
          80,
          90,
          100,
          110,
          120,
          130,
          140,
          150,
          160
        ],
        "total_mem_mib": [
          9.4,
          9.1,
          9.2,
          9.4,
          9.3,
          9.3,
          9.4,
          9.4,
          9.3,
          9.3,
          9.4,
          9.3,
          9.3,
          9.2,
          9.2,
          9.3
        ],
        "per_service_mem_mib": {
          "pihole": [
            9.4,
            9.1,
            9.2,
            9.4,
            9.3,
            9.3,
            9.4,
            9.4,
            9.3,
            9.3,
            9.4,
            9.3,
            9.3,
            9.2,
            9.2,
            9.3
          ]
        }
      },
      "load": {
        "per_service": {
          "pihole": {
            "samples": 53,
            "mem_mean_mib": 12.32,
            "mem_max_mib": 18.18,
            "cpu_mean_pct": 4.48,
            "cpu_max_pct": 16.53
          }
        },
        "total": {
          "samples": 53,
          "dropped_for_total": 0,
          "mem_mean_mib": 12.32,
          "mem_max_mib": 18.18,
          "cpu_mean_pct": 4.48,
          "cpu_max_pct": 16.53
        },
        "description": "A Python script (standard library only) in a container on the Compose network sends plain UDP DNS queries (type A) to Pi-hole's port 53. The same script and the same 900 names run against AdGuard Home: 500 real popular domains, 300 names from the StevenBlack blocklist that both apps load (so they are blocked and answered 0.0.0.0) and 100 random .com names that do not exist (NXDOMAIN). Three phases, queries sent on a fixed schedule whether or not earlier ones were answered: every name once at 100 queries/s (first-time latency, 9 s); 200 queries/s for 60 s, names drawn in shuffled rounds so most are repeats; then a burst of 1,000 queries/s for 20 s. A query not answered within 2 s is a timeout. The script records queries sent, answered, timeouts, blocked answers and the p50, p95 and p99 latency of first-time and of repeated (cache-expected) queries, and reads the resolver's own query counters before and after. No real clients, no DoH/DoT, no DHCP, no reverse lookups.",
        "duration_s": 102.1,
        "window": "from LAB_MARK load_start (or step start) until 2 s after the step ended",
        "reported_by_script": {
          "dns_load": {
            "target": "pihole",
            "resolver": "pihole:53",
            "protocol": "udp",
            "query_type": "A",
            "domain_list": {
              "names": 900,
              "allow": 500,
              "block": 300,
              "nx": 100,
              "sha256_16": "459be49b880c5849"
            },
            "preflight": {
              "blocked_probe": "doubleclick.net",
              "allowed_probe": "example.org"
            },
            "seed": 20261013,
            "names_left_out_after_cold_pass": {
              "total": 0,
              "by_category": {},
              "by_class": {}
            },
            "query_timeout_s": 2.0,
            "pause_s": 5,
            "phases": [
              {
                "sent": 900,
                "answered": 900,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 525,
                  "blocked": 262,
                  "nxdomain": 113
                },
                "blocked_answers": 262,
                "block_names_not_blocked": 38,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 0,
                "latency_ms": {
                  "first_allow": {
                    "n": 500,
                    "p50": 5.961,
                    "p95": 173.015,
                    "p99": 299.027,
                    "max": 920.409,
                    "mean": 34.412
                  },
                  "first_nx": {
                    "n": 100,
                    "p50": 6.102,
                    "p95": 10.058,
                    "p99": 12.581,
                    "max": 33.82,
                    "mean": 6.946
                  },
                  "cached_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "blocked": {
                    "n": 262,
                    "p50": 0.175,
                    "p95": 0.249,
                    "p99": 0.285,
                    "max": 0.476,
                    "mean": 0.177
                  }
                },
                "phase": "cold",
                "target_qps": 100,
                "planned_seconds": 9.0,
                "achieved_qps": 100.0,
                "send_lag_ms": {
                  "p50": 0.114,
                  "p99": 0.311,
                  "max": 0.431
                },
                "names_not_answered": {
                  "total": 0,
                  "by_category": {},
                  "by_class": {}
                }
              },
              {
                "sent": 12000,
                "answered": 12000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 6998,
                  "blocked": 3487,
                  "nxdomain": 1515
                },
                "blocked_answers": 3487,
                "block_names_not_blocked": 509,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 446,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 6218,
                    "p50": 0.111,
                    "p95": 0.15,
                    "p99": 0.235,
                    "max": 0.518,
                    "mean": 0.11
                  },
                  "cached_nx": {
                    "n": 1340,
                    "p50": 0.105,
                    "p95": 0.135,
                    "p99": 0.168,
                    "max": 1.056,
                    "mean": 0.104
                  },
                  "blocked": {
                    "n": 3487,
                    "p50": 0.102,
                    "p95": 0.129,
                    "p99": 0.167,
                    "max": 1.432,
                    "mean": 0.101
                  }
                },
                "phase": "sustained",
                "target_qps": 200,
                "planned_seconds": 60,
                "achieved_qps": 200.0,
                "send_lag_ms": {
                  "p50": 0.106,
                  "p99": 0.226,
                  "max": 1.577
                }
              },
              {
                "sent": 20000,
                "answered": 20000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 11662,
                  "blocked": 5832,
                  "nxdomain": 2506
                },
                "blocked_answers": 5832,
                "block_names_not_blocked": 847,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 141,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 10964,
                    "p50": 0.099,
                    "p95": 0.129,
                    "p99": 0.202,
                    "max": 1.221,
                    "mean": 0.1
                  },
                  "cached_nx": {
                    "n": 2216,
                    "p50": 0.095,
                    "p95": 0.114,
                    "p99": 0.16,
                    "max": 0.661,
                    "mean": 0.094
                  },
                  "blocked": {
                    "n": 5832,
                    "p50": 0.093,
                    "p95": 0.112,
                    "p99": 0.156,
                    "max": 1.594,
                    "mean": 0.093
                  }
                },
                "phase": "burst",
                "target_qps": 1000,
                "planned_seconds": 20,
                "achieved_qps": 1000.0,
                "send_lag_ms": {
                  "p50": 0.095,
                  "p99": 0.226,
                  "max": 1.862
                }
              }
            ],
            "overall": {
              "sent": 32900,
              "answered": 32900,
              "timeouts": 0,
              "answers_by_class": {
                "answer": 19185,
                "blocked": 9581,
                "nxdomain": 4134
              },
              "blocked_answers": 9581,
              "block_names_not_blocked": 1394,
              "other_names_blocked": 0,
              "repeats_not_expected_cached": 587,
              "latency_ms": {
                "first_allow": {
                  "n": 500,
                  "p50": 5.961,
                  "p95": 173.015,
                  "p99": 299.027,
                  "max": 920.409,
                  "mean": 34.412
                },
                "first_nx": {
                  "n": 100,
                  "p50": 6.102,
                  "p95": 10.058,
                  "p99": 12.581,
                  "max": 33.82,
                  "mean": 6.946
                },
                "cached_allow": {
                  "n": 17182,
                  "p50": 0.103,
                  "p95": 0.142,
                  "p99": 0.208,
                  "max": 1.221,
                  "mean": 0.104
                },
                "cached_nx": {
                  "n": 3556,
                  "p50": 0.098,
                  "p95": 0.126,
                  "p99": 0.166,
                  "max": 1.056,
                  "mean": 0.098
                },
                "blocked": {
                  "n": 9581,
                  "p50": 0.097,
                  "p95": 0.132,
                  "p99": 0.209,
                  "max": 1.594,
                  "mean": 0.098
                }
              }
            },
            "elapsed_s": 99.9,
            "resolver_counters": {
              "before": {
                "queries_total": 2,
                "queries_blocked": 1,
                "queries_forwarded": 1,
                "queries_cached": 0,
                "gravity_domains": 71493
              },
              "after": {
                "queries_total": 32908,
                "queries_blocked": 9582,
                "queries_forwarded": 640,
                "queries_cached": 22686,
                "gravity_domains": 71493
              },
              "delta": {
                "queries_total": 32906,
                "queries_blocked": 9581,
                "queries_forwarded": 639,
                "queries_cached": 22686
              }
            },
            "cache_rule": "a repeat counts as cache-expected when an earlier answer for the name arrived less than half its TTL ago"
          },
          "queries_sent": 32900,
          "queries_answered": 32900,
          "timeouts": 0,
          "blocked_answers": 9581,
          "first_allow_p50_ms": 5.961,
          "first_allow_p95_ms": 173.015,
          "first_allow_p99_ms": 299.027,
          "cached_allow_p50_ms": 0.111,
          "cached_allow_p95_ms": 0.15,
          "cached_allow_p99_ms": 0.235,
          "blocked_p50_ms": 0.102,
          "blocked_p95_ms": 0.129,
          "blocked_p99_ms": 0.167,
          "burst_cached_allow_p50_ms": 0.099,
          "burst_cached_allow_p95_ms": 0.129,
          "burst_cached_allow_p99_ms": 0.202
        },
        "output_tail": [
          "resolver pihole = 172.18.0.2:53; domain list 900 names {'allow': 500, 'block': 300, 'nx': 100}, sha256 459be49b880c5849",
          "cold: sent 900 answered 900 timeouts 0 blocked 262 achieved 100.0 qps; cached allow p50/p95/p99 None/None/None ms; first allow p50 5.961 ms",
          "sustained: sent 12000 answered 12000 timeouts 0 blocked 3487 achieved 200.0 qps; cached allow p50/p95/p99 0.111/0.15/0.235 ms; first allow p50 None ms",
          "burst: sent 20000 answered 20000 timeouts 0 blocked 5832 achieved 1000.0 qps; cached allow p50/p95/p99 0.099/0.129/0.202 ms; first allow p50 None ms"
        ],
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.63,
            "total_cpu_pct": 6.56
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 9.45,
            "total_cpu_pct": 3.46
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.89,
            "total_cpu_pct": 2.76
          },
          {
            "t_offset_s": 6.0,
            "total_mem_mib": 9.61,
            "total_cpu_pct": 2.48
          },
          {
            "t_offset_s": 8.0,
            "total_mem_mib": 9.78,
            "total_cpu_pct": 2.85
          },
          {
            "t_offset_s": 10.0,
            "total_mem_mib": 9.73,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 12.0,
            "total_mem_mib": 9.73,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 14.0,
            "total_mem_mib": 9.84,
            "total_cpu_pct": 0.48
          },
          {
            "t_offset_s": 16.0,
            "total_mem_mib": 10.08,
            "total_cpu_pct": 2.92
          },
          {
            "t_offset_s": 18.0,
            "total_mem_mib": 9.84,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 20.0,
            "total_mem_mib": 9.95,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 22.0,
            "total_mem_mib": 10.09,
            "total_cpu_pct": 2.59
          },
          {
            "t_offset_s": 24.1,
            "total_mem_mib": 10.11,
            "total_cpu_pct": 2.49
          },
          {
            "t_offset_s": 26.1,
            "total_mem_mib": 10.16,
            "total_cpu_pct": 2.53
          },
          {
            "t_offset_s": 28.1,
            "total_mem_mib": 10.27,
            "total_cpu_pct": 2.67
          },
          {
            "t_offset_s": 30.1,
            "total_mem_mib": 10.46,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 32.1,
            "total_mem_mib": 10.69,
            "total_cpu_pct": 2.83
          },
          {
            "t_offset_s": 34.1,
            "total_mem_mib": 10.63,
            "total_cpu_pct": 2.6
          },
          {
            "t_offset_s": 36.1,
            "total_mem_mib": 10.93,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 38.1,
            "total_mem_mib": 10.9,
            "total_cpu_pct": 2.99
          },
          {
            "t_offset_s": 40.1,
            "total_mem_mib": 11.02,
            "total_cpu_pct": 2.66
          },
          {
            "t_offset_s": 42.1,
            "total_mem_mib": 11.06,
            "total_cpu_pct": 2.99
          },
          {
            "t_offset_s": 44.1,
            "total_mem_mib": 11.0,
            "total_cpu_pct": 2.49
          },
          {
            "t_offset_s": 46.1,
            "total_mem_mib": 11.46,
            "total_cpu_pct": 3.21
          },
          {
            "t_offset_s": 48.1,
            "total_mem_mib": 11.54,
            "total_cpu_pct": 2.16
          },
          {
            "t_offset_s": 50.1,
            "total_mem_mib": 11.52,
            "total_cpu_pct": 2.82
          },
          {
            "t_offset_s": 52.1,
            "total_mem_mib": 11.7,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 54.1,
            "total_mem_mib": 11.81,
            "total_cpu_pct": 2.69
          },
          {
            "t_offset_s": 56.1,
            "total_mem_mib": 11.93,
            "total_cpu_pct": 2.88
          },
          {
            "t_offset_s": 58.1,
            "total_mem_mib": 11.94,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 60.1,
            "total_mem_mib": 11.97,
            "total_cpu_pct": 2.59
          },
          {
            "t_offset_s": 62.1,
            "total_mem_mib": 12.65,
            "total_cpu_pct": 2.3
          },
          {
            "t_offset_s": 64.1,
            "total_mem_mib": 11.98,
            "total_cpu_pct": 2.73
          },
          {
            "t_offset_s": 66.2,
            "total_mem_mib": 12.31,
            "total_cpu_pct": 2.7
          },
          {
            "t_offset_s": 68.2,
            "total_mem_mib": 12.32,
            "total_cpu_pct": 3.67
          },
          {
            "t_offset_s": 70.2,
            "total_mem_mib": 12.49,
            "total_cpu_pct": 2.7
          },
          {
            "t_offset_s": 72.2,
            "total_mem_mib": 12.86,
            "total_cpu_pct": 2.81
          },
          {
            "t_offset_s": 74.2,
            "total_mem_mib": 12.66,
            "total_cpu_pct": 1.86
          },
          {
            "t_offset_s": 76.2,
            "total_mem_mib": 13.21,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 78.2,
            "total_mem_mib": 13.05,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 80.2,
            "total_mem_mib": 13.43,
            "total_cpu_pct": 9.62
          },
          {
            "t_offset_s": 82.2,
            "total_mem_mib": 14.4,
            "total_cpu_pct": 11.03
          },
          {
            "t_offset_s": 84.2,
            "total_mem_mib": 14.36,
            "total_cpu_pct": 14.31
          },
          {
            "t_offset_s": 86.2,
            "total_mem_mib": 14.75,
            "total_cpu_pct": 14.16
          },
          {
            "t_offset_s": 88.2,
            "total_mem_mib": 15.43,
            "total_cpu_pct": 16.53
          },
          {
            "t_offset_s": 90.2,
            "total_mem_mib": 15.71,
            "total_cpu_pct": 14.76
          },
          {
            "t_offset_s": 92.2,
            "total_mem_mib": 16.43,
            "total_cpu_pct": 13.31
          },
          {
            "t_offset_s": 94.2,
            "total_mem_mib": 16.66,
            "total_cpu_pct": 14.74
          },
          {
            "t_offset_s": 96.2,
            "total_mem_mib": 18.18,
            "total_cpu_pct": 14.01
          },
          {
            "t_offset_s": 98.2,
            "total_mem_mib": 17.54,
            "total_cpu_pct": 14.05
          },
          {
            "t_offset_s": 100.2,
            "total_mem_mib": 17.96,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 102.2,
            "total_mem_mib": 18.04,
            "total_cpu_pct": 0.2
          },
          {
            "t_offset_s": 104.2,
            "total_mem_mib": 17.96,
            "total_cpu_pct": 0.16
          }
        ],
        "series_start_offset_s": 1.4,
        "peak_note": "peak = highest value seen across docker stats samples (about one every 2-3 s); shorter spikes between samples are not seen",
        "peak_memory_stat": {
          "read_at": "2026-10-19T03:01:21Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 10.79,
              "file_mib": 47.64,
              "active_file_mib": 0.0,
              "inactive_file_mib": 43.93,
              "shmem_mib": 3.71
            }
          },
          "read_lag_s": 0.1,
          "series_index": 48,
          "sample_t_offset_s": 96.2,
          "total_mem_mib": 18.18
        }
      },
      "container_end_state": {
        "pihole": {
          "restart_count": 0,
          "oom_killed": false
        }
      },
      "teardown": {
        "done": true,
        "volumes_removed": true,
        "images_removed": false,
        "error": null
      },
      "warnings": [],
      "finished_at": "2026-10-19T03:01:33Z",
      "elapsed_seconds": 265.1
    },
    {
      "run": 3,
      "started_at": "2026-10-19T03:01:33Z",
      "host": {
        "label": "github-actions ubuntu-latest (2 vCPU, 8 GB)",
        "ci": {
          "runs_on": "ubuntu-latest",
          "runner_environment": "github-hosted",
          "runner_os": "Linux",
          "runner_arch": "X64",
          "image_os": "ubuntu24",
          "image_version": "20261012.334.1"
        },
        "cpu_model": "AMD EPYC 9V74 80-Core Processor",
        "cpu_model_note": null,
        "cores": 2,
        "mem_total_mib": 7938,
        "kernel": "6.17.0-1022-azure",
        "os": "Ubuntu 24.04.5 LTS",
        "arch": "x86_64",
        "oci_arch": "amd64",
        "docker_server_version": "28.0.4",
        "docker_client_version": "28.0.4",
        "compose_client_version": "2.38.2",
        "storage_driver": "overlay2",
        "containerd_image_store": false,
        "source": "docker info + throwaway container reading /proc/cpuinfo on the daemon host",
        "compose_note": "Compose runs on the client machine; Docker server fields are from the daemon host"
      },
      "date": "2026-10-19",
      "startup": {
        "seconds": 4.2,
        "ok": true,
        "checks": [
          {
            "type": "http",
            "url": "http://pihole:80/api/info/login",
            "expect_status": [
              200
            ],
            "ok": true,
            "ready_after_s": 4.2,
            "detail": "OK 200"
          }
        ],
        "up_command_seconds": 0.7,
        "definition": "wall clock from starting `docker compose up -d` until every health check passed; checks poll every 0.5 s from a throwaway container on the compose network, so resolution is about +/- 1 s"
      },
      "versions": {
        "pihole": "2026.10.0"
      },
      "setup": [
        {
          "exit_code": 0,
          "duration_s": 0.6,
          "reported": {
            "versions_reported": {
              "core": null,
              "web": null,
              "ftl": "v6.8",
              "docker": null
            },
            "blocklist": {
              "url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
              "entries_reported": 71493,
              "list_valid_domains": 71493,
              "list_invalid_domains": 1,
              "reported_by": "GET /api/stats/summary, gravity.domains_being_blocked"
            },
            "dns_config": {
              "upstreams": [
                "1.1.1.1"
              ],
              "listeningMode": "ALL",
              "rateLimit": {
                "count": 0,
                "interval": 60
              },
              "blocking": {
                "active": true,
                "mode": "NULL",
                "edns": "TEXT"
              },
              "cache_size": 10000,
              "dnssec": false,
              "queryLogging": true
            }
          },
          "output_tail": [
            "waiting for the Pi-hole API",
            "gravity: 71493 domains; the list itself: 71493 valid, 1 invalid",
            "versions: {\"core\": null, \"web\": null, \"ftl\": \"v6.8\", \"docker\": null}"
          ],
          "description": "Log in to the API with the lab password, check that the one adlist is the default (StevenBlack unified hosts) and that gravity holds its domains, and set the upstream resolver to plain 1.1.1.1, the query rate limit off and blocking mode NULL (blocked names answer 0.0.0.0). Reports how many domains Pi-hole says it loaded."
        }
      ],
      "idle": {
        "per_service": {
          "pihole": {
            "samples": 16,
            "mem_mean_mib": 9.15,
            "mem_max_mib": 9.38,
            "cpu_mean_pct": 0.18,
            "cpu_max_pct": 0.35
          }
        },
        "total": {
          "samples": 16,
          "dropped_for_total": 0,
          "mem_mean_mib": 9.15,
          "mem_max_mib": 9.38,
          "cpu_mean_pct": 0.18,
          "cpu_max_pct": 0.35
        },
        "window_seconds": 30.3,
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.05,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 9.05,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.1,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 6.1,
            "total_mem_mib": 9.33,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 8.1,
            "total_mem_mib": 9.07,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 10.1,
            "total_mem_mib": 8.95,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 12.1,
            "total_mem_mib": 9.03,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 14.1,
            "total_mem_mib": 9.05,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 16.1,
            "total_mem_mib": 9.18,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 18.2,
            "total_mem_mib": 9.01,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 20.2,
            "total_mem_mib": 9.16,
            "total_cpu_pct": 0.35
          },
          {
            "t_offset_s": 22.2,
            "total_mem_mib": 9.33,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 24.2,
            "total_mem_mib": 9.21,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 26.2,
            "total_mem_mib": 9.36,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 28.2,
            "total_mem_mib": 9.1,
            "total_cpu_pct": 0.15
          },
          {
            "t_offset_s": 30.3,
            "total_mem_mib": 9.38,
            "total_cpu_pct": 0.15
          }
        ],
        "memory_stat": {
          "read_at": "2026-10-19T03:04:11Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 4.94,
              "file_mib": 39.81,
              "active_file_mib": 0.0,
              "inactive_file_mib": 37.91,
              "shmem_mib": 1.9
            }
          },
          "read_lag_s": 0.0,
          "method": "docker exec <container> cat /sys/fs/cgroup/memory.stat (cgroup v1: /sys/fs/cgroup/memory/memory.stat); MiB; splits what docker stats shows as one number into anon (heap) and file cache; a field that is absent was not printed by the kernel or not readable (see reason)",
          "taken": "right after the idle window"
        }
      },
      "settle": {
        "settled": true,
        "mode": "detected",
        "settle_seconds_used": 121.0,
        "settle_started_after_healthy_s": 1.5,
        "params": {
          "window_seconds": 60,
          "threshold_pct": 2.0,
          "threshold_min_mib": 10.0,
          "min_seconds": 120,
          "max_seconds": 600
        },
        "last_window": {
          "n": 30,
          "mean_mib": 9.22,
          "min_mib": 9.05,
          "max_mib": 9.46,
          "spread_mib": 0.41,
          "threshold_mib": 10.0
        },
        "rechecks": 0,
        "rule": "settled = over the last window_seconds the stack total varied (max - min) by less than max(threshold_pct of the window mean, threshold_min_mib); not before min_seconds, at the latest max_seconds, both counted from the end of the setup steps; the idle window must also be flat and agree with it. If max_seconds passes without settling, settled=false and the idle window is measured anyway."
      },
      "startup_peak": {
        "per_service_mem_mib": {
          "pihole": 12.67
        },
        "total_mem_mib": 12.67,
        "samples": 61,
        "note": "highest value across docker stats samples (about one every 2-3 s) from healthy until the stack settled; shorter spikes between samples are not seen"
      },
      "timeline": {
        "step_s": 10,
        "t0": "healthy",
        "offsets_s": [
          10,
          20,
          30,
          40,
          50,
          60,
          70,
          80,
          90,
          100,
          110,
          120,
          130,
          140,
          150,
          160
        ],
        "total_mem_mib": [
          9.8,
          9.1,
          9.1,
          9.3,
          9.3,
          9.1,
          9.2,
          9.2,
          9.2,
          9.2,
          9.3,
          9.2,
          9.2,
          9.1,
          9.2,
          9.2
        ],
        "per_service_mem_mib": {
          "pihole": [
            9.8,
            9.1,
            9.1,
            9.3,
            9.3,
            9.1,
            9.2,
            9.2,
            9.2,
            9.2,
            9.3,
            9.2,
            9.2,
            9.1,
            9.2,
            9.2
          ]
        }
      },
      "load": {
        "per_service": {
          "pihole": {
            "samples": 53,
            "mem_mean_mib": 12.15,
            "mem_max_mib": 17.5,
            "cpu_mean_pct": 4.37,
            "cpu_max_pct": 16.38
          }
        },
        "total": {
          "samples": 53,
          "dropped_for_total": 0,
          "mem_mean_mib": 12.15,
          "mem_max_mib": 17.5,
          "cpu_mean_pct": 4.37,
          "cpu_max_pct": 16.38
        },
        "description": "A Python script (standard library only) in a container on the Compose network sends plain UDP DNS queries (type A) to Pi-hole's port 53. The same script and the same 900 names run against AdGuard Home: 500 real popular domains, 300 names from the StevenBlack blocklist that both apps load (so they are blocked and answered 0.0.0.0) and 100 random .com names that do not exist (NXDOMAIN). Three phases, queries sent on a fixed schedule whether or not earlier ones were answered: every name once at 100 queries/s (first-time latency, 9 s); 200 queries/s for 60 s, names drawn in shuffled rounds so most are repeats; then a burst of 1,000 queries/s for 20 s. A query not answered within 2 s is a timeout. The script records queries sent, answered, timeouts, blocked answers and the p50, p95 and p99 latency of first-time and of repeated (cache-expected) queries, and reads the resolver's own query counters before and after. No real clients, no DoH/DoT, no DHCP, no reverse lookups.",
        "duration_s": 102.2,
        "window": "from LAB_MARK load_start (or step start) until 2 s after the step ended",
        "reported_by_script": {
          "dns_load": {
            "target": "pihole",
            "resolver": "pihole:53",
            "protocol": "udp",
            "query_type": "A",
            "domain_list": {
              "names": 900,
              "allow": 500,
              "block": 300,
              "nx": 100,
              "sha256_16": "459be49b880c5849"
            },
            "preflight": {
              "blocked_probe": "doubleclick.net",
              "allowed_probe": "example.org"
            },
            "seed": 20261013,
            "names_left_out_after_cold_pass": {
              "total": 0,
              "by_category": {},
              "by_class": {}
            },
            "query_timeout_s": 2.0,
            "pause_s": 5,
            "phases": [
              {
                "sent": 900,
                "answered": 900,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 525,
                  "blocked": 262,
                  "nxdomain": 113
                },
                "blocked_answers": 262,
                "block_names_not_blocked": 38,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 0,
                "latency_ms": {
                  "first_allow": {
                    "n": 500,
                    "p50": 6.367,
                    "p95": 188.076,
                    "p99": 311.868,
                    "max": 1048.14,
                    "mean": 35.962
                  },
                  "first_nx": {
                    "n": 100,
                    "p50": 6.737,
                    "p95": 10.651,
                    "p99": 12.918,
                    "max": 13.501,
                    "mean": 7.26
                  },
                  "cached_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "blocked": {
                    "n": 262,
                    "p50": 0.17,
                    "p95": 0.223,
                    "p99": 0.257,
                    "max": 0.363,
                    "mean": 0.165
                  }
                },
                "phase": "cold",
                "target_qps": 100,
                "planned_seconds": 9.0,
                "achieved_qps": 100.0,
                "send_lag_ms": {
                  "p50": 0.112,
                  "p99": 0.307,
                  "max": 0.394
                },
                "names_not_answered": {
                  "total": 0,
                  "by_category": {},
                  "by_class": {}
                }
              },
              {
                "sent": 12000,
                "answered": 12000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 6998,
                  "blocked": 3487,
                  "nxdomain": 1515
                },
                "blocked_answers": 3487,
                "block_names_not_blocked": 509,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 459,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 6205,
                    "p50": 0.107,
                    "p95": 0.145,
                    "p99": 0.212,
                    "max": 2.006,
                    "mean": 0.107
                  },
                  "cached_nx": {
                    "n": 1340,
                    "p50": 0.102,
                    "p95": 0.132,
                    "p99": 0.16,
                    "max": 0.357,
                    "mean": 0.101
                  },
                  "blocked": {
                    "n": 3487,
                    "p50": 0.1,
                    "p95": 0.127,
                    "p99": 0.161,
                    "max": 0.968,
                    "mean": 0.098
                  }
                },
                "phase": "sustained",
                "target_qps": 200,
                "planned_seconds": 60,
                "achieved_qps": 200.0,
                "send_lag_ms": {
                  "p50": 0.104,
                  "p99": 0.222,
                  "max": 1.134
                }
              },
              {
                "sent": 20000,
                "answered": 20000,
                "timeouts": 0,
                "answers_by_class": {
                  "answer": 11662,
                  "blocked": 5832,
                  "nxdomain": 2506
                },
                "blocked_answers": 5832,
                "block_names_not_blocked": 847,
                "other_names_blocked": 0,
                "repeats_not_expected_cached": 173,
                "latency_ms": {
                  "first_allow": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "first_nx": {
                    "n": 0,
                    "p50": null,
                    "p95": null,
                    "p99": null,
                    "max": null,
                    "mean": null
                  },
                  "cached_allow": {
                    "n": 10932,
                    "p50": 0.1,
                    "p95": 0.131,
                    "p99": 0.22,
                    "max": 0.865,
                    "mean": 0.1
                  },
                  "cached_nx": {
                    "n": 2216,
                    "p50": 0.096,
                    "p95": 0.114,
                    "p99": 0.173,
                    "max": 1.288,
                    "mean": 0.095
                  },
                  "blocked": {
                    "n": 5832,
                    "p50": 0.094,
                    "p95": 0.112,
                    "p99": 0.145,
                    "max": 2.17,
                    "mean": 0.093
                  }
                },
                "phase": "burst",
                "target_qps": 1000,
                "planned_seconds": 20,
                "achieved_qps": 1000.0,
                "send_lag_ms": {
                  "p50": 0.095,
                  "p99": 0.231,
                  "max": 1.487
                }
              }
            ],
            "overall": {
              "sent": 32900,
              "answered": 32900,
              "timeouts": 0,
              "answers_by_class": {
                "answer": 19185,
                "blocked": 9581,
                "nxdomain": 4134
              },
              "blocked_answers": 9581,
              "block_names_not_blocked": 1394,
              "other_names_blocked": 0,
              "repeats_not_expected_cached": 632,
              "latency_ms": {
                "first_allow": {
                  "n": 500,
                  "p50": 6.367,
                  "p95": 188.076,
                  "p99": 311.868,
                  "max": 1048.14,
                  "mean": 35.962
                },
                "first_nx": {
                  "n": 100,
                  "p50": 6.737,
                  "p95": 10.651,
                  "p99": 12.918,
                  "max": 13.501,
                  "mean": 7.26
                },
                "cached_allow": {
                  "n": 17137,
                  "p50": 0.103,
                  "p95": 0.138,
                  "p99": 0.217,
                  "max": 2.006,
                  "mean": 0.102
                },
                "cached_nx": {
                  "n": 3556,
                  "p50": 0.098,
                  "p95": 0.124,
                  "p99": 0.169,
                  "max": 1.288,
                  "mean": 0.097
                },
                "blocked": {
                  "n": 9581,
                  "p50": 0.096,
                  "p95": 0.127,
                  "p99": 0.202,
                  "max": 2.17,
                  "mean": 0.097
                }
              }
            },
            "elapsed_s": 100.0,
            "resolver_counters": {
              "before": {
                "queries_total": 2,
                "queries_blocked": 1,
                "queries_forwarded": 1,
                "queries_cached": 0,
                "gravity_domains": 71493
              },
              "after": {
                "queries_total": 32905,
                "queries_blocked": 9582,
                "queries_forwarded": 640,
                "queries_cached": 22683,
                "gravity_domains": 71493
              },
              "delta": {
                "queries_total": 32903,
                "queries_blocked": 9581,
                "queries_forwarded": 639,
                "queries_cached": 22683
              }
            },
            "cache_rule": "a repeat counts as cache-expected when an earlier answer for the name arrived less than half its TTL ago"
          },
          "queries_sent": 32900,
          "queries_answered": 32900,
          "timeouts": 0,
          "blocked_answers": 9581,
          "first_allow_p50_ms": 6.367,
          "first_allow_p95_ms": 188.076,
          "first_allow_p99_ms": 311.868,
          "cached_allow_p50_ms": 0.107,
          "cached_allow_p95_ms": 0.145,
          "cached_allow_p99_ms": 0.212,
          "blocked_p50_ms": 0.1,
          "blocked_p95_ms": 0.127,
          "blocked_p99_ms": 0.161,
          "burst_cached_allow_p50_ms": 0.1,
          "burst_cached_allow_p95_ms": 0.131,
          "burst_cached_allow_p99_ms": 0.22
        },
        "output_tail": [
          "resolver pihole = 172.18.0.2:53; domain list 900 names {'allow': 500, 'block': 300, 'nx': 100}, sha256 459be49b880c5849",
          "cold: sent 900 answered 900 timeouts 0 blocked 262 achieved 100.0 qps; cached allow p50/p95/p99 None/None/None ms; first allow p50 6.367 ms",
          "sustained: sent 12000 answered 12000 timeouts 0 blocked 3487 achieved 200.0 qps; cached allow p50/p95/p99 0.107/0.145/0.212 ms; first allow p50 None ms",
          "burst: sent 20000 answered 20000 timeouts 0 blocked 5832 achieved 1000.0 qps; cached allow p50/p95/p99 0.1/0.131/0.22 ms; first allow p50 None ms"
        ],
        "series": [
          {
            "t_offset_s": 0.0,
            "total_mem_mib": 9.64,
            "total_cpu_pct": 6.56
          },
          {
            "t_offset_s": 2.0,
            "total_mem_mib": 9.57,
            "total_cpu_pct": 2.93
          },
          {
            "t_offset_s": 4.0,
            "total_mem_mib": 9.64,
            "total_cpu_pct": 2.72
          },
          {
            "t_offset_s": 6.0,
            "total_mem_mib": 9.74,
            "total_cpu_pct": 2.47
          },
          {
            "t_offset_s": 8.0,
            "total_mem_mib": 9.72,
            "total_cpu_pct": 2.7
          },
          {
            "t_offset_s": 10.0,
            "total_mem_mib": 9.67,
            "total_cpu_pct": 0.2
          },
          {
            "t_offset_s": 12.0,
            "total_mem_mib": 9.82,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 14.0,
            "total_mem_mib": 9.73,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 16.0,
            "total_mem_mib": 10.03,
            "total_cpu_pct": 2.58
          },
          {
            "t_offset_s": 18.0,
            "total_mem_mib": 9.79,
            "total_cpu_pct": 2.61
          },
          {
            "t_offset_s": 20.0,
            "total_mem_mib": 9.82,
            "total_cpu_pct": 2.59
          },
          {
            "t_offset_s": 22.0,
            "total_mem_mib": 10.02,
            "total_cpu_pct": 2.34
          },
          {
            "t_offset_s": 24.1,
            "total_mem_mib": 10.23,
            "total_cpu_pct": 2.53
          },
          {
            "t_offset_s": 26.1,
            "total_mem_mib": 10.39,
            "total_cpu_pct": 2.5
          },
          {
            "t_offset_s": 28.1,
            "total_mem_mib": 10.39,
            "total_cpu_pct": 2.6
          },
          {
            "t_offset_s": 30.1,
            "total_mem_mib": 10.36,
            "total_cpu_pct": 2.32
          },
          {
            "t_offset_s": 32.1,
            "total_mem_mib": 10.52,
            "total_cpu_pct": 2.39
          },
          {
            "t_offset_s": 34.1,
            "total_mem_mib": 10.81,
            "total_cpu_pct": 2.46
          },
          {
            "t_offset_s": 36.1,
            "total_mem_mib": 10.71,
            "total_cpu_pct": 2.51
          },
          {
            "t_offset_s": 38.1,
            "total_mem_mib": 10.6,
            "total_cpu_pct": 2.63
          },
          {
            "t_offset_s": 40.1,
            "total_mem_mib": 10.91,
            "total_cpu_pct": 2.6
          },
          {
            "t_offset_s": 42.1,
            "total_mem_mib": 11.14,
            "total_cpu_pct": 2.55
          },
          {
            "t_offset_s": 44.1,
            "total_mem_mib": 10.83,
            "total_cpu_pct": 2.79
          },
          {
            "t_offset_s": 46.1,
            "total_mem_mib": 10.89,
            "total_cpu_pct": 2.59
          },
          {
            "t_offset_s": 48.1,
            "total_mem_mib": 11.17,
            "total_cpu_pct": 3.32
          },
          {
            "t_offset_s": 50.1,
            "total_mem_mib": 11.21,
            "total_cpu_pct": 2.68
          },
          {
            "t_offset_s": 52.1,
            "total_mem_mib": 11.84,
            "total_cpu_pct": 2.87
          },
          {
            "t_offset_s": 54.1,
            "total_mem_mib": 11.94,
            "total_cpu_pct": 2.62
          },
          {
            "t_offset_s": 56.1,
            "total_mem_mib": 12.05,
            "total_cpu_pct": 2.54
          },
          {
            "t_offset_s": 58.1,
            "total_mem_mib": 11.75,
            "total_cpu_pct": 2.7
          },
          {
            "t_offset_s": 60.1,
            "total_mem_mib": 12.05,
            "total_cpu_pct": 2.65
          },
          {
            "t_offset_s": 62.1,
            "total_mem_mib": 11.92,
            "total_cpu_pct": 2.56
          },
          {
            "t_offset_s": 64.1,
            "total_mem_mib": 12.08,
            "total_cpu_pct": 2.82
          },
          {
            "t_offset_s": 66.1,
            "total_mem_mib": 12.39,
            "total_cpu_pct": 2.99
          },
          {
            "t_offset_s": 68.1,
            "total_mem_mib": 12.38,
            "total_cpu_pct": 2.94
          },
          {
            "t_offset_s": 70.1,
            "total_mem_mib": 12.36,
            "total_cpu_pct": 2.25
          },
          {
            "t_offset_s": 72.2,
            "total_mem_mib": 12.62,
            "total_cpu_pct": 2.82
          },
          {
            "t_offset_s": 74.2,
            "total_mem_mib": 12.58,
            "total_cpu_pct": 2.58
          },
          {
            "t_offset_s": 76.2,
            "total_mem_mib": 12.59,
            "total_cpu_pct": 0.17
          },
          {
            "t_offset_s": 78.2,
            "total_mem_mib": 12.91,
            "total_cpu_pct": 0.18
          },
          {
            "t_offset_s": 80.2,
            "total_mem_mib": 13.03,
            "total_cpu_pct": 13.61
          },
          {
            "t_offset_s": 82.2,
            "total_mem_mib": 13.45,
            "total_cpu_pct": 15.19
          },
          {
            "t_offset_s": 84.2,
            "total_mem_mib": 14.11,
            "total_cpu_pct": 9.49
          },
          {
            "t_offset_s": 86.2,
            "total_mem_mib": 15.18,
            "total_cpu_pct": 13.01
          },
          {
            "t_offset_s": 88.2,
            "total_mem_mib": 15.03,
            "total_cpu_pct": 13.77
          },
          {
            "t_offset_s": 90.2,
            "total_mem_mib": 15.39,
            "total_cpu_pct": 16.38
          },
          {
            "t_offset_s": 92.2,
            "total_mem_mib": 15.89,
            "total_cpu_pct": 13.54
          },
          {
            "t_offset_s": 94.2,
            "total_mem_mib": 17.05,
            "total_cpu_pct": 11.5
          },
          {
            "t_offset_s": 96.2,
            "total_mem_mib": 16.82,
            "total_cpu_pct": 13.28
          },
          {
            "t_offset_s": 98.2,
            "total_mem_mib": 16.94,
            "total_cpu_pct": 14.13
          },
          {
            "t_offset_s": 100.2,
            "total_mem_mib": 17.5,
            "total_cpu_pct": 0.34
          },
          {
            "t_offset_s": 102.2,
            "total_mem_mib": 17.43,
            "total_cpu_pct": 0.16
          },
          {
            "t_offset_s": 104.2,
            "total_mem_mib": 17.39,
            "total_cpu_pct": 0.17
          }
        ],
        "series_start_offset_s": 1.4,
        "peak_note": "peak = highest value seen across docker stats samples (about one every 2-3 s); shorter spikes between samples are not seen",
        "peak_memory_stat": {
          "read_at": "2026-10-19T03:05:53Z",
          "per_service": {
            "pihole": {
              "cgroup": "v2",
              "anon_mib": 10.98,
              "file_mib": 47.95,
              "active_file_mib": 0.0,
              "inactive_file_mib": 43.98,
              "shmem_mib": 3.96
            }
          },
          "read_lag_s": 0.0,
          "series_index": 50,
          "sample_t_offset_s": 100.2,
          "total_mem_mib": 17.5
        }
      },
      "container_end_state": {
        "pihole": {
          "restart_count": 0,
          "oom_killed": false
        }
      },
      "teardown": {
        "done": true,
        "volumes_removed": true,
        "images_removed": false,
        "error": null
      },
      "warnings": [],
      "finished_at": "2026-10-19T03:06:01Z",
      "elapsed_seconds": 267.9
    }
  ],
  "load_defined": true,
  "notes": [
    "Host: a GitHub Actions runner (ubuntu-latest, x86_64, 2 vCPU, 8 GB RAM; the CPU model varies between runners (AMD EPYC and Intel Xeon models so far) and each run records its own). Not a Raspberry Pi: no Arm result, and the numbers are valid for that host only.",
    "Measured: idle RAM and CPU after start-up with the default list loaded and no client; RAM and CPU while one client sends the DNS load of load.description (200 queries/s for 60 s, then 1,000 queries/s for 20 s); image size; start-up time; answer latency seen by that client. Not measured: real clients or a real household's query mix, DNS-over-HTTPS or DNS-over-TLS, DHCP, IPv6, DNSSEC validation, long-term growth of the query database, adlists beyond the default one, Arm hardware.",
    "Fair comparison: both apps use the same upstream resolver (1.1.1.1, plain UDP), the same single blocklist (StevenBlack unified hosts, Pi-hole's default list) and the same blocking answer (0.0.0.0). The upstream resolver's response time varies from run to run, so the first-time latency (which includes it) is not comparable between runs or apps; the latency of repeated queries that the resolver should answer from its cache is. The script counts a repeat as cache-expected when an earlier answer for that name arrived less than half its TTL ago.",
    "Settings changed from the app's defaults, in setup: upstream 1.1.1.1 (default 8.8.8.8 and 8.8.4.4) and the rate limit off (default 1,000 queries per 60 s per client). Left at default: query logging on, cache size 10,000 entries, DNSSEC off, blocking mode NULL. Settings in the published compose: listening mode ALL (needed on Docker's bridge network), password, time zone.",
    "The 'domains loaded' figure is Pi-hole's gravity count (unique domains after de-duplication). AdGuard Home reports rules, which also count a few non-domain lines of the hosts file; the two numbers are close but not the same quantity.",
    "Lab-only compose override compose.lab.yaml (NOT in the published compose): The lab run publishes no host ports (53, 80 and 443 of the published file are not mapped): the runner's systemd-resolved listens on 127.0.0.53:53 and Docker's port 53 publishing can clash with it, and the setup and load steps reach Pi-hole by service name over the Compose network instead. Image, environment, volume and capabilities are the published file's. The published file has not been run with its ports mapped in the lab."
  ],
  "warnings": [],
  "files": {
    "compose": "pihole.compose.yaml",
    "env": "pihole.env"
  },
  "definitions": {
    "memory": "cgroup working set as reported by `docker stats` (usage minus inactive file cache), MiB = 2^20 bytes",
    "cpu": "percent of ONE core as reported by `docker stats` (4 busy cores = 400)",
    "total": "stats over the sum of all containers at the same instant",
    "sample_interval": "docker stats --no-stream takes about 1-2 s per call, so samples are 2-3 s apart",
    "runs": "each run is a full fresh cycle (up, health, setup, settle, idle, load, down -v); the published figure is the median over the runs, shown with the min-max range; see summary.rule"
  }
}
