Практика: от жалобы к проверяемому выводу
В практике сервис products-api запускается на своём компьютере, проводится эксперимент из двадцати запросов, по сохранённым данным считаются показатели и оформляется отчёт. Для обязательной части нужен только Python 3.12.
Вывод в отчёте должен опираться на данные, которые другой человек может открыть и пересчитать.
- Раздел
- Основы наблюдаемости
- Уровень
- Начальный · Python знаком
- Что понадобится
- Python 3.12, терминал, браузер; Docker — только для дополнительной части
- Результат
- Файлы эксперимента и отчёт по бланку расследования
§1Что в архиве
Практика выполняется на том же сервисе, что и все главы темы. Скачайте архив и распакуйте его в отдельную папку, путь к которой не содержит пробелов и русских букв.
| Файл или папка | Назначение |
|---|---|
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Запуск сервиса
Откройте терминал в папке, куда распаковали архив. Выберите свою операционную систему и выполните команды по одной.
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 8015cd 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 строит эту страницу по коду обработчиков.
Раздел GET /products раскрыт: видны параметр scenario и два возможных ответа.
- Теги. маршруты сгруппированы по тегам из кода: «Проверка процесса», «Эксперимент», «Журнал событий».
- GET /products. раскрытый раздел маршрута.
- Try it out. выполнить настоящий запрос прямо со страницы.
- scenario (query). параметр типа string в строке запроса.
- Available values. допустимые значения и значение по умолчанию.
- 200. обычный ответ; пример
"string"не показывает структуру ответа. - 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Задания по данным
Все ответы записывайте в отчёт с указанием, из какого файла взяты данные и как посчитан результат.
- Проверить файл
Откройте
requests.csv. Убедитесь, что строк двадцать, медленные запросы — седьмой и семнадцатый, ошибочный — тринадцатый. - Посчитать доли
Долю ответов 500 и долю запросов дольше 1000 мс. Для каждой доли укажите числитель и знаменатель.
- Сравнить среднее и p95
Возьмите их из
summary.json. Объясните, почему среднее не похоже ни на один запрос прогона. - Найти медиану
Программой или вручную по отсортированному столбцу времени клиента.
- Разность времён
Для каждой строки вычислите
client_ms − handler_msи объясните, из чего она складывается. - Медленный запрос
Найдите в
events.jsonего запись поrequest_id. Выпишите этапы и проверьте, что их сумма близка кduration_ms. - Ошибочный запрос
Найдите его запись. Прочитайте traceback снизу вверх и укажите тип исключения, файл и строку.
Первый прогон разобран. Этап C проверяет, как изменятся показатели, если изменить одну величину.
§7Этап C. Изменение одной величины
В опыте меняют одну величину за раз, иначе нельзя сказать, какое изменение вызвало результат.
Два медленных запроса
scenarios[6] = scenarios[16] = "slow"Один медленный запрос
scenarios[6] = "slow"Индексы в Python начинаются с нуля, поэтому scenarios[6] — седьмой запрос. После замены медленным останется только он.
- Сохранить первый прогон
Переименуйте папку
results, например вresults-run1. Скрипт перезаписывает файлы, и без этого первый прогон пропадёт. - Записать прогноз
До запуска запишите в отчёт, как изменятся доля медленных запросов, среднее, медиана и p95.
- Запустить и сравнить
Запустите скрипт и сравните прогноз с новым
summary.json.
Метод расчёта, число запросов и длительность медленного сценария не меняйте.
Обязательная часть закончена. Следующий раздел необязательный: тот же сервис с Prometheus и Grafana.
§8Дополнительно: тот же сервис в Prometheus и Grafana
Эта часть показывает на вашем компьютере путь метрики из главы 1.8. Для неё нужен Docker Desktop или Docker Engine с Compose.
- Перезапустить API
Остановите сервер в первом терминале (Ctrl+C) и запустите снова с адресом
--host 0.0.0.0, чтобы контейнер Prometheus мог к нему обратиться. - Запустить стенд
Во втором терминале выполните
docker compose up -dв папкеtelemetry-lab. - Проверить цель
Откройте
http://127.0.0.1:9095/targetsи дождитесь состояния UP у целиtelemetry-lab. - Дать нагрузку
Запустите
experiment.pyдва-три раза подряд. - Сверить числа
Откройте
http://127.0.0.1:3015/d/telemetry-introи сравните число на панели «Завершённые запросы» с суммой рядовlab_http_requests_totalв Prometheus. - Остановить
Выполните
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 не найден в PowerShell | Python установлен без py launcher | Использовать полный путь к python.exe версии 3.12 |
| Пустая история после перезапуска сервера | События хранятся в памяти процесса | Использовать CSV и JSON, сохранённые на диске |
| Цель DOWN в Prometheus | API запущен с --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.
- В отчёте обязательно названы ограничения опыта.