expr-tracker 0.2.6__tar.gz → 0.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/PKG-INFO +1 -1
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/distributed.md +6 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/spans.md +8 -6
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/reference/api.md +1 -1
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/spans.py +5 -1
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_distributed.py +22 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_examples.py +1 -1
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_span_plugins.py +46 -13
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/.github/workflows/docs.yaml +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/.github/workflows/release.yaml +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/.gitignore +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/LICENSE +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/README.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/architecture.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/design.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/examples.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/getting-started.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/alerts.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/artifacts.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/backends.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/cli.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/history.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/logging.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/guide/streams.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/index.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/reference/configuration.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/docs/reference/expressions.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/README.md +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/alert_rules.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/checkpoints.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/early_stopping.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/multiprocess_pipeline.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/profile_step.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/examples/quickstart.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/mkdocs.yml +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/pyproject.toml +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/__init__.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/_compat.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/__init__.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/backends/__init__.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/backends/base.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/backends/cards.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/dispatch.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/engine.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/__init__.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/eval.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/functions.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/lexer.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/nodes.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/parser.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/expr/rule.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/alerts/models.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/artifacts.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/cli.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/encoders.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/__init__.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/codec.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/frame.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/naming.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/reader.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/series.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/store.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/history/writer.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/plugins.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/py.typed +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/run.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/summary.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/trace.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/tracker.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/src/expr_tracker/types.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/conftest.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_backends.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_delivery.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_dispatch.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_engine.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_models.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_alert_routing.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_artifacts.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_benchmark.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_cache.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_cli.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_correctness.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_e2e.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_expr_builder.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_expr_eval.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_expr_functions.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_expr_parser.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_expr_properties.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_failure_modes.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_features.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_frame_codec_summary.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_history.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_hot_paths.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_integration.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_lark.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_perf.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_public_surfaces.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_review_regressions.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_rule_lifecycle.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_run_backends.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_scenarios.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_spans.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_step_commit.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_streams.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_stress.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_trace.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_trackio.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_value_encoding.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_wandb.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_writer_buffer.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/tests/test_writer_durability.py +0 -0
- {expr_tracker-0.2.6 → expr_tracker-0.2.7}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: expr_tracker
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Local-first experiment tracking with queryable history and expression-based alerts on your training metrics
|
|
5
5
|
Project-URL: Homepage, https://hspk.github.io/expr_tracker/
|
|
6
6
|
Project-URL: Documentation, https://hspk.github.io/expr_tracker/
|
|
@@ -73,6 +73,12 @@ together with `group`, which both backends understand:
|
|
|
73
73
|
| rank 2 | `sft-1-rank2` | `sft-1` |
|
|
74
74
|
| rank 2 of stream `data` | `sft-1-data-rank2` | `sft-1` |
|
|
75
75
|
|
|
76
|
+
`rank_aware` does not affect this. It decides whether the *local* file is
|
|
77
|
+
sharded; the remote id is decided by `backend_on_rank`. They stay independent on
|
|
78
|
+
purpose — `rank_aware=False` says you serialise the writes to one file yourself,
|
|
79
|
+
and there is no equivalent lock between two processes' wandb clients, so a rank
|
|
80
|
+
that reports still needs an id of its own.
|
|
81
|
+
|
|
76
82
|
## Typical setup
|
|
77
83
|
|
|
78
84
|
```python
|
|
@@ -158,7 +158,7 @@ await asyncio.gather(work("a"), work("b"))
|
|
|
158
158
|
|
|
159
159
|
## Printing a span as it runs
|
|
160
160
|
|
|
161
|
-
Pass `print_fn` and the span announces itself, indented
|
|
161
|
+
Pass `print_fn` and the span announces itself, indented two spaces per level:
|
|
162
162
|
|
|
163
163
|
```python
|
|
164
164
|
with et.span("step", print_fn=print):
|
|
@@ -169,10 +169,10 @@ with et.span("step", print_fn=print):
|
|
|
169
169
|
|
|
170
170
|
```
|
|
171
171
|
-> step 16:41:38
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
-> forward 16:41:38
|
|
173
|
+
-> attention 16:41:38
|
|
174
|
+
<- attention 16:41:38 3.074ms
|
|
175
|
+
<- forward 16:41:38 5.481ms
|
|
176
176
|
<- step 16:41:38 9.641ms
|
|
177
177
|
```
|
|
178
178
|
|
|
@@ -188,7 +188,9 @@ and interleave their lines; each is still indented by its own depth, and the
|
|
|
188
188
|
`->` and `<-` markers pair them up.
|
|
189
189
|
|
|
190
190
|
Any callable taking one string works — `print`, `logger.info`, a list's `append`
|
|
191
|
-
in tests.
|
|
191
|
+
in tests. The indent is spaces rather than a tab so that it survives a log
|
|
192
|
+
prefix: a terminal measures tab stops from the start of the line, so behind
|
|
193
|
+
`... | INFO | ` the first level would collapse to whatever is left of the stop. It is called on the thread that ran the span, and an exception in it is
|
|
192
194
|
logged and swallowed.
|
|
193
195
|
|
|
194
196
|
Set it for the whole run instead of per span with `span_print_fn`:
|
|
@@ -177,7 +177,7 @@ span.metrics # what the plugins measured
|
|
|
177
177
|
`<path>/duration_ms`, `<path>/count` and one key per plugin metric to the open
|
|
178
178
|
row, and appends the full record to `spans.jsonl`.
|
|
179
179
|
|
|
180
|
-
`print_fn(line)` announces the span's start and end, indented
|
|
180
|
+
`print_fn(line)` announces the span's start and end, indented two spaces per level.
|
|
181
181
|
`plugins` measure a resource across the span; both are inherited by child spans
|
|
182
182
|
and default to the run's `span_print_fn` and `span_plugins`.
|
|
183
183
|
|
|
@@ -26,6 +26,10 @@ _STACK: ContextVar[tuple[Span, ...]] = ContextVar("et_span_stack", default=())
|
|
|
26
26
|
DURATION_SUFFIX = "duration_ms"
|
|
27
27
|
COUNT_SUFFIX = "count"
|
|
28
28
|
RESERVED_METRICS = frozenset({DURATION_SUFFIX, COUNT_SUFFIX})
|
|
29
|
+
# Spaces, not a tab: a terminal measures tab stops from the start of the line, so
|
|
30
|
+
# behind a log prefix the first level collapses to whatever is left of the stop.
|
|
31
|
+
# Two spaces render the same width wherever the line begins.
|
|
32
|
+
INDENT = " "
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
_WARNED: set[tuple[str, str, str]] = set()
|
|
@@ -180,7 +184,7 @@ class Span:
|
|
|
180
184
|
def _announce(self, marker: str, duration_ms: float | None):
|
|
181
185
|
if self.print_fn is None:
|
|
182
186
|
return
|
|
183
|
-
indent =
|
|
187
|
+
indent = INDENT * self.print_depth
|
|
184
188
|
stamp = time.strftime("%H:%M:%S", time.localtime())
|
|
185
189
|
if duration_ms is None:
|
|
186
190
|
line = f"{indent}{marker} {self.name} {stamp}"
|
|
@@ -456,3 +456,25 @@ def test_streams_and_ranks_both_reach_the_backend_name(rank, tmp_path):
|
|
|
456
456
|
call = backend.inits[0]
|
|
457
457
|
assert call["id"] == "job-data-rank3"
|
|
458
458
|
assert call["group"] == "job" and call["job_type"] == "data"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_the_backend_id_does_not_follow_rank_aware(rank, tmp_path):
|
|
462
|
+
"""Local sharding and the remote id are separate concerns.
|
|
463
|
+
|
|
464
|
+
rank_aware=False promises you serialise writes to one file. Two processes'
|
|
465
|
+
wandb clients have no such lock, so a reporting rank still needs its own id.
|
|
466
|
+
"""
|
|
467
|
+
backend = RecordingBackend()
|
|
468
|
+
rank(2)
|
|
469
|
+
run = Run(
|
|
470
|
+
project="dist",
|
|
471
|
+
name="job",
|
|
472
|
+
dir=str(tmp_path),
|
|
473
|
+
backends=[backend],
|
|
474
|
+
backend_on_rank=None,
|
|
475
|
+
rank_aware=False,
|
|
476
|
+
)
|
|
477
|
+
run.log({"loss": 1.0})
|
|
478
|
+
run.finish()
|
|
479
|
+
assert backend.inits[0]["id"] == "job-rank2"
|
|
480
|
+
assert run.history.log_fp.name == "metrics.jsonl" # unsharded, as asked
|
|
@@ -342,7 +342,7 @@ def test_printing_spans_is_opt_in(profile_step, tmp_path, capsys):
|
|
|
342
342
|
assert "-> step" not in capsys.readouterr().out
|
|
343
343
|
profile_step.main([*argv(**{"--steps": 2, "--dir": tmp_path}), "--print-spans"])
|
|
344
344
|
printed = capsys.readouterr().out
|
|
345
|
-
assert "-> step" in printed and "\
|
|
345
|
+
assert "-> step" in printed and "\n -> data" in printed
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
# ------------------------------------------------------------------ early_stopping
|
|
@@ -14,6 +14,7 @@ import expr_tracker as et
|
|
|
14
14
|
from expr_tracker import plugins as pl
|
|
15
15
|
from expr_tracker.history.naming import spans_filename
|
|
16
16
|
from expr_tracker.plugins import CpuTime, GpuStats, TorchMemory, stop_samplers
|
|
17
|
+
from expr_tracker.spans import INDENT
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@pytest.fixture
|
|
@@ -45,6 +46,11 @@ def lines():
|
|
|
45
46
|
return []
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
def depth_of(line):
|
|
50
|
+
"""Nesting depth of a printed line, measured from its leading indent."""
|
|
51
|
+
return (len(line) - len(line.lstrip(" "))) // len(INDENT)
|
|
52
|
+
|
|
53
|
+
|
|
48
54
|
def span_records(run_obj):
|
|
49
55
|
path = run_obj.history.log_dir / spans_filename(None, True)
|
|
50
56
|
if not path.exists():
|
|
@@ -71,8 +77,8 @@ def test_indents_one_tab_per_level(run, lines):
|
|
|
71
77
|
run()
|
|
72
78
|
with et.span("a", print_fn=lines.append), et.span("b"), et.span("c"):
|
|
73
79
|
pass
|
|
74
|
-
assert [line
|
|
75
|
-
assert lines[2].startswith("
|
|
80
|
+
assert [depth_of(line) for line in lines] == [0, 1, 2, 2, 1, 0]
|
|
81
|
+
assert lines[2].startswith(INDENT * 2 + "-> c")
|
|
76
82
|
|
|
77
83
|
|
|
78
84
|
def test_indentation_is_relative_to_where_printing_started(run, lines):
|
|
@@ -85,8 +91,8 @@ def test_indentation_is_relative_to_where_printing_started(run, lines):
|
|
|
85
91
|
et.span("leaf"),
|
|
86
92
|
):
|
|
87
93
|
pass
|
|
88
|
-
assert [line
|
|
89
|
-
assert lines[0] == lines[0].lstrip("
|
|
94
|
+
assert [depth_of(line) for line in lines] == [0, 1, 1, 0]
|
|
95
|
+
assert lines[0] == lines[0].lstrip(" ")
|
|
90
96
|
|
|
91
97
|
|
|
92
98
|
def test_an_overriding_child_roots_its_own_indentation(run, lines):
|
|
@@ -99,7 +105,7 @@ def test_an_overriding_child_roots_its_own_indentation(run, lines):
|
|
|
99
105
|
et.span("d"),
|
|
100
106
|
):
|
|
101
107
|
pass
|
|
102
|
-
assert [line
|
|
108
|
+
assert [depth_of(line) for line in other] == [0, 1, 1, 0]
|
|
103
109
|
|
|
104
110
|
|
|
105
111
|
def test_the_run_default_still_indents_the_whole_tree(run, lines):
|
|
@@ -107,7 +113,7 @@ def test_the_run_default_still_indents_the_whole_tree(run, lines):
|
|
|
107
113
|
run(span_print_fn=lines.append)
|
|
108
114
|
with et.span("a"), et.span("b"), et.span("c"):
|
|
109
115
|
pass
|
|
110
|
-
assert [line
|
|
116
|
+
assert [depth_of(line) for line in lines] == [0, 1, 2, 2, 1, 0]
|
|
111
117
|
|
|
112
118
|
|
|
113
119
|
def test_indentation_keeps_up_with_deep_nesting(run, lines):
|
|
@@ -117,7 +123,7 @@ def test_indentation_keeps_up_with_deep_nesting(run, lines):
|
|
|
117
123
|
spans += [et.span(f"s{i}").__enter__() for i in range(1, depth)]
|
|
118
124
|
for sp in reversed(spans):
|
|
119
125
|
sp.end()
|
|
120
|
-
assert [line
|
|
126
|
+
assert [depth_of(line) for line in lines[:depth]] == list(range(depth))
|
|
121
127
|
|
|
122
128
|
|
|
123
129
|
def test_a_new_thread_starts_a_fresh_tree(run, lines):
|
|
@@ -133,7 +139,7 @@ def test_a_new_thread_starts_a_fresh_tree(run, lines):
|
|
|
133
139
|
thread = threading.Thread(target=work)
|
|
134
140
|
thread.start()
|
|
135
141
|
thread.join()
|
|
136
|
-
assert [line
|
|
142
|
+
assert [depth_of(line) for line in inner] == [0, 0]
|
|
137
143
|
|
|
138
144
|
|
|
139
145
|
def test_concurrent_tasks_are_each_indented_by_their_own_depth(run, lines):
|
|
@@ -151,7 +157,7 @@ def test_concurrent_tasks_are_each_indented_by_their_own_depth(run, lines):
|
|
|
151
157
|
by_name = {}
|
|
152
158
|
for line in lines:
|
|
153
159
|
parts = line.split()
|
|
154
|
-
by_name.setdefault(parts[1], []).append(line
|
|
160
|
+
by_name.setdefault(parts[1], []).append(depth_of(line))
|
|
155
161
|
assert by_name["gather"] == [0, 0]
|
|
156
162
|
assert by_name["a"] == [1, 1] and by_name["b"] == [1, 1]
|
|
157
163
|
assert by_name["sub"] == [2, 2, 2, 2]
|
|
@@ -165,7 +171,7 @@ def test_a_recursive_span_indents_by_recursion_depth(run, lines):
|
|
|
165
171
|
return n if n < 2 else fib(n - 1) + fib(n - 2)
|
|
166
172
|
|
|
167
173
|
fib(2)
|
|
168
|
-
assert [line
|
|
174
|
+
assert [depth_of(line) for line in lines] == [0, 1, 1, 1, 1, 0]
|
|
169
175
|
|
|
170
176
|
|
|
171
177
|
def test_a_span_ended_out_of_order_does_not_resurrect_itself(run):
|
|
@@ -192,7 +198,7 @@ def test_out_of_order_ends_keep_the_indentation_honest(run, lines):
|
|
|
192
198
|
inner.end()
|
|
193
199
|
later = et.start_span("later", print_fn=lines.append)
|
|
194
200
|
later.end()
|
|
195
|
-
assert [line
|
|
201
|
+
assert [depth_of(line) for line in lines] == [0, 1, 0, 1, 0, 0]
|
|
196
202
|
|
|
197
203
|
|
|
198
204
|
def test_a_slashed_name_does_not_fake_a_level(run, lines):
|
|
@@ -202,11 +208,38 @@ def test_a_slashed_name_does_not_fake_a_level(run, lines):
|
|
|
202
208
|
pass
|
|
203
209
|
et.log({"loss": 1.0})
|
|
204
210
|
et.finish()
|
|
205
|
-
assert [line
|
|
211
|
+
assert [depth_of(line) for line in lines] == [0, 1, 1, 0]
|
|
206
212
|
records = {rec["name"]: rec["depth"] for rec in span_records(r)}
|
|
207
213
|
assert records == {"data/load": 0, "data/load/decode": 1}
|
|
208
214
|
|
|
209
215
|
|
|
216
|
+
def test_the_indent_survives_a_log_prefix(run, lines):
|
|
217
|
+
"""A tab is measured from the start of the line, so behind a logger prefix
|
|
218
|
+
the first level collapses to whatever is left of the tab stop. Spaces do
|
|
219
|
+
not care where the line begins."""
|
|
220
|
+
run()
|
|
221
|
+
with et.span("a", print_fn=lines.append), et.span("b"), et.span("c"):
|
|
222
|
+
pass
|
|
223
|
+
assert "\t" not in "".join(lines)
|
|
224
|
+
for prefix in ("", "x" * 11, "x" * 71, "x" * 72):
|
|
225
|
+
columns = [_visual_indent(prefix + line) for line in lines]
|
|
226
|
+
relative = [column - len(prefix) for column in columns]
|
|
227
|
+
assert relative == [0, 2, 4, 4, 2, 0], prefix
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _visual_indent(line, tabsize=8):
|
|
231
|
+
"""Where the marker lands once a terminal has expanded the tabs."""
|
|
232
|
+
column = 0
|
|
233
|
+
for index, char in enumerate(line):
|
|
234
|
+
if char == "\t":
|
|
235
|
+
column += tabsize - (column % tabsize)
|
|
236
|
+
elif line.startswith(("-> ", "<- "), index):
|
|
237
|
+
return column
|
|
238
|
+
else:
|
|
239
|
+
column += 1
|
|
240
|
+
return column
|
|
241
|
+
|
|
242
|
+
|
|
210
243
|
def test_children_inherit_the_handler(run, lines):
|
|
211
244
|
run()
|
|
212
245
|
with et.span("outer", print_fn=lines.append), et.span("inner"):
|
|
@@ -351,7 +384,7 @@ def test_sibling_spans_restart_the_indentation(run, lines):
|
|
|
351
384
|
pass
|
|
352
385
|
with et.span("two"):
|
|
353
386
|
pass
|
|
354
|
-
assert [line
|
|
387
|
+
assert [depth_of(line) for line in lines] == [0, 1, 1, 1, 1, 0]
|
|
355
388
|
|
|
356
389
|
|
|
357
390
|
# --------------------------------------------------------------------------- #
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|