lumitrace 0.1.2 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e03b4d880369e5f87e97dc3d9512afe78f27e310efd2b960a1b2ce545e3a70d1
4
- data.tar.gz: '0970a1a220cf484be1d75aa1d51f6004c08ce7bc1154857180247edc44253353'
3
+ metadata.gz: 8ffc13efecd8f5143486eff6929c93bf73aaa3934bf24fa57e906a2f90498e2c
4
+ data.tar.gz: df8ce2960f59409dc832d21f9d1df7a2b33cc431fde00c76633f1149a5da3ab3
5
5
  SHA512:
6
- metadata.gz: 28a83ebc1a957b33da7a9421a78176f4d348cd3963d702f2b926eba5fa3e1b1fb7277d1a8e9635644a71af25c63e5072829ba32e7c3671636d956d266b2eb22a
7
- data.tar.gz: 83956ce933a06f9cfd128e35146b7745410215a392b9bcd22008ae26b4568db8369cafdbb93d48bdb7d92785ccb7f342fe3445f4cf38fbba985c392b95637825
6
+ metadata.gz: 2030ec337dc9a941f1c9878b1faa74e15e605a715e1d2049675115573c7c1357d821ded3a200b348521a9593faf6f719ff93766f5ffe1c623d9a9c6dcaa663b1
7
+ data.tar.gz: 68564fe5bde3b1d00edd5390211c922b72f3565069446affed3dfdf042fc86491949f8170e175df8cdd99c55b1c05f030adab69fe140ce05cfd003f3f3a51719
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.
@@ -10,29 +20,41 @@ Lumitrace hooks `RubyVM::InstructionSequence.translate` (when available) to rewr
10
20
 
11
21
  ### CLI
12
22
 
13
- Run a script and emit HTML (default output: `lumitrace_recorded.html`):
23
+ Run a script and emit text output (default):
24
+
25
+ ```bash
26
+ lumitrace path/to/entry.rb
27
+ ```
28
+
29
+ Run another command via exec:
30
+
31
+ ```bash
32
+ lumitrace exec rake test
33
+ ```
34
+
35
+ Emit HTML output:
14
36
 
15
37
  ```bash
16
- ruby exe/lumitrace path/to/entry.rb
38
+ lumitrace -h path/to/entry.rb
17
39
  ```
18
40
 
19
41
  Limit the number of recorded values per expression (defaults to 3):
20
42
 
21
43
  ```bash
22
- LUMITRACE_VALUES_MAX=5 ruby exe/lumitrace path/to/entry.rb
44
+ LUMITRACE_VALUES_MAX=5 lumitrace path/to/entry.rb
23
45
  ```
24
46
 
25
47
  Write JSON output explicitly:
26
48
 
27
49
  ```bash
28
- ruby exe/lumitrace path/to/entry.rb --json
29
- ruby exe/lumitrace path/to/entry.rb --json out/lumitrace_recorded.json
50
+ lumitrace -j path/to/entry.rb
51
+ lumitrace --json=out/lumitrace_recorded.json path/to/entry.rb
30
52
  ```
31
53
 
32
54
  Restrict to specific line ranges:
33
55
 
34
56
  ```bash
35
- ruby exe/lumitrace path/to/entry.rb --range path/to/entry.rb:10-20,30-35
57
+ lumitrace --range path/to/entry.rb:10-20,30-35 path/to/entry.rb
36
58
  ```
37
59
 
38
60
  ### Library
@@ -58,18 +80,27 @@ require "lumitrace/enable"
58
80
 
59
81
  ## Output
60
82
 
61
- - HTML: `lumitrace_recorded.html` by default, override with `LUMITRACE_HTML_OUT`.
62
- - JSON: written only when `--json` (CLI) or `LUMITRACE_JSON_OUT` (library) is provided. Default filename is `lumitrace_recorded.json`.
83
+ - Text: printed by default; use `--text=PATH` to write to a file.
84
+ - HTML: `lumitrace_recorded.html` by default, or `--html=PATH`.
85
+ - JSON: written only when `--json` (CLI) or `LUMITRACE_JSON` (library/CLI) is provided. Default filename is `lumitrace_recorded.json`.
86
+ - Fork/exec: merged by default. Child processes write fragments under `LUMITRACE_RESULTS_DIR`.
63
87
 
64
88
  ## Environment Variables
65
89
 
66
90
  - `LUMITRACE_VALUES_MAX`: default max values per expression (default 3 if unset).
67
91
  - `LUMITRACE_ROOT`: root directory used to decide which files are instrumented.
68
- - `LUMITRACE_HTML_OUT`: override HTML output path.
69
- - `LUMITRACE_JSON_OUT`: if set, writes JSON to this path at exit.
92
+ - `LUMITRACE_TEXT`: control text output. `1` forces text on, `0`/`false` disables. Any other value is treated as the text output path.
93
+ - `LUMITRACE_HTML`: enable HTML output; `1` uses the default path, otherwise treats the value as the HTML output path. `0`/`false` disables.
94
+ - `LUMITRACE_JSON`: enable JSON output; `1` uses the default path, otherwise treats the value as the JSON output path. `0`/`false` disables.
95
+ - `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!`.
96
+ - `LUMITRACE_VERBOSE`: when `1`/`true`, prints verbose logs to stderr.
97
+ - `LUMITRACE_RANGE`: semicolon-separated range specs (e.g. `a.rb:1-3,5-6;b.rb`).
98
+ - `LUMITRACE_RESULTS_DIR`: internal use. Shared results directory for fork/exec merge (default: `Dir.tmpdir/lumitrace_results/<user>_<parent_pid>`).
99
+ - `LUMITRACE_RESULTS_PARENT_PID`: internal use. Parent PID for fork/exec merge (auto-set).
70
100
  - `LUMITRACE_GIT_DIFF=working|staged|base:REV|range:SPEC`: diff source for `enable_git_diff`.
71
101
  - `LUMITRACE_GIT_DIFF_CONTEXT=N`: expand diff hunks by +/-N lines (default 3).
72
102
  - `LUMITRACE_GIT_CMD`: git executable override (default `git`).
103
+ - `LUMITRACE_GIT_DIFF_UNTRACKED`: include untracked files in git diff ranges (`1` default). Set to `0` to exclude.
73
104
 
74
105
  ## Notes And Limitations
75
106
 
@@ -88,5 +119,5 @@ bundle install
88
119
  Run the CLI locally:
89
120
 
90
121
  ```bash
91
- ruby exe/lumitrace path/to/entry.rb
122
+ lumitrace path/to/entry.rb
92
123
  ```
data/docs/spec.md ADDED
@@ -0,0 +1,225 @@
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="-t -h -j ..."` + `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
+ - `LUMITRACE_RANGE`: semicolon-separated range specs, e.g. `a.rb:1-3,5-6;b.rb`.
64
+ - `LUMITRACE_RESULTS_DIR`: internal use. Shared results directory for fork/exec merge (default: `Dir.tmpdir/lumitrace_results/<user>_<parent_pid>`).
65
+ - `LUMITRACE_RESULTS_PARENT_PID`: internal use. Parent PID for fork/exec merge (auto-set).
66
+
67
+ ### `Lumitrace.disable!`
68
+
69
+ - Arguments: none.
70
+ - Returns: `nil`.
71
+ - Side effects: disables instrumentation (does not clear recorded events).
72
+
73
+
74
+ ### `require "lumitrace/enable"`
75
+
76
+ - Arguments: none.
77
+ - Returns: nothing.
78
+ - Side effects: calls `Lumitrace.enable!` with default arguments.
79
+
80
+ ### `require "lumitrace/enable_git_diff"`
81
+
82
+ - Arguments: none.
83
+ - Returns: nothing.
84
+ - Side effects:
85
+ - Computes `ranges_by_file` from `git diff` scoped to the current program file.
86
+ - Calls `Lumitrace.enable!` when diff is non-empty.
87
+ - Environment variables:
88
+ - `LUMITRACE_GIT_DIFF=working|staged|base:REV|range:SPEC` selects diff source.
89
+ - `LUMITRACE_GIT_DIFF_CONTEXT=N` expands hunks by +/-N lines (default 3; negative treated as 0).
90
+ - `LUMITRACE_GIT_CMD` overrides the git executable (default: `git`).
91
+ - `LUMITRACE_RANGE` can be used to pass explicit ranges via env.
92
+
93
+ ## Instrumentation
94
+
95
+ ### Activation
96
+
97
+ - Call `Lumitrace.enable!`.
98
+ - Hook `RubyVM::InstructionSequence.translate` to rewrite files at load time.
99
+ - Only instrument files under the configured root directory.
100
+ - Optional: restrict instrumentation to specific line ranges per file.
101
+
102
+ ### Root Scope
103
+
104
+ - Root is `Dir.pwd` (or `ENV["LUMITRACE_ROOT"]` if set).
105
+ - Files outside root are ignored.
106
+
107
+ ### Exclusions
108
+
109
+ - Tool files are excluded to avoid self-instrumentation:
110
+ - `record_instrument.rb`
111
+ - `record_require.rb`
112
+ - `generate_resulted_html.rb`
113
+
114
+ ### Rewriting Strategy
115
+
116
+ - AST is parsed with Prism.
117
+ - For each node, if it matches “wrapable” expression classes, injects:
118
+ - `RecordInstrument.expr_record(file, start_line, start_col, end_line, end_col, (expr))`
119
+ - Insertions are done by offset to preserve original formatting.
120
+
121
+ ### Range Filtering
122
+
123
+ - `ranges_by_file` is a hash like `{ "/path/to/file.rb" => [1..5, 10..12] }`.
124
+ - When provided, only files listed in the hash are instrumented.
125
+ - For a listed file, only expressions whose start line falls within the listed ranges are instrumented.
126
+ - If a listed file has `nil` or an empty array for ranges, the entire file is instrumented.
127
+ - HTML rendering respects the same ranges and only shows files that produced events.
128
+
129
+ ### Wrap Targets
130
+
131
+ - `CallNode` (except those with block bodies)
132
+ - Variable reads:
133
+ - `LocalVariableReadNode`
134
+ - `ConstantReadNode`
135
+ - `InstanceVariableReadNode`
136
+ - `ClassVariableReadNode`
137
+ - `GlobalVariableReadNode`
138
+ - Literal nodes are excluded (e.g. integer, string, true/false, nil, etc.)
139
+
140
+ ## Recording
141
+
142
+ - Results are stored per expression key:
143
+ - `(file, start_line, start_col, end_line, end_col)`
144
+ - Keep only the last N values (`max_values_per_expr`, default 3).
145
+ - Track `total` count for how many times the expression executed.
146
+ - Values are stored via `inspect` for non-primitive types.
147
+ - String values are truncated to 1000 bytes for storage.
148
+
149
+ ## Fork/Exec Merge
150
+
151
+ - Fork/exec results are merged by default.
152
+ - The parent process writes final text/HTML/JSON.
153
+ - Child processes write JSON fragments under `LUMITRACE_RESULTS_DIR` and do not write final outputs.
154
+ - When `Process._fork` is available, Lumitrace hooks it to reset child events immediately after fork.
155
+ - `exec` inherits `LUMITRACE_RESULTS_DIR` and `LUMITRACE_RESULTS_PARENT_PID` via the environment. `Lumitrace.enable!` also appends `-rlumitrace` to `RUBYOPT` to ensure the exec'd process loads Lumitrace.
156
+
157
+ ### Output JSON
158
+
159
+ `lumitrace_recorded.json` contains an array of entries:
160
+
161
+ ```json
162
+ {
163
+ "file": "/path/to/file.rb",
164
+ "start_line": 10,
165
+ "start_col": 4,
166
+ "end_line": 10,
167
+ "end_col": 20,
168
+ "values": ["..."],
169
+ "total": 123
170
+ }
171
+ ```
172
+
173
+ ## CLI
174
+
175
+ ### `lumitrace`
176
+
177
+ ```
178
+ lumitrace [options] script.rb [ruby_opt]
179
+ lumitrace [options] exec CMD [args...]
180
+ ```
181
+
182
+ - Text is rendered by default (from in-memory events; no JSON file is required).
183
+ - `-t` enables text output to stdout. `--text=PATH` writes to a file.
184
+ - `-h` enables HTML output (default path). `--html=PATH` writes to a file.
185
+ - `-j` enables JSON output (default path). `--json=PATH` writes to a file.
186
+ - `-g` enables git diff with `working` mode. `--git-diff=MODE` selects `staged|base:REV|range:SPEC`.
187
+ - `--max` sets max values per expression.
188
+ - `--range` restricts instrumentation per file (`FILE` or `FILE:1-5,10-12`). Can be repeated.
189
+ - `--git-diff=MODE` restricts instrumentation to diff hunks (`staged|base:REV|range:SPEC`).
190
+ - `--git-diff-context` expands hunks by +/-N lines.
191
+ - `--git-cmd` overrides the git executable.
192
+ - `--git-diff-no-untracked` excludes untracked files (untracked files are included by default).
193
+ - `--verbose` prints verbose logs to stderr.
194
+ - `LUMITRACE_VALUES_MAX` sets the default max values per expression.
195
+ - The CLI launches a child process (Ruby or `exec` target) with `RUBYOPT=-rlumitrace` and `LUMITRACE_*` env vars.
196
+
197
+ ### Text Output (CLI)
198
+
199
+ - Text output starts with a header line: `=== Lumitrace Results (text) ===`.
200
+ - Each file is printed with a header: `### path/to/file.rb`.
201
+ - Each line is prefixed with a line number like ` 12| `.
202
+ - Skipped ranges are represented by a line containing `...`.
203
+ - 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)`).
204
+ - When `--text` is used and `--max` is not provided, `max_values` defaults to `1`.
205
+ - When `ranges_by_file` is provided, only files present in the hash are shown in text output.
206
+
207
+ ## HTML Rendering
208
+
209
+ - `GenerateResultedHtml.render_all` renders all files in one page.
210
+ - Each file is shown in its own section.
211
+ - Expressions are marked with an inline icon.
212
+ - Hovering the icon shows recorded values.
213
+ - Only the last 3 values are shown in the tooltip; additional values are summarized as `... (+N more)`.
214
+ - Tooltip is scrollable horizontally for long values.
215
+
216
+ ### Copy/Paste Behavior
217
+
218
+ - Inline icon uses a separate marker span to reduce copy/paste artifacts.
219
+ - Lines are rendered as inline spans with explicit `\n` inserted.
220
+
221
+ ## Known Limitations
222
+
223
+ - Requires `RubyVM::InstructionSequence.translate` support in the Ruby build.
224
+ - Instrumentation is for debugging; semantics may change for unusual edge cases.
225
+ - 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.