Skip to content

Commit 1cf4478

Browse files
committed
Add adaptive trace buffer growth option
1 parent ede8237 commit 1cf4478

10 files changed

Lines changed: 284 additions & 15 deletions

File tree

docs/source/viztracer.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ VizTracer
2020
log_torch=False,\
2121
log_audit=False,\
2222
pid_suffix=False,\
23+
grow_on_overflow=False,\
24+
max_tracer_entries=None,\
2325
file_info=True,\
2426
register_global=True,\
2527
trace_self=False,\
@@ -266,6 +268,26 @@ VizTracer
266268
267269
viztracer --pid_suffix
268270
271+
.. py:attribute:: grow_on_overflow
272+
:type: bool
273+
:value: False
274+
275+
Whether grow the internal trace buffer instead of overwriting old events
276+
when it becomes full.
277+
278+
Equivalent to
279+
280+
.. code-block::
281+
282+
viztracer --grow_on_overflow
283+
284+
.. py:attribute:: max_tracer_entries
285+
:type: Optional[int]
286+
:value: None
287+
288+
Maximum buffer size to grow to when ``grow_on_overflow`` is enabled.
289+
``None`` means there is no explicit cap.
290+
269291
.. py:attribute:: file_info
270292
:type: bool
271293
:value: False

src/viztracer/main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ def create_parser(self) -> argparse.ArgumentParser:
7575
default=1000000,
7676
help="size of circular buffer. How many entries can it store",
7777
)
78+
parser.add_argument(
79+
"--grow_on_overflow",
80+
action="store_true",
81+
default=False,
82+
help="grow the trace buffer instead of overwriting old entries when it fills",
83+
)
84+
parser.add_argument(
85+
"--max_tracer_entries",
86+
nargs="?",
87+
type=int,
88+
default=None,
89+
help="maximum buffer size to grow to when --grow_on_overflow is enabled",
90+
)
7891
filename_group = parser.add_mutually_exclusive_group()
7992
filename_group.add_argument(
8093
"--output_file",
@@ -500,6 +513,12 @@ def parse(self, argv: list[str]) -> VizProcedureResult:
500513
f"Can't convert {options.min_duration} to time. Format should be 0.3ms or 13us",
501514
)
502515

516+
if (
517+
options.max_tracer_entries is not None
518+
and options.max_tracer_entries < options.tracer_entries
519+
):
520+
return False, "--max_tracer_entries can't be smaller than --tracer_entries"
521+
503522
if options.log_torch:
504523
try:
505524
import torch # type: ignore # noqa: F401
@@ -543,6 +562,8 @@ def parse(self, argv: list[str]) -> VizProcedureResult:
543562
"log_audit": options.log_audit,
544563
"log_torch": options.log_torch,
545564
"pid_suffix": options.pid_suffix,
565+
"grow_on_overflow": options.grow_on_overflow,
566+
"max_tracer_entries": options.max_tracer_entries,
546567
"file_info": False,
547568
"register_global": True,
548569
"report_endpoint": options.report_endpoint,

src/viztracer/modules/snaptrace.c

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,86 @@ get_ts()
6565
#endif
6666
}
6767

68+
static inline long
69+
get_buffered_entry_count(TracerObject* self)
70+
{
71+
if (self->buffer_tail_idx >= self->buffer_head_idx) {
72+
return self->buffer_tail_idx - self->buffer_head_idx;
73+
}
74+
return self->buffer_size - self->buffer_head_idx + self->buffer_tail_idx;
75+
}
76+
77+
static int
78+
grow_buffer(TracerObject* self)
79+
{
80+
long curr_entries = self->buffer_size - 1;
81+
long new_entries = curr_entries * 2;
82+
long buffered_entries = get_buffered_entry_count(self);
83+
struct EventNode* new_buffer = NULL;
84+
85+
if (self->max_buffer_entries > 0 && new_entries > self->max_buffer_entries) {
86+
new_entries = self->max_buffer_entries;
87+
}
88+
89+
if (new_entries <= curr_entries) {
90+
return -1;
91+
}
92+
93+
new_buffer = (struct EventNode*) PyMem_Calloc(new_entries + 1, sizeof(struct EventNode));
94+
if (!new_buffer) {
95+
return -1;
96+
}
97+
98+
for (long i = 0; i < buffered_entries; i++) {
99+
long src_idx = self->buffer_head_idx + i;
100+
if (src_idx >= self->buffer_size) {
101+
src_idx -= self->buffer_size;
102+
}
103+
new_buffer[i] = self->buffer[src_idx];
104+
}
105+
106+
PyMem_FREE(self->buffer);
107+
self->buffer = new_buffer;
108+
self->buffer_size = new_entries + 1;
109+
self->buffer_head_idx = 0;
110+
self->buffer_tail_idx = buffered_entries;
111+
self->total_entries = buffered_entries;
112+
113+
return 0;
114+
}
115+
68116
static inline struct EventNode*
69117
get_next_node(TracerObject* self)
70118
{
71119
struct EventNode* node = NULL;
120+
long next_tail_idx = 0;
121+
int overwrote = 0;
72122

73123
SNAPTRACE_THREAD_PROTECT_START(self);
74-
node = self->buffer + self->buffer_tail_idx;
75124
// This is actually faster than modulo
76-
self->buffer_tail_idx = self->buffer_tail_idx + 1;
77-
if (self->buffer_tail_idx >= self->buffer_size) {
78-
self->buffer_tail_idx = 0;
79-
}
80-
if (self->buffer_tail_idx == self->buffer_head_idx) {
81-
self->buffer_head_idx = self->buffer_head_idx + 1;
82-
if (self->buffer_head_idx >= self->buffer_size) {
83-
self->buffer_head_idx = 0;
125+
next_tail_idx = self->buffer_tail_idx + 1;
126+
if (next_tail_idx >= self->buffer_size) {
127+
next_tail_idx = 0;
128+
}
129+
if (next_tail_idx == self->buffer_head_idx) {
130+
if (!(self->grow_on_full && grow_buffer(self) == 0)) {
131+
overwrote = 1;
132+
self->overflowed = 1;
133+
clear_node(self->buffer + next_tail_idx);
134+
self->buffer_head_idx = next_tail_idx + 1;
135+
if (self->buffer_head_idx >= self->buffer_size) {
136+
self->buffer_head_idx = 0;
137+
}
138+
} else {
139+
next_tail_idx = self->buffer_tail_idx + 1;
140+
if (next_tail_idx >= self->buffer_size) {
141+
next_tail_idx = 0;
142+
}
84143
}
85-
clear_node(self->buffer + self->buffer_tail_idx);
86-
} else {
144+
}
145+
node = self->buffer + self->buffer_tail_idx;
146+
self->buffer_tail_idx = next_tail_idx;
147+
if (!overwrote) {
87148
self->total_entries += 1;
88149
}
89150
SNAPTRACE_THREAD_PROTECT_END(self);
@@ -1072,6 +1133,11 @@ tracer_start(TracerObject* self, PyObject* Py_UNUSED(unused))
10721133
curr_tracer = self;
10731134
}
10741135

1136+
if (self->buffer_head_idx == self->buffer_tail_idx) {
1137+
self->total_entries = 0;
1138+
self->overflowed = 0;
1139+
}
1140+
10751141
self->collecting = 1;
10761142
#if PY_VERSION_HEX >= 0x030C0000
10771143
if (enable_monitoring(self) != 0) {
@@ -1493,7 +1559,6 @@ tracer_dump(TracerObject* self, PyObject* args, PyObject* kw)
14931559
SNAPTRACE_THREAD_PROTECT_START(self);
14941560
struct EventNode* curr = self->buffer + self->buffer_head_idx;
14951561
unsigned long pid = 0;
1496-
uint8_t overflowed = ((self->buffer_tail_idx + 1) % self->buffer_size) == self->buffer_head_idx;
14971562
struct MetadataNode* metadata_node = NULL;
14981563
PyObject* task_dict = NULL;
14991564

@@ -1681,7 +1746,7 @@ tracer_dump(TracerObject* self, PyObject* args, PyObject* kw)
16811746

16821747
self->buffer_tail_idx = self->buffer_head_idx;
16831748
fseek(fptr, -1, SEEK_CUR);
1684-
fprintf(fptr, "], \"viztracer_metadata\": {\"overflow\":%s", overflowed? "true": "false");
1749+
fprintf(fptr, "], \"viztracer_metadata\": {\"overflow\":%s", self->overflowed? "true": "false");
16851750

16861751
if (self->sync_marker > 0)
16871752
{
@@ -1708,6 +1773,8 @@ tracer_clear(TracerObject* self, PyObject* Py_UNUSED(unused))
17081773
}
17091774
}
17101775
self->buffer_tail_idx = self->buffer_head_idx;
1776+
self->total_entries = 0;
1777+
self->overflowed = 0;
17111778

17121779
Py_RETURN_NONE;
17131780
}
@@ -2062,10 +2129,13 @@ Tracer_New(PyTypeObject* type, PyObject* args, PyObject* kwargs)
20622129
self->collecting = 0;
20632130
self->fix_pid = 0;
20642131
self->total_entries = 0;
2132+
self->overflowed = 0;
2133+
self->grow_on_full = 0;
20652134
self->check_flags = 0;
20662135
self->verbose = 0;
20672136
self->lib_file_path = NULL;
20682137
self->max_stack_depth = 0;
2138+
self->max_buffer_entries = 0;
20692139
self->include_files = NULL;
20702140
self->exclude_files = NULL;
20712141
self->min_duration = 0;

src/viztracer/modules/snaptrace.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@ typedef struct {
108108
// otherwise it uses this pid
109109
long fix_pid;
110110
unsigned long total_entries;
111+
int overflowed;
112+
int grow_on_full;
111113
unsigned int check_flags;
112114
int verbose;
113115
char* lib_file_path;
114116
int max_stack_depth;
117+
long max_buffer_entries;
115118
PyObject* process_name;
116119
PyObject* include_files;
117120
PyObject* exclude_files;

src/viztracer/modules/snaptrace_member.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,71 @@ Tracer_trace_self_getter(TracerObject* self, void* closure)
431431
}
432432
}
433433

434+
static int
435+
Tracer_grow_on_full_setter(TracerObject* self, PyObject* value, void* closure)
436+
{
437+
if (value == NULL) {
438+
PyErr_SetString(PyExc_AttributeError, "Cannot delete the attribute");
439+
return -1;
440+
}
441+
442+
if (!PyBool_Check(value)) {
443+
PyErr_SetString(PyExc_TypeError, "grow_on_full must be a boolean");
444+
return -1;
445+
}
446+
447+
self->grow_on_full = (value == Py_True);
448+
449+
return 0;
450+
}
451+
452+
static PyObject*
453+
Tracer_grow_on_full_getter(TracerObject* self, void* closure)
454+
{
455+
if (self->grow_on_full) {
456+
Py_RETURN_TRUE;
457+
} else {
458+
Py_RETURN_FALSE;
459+
}
460+
}
461+
462+
static int
463+
Tracer_max_buffer_entries_setter(TracerObject* self, PyObject* value, void* closure)
464+
{
465+
if (value == NULL) {
466+
PyErr_SetString(PyExc_AttributeError, "Cannot delete the attribute");
467+
return -1;
468+
}
469+
470+
if (!PyLong_Check(value)) {
471+
PyErr_SetString(PyExc_TypeError, "max_buffer_entries must be an integer");
472+
return -1;
473+
}
474+
475+
self->max_buffer_entries = PyLong_AsLong(value);
476+
if (self->max_buffer_entries < 0) {
477+
self->max_buffer_entries = 0;
478+
}
479+
480+
return 0;
481+
}
482+
483+
static PyObject*
484+
Tracer_max_buffer_entries_getter(TracerObject* self, void* closure)
485+
{
486+
return PyLong_FromLong(self->max_buffer_entries);
487+
}
488+
489+
static PyObject*
490+
Tracer_overflowed_getter(TracerObject* self, void* closure)
491+
{
492+
if (self->overflowed) {
493+
Py_RETURN_TRUE;
494+
} else {
495+
Py_RETURN_FALSE;
496+
}
497+
}
498+
434499
static int
435500
Tracer_log_func_repr_setter(TracerObject* self, PyObject* value, void* closure)
436501
{
@@ -478,6 +543,9 @@ PyGetSetDef Tracer_getsetters[] = {
478543
{"log_func_args", (getter)Tracer_log_func_args_getter, (setter)Tracer_log_func_args_setter, "log_func_args", NULL},
479544
{"log_async", (getter)Tracer_log_async_getter, (setter)Tracer_log_async_setter, "log_async", NULL},
480545
{"trace_self", (getter)Tracer_trace_self_getter, (setter)Tracer_trace_self_setter, "trace_self", NULL},
546+
{"grow_on_full", (getter)Tracer_grow_on_full_getter, (setter)Tracer_grow_on_full_setter, "grow_on_full", NULL},
547+
{"max_buffer_entries", (getter)Tracer_max_buffer_entries_getter, (setter)Tracer_max_buffer_entries_setter, "max_buffer_entries", NULL},
548+
{"overflowed", (getter)Tracer_overflowed_getter, NULL, "overflowed", NULL},
481549
{"log_func_repr", (getter)Tracer_log_func_repr_getter, (setter)Tracer_log_func_repr_setter, "log_func_repr", NULL},
482550
{NULL}
483551
};

src/viztracer/report_builder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ def print_messages(self):
336336
"WARNING",
337337
' If you need more buffer, use "viztracer --tracer_entries <entry_number>"',
338338
)
339+
color_print(
340+
"WARNING",
341+
(
342+
" Or, enable --grow_on_overflow to expand the buffer "
343+
"automatically as needed"
344+
),
345+
)
339346
color_print(
340347
"WARNING",
341348
" Or, you can try the filter options to filter out some data you don't need",

src/viztracer/snaptrace.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ class Tracer:
88

99
include_files: list[str] | None
1010
exclude_files: list[str] | None
11+
grow_on_full: bool
12+
max_buffer_entries: int
13+
overflowed: bool
1114

1215
def __init__(self, tracer_entries: int, /) -> None: ...
1316
def start(self) -> None: ...

0 commit comments

Comments
 (0)