rotoscope 0.3.0 → 0.3.1.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +26 -0
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +5 -3
- data/.rubocop_todo.yml +13 -0
- data/Gemfile +6 -1
- data/README.md +232 -24
- data/Rakefile +21 -11
- data/dev.yml +1 -1
- data/ext/rotoscope/callsite.c +37 -24
- data/ext/rotoscope/callsite.h +3 -3
- data/ext/rotoscope/extconf.rb +7 -6
- data/ext/rotoscope/rotoscope.c +34 -23
- data/ext/rotoscope/rotoscope.h +3 -0
- data/ext/rotoscope/stack.c +2 -0
- data/ext/rotoscope/stack.h +1 -0
- data/lib/rotoscope/call_logger.rb +41 -23
- data/lib/rotoscope/version.rb +2 -1
- data/lib/rotoscope.rb +3 -2
- data/rotoscope.gemspec +11 -10
- data/suppressions/rotoscope_ruby-2.7.supp +9 -0
- data/suppressions/rotoscope_ruby-3.0.supp +9 -0
- metadata +18 -30
- data/.github/probots.yml +0 -2
- data/.ruby-version +0 -1
- data/.travis.yml +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f235b178221927dc48cb085e138c9d26981f32a2731dd98c2fb1a064f7ea23c3
|
4
|
+
data.tar.gz: 27b4ffd1e8f2a73fcf293ff4998cf9baf804a4a9d26c5a2e268bea08bc72fe11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2676387c826f085de81851e59cca0326a8144b2a5b341acf2cce1e6f4ec0caa3112e9b4170702ea4587f0f6ba896008b2355b184022d7e900c065eb72dc5125c
|
7
|
+
data.tar.gz: 7a2b9f6e7b1f568d5f003ef747233462b0ee6d58b7b5b879d3ff52e5de9a3afee64557d27ce1795c6fa0fdd0f390e23f1333c8727597a826d65927b6e2615eca
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
test:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 2.7
|
14
|
+
bundler-cache: true
|
15
|
+
- run: bundle exec rake
|
16
|
+
|
17
|
+
valgrind:
|
18
|
+
runs-on: ubuntu-latest
|
19
|
+
steps:
|
20
|
+
- run: sudo apt-get install -y valgrind
|
21
|
+
- uses: actions/checkout@v2
|
22
|
+
- uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: 2.7
|
25
|
+
bundler-cache: true
|
26
|
+
- run: bundle exec rake test:valgrind
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Contributor License Agreement (CLA)
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request_target:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
issue_comment:
|
7
|
+
types: [created]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
cla:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
if: |
|
13
|
+
(github.event.issue.pull_request
|
14
|
+
&& !github.event.issue.pull_request.merged_at
|
15
|
+
&& contains(github.event.comment.body, 'signed')
|
16
|
+
)
|
17
|
+
|| (github.event.pull_request && !github.event.pull_request.merged)
|
18
|
+
steps:
|
19
|
+
- uses: Shopify/shopify-cla-action@v1
|
20
|
+
with:
|
21
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
22
|
+
cla-token: ${{ secrets.CLA_TOKEN }}
|
data/.rubocop.yml
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
inherit_from:
|
2
|
-
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
inherit_gem:
|
4
|
+
rubocop-shopify: rubocop.yml
|
3
5
|
|
4
6
|
AllCops:
|
5
7
|
Exclude:
|
6
8
|
- 'tmp/**/*'
|
7
|
-
TargetRubyVersion: 2.
|
9
|
+
TargetRubyVersion: 2.7
|
8
10
|
|
9
11
|
Metrics/LineLength:
|
10
12
|
Exclude:
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2021-10-20 17:19:27 UTC using RuboCop version 1.22.1.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 5
|
10
|
+
# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.
|
11
|
+
Naming/InclusiveLanguage:
|
12
|
+
Exclude:
|
13
|
+
- 'lib/rotoscope/call_logger.rb'
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,8 @@ Rotoscope is a high-performance logger of Ruby method invocations.
|
|
4
4
|
|
5
5
|
## Status
|
6
6
|
|
7
|
-
[![Build Status](https://
|
7
|
+
[![Build Status](https://github.com/Shopify/rotoscope/actions/workflows/ci.yml/badge.svg)](https://github.com/Shopify/rotoscope/actions?query=branch%3Amain)
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/rotoscope.svg)](https://badge.fury.io/rb/rotoscope)
|
8
9
|
|
9
10
|
Rotoscope is subject to breaking changes in minor versions until `1.0` is available.
|
10
11
|
|
@@ -52,45 +53,39 @@ IO,write,instance,example/dog.rb,11,IO,puts,instance
|
|
52
53
|
|
53
54
|
## API
|
54
55
|
|
55
|
-
|
56
|
-
- [`trace`](#rotoscopecallloggertracedest-blacklist-)
|
57
|
-
- [`new`](#rotoscopecallloggernewdest-blacklist-)
|
58
|
-
- [Public Instance Methods](#public-instance-methods)
|
59
|
-
- [`trace`](#rotoscopecallloggertraceblock)
|
60
|
-
- [`start_trace`](#rotoscopecallloggerstart_trace)
|
61
|
-
- [`stop_trace`](#rotoscopecallloggerstop_trace)
|
62
|
-
- [`mark`](#rotoscopecallloggermarkstr--)
|
63
|
-
- [`close`](#rotoscopecallloggerclose)
|
64
|
-
- [`state`](#rotoscopecallloggerstate)
|
65
|
-
- [`closed?`](#rotoscopecallloggerclosed)
|
56
|
+
### Default Logging Interface
|
66
57
|
|
67
|
-
|
58
|
+
Rotoscope ships with a default logger, `Rotoscope::CallLogger`. This provides a simple-to-use interface to the tracing engine that maintains performance as much as possible.
|
68
59
|
|
69
|
-
|
60
|
+
- [`.trace`](#rotoscopecallloggertracedest-excludelist-)
|
61
|
+
- [`.new`](#rotoscopecallloggernewdest-excludelist-)
|
62
|
+
- [`#trace`](#rotoscopecallloggertraceblock)
|
63
|
+
- [`#start_trace`](#rotoscopecallloggerstart_trace)
|
64
|
+
- [`#stop_trace`](#rotoscopecallloggerstop_trace)
|
65
|
+
- [`#mark`](#rotoscopecallloggermarkstr--)
|
66
|
+
- [`#close`](#rotoscopecallloggerclose)
|
67
|
+
- [`#state`](#rotoscopecallloggerstate)
|
68
|
+
- [`#closed?`](#rotoscopecallloggerclosed)
|
70
69
|
|
71
|
-
#### `Rotoscope::CallLogger
|
70
|
+
#### `Rotoscope::CallLogger.trace(dest, excludelist: [])`
|
72
71
|
|
73
|
-
Writes all calls of methods to `dest`, except for those whose filepath contains any entry in `
|
72
|
+
Writes all calls of methods to `dest`, except for those whose filepath contains any entry in `excludelist`. `dest` is either a filename or an `IO`. Methods invoked at the top of the trace will have a caller entity of `<ROOT>` and a caller method name of `<UNKNOWN>`.
|
74
73
|
|
75
74
|
```ruby
|
76
|
-
Rotoscope::CallLogger.trace(dest) { |
|
75
|
+
Rotoscope::CallLogger.trace(dest) { |call| ... }
|
77
76
|
# or...
|
78
|
-
Rotoscope::CallLogger.trace(dest,
|
77
|
+
Rotoscope::CallLogger.trace(dest, excludelist: ["/.gem/"]) { |call| ... }
|
79
78
|
```
|
80
79
|
|
81
|
-
#### `Rotoscope::CallLogger
|
80
|
+
#### `Rotoscope::CallLogger.new(dest, excludelist: [])`
|
82
81
|
|
83
82
|
Same interface as `Rotoscope::CallLogger::trace`, but returns a `Rotoscope::CallLogger` instance, allowing fine-grain control via `Rotoscope::CallLogger#start_trace` and `Rotoscope::CallLogger#stop_trace`.
|
84
83
|
```ruby
|
85
84
|
rs = Rotoscope::CallLogger.new(dest)
|
86
85
|
# or...
|
87
|
-
rs = Rotoscope::CallLogger.new(dest,
|
86
|
+
rs = Rotoscope::CallLogger.new(dest, excludelist: ["/.gem/"])
|
88
87
|
```
|
89
88
|
|
90
|
-
---
|
91
|
-
|
92
|
-
### Public Instance Methods
|
93
|
-
|
94
89
|
#### `Rotoscope::CallLogger#trace(&block)`
|
95
90
|
|
96
91
|
Similar to `Rotoscope::CallLogger::trace`, but does not need to create a file handle on invocation.
|
@@ -171,3 +166,216 @@ rs.closed? # false
|
|
171
166
|
rs.close
|
172
167
|
rs.closed? # true
|
173
168
|
```
|
169
|
+
|
170
|
+
### Low-level API
|
171
|
+
|
172
|
+
For those who prefer to define their own logging logic, Rotoscope also provides a low-level API. This is the same one used by `Rotoscope::CallLogger` internally. Users may specify a block that is invoked on each detected method call.
|
173
|
+
|
174
|
+
- [`.new`](#rotoscopenewblk)
|
175
|
+
- [`#trace`](#rotoscopetraceblk)
|
176
|
+
- [`#start_trace`](#rotoscopestart_trace)
|
177
|
+
- [`#stop_trace`](#rotoscopestop_trace)
|
178
|
+
- [`#tracing?`](#rotoscopetracing)
|
179
|
+
- [`#receiver`](#rotoscopereceiver)
|
180
|
+
- [`#receiver_class`](#rotoscopereceiver_class)
|
181
|
+
- [`#receiver_class_name`](#rotoscopereceiver_class_name)
|
182
|
+
- [`#method_name`](#rotoscopemethod_name)
|
183
|
+
- [`#singleton_method?`](#rotoscopesingleton_method)
|
184
|
+
- [`#caller_object`](#rotoscopecaller_object)
|
185
|
+
- [`#caller_class`](#rotoscopecaller_class)
|
186
|
+
- [`#caller_class_name`](#rotoscopecaller_class_name)
|
187
|
+
- [`#caller_method_name`](#rotoscopecaller_method_name)
|
188
|
+
- [`#caller_singleton_method?`](#rotoscopecaller_singleton_method)
|
189
|
+
- [`#caller_path`](#rotoscopecaller_path)
|
190
|
+
- [`#caller_lineno`](#rotoscopecaller_lineno)
|
191
|
+
|
192
|
+
#### `Rotoscope.new(&blk)`
|
193
|
+
|
194
|
+
Creates a new instance of the `Rotoscope` class. The block argument is invoked on every call detected by Rotoscope. The block is passed the same instance returned by `Rotoscope#new` allowing the low-level methods to be called.
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
rs = Rotoscope.new do |call|
|
198
|
+
# We likely don't want to record calls to Rotoscope
|
199
|
+
return if self == call.receiver
|
200
|
+
...
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
|
205
|
+
#### `Rotoscope#trace(&blk)`
|
206
|
+
|
207
|
+
The equivalent of calling [`Rotoscope#start_trace`](#rotoscopestart_trace) and then [`Rotoscope#stop_trace`](#rotoscopestop_trace). The call to `#stop_trace` is within an `ensure` block so it is always called when the block terminates.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
rs = Rotoscope.new do |call|
|
211
|
+
...
|
212
|
+
end
|
213
|
+
|
214
|
+
rs.trace do
|
215
|
+
# call some code
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
#### `Rotoscope#start_trace`
|
220
|
+
|
221
|
+
Begins detecting method calls invoked after this point.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
rs = Rotoscope.new do |call|
|
225
|
+
...
|
226
|
+
end
|
227
|
+
|
228
|
+
rs.start_trace
|
229
|
+
# Calls after this points invoke the
|
230
|
+
# block passed to `Rotoscope.new`
|
231
|
+
```
|
232
|
+
|
233
|
+
#### `Rotoscope#stop_trace`
|
234
|
+
|
235
|
+
Disables method call detection invoked after this point.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
rs = Rotoscope.new do |call|
|
239
|
+
...
|
240
|
+
end
|
241
|
+
|
242
|
+
rs.start_trace
|
243
|
+
...
|
244
|
+
rs.stop_trace
|
245
|
+
# Calls after this points will no longer
|
246
|
+
# invoke the block passed to `Rotoscope.new`
|
247
|
+
```
|
248
|
+
|
249
|
+
#### `Rotoscope#tracing?`
|
250
|
+
|
251
|
+
Identifies whether the Rotoscope object is actively tracing method calls.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
rs = Rotoscope.new do |call|
|
255
|
+
...
|
256
|
+
end
|
257
|
+
|
258
|
+
rs.tracing? # => false
|
259
|
+
rs.start_trace
|
260
|
+
rs.tracing? # => true
|
261
|
+
```
|
262
|
+
|
263
|
+
#### `Rotoscope#receiver`
|
264
|
+
|
265
|
+
Returns the object that the method is being called against.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
rs = Rotoscope.new do |call|
|
269
|
+
call.receiver # => #<Foo:0x00007fa3d2197c10>
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
#### `Rotoscope#receiver_class`
|
274
|
+
|
275
|
+
Returns the class of the object that the method is being called against.
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
rs = Rotoscope.new do |call|
|
279
|
+
call.receiver_class # => Foo
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
#### `Rotoscope#receiver_class_name`
|
284
|
+
|
285
|
+
Returns the stringified class of the object that the method is being called against.
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
rs = Rotoscope.new do |call|
|
289
|
+
call.receiver_class_name # => "Foo"
|
290
|
+
end
|
291
|
+
```
|
292
|
+
|
293
|
+
#### `Rotoscope#method_name`
|
294
|
+
|
295
|
+
Returns the name of the method being invoked.
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
rs = Rotoscope.new do |call|
|
299
|
+
call.method_name # => "bar"
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
#### `Rotoscope#singleton_method?`
|
304
|
+
|
305
|
+
Returns `true` if the method called is defined at the class level. If the call is to an instance method, this returns `false`.
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
rs = Rotoscope.new do |call|
|
309
|
+
call.singleton_method? # => false
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
#### `Rotoscope#caller_object`
|
314
|
+
|
315
|
+
Returns the object whose context we invoked the call from.
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
rs = Rotoscope.new do |call|
|
319
|
+
call.caller_object # => #<SomeClass:0x00008aa6d2cd91b61>
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
#### `Rotoscope#caller_class`
|
324
|
+
|
325
|
+
Returns the class of the object whose context we invoked the call from.
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
rs = Rotoscope.new do |call|
|
329
|
+
call.caller_class # => SomeClass
|
330
|
+
end
|
331
|
+
```
|
332
|
+
|
333
|
+
#### `Rotoscope#caller_class_name`
|
334
|
+
|
335
|
+
Returns the tringified class of the object whose context we invoked the call from.
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
rs = Rotoscope.new do |call|
|
339
|
+
call.caller_class_name # => "SomeClass"
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
#### `Rotoscope#caller_method_name`
|
344
|
+
|
345
|
+
Returns the stringified class of the object whose context we invoked the call from.
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
rs = Rotoscope.new do |call|
|
349
|
+
call.caller_method_name # => "call_foobar"
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
353
|
+
#### `Rotoscope#caller_singleton_method?`
|
354
|
+
|
355
|
+
Returns `true` if the method invoking the call is defined at the class level. If the call is to an instance method, this returns `false`.
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
rs = Rotoscope.new do |call|
|
359
|
+
call.caller_singleton_method? # => true
|
360
|
+
end
|
361
|
+
```
|
362
|
+
|
363
|
+
#### `Rotoscope#caller_path`
|
364
|
+
|
365
|
+
Returns the path to the file where the call was invoked.
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
rs = Rotoscope.new do |call|
|
369
|
+
call.caller_path # => "/rotoscope_test.rb"
|
370
|
+
end
|
371
|
+
```
|
372
|
+
|
373
|
+
#### `Rotoscope#caller_lineno`
|
374
|
+
|
375
|
+
Returns the line number corresponding to the `#caller_path` where the call was invoked. If unknown, returns `-1`.
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
rs = Rotoscope.new do |call|
|
379
|
+
call.caller_lineno # => 113
|
380
|
+
end
|
381
|
+
```
|
data/Rakefile
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# ==========================================================
|
3
4
|
# Packaging
|
4
5
|
# ==========================================================
|
5
|
-
GEMSPEC = Gem::Specification.load(
|
6
|
+
GEMSPEC = Gem::Specification.load("rotoscope.gemspec")
|
6
7
|
|
7
|
-
require
|
8
|
-
require
|
8
|
+
require "bundler/gem_tasks"
|
9
|
+
require "rubygems/package_task"
|
9
10
|
|
10
11
|
Gem::PackageTask.new(GEMSPEC) do |pkg|
|
11
12
|
end
|
@@ -14,9 +15,9 @@ end
|
|
14
15
|
# Ruby Extension
|
15
16
|
# ==========================================================
|
16
17
|
|
17
|
-
require
|
18
|
-
Rake::ExtensionTask.new(
|
19
|
-
ext.lib_dir =
|
18
|
+
require "rake/extensiontask"
|
19
|
+
Rake::ExtensionTask.new("rotoscope", GEMSPEC) do |ext|
|
20
|
+
ext.lib_dir = "lib/rotoscope"
|
20
21
|
end
|
21
22
|
|
22
23
|
task(build: :compile)
|
@@ -29,14 +30,23 @@ end
|
|
29
30
|
# Testing
|
30
31
|
# ==========================================================
|
31
32
|
|
32
|
-
require
|
33
|
-
|
34
|
-
|
33
|
+
require "rake/testtask"
|
34
|
+
require "ruby_memcheck"
|
35
|
+
|
36
|
+
RubyMemcheck.config(binary_name: "rotoscope")
|
37
|
+
|
38
|
+
test_config = lambda do |t|
|
39
|
+
t.test_files = FileList["test/*_test.rb"]
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::TestTask.new(test: :build, &test_config)
|
43
|
+
|
44
|
+
namespace :test do
|
45
|
+
RubyMemcheck::TestTask.new(valgrind: :build, &test_config)
|
35
46
|
end
|
36
|
-
task(test: :build)
|
37
47
|
|
38
48
|
task :rubocop do
|
39
|
-
require
|
49
|
+
require "rubocop/rake_task"
|
40
50
|
RuboCop::RakeTask.new
|
41
51
|
end
|
42
52
|
|
data/dev.yml
CHANGED
data/ext/rotoscope/callsite.c
CHANGED
@@ -1,39 +1,52 @@
|
|
1
1
|
#include "callsite.h"
|
2
|
+
|
2
3
|
#include <ruby.h>
|
3
4
|
#include <ruby/debug.h>
|
5
|
+
#include <stdbool.h>
|
4
6
|
|
5
|
-
VALUE
|
7
|
+
static VALUE caller_frame(int *line, bool ruby_call) {
|
8
|
+
VALUE frames[2] = {Qnil, Qnil};
|
9
|
+
int lines[2] = {0, 0};
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
// At this point, for ruby calls, the top ruby stack frame is
|
12
|
+
// for the method being called, so we want to skip that frame
|
13
|
+
// and get the caller location. This is why we use 1 for ruby
|
14
|
+
// calls.
|
15
|
+
//
|
16
|
+
// However, for C call, the top stack frame is for the caller,
|
17
|
+
// so we don't want to have to skip over any extra stack frames
|
18
|
+
// for C calls.
|
19
|
+
int frame_index = ruby_call ? 1 : 0;
|
14
20
|
|
15
|
-
rs_callsite_t ruby_callsite(rb_trace_arg_t *trace_arg) {
|
16
|
-
VALUE frames[2];
|
17
|
-
int lines[2];
|
18
21
|
// There is currently a bug in rb_profile_frames that
|
19
22
|
// causes the start argument to effectively always
|
20
23
|
// act as if it were 0, so we need to also get the top
|
21
|
-
// frame.
|
22
|
-
|
23
|
-
return (rs_callsite_t){
|
24
|
-
.filepath = empty_ruby_string,
|
25
|
-
.lineno = 0,
|
26
|
-
};
|
27
|
-
}
|
24
|
+
// frame. (https://bugs.ruby-lang.org/issues/14607)
|
25
|
+
rb_profile_frames(0, frame_index + 1, frames, lines);
|
28
26
|
|
27
|
+
*line = lines[frame_index];
|
28
|
+
return frames[frame_index];
|
29
|
+
}
|
30
|
+
|
31
|
+
rs_callsite_t c_callsite(rb_trace_arg_t *trace_arg) {
|
32
|
+
int line;
|
33
|
+
VALUE frame = caller_frame(&line, false);
|
29
34
|
return (rs_callsite_t){
|
30
|
-
.filepath =
|
31
|
-
.lineno =
|
35
|
+
.filepath = rb_tracearg_path(trace_arg),
|
36
|
+
.lineno = FIX2INT(rb_tracearg_lineno(trace_arg)),
|
37
|
+
.method_name = rb_profile_frame_method_name(frame),
|
38
|
+
.singleton_p = rb_profile_frame_singleton_method_p(frame),
|
32
39
|
};
|
33
40
|
}
|
34
41
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
42
|
+
rs_callsite_t ruby_callsite() {
|
43
|
+
int line;
|
44
|
+
VALUE frame = caller_frame(&line, true);
|
45
|
+
|
46
|
+
return (rs_callsite_t){
|
47
|
+
.filepath = rb_profile_frame_path(frame),
|
48
|
+
.lineno = line,
|
49
|
+
.method_name = rb_profile_frame_method_name(frame),
|
50
|
+
.singleton_p = rb_profile_frame_singleton_method_p(frame),
|
51
|
+
};
|
39
52
|
}
|
data/ext/rotoscope/callsite.h
CHANGED
@@ -7,11 +7,11 @@
|
|
7
7
|
typedef struct {
|
8
8
|
VALUE filepath;
|
9
9
|
unsigned int lineno;
|
10
|
+
VALUE method_name;
|
11
|
+
VALUE singleton_p;
|
10
12
|
} rs_callsite_t;
|
11
13
|
|
12
|
-
void init_callsite();
|
13
|
-
|
14
14
|
rs_callsite_t c_callsite(rb_trace_arg_t *trace_arg);
|
15
|
-
rs_callsite_t ruby_callsite(
|
15
|
+
rs_callsite_t ruby_callsite();
|
16
16
|
|
17
17
|
#endif
|
data/ext/rotoscope/extconf.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "mkmf"
|
3
4
|
|
4
|
-
$CFLAGS <<
|
5
|
-
$CFLAGS <<
|
6
|
-
$CFLAGS <<
|
5
|
+
$CFLAGS << " -std=c99"
|
6
|
+
$CFLAGS << " -Wall"
|
7
|
+
$CFLAGS << " -Wno-declaration-after-statement"
|
7
8
|
|
8
|
-
unless [
|
9
|
-
$CFLAGS <<
|
9
|
+
unless ["0", "", nil].include?(ENV["ROTOSCOPE_COMPILE_ERROR"])
|
10
|
+
$CFLAGS << " -Werror"
|
10
11
|
end
|
11
12
|
|
12
13
|
$defs << "-D_POSIX_SOURCE"
|
13
14
|
|
14
|
-
create_makefile(
|
15
|
+
create_makefile("rotoscope/rotoscope")
|
data/ext/rotoscope/rotoscope.c
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
#include "rotoscope.h"
|
2
|
+
|
1
3
|
#include <errno.h>
|
2
|
-
#include <ruby.h>
|
3
4
|
#include <ruby/debug.h>
|
4
5
|
#include <ruby/intern.h>
|
5
6
|
#include <ruby/io.h>
|
@@ -8,15 +9,13 @@
|
|
8
9
|
#include <stdio.h>
|
9
10
|
#include <sys/file.h>
|
10
11
|
|
11
|
-
#include "callsite.h"
|
12
12
|
#include "method_desc.h"
|
13
|
-
#include "rotoscope.h"
|
14
13
|
#include "stack.h"
|
15
14
|
|
16
15
|
VALUE cRotoscope, cTracePoint;
|
17
16
|
ID id_initialize, id_call;
|
18
17
|
|
19
|
-
static unsigned long
|
18
|
+
static unsigned long current_thread_id() {
|
20
19
|
return NUM2ULONG(rb_obj_id(rb_thread_current()));
|
21
20
|
}
|
22
21
|
|
@@ -49,7 +48,7 @@ static rs_callsite_t tracearg_path(rb_trace_arg_t *trace_arg) {
|
|
49
48
|
case RUBY_EVENT_C_CALL:
|
50
49
|
return c_callsite(trace_arg);
|
51
50
|
default:
|
52
|
-
return ruby_callsite(
|
51
|
+
return ruby_callsite();
|
53
52
|
}
|
54
53
|
}
|
55
54
|
|
@@ -101,7 +100,7 @@ static void event_hook(VALUE tpval, void *data) {
|
|
101
100
|
return;
|
102
101
|
}
|
103
102
|
|
104
|
-
if (config->tid !=
|
103
|
+
if (config->tid != current_thread_id()) return;
|
105
104
|
if (in_fork(config)) {
|
106
105
|
rb_tracepoint_disable(config->tracepoint);
|
107
106
|
config->tracing = false;
|
@@ -133,29 +132,49 @@ static void event_hook(VALUE tpval, void *data) {
|
|
133
132
|
rb_funcall(config->trace_proc, id_call, 1, config->self);
|
134
133
|
}
|
135
134
|
|
136
|
-
static void rs_gc_mark(
|
135
|
+
static void rs_gc_mark(void *data) {
|
136
|
+
Rotoscope *config = (Rotoscope *)data;
|
137
137
|
rb_gc_mark(config->tracepoint);
|
138
138
|
rb_gc_mark(config->trace_proc);
|
139
139
|
rs_stack_mark(&config->stack);
|
140
140
|
}
|
141
141
|
|
142
|
-
void rs_dealloc(
|
142
|
+
static void rs_dealloc(void *data) {
|
143
|
+
Rotoscope *config = (Rotoscope *)data;
|
143
144
|
stop_tracing_on_cleanup(config);
|
144
145
|
rs_stack_free(&config->stack);
|
145
146
|
xfree(config);
|
146
147
|
}
|
147
148
|
|
149
|
+
static size_t rs_memsize(const void *data) {
|
150
|
+
return sizeof(Rotoscope);
|
151
|
+
}
|
152
|
+
|
153
|
+
static const rb_data_type_t rs_data_type = {
|
154
|
+
.wrap_struct_name = "Rotoscope",
|
155
|
+
.function = {
|
156
|
+
.dmark = rs_gc_mark,
|
157
|
+
.dfree = rs_dealloc,
|
158
|
+
.dsize = rs_memsize,
|
159
|
+
},
|
160
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
161
|
+
};
|
162
|
+
|
148
163
|
static VALUE rs_alloc(VALUE klass) {
|
149
164
|
Rotoscope *config;
|
150
165
|
VALUE self =
|
151
|
-
|
166
|
+
TypedData_Make_Struct(klass, Rotoscope, &rs_data_type, config);
|
152
167
|
config->self = self;
|
153
168
|
config->pid = getpid();
|
154
|
-
config->tid =
|
169
|
+
config->tid = current_thread_id();
|
155
170
|
config->tracing = false;
|
156
171
|
config->caller = NULL;
|
157
|
-
config->callsite
|
158
|
-
|
172
|
+
config->callsite = (rs_callsite_t){
|
173
|
+
.filepath = Qnil,
|
174
|
+
.lineno = 0,
|
175
|
+
.method_name = Qnil,
|
176
|
+
.singleton_p = Qnil,
|
177
|
+
};
|
159
178
|
config->trace_proc = Qnil;
|
160
179
|
rs_stack_init(&config->stack, STACK_CAPACITY);
|
161
180
|
config->tracepoint = rb_tracepoint_new(Qnil, EVENT_CALL | EVENT_RETURN,
|
@@ -165,7 +184,7 @@ static VALUE rs_alloc(VALUE klass) {
|
|
165
184
|
|
166
185
|
static Rotoscope *get_config(VALUE self) {
|
167
186
|
Rotoscope *config;
|
168
|
-
|
187
|
+
TypedData_Get_Struct(self, Rotoscope, &rs_data_type, config);
|
169
188
|
return config;
|
170
189
|
}
|
171
190
|
|
@@ -264,18 +283,12 @@ VALUE rotoscope_caller_class_name(VALUE self) {
|
|
264
283
|
|
265
284
|
VALUE rotoscope_caller_method_name(VALUE self) {
|
266
285
|
Rotoscope *config = get_config(self);
|
267
|
-
|
268
|
-
return Qnil;
|
269
|
-
}
|
270
|
-
return rb_sym2str(config->caller->method.id);
|
286
|
+
return config->callsite.method_name;
|
271
287
|
}
|
272
288
|
|
273
289
|
VALUE rotoscope_caller_singleton_method_p(VALUE self) {
|
274
290
|
Rotoscope *config = get_config(self);
|
275
|
-
|
276
|
-
return Qnil;
|
277
|
-
}
|
278
|
-
return config->caller->method.singleton_p ? Qtrue : Qfalse;
|
291
|
+
return config->callsite.singleton_p;
|
279
292
|
}
|
280
293
|
|
281
294
|
VALUE rotoscope_caller_path(VALUE self) {
|
@@ -317,6 +330,4 @@ void Init_rotoscope(void) {
|
|
317
330
|
rotoscope_caller_singleton_method_p, 0);
|
318
331
|
rb_define_method(cRotoscope, "caller_path", rotoscope_caller_path, 0);
|
319
332
|
rb_define_method(cRotoscope, "caller_lineno", rotoscope_caller_lineno, 0);
|
320
|
-
|
321
|
-
init_callsite();
|
322
333
|
}
|
data/ext/rotoscope/rotoscope.h
CHANGED
data/ext/rotoscope/stack.c
CHANGED
data/ext/rotoscope/stack.h
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "csv"
|
4
4
|
|
5
5
|
class Rotoscope
|
6
6
|
class CallLogger
|
7
|
+
UNSPECIFIED = Object.new
|
8
|
+
private_constant :UNSPECIFIED
|
9
|
+
|
7
10
|
class << self
|
8
|
-
def trace(dest, blacklist: [])
|
9
|
-
|
11
|
+
def trace(dest, blacklist: UNSPECIFIED, excludelist: [])
|
12
|
+
if blacklist != UNSPECIFIED
|
13
|
+
excludelist = blacklist
|
14
|
+
warn("Rotoscope::CallLogger.trace blacklist argument is deprecated, use excludelist instead")
|
15
|
+
end
|
16
|
+
rs = new(dest, excludelist: excludelist)
|
10
17
|
rs.trace { yield rs }
|
11
18
|
rs
|
12
19
|
ensure
|
@@ -16,21 +23,30 @@ class Rotoscope
|
|
16
23
|
|
17
24
|
HEADER = "entity,caller_entity,filepath,lineno,method_name,method_level,caller_method_name,caller_method_level\n"
|
18
25
|
|
19
|
-
attr_reader :io, :
|
26
|
+
attr_reader :io, :excludelist
|
27
|
+
|
28
|
+
def blacklist
|
29
|
+
warn("Rotoscope::CallLogger#blacklist is deprecated, use excludelist instead")
|
30
|
+
excludelist
|
31
|
+
end
|
20
32
|
|
21
|
-
def initialize(output = nil, blacklist: nil)
|
22
|
-
|
23
|
-
|
33
|
+
def initialize(output = nil, blacklist: UNSPECIFIED, excludelist: nil)
|
34
|
+
if blacklist != UNSPECIFIED
|
35
|
+
excludelist = blacklist
|
36
|
+
warn("Rotoscope::CallLogger#initialize blacklist argument is deprecated, use excludelist instead")
|
24
37
|
end
|
25
|
-
|
38
|
+
unless excludelist.is_a?(Regexp)
|
39
|
+
excludelist = Regexp.union(excludelist || [])
|
40
|
+
end
|
41
|
+
@excludelist = excludelist
|
26
42
|
|
27
43
|
if output.is_a?(String)
|
28
|
-
@io = File.open(output,
|
44
|
+
@io = File.open(output, "w")
|
29
45
|
prevent_flush_from_finalizer_in_fork(@io)
|
30
46
|
else
|
31
47
|
@io = output
|
32
48
|
end
|
33
|
-
@output_buffer =
|
49
|
+
@output_buffer = "".dup
|
34
50
|
@pid = Process.pid
|
35
51
|
@thread = Thread.current
|
36
52
|
|
@@ -82,38 +98,39 @@ class Rotoscope
|
|
82
98
|
|
83
99
|
def state
|
84
100
|
return :closed if io.closed?
|
101
|
+
|
85
102
|
@rotoscope.tracing? ? :tracing : :open
|
86
103
|
end
|
87
104
|
|
88
105
|
private
|
89
106
|
|
90
107
|
def log_call(call)
|
91
|
-
|
108
|
+
caller_path = call.caller_path || ""
|
109
|
+
return if excludelist.match?(caller_path)
|
92
110
|
return if self == call.receiver
|
93
111
|
|
112
|
+
caller_class_name = call.caller_class_name || "<UNKNOWN>"
|
94
113
|
if call.caller_method_name.nil?
|
95
|
-
|
96
|
-
|
97
|
-
caller_method_level = '<UNKNOWN>'
|
114
|
+
caller_method_name = "<UNKNOWN>"
|
115
|
+
caller_method_level = "<UNKNOWN>"
|
98
116
|
else
|
99
|
-
caller_class_name = call.caller_class_name
|
100
117
|
caller_method_name = escape_csv_string(call.caller_method_name)
|
101
|
-
caller_method_level = call.caller_singleton_method? ?
|
118
|
+
caller_method_level = call.caller_singleton_method? ? "class" : "instance"
|
102
119
|
end
|
103
120
|
|
104
|
-
call_method_level = call.singleton_method? ?
|
121
|
+
call_method_level = call.singleton_method? ? "class" : "instance"
|
105
122
|
method_name = escape_csv_string(call.method_name)
|
106
123
|
|
107
124
|
buffer = @output_buffer
|
108
125
|
buffer.clear
|
109
126
|
buffer <<
|
110
127
|
'"' << call.receiver_class_name << '",' \
|
111
|
-
|
112
|
-
|
113
|
-
<< call.caller_lineno.to_s <<
|
114
|
-
|
115
|
-
<< call_method_level <<
|
116
|
-
|
128
|
+
'"' << caller_class_name << '",' \
|
129
|
+
'"' << caller_path << '",' \
|
130
|
+
<< call.caller_lineno.to_s << "," \
|
131
|
+
'"' << method_name << '",' \
|
132
|
+
<< call_method_level << "," \
|
133
|
+
'"' << caller_method_name << '",' \
|
117
134
|
<< caller_method_level << "\n"
|
118
135
|
io.write(buffer)
|
119
136
|
end
|
@@ -126,6 +143,7 @@ class Rotoscope
|
|
126
143
|
pid = Process.pid
|
127
144
|
finalizer = lambda do |_|
|
128
145
|
next if Process.pid == pid
|
146
|
+
|
129
147
|
# close the file descriptor from another IO object so
|
130
148
|
# buffered writes aren't flushed
|
131
149
|
IO.for_fd(io.fileno).close
|
data/lib/rotoscope/version.rb
CHANGED
data/lib/rotoscope.rb
CHANGED
data/rotoscope.gemspec
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require "rotoscope/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
+
s.name = "rotoscope"
|
8
9
|
s.version = Rotoscope::VERSION
|
9
|
-
s.date = '2017-09-20'
|
10
10
|
|
11
11
|
s.authors = ["Jahfer Husain", "Dylan Thacker-Smith"]
|
12
|
-
s.email =
|
13
|
-
s.homepage =
|
14
|
-
s.license =
|
12
|
+
s.email = "jahfer.husain@shopify.com"
|
13
|
+
s.homepage = "https://github.com/shopify/rotoscope"
|
14
|
+
s.license = "MIT"
|
15
15
|
|
16
16
|
s.summary = "Tracing Ruby"
|
17
17
|
s.description = "High-performance logger of Ruby method invocations"
|
@@ -19,11 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.files = %x(git ls-files -z).split("\x0").reject do |f|
|
20
20
|
f.match(%r{^(test)/})
|
21
21
|
end
|
22
|
+
s.metadata["allowed_push_host"] = "https://rubygems.org/"
|
23
|
+
|
22
24
|
s.required_ruby_version = ">= 2.2.0"
|
23
|
-
s.extensions =
|
25
|
+
s.extensions = ["ext/rotoscope/extconf.rb"]
|
24
26
|
|
25
|
-
s.add_development_dependency(
|
26
|
-
s.add_development_dependency(
|
27
|
-
s.add_development_dependency(
|
28
|
-
s.add_development_dependency('rubocop', '~> 0.56')
|
27
|
+
s.add_development_dependency("minitest", "~> 5.0")
|
28
|
+
s.add_development_dependency("mocha", "~> 0.14")
|
29
|
+
s.add_development_dependency("rake-compiler", "~> 0.9")
|
29
30
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rotoscope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1.pre.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jahfer Husain
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-11-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: minitest
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '0
|
20
|
+
version: '5.0'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '0
|
27
|
+
version: '5.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: mocha
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -40,33 +40,19 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0.14'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - "~>"
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '5.0'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '5.0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: rubocop
|
43
|
+
name: rake-compiler
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
59
45
|
requirements:
|
60
46
|
- - "~>"
|
61
47
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0.
|
48
|
+
version: '0.9'
|
63
49
|
type: :development
|
64
50
|
prerelease: false
|
65
51
|
version_requirements: !ruby/object:Gem::Requirement
|
66
52
|
requirements:
|
67
53
|
- - "~>"
|
68
54
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0.
|
55
|
+
version: '0.9'
|
70
56
|
description: High-performance logger of Ruby method invocations
|
71
57
|
email: jahfer.husain@shopify.com
|
72
58
|
executables: []
|
@@ -76,11 +62,11 @@ extra_rdoc_files: []
|
|
76
62
|
files:
|
77
63
|
- ".clang-format"
|
78
64
|
- ".github/PULL_REQUEST_TEMPLATE"
|
79
|
-
- ".github/
|
65
|
+
- ".github/workflows/ci.yml"
|
66
|
+
- ".github/workflows/cla.yml"
|
80
67
|
- ".gitignore"
|
81
68
|
- ".rubocop.yml"
|
82
|
-
- ".
|
83
|
-
- ".travis.yml"
|
69
|
+
- ".rubocop_todo.yml"
|
84
70
|
- Gemfile
|
85
71
|
- LICENSE
|
86
72
|
- README.md
|
@@ -100,10 +86,13 @@ files:
|
|
100
86
|
- lib/rotoscope/call_logger.rb
|
101
87
|
- lib/rotoscope/version.rb
|
102
88
|
- rotoscope.gemspec
|
89
|
+
- suppressions/rotoscope_ruby-2.7.supp
|
90
|
+
- suppressions/rotoscope_ruby-3.0.supp
|
103
91
|
homepage: https://github.com/shopify/rotoscope
|
104
92
|
licenses:
|
105
93
|
- MIT
|
106
|
-
metadata:
|
94
|
+
metadata:
|
95
|
+
allowed_push_host: https://rubygems.org/
|
107
96
|
post_install_message:
|
108
97
|
rdoc_options: []
|
109
98
|
require_paths:
|
@@ -115,12 +104,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
104
|
version: 2.2.0
|
116
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
106
|
requirements:
|
118
|
-
- - "
|
107
|
+
- - ">"
|
119
108
|
- !ruby/object:Gem::Version
|
120
|
-
version:
|
109
|
+
version: 1.3.1
|
121
110
|
requirements: []
|
122
|
-
|
123
|
-
rubygems_version: 2.7.6
|
111
|
+
rubygems_version: 3.4.21
|
124
112
|
signing_key:
|
125
113
|
specification_version: 4
|
126
114
|
summary: Tracing Ruby
|
data/.github/probots.yml
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.4.3
|
data/.travis.yml
DELETED