Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a sync marker #542

Merged
merged 21 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d6bf4d7
Add sync_marker to snaptrace
sergey-miryanov Jan 19, 2025
d38cd78
Store sync_marker to viztracer_metadata
sergey-miryanov Jan 19, 2025
5b176e9
Use sync_marker to align combined json to particular time marker
sergey-miryanov Jan 25, 2025
cf8208f
Add tests for align_combine with sync_marker
sergey-miryanov Jan 25, 2025
6946f4b
Make linter happy
sergey-miryanov Jan 26, 2025
6bb2cc8
Fix typo in set_sync_marker warning
sergey-miryanov Jan 26, 2025
0946f4d
Add test_set_sync_marker and simplify test_align_combine_sync_marker
sergey-miryanov Jan 26, 2025
4671359
get_sync_marker return None if not set
sergey-miryanov Jan 26, 2025
514ae36
Use dedent for test_scripts in sync_marker tests
sergey-miryanov Jan 26, 2025
c4b4f59
Remove package_matrix from align_combine_sync_marker test
sergey-miryanov Jan 26, 2025
88f592f
Simplify checks for set_sync_marker test
sergey-miryanov Jan 26, 2025
9b3c2cb
Simplify checks for test_align_combine_sync_marker
sergey-miryanov Jan 27, 2025
b32bee4
Try to increase time delta for aligned and unaligned events
sergey-miryanov Jan 27, 2025
6907d41
Another one try to fix time deltas
sergey-miryanov Jan 27, 2025
858e9fb
More simplify checks in test_align_combine_sync_marker
sergey-miryanov Jan 27, 2025
42253b3
Make linter happy
sergey-miryanov Jan 27, 2025
6369def
More simplify tests
sergey-miryanov Jan 27, 2025
1fa04f3
Replace test_align with for-loop
sergey-miryanov Jan 28, 2025
4a3f2e6
Rename test function and fix comment in test script
sergey-miryanov Jan 28, 2025
81576e1
Update concurency.rst
sergey-miryanov Jan 28, 2025
5f3ebfa
Move set_sync_marker doc to basic_usage
sergey-miryanov Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/source/basic_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ this usage.

viztracer --align_combine run1.json run2.json -o compare_report.json

You can also set a sync-marker from your source code, and VizTracer will align both reports to this particular timestamp.

.. code-block::

from viztracer import get_tracer
get_tracer().set_sync_marker()


Compress Your Report
--------------------

Expand Down
36 changes: 35 additions & 1 deletion src/viztracer/modules/snaptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,15 @@ tracer_dump(TracerObject* self, PyObject* args, PyObject* kw)

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

if (self->sync_marker > 0)
{
long long ts_sync_marker = system_ts_to_ns(self->sync_marker);
fprintf(fptr, ",\"sync_marker\":%lld.%03lld", ts_sync_marker / 1000, ts_sync_marker % 1000);
}

fprintf(fptr, "}}");
fclose(fptr);
SNAPTRACE_THREAD_PROTECT_END(self);
Py_RETURN_NONE;
Expand Down Expand Up @@ -1925,6 +1933,29 @@ tracer_getfunctionarg(TracerObject* self, PyObject* Py_UNUSED(unused))
return Py_NewRef(fnode->args);
}

static PyObject*
tracer_set_sync_marker(TracerObject* self, PyObject* Py_UNUSED(unused))
{
if (self->sync_marker != 0)
{
PyErr_WarnFormat(PyExc_RuntimeWarning, 1, "Synchronization marker already set");
}
self->sync_marker = get_ts();
Py_RETURN_NONE;
}

static PyObject*
tracer_get_sync_marker(TracerObject* self, PyObject* Py_UNUSED(unused))
{
if (self->sync_marker == 0)
{
Py_RETURN_NONE;
}

double ts_sync_marker = system_ts_to_us(self->sync_marker);
return PyFloat_FromDouble(ts_sync_marker);
}

static PyMethodDef Tracer_methods[] = {
{"threadtracefunc", (PyCFunction)tracer_threadtracefunc, METH_VARARGS, "trace function"},
{"start", (PyCFunction)tracer_start, METH_NOARGS, "start profiling"},
Expand All @@ -1945,6 +1976,8 @@ static PyMethodDef Tracer_methods[] = {
{"pause", (PyCFunction)tracer_pause, METH_NOARGS, "pause profiling"},
{"resume", (PyCFunction)tracer_resume, METH_NOARGS, "resume profiling"},
{"setignorestackcounter", (PyCFunction)tracer_setignorestackcounter, METH_O, "reset ignore stack depth"},
{"set_sync_marker", (PyCFunction)tracer_set_sync_marker, METH_NOARGS, "set current timestamp to synchronization marker"},
{"get_sync_marker", (PyCFunction)tracer_get_sync_marker, METH_NOARGS, "get synchronization marker or None if not set"},
{NULL, NULL, 0, NULL}
};

Expand All @@ -1970,6 +2003,7 @@ Tracer_New(PyTypeObject* type, PyObject* args, PyObject* kwargs)
self->buffer = NULL;
self->buffer_head_idx = 0;
self->buffer_tail_idx = 0;
self->sync_marker = 0;
self->metadata_head = NULL;
}

Expand Down
1 change: 1 addition & 0 deletions src/viztracer/modules/snaptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ typedef struct {
long buffer_size;
long buffer_head_idx;
long buffer_tail_idx;
int64_t sync_marker;
struct MetadataNode* metadata_head;
} TracerObject;

Expand Down
12 changes: 9 additions & 3 deletions src/viztracer/report_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def combine_json(self) -> None:
raise ValueError("Can't get report of nothing")
if self.align:
for one in self.jsons:
self.align_events(one["traceEvents"])
self.align_events(one["traceEvents"], one['viztracer_metadata'].get('sync_marker', None))
self.combined_json = self.jsons[0]
if "viztracer_metadata" not in self.combined_json:
self.combined_json["viztracer_metadata"] = {}
Expand All @@ -143,14 +143,20 @@ def combine_json(self) -> None:
self.combined_json["file_info"]["files"].update(one["file_info"]["files"])
self.combined_json["file_info"]["functions"].update(one["file_info"]["functions"])

def align_events(self, original_events: list[dict[str, Any]]) -> list[dict[str, Any]]:
def align_events(self, original_events: list[dict[str, Any]], sync_marker: Optional[float] = None) -> list[dict[str, Any]]:
"""
Apply an offset to all the trace events, making the start timestamp 0
This is useful when comparing multiple runs of the same script

If sync_marker is not None then sync_marker be used as an offset

This function will change the timestamp in place, and return the original list
"""
offset_ts = min((event["ts"] for event in original_events if "ts" in event))
if sync_marker is None:
offset_ts = min((event["ts"] for event in original_events if "ts" in event))
else:
offset_ts = sync_marker

for event in original_events:
if "ts" in event:
event["ts"] -= offset_ts
Expand Down
8 changes: 8 additions & 0 deletions src/viztracer/snaptrace.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,11 @@ class Tracer:

def add_instant(self, name: str, args: Any = None, scope: Literal["g", "p", "t"] = "g") -> None:
...

def set_sync_marker(self) -> None:
"""set current timestamp to synchronization marker"""
...

def get_sync_marker(self) -> Optional[float]:
"""get synchronization marker or None if not set"""
...
4 changes: 4 additions & 0 deletions src/viztracer/viztracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ def parse(self) -> int:
"overflow": False,
},
}
sync_marker = self.get_sync_marker()
if sync_marker is not None:
self.data['viztracer_metadata']['sync_marker'] = sync_marker

metadata_count = 0
for d in self.data["traceEvents"]:
if d["ph"] == "M":
Expand Down
70 changes: 70 additions & 0 deletions tests/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,76 @@ def test_combine(self):
os.path.join(example_json_dir, "different_sorts.json")],
expected_output_file="result.json")

def test_set_sync_marker(self):
test_script = textwrap.dedent("""
from viztracer import get_tracer

get_tracer().set_sync_marker()
get_tracer().set_sync_marker()

""")

def expect_sync_marker(data):
self.assertGreater(data['viztracer_metadata'].get('sync_marker'), 0)
sergey-miryanov marked this conversation as resolved.
Show resolved Hide resolved

self.template(
[sys.executable, "-m", "viztracer", "--ignore_frozen", "-o", "result.json", "cmdline_test.py"],
expected_output_file="result.json",
expected_stderr="Synchronization marker already set",
script=test_script,
check_func=expect_sync_marker,
)

def test_align_combine_sync_marker(self):
test_script = textwrap.dedent("""
import random
import time
from viztracer import get_tracer

def test_func():
return 2 * 4

# should sleep from 50ms to 100ms
time.sleep(random.uniform(0.05, 0.1))

get_tracer().set_sync_marker()
test_func()
""")

def expect_aligned_to_sync_marker(data):

funcs = [event for event in data['traceEvents'] if 'ts' in event and event['name'].startswith('test_func')]
self.assertEqual(len(funcs), 2)

# we expect that aligned events shifted not more than 1ms
aligned_diff = abs(funcs[1]['ts'] - funcs[0]['ts'])
self.assertLessEqual(aligned_diff, 1000.0, str(data))

for extra_args in [[], ["--dump_raw"]]:
with tempfile.TemporaryDirectory() as tmpdir:
res1_filename = os.path.join(tmpdir, 'res1.json')
res2_filename = os.path.join(tmpdir, 'res2.json')

common_cmd_line = [sys.executable, "-m", "viztracer", "--ignore_frozen"] + extra_args
self.template(
common_cmd_line + ["-o", res1_filename, "cmdline_test.py"],
expected_output_file=res1_filename,
script=test_script,
cleanup=False,
)
self.template(
common_cmd_line + ["-o", res2_filename, "cmdline_test.py"],
expected_output_file=res2_filename,
script=test_script,
cleanup=False,
)

self.template(
[sys.executable, "-m", "viztracer", "--align_combine", res1_filename, res2_filename],
expected_output_file="result.json",
check_func=expect_aligned_to_sync_marker,
)

def test_tracer_entries(self):
self.template([sys.executable, "-m", "viztracer", "--tracer_entries", "1000", "cmdline_test.py"])
self.template([sys.executable, "-m", "viztracer", "--tracer_entries", "50", "cmdline_test.py"])
Expand Down
Loading