∿Метрики и
наблюдаемость
Главы темы 1
1.9

Практика: от жалобы к проверяемому выводу

В практике сервис products-api запускается на своём компьютере, проводится эксперимент из двадцати запросов, по сохранённым данным считаются показатели и оформляется отчёт. Для обязательной части нужен только Python 3.12.

Главное в главе

Вывод в отчёте должен опираться на данные, которые другой человек может открыть и пересчитать.

Раздел
Основы наблюдаемости
Уровень
Начальный · Python знаком
Что понадобится
Python 3.12, терминал, браузер; Docker — только для дополнительной части
Результат
Файлы эксперимента и отчёт по бланку расследования

§1Что в архиве

Практика выполняется на том же сервисе, что и все главы темы. Скачайте архив и распакуйте его в отдельную папку, путь к которой не содержит пробелов и русских букв.

Скачать telemetry-lab.zip ↓

Файл или папкаНазначение
app.pyСервис products-api: /products, /health, /events, /metrics, /lab
console.htmlСтраница стенда, которую отдаёт адрес /lab
experiment.pyСкрипт эксперимента: 20 запросов и сохранение результатов
requirements.txtЗависимости: FastAPI 0.115.6 и Uvicorn 0.34.0
reference-results/Результаты авторского прогона для сравнения
investigation.mdБланк отчёта о расследовании
compose.yaml, prometheus.yml, grafana/Prometheus и Grafana для дополнительной части
README.mdКраткая инструкция по запуску

Если виртуальные окружения и установка зависимостей пока непривычны, сначала прочитайте материал о структуре Python-проекта. Для практики достаточно помнить одно: сервер работает в одном терминале, а скрипт эксперимента запускается во втором.

Работа идёт в таком порядке. На этапе A выполняются три ручных запроса. На этапе B скрипт проводит эксперимент из двадцати запросов. Затем по сохранённым файлам выполняются задания с расчётами. На этапе C в эксперименте меняется одна величина, и прогон повторяется. В конце заполняется бланк investigation.md. Дополнительная часть с Prometheus и Grafana выполняется по желанию.

Архив распакован. Первый шаг — запустить сервис.

§2Запуск сервиса

Откройте терминал в папке, куда распаковали архив. Выберите свою операционную систему и выполните команды по одной.

Терминал · macOS и Linux
cd telemetry-lab
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -m uvicorn app:app --host 127.0.0.1 --port 8015
PowerShell · Windows
cd telemetry-lab
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -m uvicorn app:app --host 127.0.0.1 --port 8015
Команда (macOS и Linux)Что делает
cd telemetry-labПерейти в папку проекта
python3 -m venv .venvСоздать виртуальное окружение в папке .venv. Библиотеки проекта установятся туда, системный Python не изменится
source .venv/bin/activateАктивировать окружение. После этого команда python указывает на Python из .venv
python -m pip install -r requirements.txtУстановить зависимости из файла
python -m uvicorn app:app --host 127.0.0.1 --port 8015Запустить сервер. app:app означает «объект app из файла app.py»

В PowerShell окружение не активируют: к его Python обращаются по полному пути .\.venv\Scripts\python.exe. Так команды работают, даже если политика выполнения скриптов запрещает Activate.ps1.

Вывод в терминале · сервер запущен
INFO:     Started server process [57038]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8015 (Press CTRL+C to quit)

Последняя строка означает, что сервер готов. Номер процесса в квадратных скобках у вас будет другим. Терминал оставьте открытым: если его закрыть, сервер остановится. Остановить сервер штатно можно сочетанием Ctrl+C.

Сервер работает. Прежде чем отправлять запросы, посмотрите, какие адреса и параметры у него есть.

§3Знакомство с API через Swagger

Откройте в браузере http://127.0.0.1:8015/docs. FastAPI строит эту страницу по коду обработчиков.

Рис. 1.9.1 документация Swagger с раскрытым /products

Раздел GET /products раскрыт: видны параметр scenario и два возможных ответа.

  1. Теги. маршруты сгруппированы по тегам из кода: «Проверка процесса», «Эксперимент», «Журнал событий».
  2. GET /products. раскрытый раздел маршрута.
  3. Try it out. выполнить настоящий запрос прямо со страницы.
  4. scenario (query). параметр типа string в строке запроса.
  5. Available values. допустимые значения и значение по умолчанию.
  6. 200. обычный ответ; пример "string" не показывает структуру ответа.
  7. 422. ответ при недопустимом значении параметра; его FastAPI добавляет сам.
Открыть в полном размере ↗

В таблице Parameters один параметр scenario типа string, он передаётся в строке запроса — отметка (query). Строка Available values: fast, slow, error взята из аннотации Literal["fast", "slow", "error"] в коде, Default value: fast — из значения по умолчанию.

В разделе Responses два кода. 200 Successful Response — обычный ответ. Пример "string" ничего не сообщает: обработчик возвращает объект Response напрямую, и FastAPI не может вывести его структуру из кода. 422 Validation Error FastAPI добавляет сам: такой ответ придёт, если передать недопустимое значение, например scenario=medium.

Адреса /metrics в документации нет: в коде для него указано include_in_schema=False.

Адреса и параметр известны. На этапе A три запроса отправляются вручную.

§4Этап A. Три ручных запроса

Откройте http://127.0.0.1:8015/lab. Нажмите по очереди «Быстрый 200», «Медленный 200» и «Ошибка 500». После каждого нажатия перенесите данные в таблицу.

СценарийHTTPКлиент, мсОбработчик, мсСамый долгий этапПервые 8 символов request_id
fast
slow
error

Затем откройте http://127.0.0.1:8015/health и запишите ответ. Объясните в отчёте, почему он успешен, хотя последний запрос к /products завершился ошибкой.

Теперь попробуйте по трём ручным запросам ответить на вопросы из жалобы: сколько запросов было за последний час, какая их доля медленная, стал ли сервис медленнее, чем вчера. Ответов нет: три запроса — это три опыта, а истории стенд не хранит. Запишите это в отчёт как ограничение. В расследовании нехватку данных фиксируют явно и не заменяют её предположением.

Трёх запросов для ответа на жалобу недостаточно. Этап B даёт выборку из двадцати.

§5Этап B. Эксперимент из двадцати запросов

Откройте второй терминал в той же папке telemetry-lab. Сервер в первом терминале должен продолжать работать. Запустите скрипт тем же Python из окружения.

Терминал · второе окно
# macOS / Linux
.venv/bin/python experiment.py

# Windows PowerShell
.\.venv\Scripts\python.exe experiment.py

Скрипт по очереди отправляет двадцать запросов. Сценарии заданы в коде: запросы 7 и 17 медленные, запрос 13 — с ошибкой. Ответ 500 не прерывает эксперимент: скрипт перехватывает HTTPError и записывает его как обычный результат.

Вывод в терминале · авторский прогон
01 fast  HTTP 200 93.5 ms
02 fast  HTTP 200 84.2 ms
03 fast  HTTP 200 85.8 ms
04 fast  HTTP 200 85.7 ms
05 fast  HTTP 200 86.1 ms
06 fast  HTTP 200 87.4 ms
07 slow  HTTP 200 3026.2 ms
08 fast  HTTP 200 86.1 ms
09 fast  HTTP 200 85.6 ms
10 fast  HTTP 200 84.7 ms
11 fast  HTTP 200 85.3 ms
12 fast  HTTP 200 85.2 ms
13 error HTTP 500 78.2 ms
14 fast  HTTP 200 84.9 ms
15 fast  HTTP 200 84.1 ms
16 fast  HTTP 200 83.8 ms
17 slow  HTTP 200 3024.2 ms
18 fast  HTTP 200 86.5 ms
19 fast  HTTP 200 85.8 ms
20 fast  HTTP 200 86.0 ms
{
  "total": 20,
  "errors": 1,
  "slow_over_1000ms": 2,
  "mean_ms": 379.47,
  "p95_nearest_rank_ms": 3024.239,
  "method": "client wall time, all statuses, sequential requests; nearest rank p95"
}

Коды ответов и порядок сценариев у вас совпадут с авторским прогоном, а миллисекунды будут другими: на длительность влияют скорость компьютера, другие программы и планировщик операционной системы. Сравнивайте закономерность: точные числа совпадать не должны.

После прогона в папке results появляются три файла. В requests.csv двадцать строк: номер, сценарий, код, client_ms, handler_ms и request_id. В summary.json записан итог: количества, среднее и p95 методом ближайшего ранга. В events.json — записи о событиях тех же двадцати запросов с этапами и traceback.

Скрипт берёт события из /events и оставляет только те, чей request_id есть в CSV. Поэтому ручные запросы из этапа A в файл не попадут, хотя сервер их тоже запомнил.

Данные сохранены в трёх файлах. Задания следующего раздела выполняются по ним.

§6Задания по данным

Все ответы записывайте в отчёт с указанием, из какого файла взяты данные и как посчитан результат.

  1. Проверить файл

    Откройте requests.csv. Убедитесь, что строк двадцать, медленные запросы — седьмой и семнадцатый, ошибочный — тринадцатый.

  2. Посчитать доли

    Долю ответов 500 и долю запросов дольше 1000 мс. Для каждой доли укажите числитель и знаменатель.

  3. Сравнить среднее и p95

    Возьмите их из summary.json. Объясните, почему среднее не похоже ни на один запрос прогона.

  4. Найти медиану

    Программой или вручную по отсортированному столбцу времени клиента.

  5. Разность времён

    Для каждой строки вычислите client_ms − handler_ms и объясните, из чего она складывается.

  6. Медленный запрос

    Найдите в events.json его запись по request_id. Выпишите этапы и проверьте, что их сумма близка к duration_ms.

  7. Ошибочный запрос

    Найдите его запись. Прочитайте traceback снизу вверх и укажите тип исключения, файл и строку.

Первый прогон разобран. Этап C проверяет, как изменятся показатели, если изменить одну величину.

§7Этап C. Изменение одной величины

В опыте меняют одну величину за раз, иначе нельзя сказать, какое изменение вызвало результат.

Было

Два медленных запроса

Python · experiment.py
scenarios[6] = scenarios[16] = "slow"
Стало

Один медленный запрос

Python · experiment.py
scenarios[6] = "slow"

Индексы в Python начинаются с нуля, поэтому scenarios[6] — седьмой запрос. После замены медленным останется только он.

  1. Сохранить первый прогон

    Переименуйте папку results, например в results-run1. Скрипт перезаписывает файлы, и без этого первый прогон пропадёт.

  2. Записать прогноз

    До запуска запишите в отчёт, как изменятся доля медленных запросов, среднее, медиана и p95.

  3. Запустить и сравнить

    Запустите скрипт и сравните прогноз с новым summary.json.

Метод расчёта, число запросов и длительность медленного сценария не меняйте.

Обязательная часть закончена. Следующий раздел необязательный: тот же сервис с Prometheus и Grafana.

§8Дополнительно: тот же сервис в Prometheus и Grafana

Эта часть показывает на вашем компьютере путь метрики из главы 1.8. Для неё нужен Docker Desktop или Docker Engine с Compose.

  1. Перезапустить API

    Остановите сервер в первом терминале (Ctrl+C) и запустите снова с адресом --host 0.0.0.0, чтобы контейнер Prometheus мог к нему обратиться.

  2. Запустить стенд

    Во втором терминале выполните docker compose up -d в папке telemetry-lab.

  3. Проверить цель

    Откройте http://127.0.0.1:9095/targets и дождитесь состояния UP у цели telemetry-lab.

  4. Дать нагрузку

    Запустите experiment.py два-три раза подряд.

  5. Сверить числа

    Откройте http://127.0.0.1:3015/d/telemetry-intro и сравните число на панели «Завершённые запросы» с суммой рядов lab_http_requests_total в Prometheus.

  6. Остановить

    Выполните docker compose down.

Число на панели будет больше, чем строк во всех ваших CSV. Счётчик приложения учитывает все запросы к /products: из эксперимента, со страницы стенда и из Swagger. CSV описывает только один прогон скрипта. Это разные области учёта, ошибки сбора здесь нет.

Осталось оформить результаты.

§9Отчёт и критерии проверки

Результат практики — заполненный бланк investigation.md ↓ и папка с результатами обоих прогонов. Разделы бланка совпадают с отчётом из главы 1.7.

КритерийЧто проверяется
ВоспроизводимостьУказаны команды запуска; другой человек повторит опыт по отчёту
Исходные данныеПриложены CSV и JSON обоих прогонов, единицы измерения указаны
РасчётыДоли, среднее, медиана и p95 посчитаны по одной выборке, указаны числители и знаменатели
Связь сигналовДля медленного и ошибочного запросов связаны ответ, запись о событии и этапы
ОграниченияНазваны смоделированная зависимость, один процесс и размер выборки
Этап CЗаписан прогноз до запуска и сравнение с результатом
Пример

Одно наблюдение, три записи в отчёте

Данные во всех трёх случаях одни и те же: файл requests.csv авторского прогона, двадцать строк.

Запись в отчётеОценка
«Сервис работает медленно»Не принимается. Не названы выборка, величина и порог; проверить нельзя, и по двадцати запросам это не следует из данных
«Средняя длительность 379 мс»Число верное, но одно среднее скрывает, что такой длительности нет ни у одного запроса прогона
«2 из 20 запросов прогона (10%) заняли больше 1000 мс; порог выбран для этого опыта; источник — results/requests.csv, столбец client_ms»Принимается. Названы выборка, величина, порог, единица и файл; другой человек повторит расчёт и получит то же число

Принятая запись отличается тремя вещами: указана область учёта, указана единица измерения и назван файл с исходными данными. Те же требования стоят в критериях выше и в отчёте главы 1.7.

Если на каком-то шаге возникла ошибка, причины и действия собраны в последнем разделе.

§10Если что-то не запускается

Сообщение или симптомПричинаЧто сделать
No module named fastapiЗависимости установлены в другой PythonЗапускать Uvicorn тем же Python, которым выполнялся pip install: из .venv
Address already in useПорт 8015 занят другой программойЗапустить сервер на другом порту и передать скрипту --base http://127.0.0.1:НОВЫЙ_ПОРТ
Connection refused в experiment.pyСервер не запущен или слушает другой адресПроверить первый терминал и совпадение порта
py не найден в PowerShellPython установлен без py launcherИспользовать полный путь к python.exe версии 3.12
Пустая история после перезапуска сервераСобытия хранятся в памяти процессаИспользовать CSV и JSON, сохранённые на диске
Цель DOWN в PrometheusAPI запущен с --host 127.0.0.1Перезапустить API с --host 0.0.0.0

§11Частые ошибки

Что делаютЧто получаетсяКак правильно
Закрывают терминал с серверомСкрипт получает Connection refusedДержать сервер в первом терминале, скрипт запускать во втором
Запускают скрипт системным Python вместо .venvРаботает не тот интерпретатор, возможны ошибки импортаУказывать путь .venv/bin/python или .\.venv\Scripts\python.exe
Требуют совпадения миллисекунд с авторским прогономИщут ошибку там, где её нетСравнивать коды, порядок сценариев и соотношения длительностей
Перезапускают эксперимент без сохранения результатовПервый прогон перезаписанПереименовать папку results перед повторным запуском
Меняют сразу несколько параметров опытаНельзя понять, что вызвало изменениеМенять одну величину и записывать прогноз до запуска
Делают выводы о сервисе в целом по двадцати запросамВывод не следует из данныхНазывать ограничения опыта в отчёте

§12Проверьте себя

Почему experiment.py запускают во втором терминале?

В первом терминале работает сервер Uvicorn и занимает его до остановки. Скрипт — отдельная программа-клиент, ей нужен свой терминал.

Как изменятся доля медленных запросов и p95, если медленным оставить только седьмой запрос?

Доля медленных станет 1 из 20 = 5%. p95 — это 19-е значение из 20; единственный медленный запрос окажется 20-м, поэтому p95 станет длительностью самого долгого быстрого запроса, около 90 мс.

В CSV двадцать запросов, а панель Grafana показывает 45. Где ошибка?

Ошибки нет. Счётчик приложения учитывает все запросы к /products с момента запуска, включая страницу стенда и Swagger. CSV содержит только один прогон скрипта.

Почему в отчёте нужно писать, что зависимость смоделирована через asyncio.sleep?

Иначе читатель решит, что измерена работа настоящей базы данных. Ожидание задано в коде константой, поэтому по прогону нельзя судить ни о скорости базы, ни о поведении под нагрузкой. Названная граница опыта отделяет измеренное от предположенного.

Что означает отметка (query) у параметра scenario в Swagger?

Параметр передаётся в строке запроса после знака вопроса: /products?scenario=slow.

§13Шпаргалка

  • Сервер: python -m uvicorn app:app --host 127.0.0.1 --port 8015 в первом терминале.
  • Эксперимент: .venv/bin/python experiment.py во втором терминале.
  • Результаты: results/requests.csv, summary.json, events.json.
  • Сравнивают коды и закономерности; точные миллисекунды совпадать не должны.
  • Перед повторным прогоном сохранить папку results под другим именем.
  • Одна изменённая величина и прогноз до запуска.
  • Для Prometheus API запускают с --host 0.0.0.0.
  • В отчёте обязательно названы ограничения опыта.

Первоисточники