lumitrace 0.1.2 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/docs/#spec.md# +248 -0
- data/docs/spec.md +211 -0
- data/docs/supported_syntax.md +117 -0
- data/docs/tutorial.ja.md +338 -0
- data/docs/tutorial.md +338 -0
- data/exe/lumitrace +27 -79
- data/lib/lumitrace/enable_git_diff.rb +15 -67
- data/lib/lumitrace/env.rb +43 -0
- data/lib/lumitrace/generate_resulted_html.rb +262 -18
- data/lib/lumitrace/git_diff.rb +93 -0
- data/lib/lumitrace/record_instrument.rb +16 -4
- data/lib/lumitrace/record_require.rb +9 -3
- data/lib/lumitrace/version.rb +1 -1
- data/lib/lumitrace.rb +204 -11
- data/runv/README.md +53 -0
- data/runv/index.html +982 -0
- data/sample/lumitrace_results_01.html +183 -0
- data/sample/lumitrace_results_01.txt +62 -0
- data/sample/lumitrace_results_02.html +123 -0
- data/sample/lumitrace_results_02.txt +24 -0
- data/sample/sample.rb +35 -0
- data/sample/sample2.rb +11 -0
- data/sample/sample3.rb +9 -0
- data/test/test_lumitrace.rb +71 -0
- metadata +22 -6
- /data/{sample_project → sample/sample_project}/.github/workflows/lumitrace-sample.yml +0 -0
- /data/{sample_project → sample/sample_project}/Gemfile +0 -0
- /data/{sample_project → sample/sample_project}/Rakefile +0 -0
- /data/{sample_project → sample/sample_project}/test/test_sample_test.rb +0 -0
- /data/{sample_project → sample/sample_project}/test.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 638e44a364f468bd57003d05c60492dc697f31faba607292dfa5768b8ede47d2
|
|
4
|
+
data.tar.gz: a930badbf11444cd914765dde643a13347414511e1401aad4bf2da55e3e8159b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3997b1814e8861e98e5b8dbd2489eab974f8d1671424a190bc2b82e37c65a6a3253eeb47074c507a1984ef5a3db19cb0aa84520e7f54a757d133f510d260c5c5
|
|
7
|
+
data.tar.gz: 935834bd5c818a528307f1359815faead6f793ffa5deaf78b039a7fb9dd5b570dd0b07ce1b1bf00edf0c4e51a39edfcb2f4fbddae5c85a6a54e7ff3ae036b699
|
data/README.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Lumitrace instruments Ruby source code at load time, records expression results, and renders an HTML view that overlays recorded values on your code. It is designed for quick, local “what happened here?” inspection during test runs or scripts.
|
|
4
4
|
|
|
5
|
+
## Useful links
|
|
6
|
+
|
|
7
|
+
- [runv/](https://ko1.github.io/lumitrace/runv/): Lumitrace demonstration Ruby playground with inlined tracing
|
|
8
|
+
- [Tutorial](https://ko1.github.io/lumitrace/docs/tutorial.html)
|
|
9
|
+
- [Tutorial in Japanese](https://ko1.github.io/lumitrace/docs/tutorial.ja.html)
|
|
10
|
+
- [Spec](https://ko1.github.io/lumitrace/docs/spec.html)
|
|
11
|
+
- [Supported Syntax](https://ko1.github.io/lumitrace/docs/supported_syntax.html)
|
|
12
|
+
- [GitHub repository](https://github.com/ko1/lumitrace)
|
|
13
|
+
|
|
14
|
+
|
|
5
15
|
## How It Works
|
|
6
16
|
|
|
7
17
|
Lumitrace hooks `RubyVM::InstructionSequence.translate` (when available) to rewrite files at require-time. It records expression results and renders an HTML view that shows them inline. Only the last N values per expression are kept to avoid huge output.
|
data/docs/#spec.md#
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Lumitrace Spec
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Lumitrace instruments Ruby source code at load time (via `RubyVM::InstructionSequence.translate` when available), records expression results, and renders an HTML view that overlays recorded values on your code. It is designed for local “what happened here?” inspection.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
- Record expression results with minimal friction.
|
|
10
|
+
- Limit recorded data size (keep only the last N values per expression).
|
|
11
|
+
- Show recorded values inline on a per-file HTML view.
|
|
12
|
+
- Support require-time instrumentation for multiple files.
|
|
13
|
+
|
|
14
|
+
## Non-Goals
|
|
15
|
+
|
|
16
|
+
- Production-safe tracing.
|
|
17
|
+
- Perfect semantic preservation for all Ruby edge cases.
|
|
18
|
+
|
|
19
|
+
## Library API
|
|
20
|
+
|
|
21
|
+
### `require "lumitrace"`
|
|
22
|
+
|
|
23
|
+
- Arguments: none.
|
|
24
|
+
- Returns: nothing.
|
|
25
|
+
- Side effects: loads core code only (no instrumentation, no `at_exit`).
|
|
26
|
+
|
|
27
|
+
### `Lumitrace.enable!(max_values: nil, ranges_by_file: nil, root: nil, at_exit: true)`
|
|
28
|
+
|
|
29
|
+
- Arguments:
|
|
30
|
+
- `max_values`: integer or nil. Sets maximum values stored per expression.
|
|
31
|
+
- `ranges_by_file`: hash or nil. `{ "/path/to/file.rb" => [1..5, 10..12] }`.
|
|
32
|
+
- `at_exit`: boolean. When true, registers HTML output at exit.
|
|
33
|
+
- Returns: `nil`.
|
|
34
|
+
- Side effects:
|
|
35
|
+
- Loads `record_require` if needed and enables require-time instrumentation.
|
|
36
|
+
- Registers a single `at_exit` hook (if `at_exit: true`).
|
|
37
|
+
- Fixes the HTML output directory to the `Dir.pwd` at call time.
|
|
38
|
+
|
|
39
|
+
### `Lumitrace.disable!`
|
|
40
|
+
|
|
41
|
+
- Arguments: none.
|
|
42
|
+
- Returns: `nil`.
|
|
43
|
+
- Side effects: disables instrumentation (does not clear recorded events).
|
|
44
|
+
|
|
45
|
+
### `Lumitrace.dump!(path = nil)`
|
|
46
|
+
|
|
47
|
+
- Arguments:
|
|
48
|
+
- `path`: string or nil. Output JSON path when provided.
|
|
49
|
+
- Returns: the path used.
|
|
50
|
+
- Side effects: writes JSON to disk immediately.
|
|
51
|
+
|
|
52
|
+
### `require "lumitrace/enable"`
|
|
53
|
+
|
|
54
|
+
- Arguments: none.
|
|
55
|
+
- Returns: nothing.
|
|
56
|
+
- Side effects: calls `Lumitrace.enable!` with default arguments.
|
|
57
|
+
|
|
58
|
+
### `require "lumitrace/enable_git_diff"`
|
|
59
|
+
|
|
60
|
+
- Arguments: none.
|
|
61
|
+
- Returns: nothing.
|
|
62
|
+
- Side effects:
|
|
63
|
+
- Computes `ranges_by_file` from `git diff` scoped to the current program file.
|
|
64
|
+
- Calls `Lumitrace.enable!` when diff is non-empty.
|
|
65
|
+
|
|
66
|
+
Environment variables for `enable_git_diff`:
|
|
67
|
+
|
|
68
|
+
- `LUMITRACE_GIT_DIFF=working|staged|base:REV|range:SPEC` selects diff source.
|
|
69
|
+
- `LUMITRACE_GIT_DIFF_CONTEXT=N` expands hunks by +/-N lines (default 3; negative treated as 0).
|
|
70
|
+
- `LUMITRACE_GIT_CMD` overrides the git executable (default: `git`).
|
|
71
|
+
|
|
72
|
+
### `RecordRequire.enable(max_values: nil, ranges_by_file: nil, root: nil)`
|
|
73
|
+
|
|
74
|
+
- Arguments:
|
|
75
|
+
- `max_values`: integer or nil. Sets maximum values stored per expression.
|
|
76
|
+
- `ranges_by_file`: hash or nil. `{ "/path/to/file.rb" => [1..5, 10..12] }`.
|
|
77
|
+
- Returns: `nil`.
|
|
78
|
+
- Side effects: enables require-time instrumentation immediately.
|
|
79
|
+
|
|
80
|
+
### `RecordInstrument.instrument_source(src, ranges, file_label: nil, record_method: "Lumitrace::RecordInstrument.expr_record")`
|
|
81
|
+
|
|
82
|
+
- Arguments:
|
|
83
|
+
- `src`: string. Ruby source.
|
|
84
|
+
- `ranges`: array of `[start_line, end_line]` pairs.
|
|
85
|
+
- `file_label`: string or nil. File label to record into events.
|
|
86
|
+
- `record_method`: string. Wrapper method name.
|
|
87
|
+
- Returns: rewritten Ruby source as a string.
|
|
88
|
+
- Side effects: none.
|
|
89
|
+
|
|
90
|
+
### `RecordInstrument.expr_record(file, start_line, start_col, end_line, end_col, value)`
|
|
91
|
+
|
|
92
|
+
- Arguments: expression location and value.
|
|
93
|
+
- Returns: `value`.
|
|
94
|
+
- Side effects: appends value into the in-memory event store and updates totals.
|
|
95
|
+
|
|
96
|
+
### `RecordInstrument.dump_json(path = default)`
|
|
97
|
+
|
|
98
|
+
- Arguments:
|
|
99
|
+
- `path`: string or nil. When nil, defaults to `Dir.pwd/lumitrace_recorded.json`.
|
|
100
|
+
- Returns: the path used.
|
|
101
|
+
- Side effects: writes JSON to disk.
|
|
102
|
+
|
|
103
|
+
### `RecordInstrument.events`
|
|
104
|
+
|
|
105
|
+
- Arguments: none.
|
|
106
|
+
- Returns: array of event hashes.
|
|
107
|
+
- Side effects: none.
|
|
108
|
+
|
|
109
|
+
### `RecordInstrument.max_values_per_expr` / `max_values_per_expr=`
|
|
110
|
+
|
|
111
|
+
- Arguments: none / integer.
|
|
112
|
+
- Returns: current max values per expression.
|
|
113
|
+
- Side effects: setter changes recording limit.
|
|
114
|
+
|
|
115
|
+
### `GenerateResultedHtml.render(source_path, events_path, ranges: nil)`
|
|
116
|
+
|
|
117
|
+
- Arguments:
|
|
118
|
+
- `source_path`: file path.
|
|
119
|
+
- `events_path`: JSON path.
|
|
120
|
+
- `ranges`: array of `[start_line, end_line]` pairs or nil.
|
|
121
|
+
- Returns: HTML string.
|
|
122
|
+
- Side effects: none.
|
|
123
|
+
|
|
124
|
+
### `GenerateResultedHtml.render_all(events_path, root: Dir.pwd, ranges_by_file: nil)`
|
|
125
|
+
|
|
126
|
+
- Arguments:
|
|
127
|
+
- `events_path`: JSON path.
|
|
128
|
+
- `root`: root directory for file headings.
|
|
129
|
+
- `ranges_by_file`: hash or nil.
|
|
130
|
+
- Returns: HTML string.
|
|
131
|
+
- Side effects: none.
|
|
132
|
+
|
|
133
|
+
### `GenerateResultedHtml.render_all_from_events(events, root: Dir.pwd, ranges_by_file: nil)`
|
|
134
|
+
|
|
135
|
+
- Arguments:
|
|
136
|
+
- `events`: array of event hashes.
|
|
137
|
+
- `root`: root directory for file headings.
|
|
138
|
+
- `ranges_by_file`: hash or nil.
|
|
139
|
+
- Returns: HTML string.
|
|
140
|
+
- Side effects: none.
|
|
141
|
+
|
|
142
|
+
## Instrumentation
|
|
143
|
+
|
|
144
|
+
### Activation
|
|
145
|
+
|
|
146
|
+
- Require `lumitrace/record_require` and call `RecordRequire.enable`.
|
|
147
|
+
- Hook `RubyVM::InstructionSequence.translate` to rewrite files at load time.
|
|
148
|
+
- Only instrument files under the configured root directory.
|
|
149
|
+
- Optional: restrict instrumentation to specific line ranges per file.
|
|
150
|
+
|
|
151
|
+
### Root Scope
|
|
152
|
+
|
|
153
|
+
- Root is `root` if set, otherwise `ENV[LUMITRACE_ROOT]` if set, otherwise `Dir.pwd`.
|
|
154
|
+
- Files outside root are ignored.
|
|
155
|
+
|
|
156
|
+
### Exclusions
|
|
157
|
+
|
|
158
|
+
- Tool files are excluded to avoid self-instrumentation:
|
|
159
|
+
- `record_instrument.rb`
|
|
160
|
+
- `record_require.rb`
|
|
161
|
+
- `generate_resulted_html.rb`
|
|
162
|
+
|
|
163
|
+
### Rewriting Strategy
|
|
164
|
+
|
|
165
|
+
- AST is parsed with Prism.
|
|
166
|
+
- For each node, if it matches “wrapable” expression classes, injects:
|
|
167
|
+
- `RecordInstrument.expr_record(file, start_line, start_col, end_line, end_col, (expr))`
|
|
168
|
+
- Insertions are done by offset to preserve original formatting.
|
|
169
|
+
|
|
170
|
+
### Range Filtering
|
|
171
|
+
|
|
172
|
+
- `ranges_by_file` is a hash like `{ "/path/to/file.rb" => [1..5, 10..12] }`.
|
|
173
|
+
- When provided, only files listed in the hash are instrumented.
|
|
174
|
+
- For a listed file, only expressions whose start line falls within the listed ranges are instrumented.
|
|
175
|
+
- If a listed file has `nil` or an empty array for ranges, the entire file is instrumented.
|
|
176
|
+
- HTML rendering respects the same ranges and only shows the listed lines/files.
|
|
177
|
+
|
|
178
|
+
### Wrap Targets
|
|
179
|
+
|
|
180
|
+
- `CallNode` (except those with block bodies)
|
|
181
|
+
- Variable reads:
|
|
182
|
+
- `LocalVariableReadNode`
|
|
183
|
+
- `ConstantReadNode`
|
|
184
|
+
- `InstanceVariableReadNode`
|
|
185
|
+
- `ClassVariableReadNode`
|
|
186
|
+
- `GlobalVariableReadNode`
|
|
187
|
+
- Literal nodes are excluded (e.g. integer, string, true/false, nil, etc.)
|
|
188
|
+
|
|
189
|
+
## Recording
|
|
190
|
+
|
|
191
|
+
- Results are stored per expression key:
|
|
192
|
+
- `(file, start_line, start_col, end_line, end_col)`
|
|
193
|
+
- Keep only the last N values (`max_values_per_expr`, default 3).
|
|
194
|
+
- Track `total` count for how many times the expression executed.
|
|
195
|
+
- Values are stored via `inspect` for non-primitive types.
|
|
196
|
+
- String values are truncated to 1000 bytes for storage.
|
|
197
|
+
|
|
198
|
+
### Output JSON
|
|
199
|
+
|
|
200
|
+
`lumitrace_recorded.json` contains an array of entries:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"file": "/path/to/file.rb",
|
|
205
|
+
"start_line": 10,
|
|
206
|
+
"start_col": 4,
|
|
207
|
+
"end_line": 10,
|
|
208
|
+
"end_col": 20,
|
|
209
|
+
"values": ["..."],
|
|
210
|
+
"total": 123
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## CLI
|
|
215
|
+
|
|
216
|
+
### `exe/lumitrace`
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
lumitrace FILE [--html PATH] [--json [PATH]] [--max N] [--range SPEC]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- HTML is rendered by default (from in-memory events; no JSON file is required).
|
|
223
|
+
- `--html` specifies the HTML output path.
|
|
224
|
+
- JSON is written only when `--json` is provided.
|
|
225
|
+
- `--json` writes JSON output (default: `lumitrace_recorded.json`).
|
|
226
|
+
- `--max` sets max values per expression.
|
|
227
|
+
- `--range` restricts instrumentation per file (`FILE` or `FILE:1-5,10-12`). Can be repeated.
|
|
228
|
+
- `LUMITRACE_VALUES_MAX` sets the default max values per expression.
|
|
229
|
+
|
|
230
|
+
## HTML Rendering
|
|
231
|
+
|
|
232
|
+
- `GenerateResultedHtml.render_all` renders all files in one page.
|
|
233
|
+
- Each file is shown in its own section.
|
|
234
|
+
- Expressions are marked with an inline icon.
|
|
235
|
+
- Hovering the icon shows recorded values.
|
|
236
|
+
- Only the last 3 values are shown in the tooltip; additional values are summarized as `... (+N more)`.
|
|
237
|
+
- Tooltip is scrollable horizontally for long values.
|
|
238
|
+
|
|
239
|
+
### Copy/Paste Behavior
|
|
240
|
+
|
|
241
|
+
- Inline icon uses a separate marker span to reduce copy/paste artifacts.
|
|
242
|
+
- Lines are rendered as inline spans with explicit `\n` inserted.
|
|
243
|
+
|
|
244
|
+
## Known Limitations
|
|
245
|
+
|
|
246
|
+
- Requires `RubyVM::InstructionSequence.translate` support in the Ruby build.
|
|
247
|
+
- Instrumentation is for debugging; semantics may change for unusual edge cases.
|
|
248
|
+
- Tool does not attempt to preserve file encoding comments.
|
data/docs/spec.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# Lumitrace Spec
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Lumitrace instruments Ruby source code at load time (via `RubyVM::InstructionSequence.translate` when available), records expression results, and renders text or HTML output that overlays recorded values on your code. It is designed for local “what happened here?” inspection.
|
|
9
|
+
|
|
10
|
+
## Goals
|
|
11
|
+
|
|
12
|
+
- Record expression results with minimal friction.
|
|
13
|
+
- Limit recorded data size (keep only the last N values per expression).
|
|
14
|
+
- Show recorded values inline on a per-file HTML view.
|
|
15
|
+
- Support require-time instrumentation for multiple files.
|
|
16
|
+
|
|
17
|
+
## Non-Goals
|
|
18
|
+
|
|
19
|
+
- Production-safe tracing.
|
|
20
|
+
- Perfect semantic preservation for all Ruby edge cases.
|
|
21
|
+
|
|
22
|
+
## Public API
|
|
23
|
+
|
|
24
|
+
### `require "lumitrace"`
|
|
25
|
+
|
|
26
|
+
- Arguments: none.
|
|
27
|
+
- Returns: nothing.
|
|
28
|
+
- Side effects: loads core code only (no instrumentation, no `at_exit`).
|
|
29
|
+
|
|
30
|
+
### Library entry points (common usage)
|
|
31
|
+
|
|
32
|
+
- `require "lumitrace"` + `Lumitrace.enable!(...)`
|
|
33
|
+
- `require "lumitrace/enable"` (calls `Lumitrace.enable!`)
|
|
34
|
+
- `require "lumitrace/enable_git_diff"` (diff-scoped `Lumitrace.enable!`)
|
|
35
|
+
- `LUMITRACE_ENABLE=1` + `require "lumitrace"` (auto-`enable!`)
|
|
36
|
+
- `LUMITRACE_ENABLE="--text --html --json ..."` + `require "lumitrace"` (CLI-style options parsed and passed to `enable!`)
|
|
37
|
+
|
|
38
|
+
### `Lumitrace.enable!(max_values: nil, ranges_by_file: nil, root: nil, text: nil, html: nil, json: nil, verbose: nil, at_exit: true)`
|
|
39
|
+
|
|
40
|
+
- Arguments:
|
|
41
|
+
- `max_values`: integer, string, or nil.
|
|
42
|
+
- `ranges_by_file`: hash or nil. `{ "/path/to/file.rb" => [1..5, 10..12] }`.
|
|
43
|
+
- `text`: boolean or string or nil. When nil, determined from environment variables. When string, uses it as the text output path.
|
|
44
|
+
- `html`: boolean or string or nil. When nil, determined from environment variables.
|
|
45
|
+
- `json`: boolean or string or nil. When nil, determined from environment variables.
|
|
46
|
+
- `verbose`: boolean or nil. When nil, determined from `LUMITRACE_VERBOSE`.
|
|
47
|
+
- `at_exit`: boolean. When true, registers output at exit.
|
|
48
|
+
- Returns: `nil`.
|
|
49
|
+
- Side effects:
|
|
50
|
+
- Enables require-time instrumentation.
|
|
51
|
+
- Registers a single `at_exit` hook (if `at_exit: true`).
|
|
52
|
+
- Fixes the HTML output directory to the `Dir.pwd` at call time.
|
|
53
|
+
- Root scope for instrumentation uses `root` if provided, otherwise `ENV["LUMITRACE_ROOT"]` if set, otherwise `Dir.pwd`.
|
|
54
|
+
- Environment variables (resolved by `Lumitrace.enable!`):
|
|
55
|
+
- `LUMITRACE_VALUES_MAX`: default max values per expression when `max_values` is nil (default 3 if unset).
|
|
56
|
+
- `LUMITRACE_ROOT`: root directory used to decide which files are instrumented.
|
|
57
|
+
- `LUMITRACE_HTML`: enable HTML output; `1` uses the default path, otherwise treats the value as the HTML output path. `0`/`false` disables.
|
|
58
|
+
- `LUMITRACE_TEXT`: control text output. `1` forces text on, `0`/`false` disables. If unset, text is enabled only when both HTML and JSON are disabled. Any other value is treated as the text output path.
|
|
59
|
+
- `LUMITRACE_JSON`: enable JSON output; `1` uses the default path, otherwise treats the value as the JSON output path. `0`/`false` disables.
|
|
60
|
+
- `LUMITRACE_GIT_DIFF_UNTRACKED`: include untracked files in git diff ranges (`1` default). Set to `0` to exclude.
|
|
61
|
+
- `LUMITRACE_VERBOSE`: when `1`/`true`, prints verbose logs to stderr.
|
|
62
|
+
- `LUMITRACE_ENABLE`: when `1`/`true`, `require "lumitrace"` will call `Lumitrace.enable!`. When set to a non-boolean string, it is parsed as CLI-style arguments and passed to `enable!`.
|
|
63
|
+
|
|
64
|
+
### `Lumitrace.disable!`
|
|
65
|
+
|
|
66
|
+
- Arguments: none.
|
|
67
|
+
- Returns: `nil`.
|
|
68
|
+
- Side effects: disables instrumentation (does not clear recorded events).
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### `require "lumitrace/enable"`
|
|
72
|
+
|
|
73
|
+
- Arguments: none.
|
|
74
|
+
- Returns: nothing.
|
|
75
|
+
- Side effects: calls `Lumitrace.enable!` with default arguments.
|
|
76
|
+
|
|
77
|
+
### `require "lumitrace/enable_git_diff"`
|
|
78
|
+
|
|
79
|
+
- Arguments: none.
|
|
80
|
+
- Returns: nothing.
|
|
81
|
+
- Side effects:
|
|
82
|
+
- Computes `ranges_by_file` from `git diff` scoped to the current program file.
|
|
83
|
+
- Calls `Lumitrace.enable!` when diff is non-empty.
|
|
84
|
+
- Environment variables:
|
|
85
|
+
- `LUMITRACE_GIT_DIFF=working|staged|base:REV|range:SPEC` selects diff source.
|
|
86
|
+
- `LUMITRACE_GIT_DIFF_CONTEXT=N` expands hunks by +/-N lines (default 3; negative treated as 0).
|
|
87
|
+
- `LUMITRACE_GIT_CMD` overrides the git executable (default: `git`).
|
|
88
|
+
|
|
89
|
+
## Instrumentation
|
|
90
|
+
|
|
91
|
+
### Activation
|
|
92
|
+
|
|
93
|
+
- Call `Lumitrace.enable!`.
|
|
94
|
+
- Hook `RubyVM::InstructionSequence.translate` to rewrite files at load time.
|
|
95
|
+
- Only instrument files under the configured root directory.
|
|
96
|
+
- Optional: restrict instrumentation to specific line ranges per file.
|
|
97
|
+
|
|
98
|
+
### Root Scope
|
|
99
|
+
|
|
100
|
+
- Root is `Dir.pwd` (or `ENV["LUMITRACE_ROOT"]` if set).
|
|
101
|
+
- Files outside root are ignored.
|
|
102
|
+
|
|
103
|
+
### Exclusions
|
|
104
|
+
|
|
105
|
+
- Tool files are excluded to avoid self-instrumentation:
|
|
106
|
+
- `record_instrument.rb`
|
|
107
|
+
- `record_require.rb`
|
|
108
|
+
- `generate_resulted_html.rb`
|
|
109
|
+
|
|
110
|
+
### Rewriting Strategy
|
|
111
|
+
|
|
112
|
+
- AST is parsed with Prism.
|
|
113
|
+
- For each node, if it matches “wrapable” expression classes, injects:
|
|
114
|
+
- `RecordInstrument.expr_record(file, start_line, start_col, end_line, end_col, (expr))`
|
|
115
|
+
- Insertions are done by offset to preserve original formatting.
|
|
116
|
+
|
|
117
|
+
### Range Filtering
|
|
118
|
+
|
|
119
|
+
- `ranges_by_file` is a hash like `{ "/path/to/file.rb" => [1..5, 10..12] }`.
|
|
120
|
+
- When provided, only files listed in the hash are instrumented.
|
|
121
|
+
- For a listed file, only expressions whose start line falls within the listed ranges are instrumented.
|
|
122
|
+
- If a listed file has `nil` or an empty array for ranges, the entire file is instrumented.
|
|
123
|
+
- HTML rendering respects the same ranges and only shows files that produced events.
|
|
124
|
+
|
|
125
|
+
### Wrap Targets
|
|
126
|
+
|
|
127
|
+
- `CallNode` (except those with block bodies)
|
|
128
|
+
- Variable reads:
|
|
129
|
+
- `LocalVariableReadNode`
|
|
130
|
+
- `ConstantReadNode`
|
|
131
|
+
- `InstanceVariableReadNode`
|
|
132
|
+
- `ClassVariableReadNode`
|
|
133
|
+
- `GlobalVariableReadNode`
|
|
134
|
+
- Literal nodes are excluded (e.g. integer, string, true/false, nil, etc.)
|
|
135
|
+
|
|
136
|
+
## Recording
|
|
137
|
+
|
|
138
|
+
- Results are stored per expression key:
|
|
139
|
+
- `(file, start_line, start_col, end_line, end_col)`
|
|
140
|
+
- Keep only the last N values (`max_values_per_expr`, default 3).
|
|
141
|
+
- Track `total` count for how many times the expression executed.
|
|
142
|
+
- Values are stored via `inspect` for non-primitive types.
|
|
143
|
+
- String values are truncated to 1000 bytes for storage.
|
|
144
|
+
|
|
145
|
+
### Output JSON
|
|
146
|
+
|
|
147
|
+
`lumitrace_recorded.json` contains an array of entries:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"file": "/path/to/file.rb",
|
|
152
|
+
"start_line": 10,
|
|
153
|
+
"start_col": 4,
|
|
154
|
+
"end_line": 10,
|
|
155
|
+
"end_col": 20,
|
|
156
|
+
"values": ["..."],
|
|
157
|
+
"total": 123
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## CLI
|
|
162
|
+
|
|
163
|
+
### `exe/lumitrace`
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
lumitrace FILE [--text [PATH]] [--html [PATH]] [--json [PATH]] [--max N] [--range SPEC] [--git-diff [MODE]] [--git-diff-context N] [--git-cmd PATH] [--git-diff-no-untracked]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Text is rendered by default (from in-memory events; no JSON file is required).
|
|
170
|
+
- `--text` writes text output to stdout (default). When a PATH is provided, writes text output to that file.
|
|
171
|
+
- `--html` enables HTML output; optionally specify the output path.
|
|
172
|
+
- JSON is written only when `--json` is provided.
|
|
173
|
+
- `--json` writes JSON output (default: `lumitrace_recorded.json`).
|
|
174
|
+
- `--max` sets max values per expression.
|
|
175
|
+
- `--range` restricts instrumentation per file (`FILE` or `FILE:1-5,10-12`). Can be repeated.
|
|
176
|
+
- `--git-diff` restricts instrumentation to diff hunks (`working` default; `staged|base:REV|range:SPEC`).
|
|
177
|
+
- `--git-diff-context` expands hunks by +/-N lines.
|
|
178
|
+
- `--git-cmd` overrides the git executable.
|
|
179
|
+
- `--git-diff-no-untracked` excludes untracked files (untracked files are included by default).
|
|
180
|
+
- `--verbose` prints verbose logs to stderr.
|
|
181
|
+
- `LUMITRACE_VALUES_MAX` sets the default max values per expression.
|
|
182
|
+
|
|
183
|
+
### Text Output (CLI)
|
|
184
|
+
|
|
185
|
+
- Text output starts with a header line: `=== Lumitrace Results (text) ===`.
|
|
186
|
+
- Each file is printed with a header: `### path/to/file.rb`.
|
|
187
|
+
- Each line is prefixed with a line number like ` 12| `.
|
|
188
|
+
- Skipped ranges are represented by a line containing `...`.
|
|
189
|
+
- Only the last value is shown per expression; if an expression ran multiple times, the last value is annotated with the ordinal run (e.g., `#=> 2 (3rd run)`).
|
|
190
|
+
- When `--text` is used and `--max` is not provided, `max_values` defaults to `1`.
|
|
191
|
+
- When `ranges_by_file` is provided, only files present in the hash are shown in text output.
|
|
192
|
+
|
|
193
|
+
## HTML Rendering
|
|
194
|
+
|
|
195
|
+
- `GenerateResultedHtml.render_all` renders all files in one page.
|
|
196
|
+
- Each file is shown in its own section.
|
|
197
|
+
- Expressions are marked with an inline icon.
|
|
198
|
+
- Hovering the icon shows recorded values.
|
|
199
|
+
- Only the last 3 values are shown in the tooltip; additional values are summarized as `... (+N more)`.
|
|
200
|
+
- Tooltip is scrollable horizontally for long values.
|
|
201
|
+
|
|
202
|
+
### Copy/Paste Behavior
|
|
203
|
+
|
|
204
|
+
- Inline icon uses a separate marker span to reduce copy/paste artifacts.
|
|
205
|
+
- Lines are rendered as inline spans with explicit `\n` inserted.
|
|
206
|
+
|
|
207
|
+
## Known Limitations
|
|
208
|
+
|
|
209
|
+
- Requires `RubyVM::InstructionSequence.translate` support in the Ruby build.
|
|
210
|
+
- Instrumentation is for debugging; semantics may change for unusual edge cases.
|
|
211
|
+
- Tool does not attempt to preserve file encoding comments.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# Supported Syntax
|
|
5
|
+
|
|
6
|
+
Lumitrace instruments Ruby source by wrapping selected expression nodes with
|
|
7
|
+
`Lumitrace::RecordInstrument.expr_record(...)`. It does **not** rewrite the
|
|
8
|
+
entire AST, so coverage is best described as "expressions that are safe to wrap
|
|
9
|
+
in parentheses and call-position contexts."
|
|
10
|
+
|
|
11
|
+
This document lists what is supported today, and what is intentionally skipped
|
|
12
|
+
to avoid breaking valid Ruby syntax.
|
|
13
|
+
|
|
14
|
+
## Supported (Instrumented)
|
|
15
|
+
|
|
16
|
+
The following node kinds are instrumented when they appear in normal expression
|
|
17
|
+
positions:
|
|
18
|
+
|
|
19
|
+
- Method calls (`Prism::CallNode`)
|
|
20
|
+
- Example:
|
|
21
|
+
```ruby
|
|
22
|
+
foo(bar)
|
|
23
|
+
```
|
|
24
|
+
- Local variable reads (`Prism::LocalVariableReadNode`)
|
|
25
|
+
- Example:
|
|
26
|
+
```ruby
|
|
27
|
+
x
|
|
28
|
+
```
|
|
29
|
+
- Numbered block parameter reads (`Prism::ItLocalVariableReadNode`)
|
|
30
|
+
- Example:
|
|
31
|
+
```ruby
|
|
32
|
+
it
|
|
33
|
+
```
|
|
34
|
+
- Constant reads (`Prism::ConstantReadNode`)
|
|
35
|
+
- Example:
|
|
36
|
+
```ruby
|
|
37
|
+
SomeConst
|
|
38
|
+
```
|
|
39
|
+
- Instance variable reads (`Prism::InstanceVariableReadNode`)
|
|
40
|
+
- Example:
|
|
41
|
+
```ruby
|
|
42
|
+
@value
|
|
43
|
+
```
|
|
44
|
+
- Class variable reads (`Prism::ClassVariableReadNode`)
|
|
45
|
+
- Example:
|
|
46
|
+
```ruby
|
|
47
|
+
@@count
|
|
48
|
+
```
|
|
49
|
+
- Global variable reads (`Prism::GlobalVariableReadNode`)
|
|
50
|
+
- Example:
|
|
51
|
+
```ruby
|
|
52
|
+
$stdout
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
- Nodes that are not expression reads/calls are generally left as-is (e.g.,
|
|
57
|
+
definitions, control flow, assignment statements, etc.).
|
|
58
|
+
|
|
59
|
+
## Not Supported (Skipped)
|
|
60
|
+
|
|
61
|
+
These are intentionally skipped to keep output valid Ruby:
|
|
62
|
+
|
|
63
|
+
- Definitions and structural nodes (the entire node is never wrapped):
|
|
64
|
+
- `def`, `class`, `module`, `if`, `case`, `while`, `begin`, `rescue`, etc.
|
|
65
|
+
- Example:
|
|
66
|
+
```ruby
|
|
67
|
+
def foo
|
|
68
|
+
bar
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- Literals (not wrapped):
|
|
73
|
+
- `1`, `"str"`, `:sym`, `true`, `false`, `nil`
|
|
74
|
+
|
|
75
|
+
- Method calls that have a block with body (`do ... end` / `{ ... }`) are
|
|
76
|
+
instrumented at the call expression level. Example:
|
|
77
|
+
```ruby
|
|
78
|
+
items.each do |x|
|
|
79
|
+
x + 1
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- Alias statements (both aliasing globals and methods):
|
|
84
|
+
- `alias $ERROR_INFO $!`
|
|
85
|
+
- `alias old_name new_name`
|
|
86
|
+
|
|
87
|
+
- The receiver part of a singleton method definition:
|
|
88
|
+
- Example:
|
|
89
|
+
```ruby
|
|
90
|
+
def Foo.bar
|
|
91
|
+
1
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
- `Foo` is **not** instrumented here.
|
|
95
|
+
|
|
96
|
+
- Embedded variable nodes inside interpolated strings:
|
|
97
|
+
- Example:
|
|
98
|
+
```ruby
|
|
99
|
+
"#@path?#@query"
|
|
100
|
+
```
|
|
101
|
+
- `@path` and `@query` inside the interpolation are **not** instrumented.
|
|
102
|
+
|
|
103
|
+
- Implicit keyword argument values (`token:` style):
|
|
104
|
+
- Example:
|
|
105
|
+
```ruby
|
|
106
|
+
ec2_metadata_request(EC2_IAM_INFO, token:)
|
|
107
|
+
```
|
|
108
|
+
- The implicit `token` read is **not** instrumented.
|
|
109
|
+
|
|
110
|
+
## Rationale
|
|
111
|
+
|
|
112
|
+
All skips above correspond to syntactic positions where wrapping the token with
|
|
113
|
+
`expr_record(...)` would change the Ruby grammar (e.g., alias operands, method
|
|
114
|
+
name positions, or implicit keyword arguments).
|
|
115
|
+
|
|
116
|
+
If you want additional coverage, we can add more targeted rewrites, but they
|
|
117
|
+
must preserve valid syntax in those special contexts.
|