Записывает, как код на самом деле исполнялся: вызовы, доводы, результаты, исключения, длительности
Настоящий файл debug.info целиком, снятый с прогона из
README. Программа —
двадцать строк на Python, три функции, два вызова верхнего уровня: один удачный,
второй с делением на ноль.
{"p":"in","t":"2026-08-28T23:39:45.166","id":"23192bfc-8625-453d-bd48-1af02ceec638","ci":-1,"th":"2864987.129949101195776","fn":"report","a":"['get 12', 'put 30', 'get 18']","k":""}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"9f93c6ea-f76f-4768-ab35-cb41731e05f1","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'get 12'","k":""}
{"p":"out","id":"9f93c6ea-f76f-4768-ab35-cb41731e05f1","fn":"parse_line","r":"('get', 12)","d":2e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"4fc1254d-6b4f-451f-8e5f-ddb4b1597ca9","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'put 30'","k":""}
{"p":"out","id":"4fc1254d-6b4f-451f-8e5f-ddb4b1597ca9","fn":"parse_line","r":"('put', 30)","d":2e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"73eba9cc-20ae-4bd4-92d8-4e50e6dc2376","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'get 18'","k":""}
{"p":"out","id":"73eba9cc-20ae-4bd4-92d8-4e50e6dc2376","fn":"parse_line","r":"('get', 18)","d":1e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"e668ee33-6d35-4bb8-9f96-cb44f488c93f","ci":-1,"th":"2864987.129949101195776","fn":"average","a":"[12, 30, 18]","k":""}
{"p":"out","id":"e668ee33-6d35-4bb8-9f96-cb44f488c93f","fn":"average","r":"20.0","d":2e-06}
{"p":"out","id":"23192bfc-8625-453d-bd48-1af02ceec638","fn":"report","r":"20.0","d":0.000346}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"cb4e33f4-d0b2-4099-bcf2-044e4a88c1fa","ci":-1,"th":"2864987.129949101195776","fn":"report","a":"[]","k":""}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"ef71eb89-6727-4cdf-a3b7-2ea54cff81e3","ci":-1,"th":"2864987.129949101195776","fn":"average","a":"[]","k":""}
{"p":"out","id":"ef71eb89-6727-4cdf-a3b7-2ea54cff81e3","fn":"average","x":"ZeroDivisionError: division by zero","d":3e-06}
{"p":"out","id":"cb4e33f4-d0b2-4099-bcf2-044e4a88c1fa","fn":"report","x":"ZeroDivisionError: division by zero","d":6.8e-05}
14 строк, 1887 байт, 7 вызовов — около 270 байт на вызов.
Обратите внимание на порядок: строка входа report стоит первой, а её строка
выхода — десятой, после всего, что report успел позвать. Вложенность видна
по тому, какие вызовы попали между входом и выходом.
OUROBOROS_DEBUG_INFO. Не задана — файл
./debug.info в рабочем каталоге процесса.ouroboros execute подставляет OUROBOROS_DEBUG_INFO равным
<черновик>/debug.info перед запуском — поэтому обмазанный код на любом языке
пишет в один и тот же файл.| ключ | на какой строке | что значит |
|---|---|---|
p |
на обеих | вход ("in") или завершение ("out") |
t |
вход | время входа: местное ISO-8601 с точностью до миллисекунды |
id |
на обеих | UUIDv4, свой у каждого вызова; он и связывает вход с выходом |
ci |
вход | номер ядра процессора; -1 = «неизвестно» |
th |
вход | метка потока; у Python — <процесс>.<номер потока> |
fn |
на обеих | полное имя функции — нарочно на обеих строках |
a |
вход | доводы по позиции, снятые при входе, до тела |
k |
вход | именованные доводы как имя=значение; где их не бывает — пустая строка |
r |
выход | результат при обычном возврате. Не бывает вместе с x |
x |
выход | <Тип>: <сообщение> при ошибке. Не бывает вместе с r. У C исключений нет, и в его записях бывает только r |
d |
выход | длительность в секундах, числом; с монотонных часов. У завершения с исключением d тоже есть |
Ключи нарочно короткие: строк много, и лишние байты в каждой дорого обходятся на объёмных записях.
Три решения из этой таблицы стоят того, чтобы их назвать отдельно.
fn на обеих строках — нарочно. Если запись оборвалась и строка входа
потерялась, оторванная строка выхода всё равно говорит, что вернулось.
Доводы снимаются при входе, до тела. Проверено: функция, которая дописывает
в переданный ей список, всё равно записала [1, 2] — то, с чем её позвали, — а
не то, во что она список превратила.
Результат снимается до того, как уйдёт наружу. Помощник забирает его и записывает прежде, чем отдать вызывающему.
Строка входа, у которой нет парной строки выхода с тем же id, означает,
что вызов вошёл и не вернулся — зависание, падение или жёсткий выход.
Искать непарные id руками не нужно: читалка это уже сделала, и они лежат в поле
in_flight каждого ответа. Это ровно тот ответ, которого не даёт ни один способ
записи «одной строкой по завершении»: вызов, который не завершился, в такой
записи просто отсутствует и неотличим от вызова, которого не было.
Длительность при этом тоже не считается вычитанием: она стоит готовой в поле d.
ouroboros execute добавляет по одной служебной записи на запущенную команду —
правильную строку JSON, у которой p не in и не out:
{"p":"exec","cmd":["python3","stats.py"],"rc":0,"out":"20.0\n","err":""}
Так debug.info остаётся однородным JSONL и единственным местом, где написано,
что запускалось и чем кончилось.
Разбор пропускает любую правильную строку JSON, у которой p не in и не
out. Испорченными (malformed) считаются только строки, которые не
разбираются как JSON, или порванные — например от сбойного захвата с
последовательного порта при работе в ядре системы.
Длинные значения ограничивает короткое изображение значения: у Python это
reprlib с maxstring = maxother = 200, а списки, словари и наборы обрезаются
до первых десяти элементов
(ouroboros/runtime.py:53).
Остальные языки держатся тех же пределов.
Значение не обрезается «как-нибудь» и не восстанавливается по остатку. Соседний
случай — <Foo object at 0x…>: это записано, но это тождество объекта, а не его
значение.
Схема у восьми языков одна. Диалекты — как язык изображает значение, как пишет полное имя, как печатает число — нарочно оставлены родными.
Что это значит на практике — на странице Языки.
Веток, в которые не заходили. Записи описывают прогон, а не программу.
Намерения. Функция вернула -1 — код ошибки или настоящий ответ?
Смысла в порядке вызовов. Есть время входа, номер вызова, поток и длительность. Что вызов был после другого и потому дал такой ответ, не записывается.
Настоящей цены вызова. Поле d меряет обмазанный прогон.
Полностью — Границы.