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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da519185af3cb3652183905a5005c9666a8479d97075ed258524c2dbae41a22f
4
- data.tar.gz: 9afac7ae131260c434e77a2238b631f0674019a6f3abf71713a525f507a95890
3
+ metadata.gz: f235b178221927dc48cb085e138c9d26981f32a2731dd98c2fb1a064f7ea23c3
4
+ data.tar.gz: 27b4ffd1e8f2a73fcf293ff4998cf9baf804a4a9d26c5a2e268bea08bc72fe11
5
5
  SHA512:
6
- metadata.gz: bad9b2c953384b6dde92fc6cbef2a5975dd16a92e86f9b355f1ae3a6c1e5fa6715f0837a3812794fae7759bf1d7391f9adc36a515e7a1640cea913fda12e7b57
7
- data.tar.gz: eac7f35d1d1b0346f047c87eebda7cf47d59ed5dff9bc4ab073601071eb17b9f6b107216812803fa9ba13d62805c548374f5c49cca60fb9ff5de656214ee06a9
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
- - http://shopify.github.io/ruby-style-guide/rubocop.yml
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.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
@@ -1,3 +1,8 @@
1
1
  # frozen_string_literal: true
2
- source 'https://rubygems.org'
2
+
3
+ source "https://rubygems.org"
3
4
  gemspec
5
+
6
+ gem "rubocop", require: false
7
+ gem "rubocop-shopify", require: false
8
+ gem "ruby_memcheck", "~> 0.3.0", require: false
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://travis-ci.org/Shopify/rotoscope.svg?branch=master)](https://travis-ci.org/Shopify/rotoscope) [![Gem Version](https://badge.fury.io/rb/rotoscope.svg)](https://badge.fury.io/rb/rotoscope)
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
- - [Public Class Methods](#public-class-methods)
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
- ### Public Class Methods
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::trace(dest, blacklist: [])`
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 `blacklist`. `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>`.
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) { |rs| ... }
75
+ Rotoscope::CallLogger.trace(dest) { |call| ... }
77
76
  # or...
78
- Rotoscope::CallLogger.trace(dest, blacklist: ["/.gem/"]) { |rs| ... }
77
+ Rotoscope::CallLogger.trace(dest, excludelist: ["/.gem/"]) { |call| ... }
79
78
  ```
80
79
 
81
- #### `Rotoscope::CallLogger::new(dest, blacklist: [])`
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, blacklist: ["/.gem/"])
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('rotoscope.gemspec')
6
+ GEMSPEC = Gem::Specification.load("rotoscope.gemspec")
6
7
 
7
- require 'bundler/gem_tasks'
8
- require 'rubygems/package_task'
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 'rake/extensiontask'
18
- Rake::ExtensionTask.new('rotoscope', GEMSPEC) do |ext|
19
- ext.lib_dir = 'lib/rotoscope'
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 'rake/testtask'
33
- Rake::TestTask.new('test') do |t|
34
- t.test_files = FileList['test/*_test.rb']
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 'rubocop/rake_task'
49
+ require "rubocop/rake_task"
40
50
  RuboCop::RakeTask.new
41
51
  end
42
52
 
data/dev.yml CHANGED
@@ -5,7 +5,7 @@ env:
5
5
  ROTOSCOPE_COMPILE_ERROR: '1'
6
6
 
7
7
  up:
8
- - ruby: 2.4.3
8
+ - ruby: 2.7.8
9
9
  - homebrew:
10
10
  - clang-format
11
11
  - bundler
@@ -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 empty_ruby_string;
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
- rs_callsite_t c_callsite(rb_trace_arg_t *trace_arg) {
8
- VALUE path = rb_tracearg_path(trace_arg);
9
- return (rs_callsite_t){
10
- .filepath = NIL_P(path) ? empty_ruby_string : path,
11
- .lineno = FIX2INT(rb_tracearg_lineno(trace_arg)),
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
- if (rb_profile_frames(0, 2, frames, lines) < 2) {
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 = rb_profile_frame_path(frames[1]),
31
- .lineno = lines[1],
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
- void init_callsite() {
36
- empty_ruby_string = rb_str_new_literal("");
37
- RB_OBJ_FREEZE(empty_ruby_string);
38
- rb_global_variable(&empty_ruby_string);
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
  }
@@ -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(rb_trace_arg_t *trace_arg);
15
+ rs_callsite_t ruby_callsite();
16
16
 
17
17
  #endif
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "mkmf"
3
4
 
4
- $CFLAGS << ' -std=c99'
5
- $CFLAGS << ' -Wall'
6
- $CFLAGS << ' -Wno-declaration-after-statement'
5
+ $CFLAGS << " -std=c99"
6
+ $CFLAGS << " -Wall"
7
+ $CFLAGS << " -Wno-declaration-after-statement"
7
8
 
8
- unless ['0', '', nil].include?(ENV['ROTOSCOPE_COMPILE_ERROR'])
9
- $CFLAGS << ' -Werror'
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('rotoscope/rotoscope')
15
+ create_makefile("rotoscope/rotoscope")
@@ -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 gettid() {
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(trace_arg);
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 != gettid()) return;
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(Rotoscope *config) {
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(Rotoscope *config) {
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
- Data_Make_Struct(klass, Rotoscope, rs_gc_mark, rs_dealloc, config);
166
+ TypedData_Make_Struct(klass, Rotoscope, &rs_data_type, config);
152
167
  config->self = self;
153
168
  config->pid = getpid();
154
- config->tid = gettid();
169
+ config->tid = current_thread_id();
155
170
  config->tracing = false;
156
171
  config->caller = NULL;
157
- config->callsite.filepath = Qnil;
158
- config->callsite.lineno = 0;
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
- Data_Get_Struct(self, Rotoscope, config);
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
- if (config->caller == NULL) {
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
- if (config->caller == NULL) {
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
  }
@@ -1,7 +1,10 @@
1
1
  #ifndef _INC_ROTOSCOPE_H_
2
2
  #define _INC_ROTOSCOPE_H_
3
3
 
4
+ #include <ruby.h>
4
5
  #include <unistd.h>
6
+
7
+ #include "callsite.h"
5
8
  #include "stack.h"
6
9
 
7
10
  #define EVENT_CALL (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL)
@@ -1,7 +1,9 @@
1
1
  #include "stack.h"
2
+
2
3
  #include <stdio.h>
3
4
  #include <stdlib.h>
4
5
  #include <string.h>
6
+
5
7
  #include "ruby.h"
6
8
 
7
9
  static void resize_buffer(rs_stack_t *stack) {
@@ -1,6 +1,7 @@
1
1
  #ifndef _INC_ROTOSCOPE_STACK_H_
2
2
  #define _INC_ROTOSCOPE_STACK_H_
3
3
  #include <stdbool.h>
4
+
4
5
  #include "method_desc.h"
5
6
 
6
7
  typedef struct {
@@ -1,12 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'csv'
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
- rs = new(dest, blacklist: blacklist)
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, :blacklist
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
- unless blacklist.is_a?(Regexp)
23
- blacklist = Regexp.union(blacklist || [])
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
- @blacklist = blacklist
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, 'w')
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 = ''.dup
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
- return if blacklist.match?(call.caller_path)
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
- caller_class_name = '<ROOT>'
96
- caller_method_name = '<UNKNOWN>'
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? ? 'class' : 'instance'
118
+ caller_method_level = call.caller_singleton_method? ? "class" : "instance"
102
119
  end
103
120
 
104
- call_method_level = call.singleton_method? ? 'class' : 'instance'
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
- '"' << caller_class_name << '",' \
112
- '"' << call.caller_path << '",' \
113
- << call.caller_lineno.to_s << ',' \
114
- '"' << method_name << '",' \
115
- << call_method_level << ',' \
116
- '"' << caller_method_name << '",' \
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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Rotoscope
3
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1.pre.2"
4
5
  end
data/lib/rotoscope.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'rotoscope/rotoscope'
2
+
3
+ require "rotoscope/rotoscope"
3
4
 
4
5
  class Rotoscope
5
- autoload :CallLogger, 'rotoscope/call_logger'
6
+ autoload :CallLogger, "rotoscope/call_logger"
6
7
 
7
8
  def trace
8
9
  start_trace
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 = 'rotoscope'
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 = 'jahfer.husain@shopify.com'
13
- s.homepage = 'https://github.com/shopify/rotoscope'
14
- s.license = 'MIT'
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 = %w(ext/rotoscope/extconf.rb)
25
+ s.extensions = ["ext/rotoscope/extconf.rb"]
24
26
 
25
- s.add_development_dependency('rake-compiler', '~> 0.9')
26
- s.add_development_dependency('mocha', '~> 0.14')
27
- s.add_development_dependency('minitest', '~> 5.0')
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
@@ -0,0 +1,9 @@
1
+ {
2
+ Memory leak in Ruby for versions <= 2.7.4 (see https://bugs.ruby-lang.org/issues/18264)
3
+ Memcheck:Leak
4
+ ...
5
+ fun:tp_alloc
6
+ fun:tracepoint_new
7
+ fun:rb_tracepoint_new
8
+ ...
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ Memory leak in Ruby for versions <= 3.0.2 (see https://bugs.ruby-lang.org/issues/18264)
3
+ Memcheck:Leak
4
+ ...
5
+ fun:tp_alloc
6
+ fun:tracepoint_new
7
+ fun:rb_tracepoint_new
8
+ ...
9
+ }
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.0
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: 2017-09-20 00:00:00.000000000 Z
12
+ date: 2023-11-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rake-compiler
15
+ name: minitest
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '0.9'
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.9'
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: minitest
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.56'
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.56'
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/probots.yml"
65
+ - ".github/workflows/ci.yml"
66
+ - ".github/workflows/cla.yml"
80
67
  - ".gitignore"
81
68
  - ".rubocop.yml"
82
- - ".ruby-version"
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: '0'
109
+ version: 1.3.1
121
110
  requirements: []
122
- rubyforge_project:
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
@@ -1,2 +0,0 @@
1
- enabled:
2
- - cla
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.4.3
data/.travis.yml DELETED
@@ -1,2 +0,0 @@
1
- env:
2
- - ROTOSCOPE_COMPILE_ERROR=1