MEASUREMENT
bench/deadlines/practice.py
The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.
- Cited in
- /en/interview/sre/timeouts-deadlines
- How to run it
python3 bench/deadlines/chain.py > bench/deadlines/runs/chain.txt python3 bench/deadlines/practice.py > bench/deadlines/runs/practice.txt
The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.
Record of the run
Замеры для урока «Таймауты и дедлайны»
| Файл | Что делает |
|---|---|
chain.py |
цепочка из трёх сервисов на loopback: что происходит с работой после ухода клиента без бюджета и с бюджетом, и почему таймауты по цепочке не складываются в таймаут клиента |
practice.py |
ответы к задачам урока: ответ клиенту в обоих режимах и число сервисов, взявшихся за работу |
Запуск из корня репозитория:
python3 bench/deadlines/chain.py > bench/deadlines/runs/chain.txt
python3 bench/deadlines/practice.py > bench/deadlines/runs/practice.txt
Что воспроизводимо
Счётчики: без бюджета работу доводят до конца все три сервиса, с бюджетом — один, а два отказываются. И то, что суммарное время цепочки больше таймаута любого отдельного шага.
Не воспроизводимо: миллисекунды. Выдержку 400 мс задаёт сам скрипт, поэтому измерять её — измерять собственную константу; содержательны счётчики.
Требования к среде
Только loopback и право открывать порты. Сервисы поднимаются потоками внутри одного процесса и закрываются вместе с ним.
Числа сняты на CPython 3.11.15, Linux 6.18.44.
Script
86 lines"""Практика к уроку про таймауты и дедлайны: три ответа и одна цена.
ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Задачи урока проверяются сборкой
(scripts/validate-practice.mjs): показанный читателю код обязан построчно быть
в скрипте, верный вариант — дословно встречаться в записи прогона, а
заявленное число — печататься самой программой.
ЧТО ЗДЕСЬ ПРОВЕРЯЕТСЯ. Одна и та же цепочка из трёх сервисов вызывается
дважды: без бюджета и с бюджетом. Ответ клиенту в двух случаях разный, но
интереснее другое — сколько работы сделано после того, как клиент ушёл.
Сервисы берутся из `chain.py`, чтобы задача и разбор урока стояли на одном и
том же коде, а не на двух похожих.
ЗАПУСК: python3 bench/deadlines/practice.py
Вывод: runs/practice.txt
"""
import os
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from chain import CLIENT_TIMEOUT, WORK_MS, Service, call # noqa: E402
def chain_of_three() -> tuple[Service, Service, Service]:
tail = Service(work_ms=WORK_MS)
middle = Service(work_ms=WORK_MS, downstream_port=tail.port)
front = Service(work_ms=WORK_MS, downstream_port=middle.port)
for svc in (tail, middle, front):
svc.start()
time.sleep(0.1)
return tail, middle, front
def run(budget_ms: int) -> tuple[str, int, int, int]:
"""Ответ клиенту, число выполнивших работу, число отказавших не начиная и
число тех, кого вообще не позвали.
ПОЧЕМУ ТРИ ЧИСЛА, А НЕ ДВА. Суммарный счётчик отказов считает и отказ
браться за работу, и отказ, который вызывающий передал дальше, получив
`deadline` от следующего. Для урока это разные события, и складывать их
в одно число значит рассказывать про прогон то, чего в нём нет.
"""
tail, middle, front = chain_of_three()
answer = call(front.port, budget_ms=budget_ms, timeout=CLIENT_TIMEOUT)
time.sleep(1.6)
done = tail.completed + middle.completed + front.completed
refused_start = tail.refused_start + middle.refused_start + front.refused_start
never_called = sum(1 for svc in (tail, middle, front) if svc.called == 0)
return answer, done, refused_start, never_called
def main() -> None:
print(f"Python {sys.version.split()[0]} · Linux {os.uname().release} · loopback")
print()
# --- Часть 1: три ответа о поведении --------------------------------
budget = int(CLIENT_TIMEOUT * 1000)
without_deadline, worked_without, _, _ = run(budget_ms=-1)
with_deadline, worked_with, refused, uncalled = run(budget_ms=budget)
print(without_deadline)
print(worked_without)
print(with_deadline)
# --- Часть 2: сколько работы сделано впустую -------------------------
#
# ПОЧЕМУ СЧЁТ СЕРВИСОВ, А НЕ ВРЕМЯ. Миллисекунды здесь задаёт сам скрипт
# (каждый сервис «работает» 400 мс), поэтому измерять их — измерять
# собственную константу. А вот сколько звеньев цепочки взялись за работу
# после ухода клиента — свойство механизма: без бюджета не знает никто,
# с бюджетом знают все.
print()
print(f"client timeout, ms {budget}")
print(f"work each service does, ms {WORK_MS}")
print(f"services that worked without budget {float(worked_without):.1f}")
print(f"services that worked with budget {float(worked_with):.1f}")
print(f"services that refused before start {refused}")
print(f"services never called at all {uncalled}")
if __name__ == "__main__":
main()