ruby_method_tracer 0.4.0 → 0.5.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/CHANGELOG.md +34 -0
- data/README.md +88 -11
- data/lib/ruby_method_tracer/call_tree.rb +61 -69
- data/lib/ruby_method_tracer/call_tree_statistics.rb +75 -0
- data/lib/ruby_method_tracer/enhanced_tracer.rb +72 -74
- data/lib/ruby_method_tracer/exportable.rb +5 -1
- data/lib/ruby_method_tracer/formatters/base_formatter.rb +2 -1
- data/lib/ruby_method_tracer/simple_tracer.rb +130 -47
- data/lib/ruby_method_tracer/version.rb +1 -1
- data/lib/ruby_method_tracer/wrapper.rb +274 -0
- data/lib/ruby_method_tracer.rb +36 -10
- data/sig/ruby_method_tracer.rbs +210 -1
- metadata +3 -5
- data/.rspec +0 -3
- data/.rubocop.yml +0 -27
- data/CLAUDE.md +0 -61
- data/Rakefile +0 -12
data/sig/ruby_method_tracer.rbs
CHANGED
|
@@ -1,4 +1,213 @@
|
|
|
1
1
|
module RubyMethodTracer
|
|
2
2
|
VERSION: String
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
type visibility = :public | :protected | :private
|
|
8
|
+
type status = :success | :error | :incomplete
|
|
9
|
+
type export_format = :json | :flat | :tree
|
|
10
|
+
|
|
11
|
+
type call_record = {
|
|
12
|
+
method_name: String,
|
|
13
|
+
execution_time: Float,
|
|
14
|
+
status: status,
|
|
15
|
+
error: Exception?,
|
|
16
|
+
timestamp: Time
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type tree_node = {
|
|
20
|
+
method_name: String,
|
|
21
|
+
start_time: Float,
|
|
22
|
+
depth: Integer,
|
|
23
|
+
children: Array[tree_node],
|
|
24
|
+
status: status?,
|
|
25
|
+
error: Exception?,
|
|
26
|
+
execution_time: Float?,
|
|
27
|
+
timestamp: Time
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type results = {
|
|
31
|
+
total_calls: Integer,
|
|
32
|
+
total_time: Float,
|
|
33
|
+
calls: Array[call_record]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type statistics = {
|
|
37
|
+
total_calls: Integer,
|
|
38
|
+
total_time: Float,
|
|
39
|
+
unique_methods: Integer,
|
|
40
|
+
slowest_methods: Array[{ method: String, avg_time: Float }],
|
|
41
|
+
most_called_methods: Array[{ method: String, count: Integer }],
|
|
42
|
+
average_time_per_method: Hash[String, Float],
|
|
43
|
+
max_depth: Integer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
self.@configuration: Configuration
|
|
47
|
+
|
|
48
|
+
def self.configuration: () -> Configuration
|
|
49
|
+
def self.configure: () { (Configuration) -> void } -> Configuration
|
|
50
|
+
| () -> Configuration
|
|
51
|
+
def self.reset_configuration!: () -> Configuration
|
|
52
|
+
def self.included: (Module) -> void
|
|
53
|
+
|
|
54
|
+
module ClassMethods
|
|
55
|
+
def trace_methods: (*Symbol | String, **untyped) -> SimpleTracer
|
|
56
|
+
def trace_class_methods: (*Symbol | String, **untyped) -> SimpleTracer
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Configuration
|
|
60
|
+
attr_accessor threshold: Float
|
|
61
|
+
attr_accessor auto_output: bool
|
|
62
|
+
attr_accessor max_calls: Integer
|
|
63
|
+
attr_accessor logger: Logger?
|
|
64
|
+
attr_accessor track_hierarchy: bool
|
|
65
|
+
|
|
66
|
+
def initialize: () -> void
|
|
67
|
+
def reset!: () -> Configuration
|
|
68
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module Exportable
|
|
72
|
+
def render: (?format: export_format, **untyped) -> String
|
|
73
|
+
def export: (String | _ToS, ?format: export_format, **untyped) -> String
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_formatter: (export_format | String) -> Formatters::BaseFormatter
|
|
78
|
+
def write_export: (String, String) -> String
|
|
79
|
+
def validate_export_path: (String | _ToS) -> String
|
|
80
|
+
def report_source: () -> untyped
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Wrapper
|
|
84
|
+
OMITTED: Object
|
|
85
|
+
|
|
86
|
+
def self.install: (Module, Symbol, Symbol, Symbol) -> Symbol
|
|
87
|
+
|
|
88
|
+
class Signature
|
|
89
|
+
def initialize: (Array[[Symbol, Symbol?]] | Array[[Symbol]]) -> void
|
|
90
|
+
def declaration: () -> String
|
|
91
|
+
def setup: () -> String
|
|
92
|
+
def forward: (Symbol) -> String
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class SimpleTracer
|
|
97
|
+
include Exportable
|
|
98
|
+
|
|
99
|
+
@target_class: Module
|
|
100
|
+
@options: Hash[Symbol, untyped]
|
|
101
|
+
@calls: Array[call_record]
|
|
102
|
+
@lock: Thread::Mutex
|
|
103
|
+
@wrapped_methods: Hash[Symbol, visibility]
|
|
104
|
+
@qualified_names: Hash[Symbol, String]
|
|
105
|
+
@logger: Logger
|
|
106
|
+
@tracer_key: Symbol
|
|
107
|
+
@accessor: Symbol
|
|
108
|
+
@accessor_defined: bool?
|
|
109
|
+
@formatter: Formatters::BaseFormatter
|
|
110
|
+
|
|
111
|
+
def initialize: (Module, **untyped) -> void
|
|
112
|
+
def trace_method: (Symbol | String) -> bool
|
|
113
|
+
def untrace_method: (Symbol | String) -> bool
|
|
114
|
+
def untrace_all: () -> Array[Symbol]
|
|
115
|
+
def record_call: (Symbol, Float, status, ?Exception?) -> void
|
|
116
|
+
def fetch_results: () -> results
|
|
117
|
+
def clear_results: () -> void
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def default_options: () -> Hash[Symbol, untyped]
|
|
122
|
+
def method_visibility: (Symbol) -> visibility?
|
|
123
|
+
def install_wrapper: (Symbol, visibility) -> void
|
|
124
|
+
def define_accessor: () -> void
|
|
125
|
+
def already_traced?: (Symbol) -> bool
|
|
126
|
+
def missing_method: (Symbol) -> bool
|
|
127
|
+
def qualified_name: (Symbol) -> String
|
|
128
|
+
def alias_for: (Symbol) -> Symbol
|
|
129
|
+
def dispatch: (Symbol) { () -> untyped } -> untyped
|
|
130
|
+
def wrap_call: (Symbol, Symbol) { () -> untyped } -> untyped
|
|
131
|
+
def monotonic_time: () -> Float
|
|
132
|
+
def output_call: (call_record) -> void
|
|
133
|
+
def status_label: (status) -> String
|
|
134
|
+
def format_time: (Float) -> String
|
|
135
|
+
def colorize: (String, Symbol) -> String
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class EnhancedTracer < SimpleTracer
|
|
139
|
+
attr_reader call_tree: CallTree
|
|
140
|
+
|
|
141
|
+
@track_hierarchy: bool
|
|
142
|
+
@method_keys: Hash[Symbol, Symbol]
|
|
143
|
+
|
|
144
|
+
def initialize: (Module, **untyped) -> void
|
|
145
|
+
def print_tree: (?Hash[Symbol, untyped]) -> void
|
|
146
|
+
def format_tree: (?Hash[Symbol, untyped]) -> String
|
|
147
|
+
def fetch_enhanced_results: () -> {
|
|
148
|
+
flat_calls: results,
|
|
149
|
+
call_hierarchy: Array[tree_node],
|
|
150
|
+
statistics: statistics
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def run_with_hierarchy: (Symbol, Symbol, ^() -> untyped) -> untyped
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class CallTree
|
|
159
|
+
DEFAULT_MAX_CALLS: Integer
|
|
160
|
+
|
|
161
|
+
attr_reader calls: Array[tree_node]
|
|
162
|
+
attr_reader root_calls: Array[tree_node]
|
|
163
|
+
|
|
164
|
+
@threshold: Float
|
|
165
|
+
@max_calls: Integer
|
|
166
|
+
@lock: Thread::Mutex
|
|
167
|
+
@thread_key: Symbol
|
|
168
|
+
|
|
169
|
+
def initialize: (?threshold: Float, ?max_calls: Integer) -> void
|
|
170
|
+
def start_call: (String) -> tree_node
|
|
171
|
+
def end_call: (?status, ?Exception?) -> tree_node?
|
|
172
|
+
def current_depth: () -> Integer
|
|
173
|
+
def call_hierarchy: () -> Array[tree_node]
|
|
174
|
+
def statistics: () -> statistics
|
|
175
|
+
def clear: () -> void
|
|
176
|
+
def empty?: () -> bool
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def discardable?: (tree_node) -> bool
|
|
181
|
+
def discard: (tree_node, tree_node?) -> nil
|
|
182
|
+
def retain: (tree_node) -> tree_node
|
|
183
|
+
def thread_call_stack: () -> Array[tree_node]
|
|
184
|
+
def monotonic_time: () -> Float
|
|
185
|
+
def default_statistics: () -> statistics
|
|
186
|
+
def calculate_method_stats: () -> Hash[String, { calls: Integer, total_time: Float, times: Array[Float] }]
|
|
187
|
+
def slowest_methods: (Hash[String, untyped]) -> Array[{ method: String, avg_time: Float }]
|
|
188
|
+
def most_called_methods: (Hash[String, untyped]) -> Array[{ method: String, count: Integer }]
|
|
189
|
+
def average_times: (Hash[String, untyped]) -> Hash[String, Float]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
module Formatters
|
|
193
|
+
class BaseFormatter
|
|
194
|
+
def format_time: (Float) -> String
|
|
195
|
+
def colorize: (String, Symbol) -> String
|
|
196
|
+
def format: (untyped, ?Hash[Symbol, untyped]) -> String
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
class TreeFormatter < BaseFormatter
|
|
200
|
+
def format: (CallTree, ?Hash[Symbol, untyped]) -> String
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
class JsonFormatter < BaseFormatter
|
|
204
|
+
def format: (CallTree | results, ?Hash[Symbol, untyped]) -> String
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class FlatFormatter < BaseFormatter
|
|
208
|
+
HEADERS: Array[String]
|
|
209
|
+
|
|
210
|
+
def format: (CallTree | results, ?Hash[Symbol, untyped]) -> String
|
|
211
|
+
end
|
|
212
|
+
end
|
|
4
213
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_method_tracer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Seun Adekunle
|
|
@@ -17,16 +17,13 @@ executables: []
|
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
|
-
- ".rspec"
|
|
21
|
-
- ".rubocop.yml"
|
|
22
20
|
- CHANGELOG.md
|
|
23
|
-
- CLAUDE.md
|
|
24
21
|
- CODE_OF_CONDUCT.md
|
|
25
22
|
- LICENSE.txt
|
|
26
23
|
- README.md
|
|
27
|
-
- Rakefile
|
|
28
24
|
- lib/ruby_method_tracer.rb
|
|
29
25
|
- lib/ruby_method_tracer/call_tree.rb
|
|
26
|
+
- lib/ruby_method_tracer/call_tree_statistics.rb
|
|
30
27
|
- lib/ruby_method_tracer/configuration.rb
|
|
31
28
|
- lib/ruby_method_tracer/enhanced_tracer.rb
|
|
32
29
|
- lib/ruby_method_tracer/exportable.rb
|
|
@@ -36,6 +33,7 @@ files:
|
|
|
36
33
|
- lib/ruby_method_tracer/formatters/tree_formatter.rb
|
|
37
34
|
- lib/ruby_method_tracer/simple_tracer.rb
|
|
38
35
|
- lib/ruby_method_tracer/version.rb
|
|
36
|
+
- lib/ruby_method_tracer/wrapper.rb
|
|
39
37
|
- sig/ruby_method_tracer.rbs
|
|
40
38
|
homepage: https://github.com/Seunadex/ruby_method_tracer
|
|
41
39
|
licenses:
|
data/.rspec
DELETED
data/.rubocop.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
plugins:
|
|
2
|
-
- rubocop-rspec
|
|
3
|
-
|
|
4
|
-
AllCops:
|
|
5
|
-
TargetRubyVersion: 3.0
|
|
6
|
-
SuggestExtensions: false
|
|
7
|
-
NewCops: enable
|
|
8
|
-
Exclude:
|
|
9
|
-
- 'examples/**/*'
|
|
10
|
-
- 'vendor/**/*'
|
|
11
|
-
|
|
12
|
-
Style/StringLiterals:
|
|
13
|
-
EnforcedStyle: double_quotes
|
|
14
|
-
|
|
15
|
-
Style/StringLiteralsInInterpolation:
|
|
16
|
-
EnforcedStyle: double_quotes
|
|
17
|
-
|
|
18
|
-
Metrics/MethodLength:
|
|
19
|
-
Max: 15
|
|
20
|
-
Metrics/ClassLength:
|
|
21
|
-
Max: 120
|
|
22
|
-
|
|
23
|
-
RSpec/MultipleExpectations:
|
|
24
|
-
Max: 15
|
|
25
|
-
|
|
26
|
-
RSpec/ExampleLength:
|
|
27
|
-
Max: 15
|
data/CLAUDE.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Commands
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
bundle exec rspec # Run all tests (also generates coverage report in coverage/)
|
|
9
|
-
bundle exec rubocop # Lint
|
|
10
|
-
bundle exec rake # Run both spec and rubocop (default task)
|
|
11
|
-
|
|
12
|
-
# Run a single spec file
|
|
13
|
-
bundle exec rspec spec/ruby_method_tracer/simple_tracer_spec.rb
|
|
14
|
-
|
|
15
|
-
# Run a specific example by description
|
|
16
|
-
bundle exec rspec spec/ruby_method_tracer/simple_tracer_spec.rb -e "records call details"
|
|
17
|
-
|
|
18
|
-
# Install gem locally for manual testing
|
|
19
|
-
bundle exec rake install
|
|
20
|
-
|
|
21
|
-
# Release: bump version in lib/ruby_method_tracer/version.rb, then:
|
|
22
|
-
bundle exec rake release
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Commit conventions
|
|
26
|
-
|
|
27
|
-
- Do NOT add a `Co-authored-by:` trailer (or any "Generated with"/agent attribution) to commit messages.
|
|
28
|
-
|
|
29
|
-
## Architecture
|
|
30
|
-
|
|
31
|
-
The gem provides two ways to trace methods:
|
|
32
|
-
|
|
33
|
-
1. **Mixin API** — `include RubyMethodTracer` in a class, then call `trace_methods(:method_name, **opts)` at the class level. This creates a `SimpleTracer` internally and is the simplest entry point.
|
|
34
|
-
|
|
35
|
-
2. **Direct tracer API** — instantiate `SimpleTracer` or `EnhancedTracer` directly for programmatic access to results.
|
|
36
|
-
|
|
37
|
-
### Tracing mechanism (SimpleTracer)
|
|
38
|
-
|
|
39
|
-
`SimpleTracer#trace_method` wraps a method by:
|
|
40
|
-
1. Detecting visibility (public/protected/private) via `method_defined?` etc.
|
|
41
|
-
2. Aliasing the original to `__ruby_method_tracer_original_<name>__`
|
|
42
|
-
3. Redefining the method with a proc that times the call and delegates to the alias
|
|
43
|
-
4. Restoring the original visibility
|
|
44
|
-
|
|
45
|
-
The wrapper uses `Thread.current[:__ruby_method_tracer_in_trace]` as a reentrancy flag to avoid recursive double-recording. Kwargs are forwarded conditionally (`kwargs.empty?` check) to stay compatible with Ruby 3.x.
|
|
46
|
-
|
|
47
|
-
Results are stored in `@calls` (an Array) guarded by a `Mutex`. The `max_calls` option enforces a sliding window by `shift`-ing the oldest entry.
|
|
48
|
-
|
|
49
|
-
### EnhancedTracer
|
|
50
|
-
|
|
51
|
-
Inherits from `SimpleTracer` and adds call-tree tracking via `CallTree`. It overrides `trace_method` to use a per-method reentrancy key (`__ruby_method_tracer_in_trace_<method_name>`) so that *different* wrapped methods can nest inside each other (unlike `SimpleTracer` which blocks all nesting).
|
|
52
|
-
|
|
53
|
-
### CallTree
|
|
54
|
-
|
|
55
|
-
Stack-based hierarchy tracker. `start_call` pushes a record (with a `parent` pointer and `children` array) onto `@call_stack`; `end_call` pops it, fills timing/status, and appends to the flat `@calls` list. Root-level calls (depth 0) are also collected in `@root_calls` for tree rendering.
|
|
56
|
-
|
|
57
|
-
### Formatters
|
|
58
|
-
|
|
59
|
-
`Formatters::BaseFormatter` — abstract base providing `format_time` and `colorize` (ANSI).
|
|
60
|
-
|
|
61
|
-
`Formatters::TreeFormatter < BaseFormatter` — renders a `CallTree` as an ASCII tree with `└──`/`├──` connectors, then appends a statistics block (slowest methods, most-called methods).
|