Skip to content

Latest commit

 

History

History
113 lines (95 loc) · 7.19 KB

File metadata and controls

113 lines (95 loc) · 7.19 KB

Формат трейса пошагового исполнения

Раздел «Песочница» умеет не только запустить код, но и оттрассировать его исполнение: core/tracer.py исполняет код в subprocess под sys.settrace и собирает JSON-трейс «состояние после каждого шага». Фронтенд-плеер листает шаги вперёд/назад по этому трейсу без повторного исполнения — как Python Tutor.

Трейс возвращается как result job'ы kind="trace" (POST /api/v1/runs mode="trace"GET /api/v1/runs/<id>, см. api.md).

Верхний уровень

{
  "steps": [ /* см. ниже */ ],
  "stdout": "весь вывод программы (строкой)",
  "truncated": false,
  "stdout_truncated": false,
  "error": null
}
  • stdout — вывод программы; плеер показывает его нарастающим, срезая по step.stdout_len.
  • truncatedtrue, если сбор шагов прерван: по лимиту шагов (DEFAULT_MAX_STEPS = 1000), по таймауту либо по бюджету объёма (DEFAULT_MAX_TRACE_BYTES = 4 МБ). Причина в поле не различается намеренно: для читателя это один и тот же исход «показаны первые N шагов». Бюджет объёма нужен потому, что число шагов веса трейса не задаёт — каждый шаг несёт снимок всего живого состояния всех кадров, поэтому JSON растёт как O(шаги × состояние). Без него сотня строк по 300 символов давала 904 шага и ~17 МБ при внешнем лимите max_output_bytes в 10 МБ: stdout дочернего процесса резался посередине, разбор JSON падал, и вместо трейса приходил TraceError. trace_code пересчитывает бюджет от фактического max_output_bytes, поэтому его уменьшение ужимает и трейс.
  • stdout_truncatedtrue, если вывод упёрся в потолок накопления (DEFAULT_MAX_STDOUT_CHARS = 1 000 000 символов): в stdout только его начало, само исполнение при этом не прерывается (плюс пометка в stderr дочернего процесса — как на грейд-пути). Флаг независим от truncated: лимит шагов режет их ЧИСЛО, а объём вывода им не ограничен — print('x' * 10**9) это один шаг.
  • errornull либо {"type": "ZeroDivisionError", "message": "..."}, если исполнение завершилось необработанным исключением.

Шаг (steps[i])

{
  "step": 0,
  "event": "line" | "call" | "return" | "exception",
  "line": 3,
  "func": "<module>",
  "stack": [ /* кадры, [0] — внешний (module), [-1] — текущий */ ],
  "heap": { "<id>": { /* объект */ } },
  "stdout_len": 7
}

Кадр стека (stack[k]):

{ "func": "f", "line": 2, "locals": { "n": <ref>, "xs": <ref> } }

Ссылки (<ref>) и heap

<ref> — либо примитив inline, либо ссылка на объект heap:

  • null, true/false, число, строка (обрезается до 200 символов) — inline;
  • большой int (|x| ≥ 2^53, теряет точность в JS) — {"big": "<repr>"};
  • не-конечный float (inf/nan, невалиден в JSON) — строкой "inf"/"nan";
  • контейнер/объект — {"ref": "<id>"}, а сам объект лежит в heap["<id>"].

Ссылки по id дают aliasing: если a и b — один объект, их ref совпадает ({"ref": "140..."}), и плеер рисует две стрелки в один узел.

Объект heap (heap["<id>"]) всегда несёт поле type — имя Python-типа значения (type(value).__name__, напр. "list", "MyClass") — плюс kind-специфичные поля ниже. Плеер использует type как подпись узла; kind задаёт способ отрисовки.

kind Поля (помимо type) Пример типа
seq elems: [<ref>], n list, tuple, set, frozenset
map entries: [[<ref>, <ref>]], n dict
func name функция/метод
module name модуль
object attrs: {name: <ref>} экземпляр класса (по __dict__)
opaque repr всё прочее (обрезанный repr)

n — фактическая длина контейнера (элементов может быть закодировано меньше: лимит _MAX_ELEMS = 100). Глубина вложенности ограничена _MAX_DEPTH = 8 (глубже — {"type": "...", "repr": "..."}: тип + обрезанный repr, без раскрытия). При обрыве цикла ссылок объект — заглушка {"type": "..."} (только тип, без полей) — разрывает рекурсию, сохраняя ref-идентичность.

Границы

  • Трассировка — только кода пользователя (кадры целевого файла); библиотечный код внутрь не трейсится.
  • Память ограничена на двух уровнях: max_stdout_chars капит буфер вывода ВНУТРИ дочернего процесса, CONFIG.max_output_bytes (RunSpec) — накопление его stdout, то есть готового JSON-трейса, в процессе сервера. Внутренний потолок заведомо ниже внешнего: иначе программа, упёршаяся в него честно, перевалила бы внешний, и вместо трейса пришёл бы обрезанный JSON.
  • OS-sandbox нет (инвариант CLAUDE.md №4) — только доверенный код.
  • Под --sandbox пошаговый трейс недоступен: модуль core/tracer.py нельзя безопасно пробросить в изолированный дочерний процесс, поэтому он возвращает явную ошибку вместо небезопасного запуска.
  • Отмена зависшего трейса — best-effort через таймаут (cancel_event в этот путь пока не проброшен; исполнение и так ограничено max_steps + таймаутом).