Уроборос

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

View the Project on GitHub digitable-lol/ouroboros

Запись трассы

Настоящий файл 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 успел позвать. Вложенность видна по тому, какие вызовы попали между входом и выходом.

Куда пишется

Ключи

ключ на какой строке что значит
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 меряет обмазанный прогон.

Полностью — Границы.