rotoscope 0.3.1.pre.1 → 0.3.1.pre.3

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: f72d8d9a3775c27bafb2214f668282b5defe8731e04befe0957bbd628fe99a82
4
- data.tar.gz: cbc5bded38b6287945e629c6bc64fbcb1fee23c23c1ed588e04fbe2aabdb60fa
3
+ metadata.gz: 131b31e53265ab28834e10e524193c4140df6c4a550b97b4c069bd5144f2c0b9
4
+ data.tar.gz: 8bcacd887b9c1dc7dec386dfd750f2e5521da85552183211370c67292ec7ea2d
5
5
  SHA512:
6
- metadata.gz: 4bd1d1a8fdea550f69513e8b36b3753684239d6cb662655cce35b4da1e4b163041265bd3771f6dd2c37e8c07b6efedef2b7b51d1d2a7f313b70adbe0e093e2d4
7
- data.tar.gz: '009a2baef7ec589dd738077f523aecfa8dbc7af486a1a67eba91cd990530c494caf7134b92f9e6a83e3991bf3484bad69bd5123755cdd24440c0209378e64e80'
6
+ metadata.gz: 1acb66647386b79262fc77ad90433f920ffb36b75d2c7527b005abb68c377563a448b99d90e573518369171ddb05908c5a95fc5d9f88a646de72e385405c4087
7
+ data.tar.gz: fc010fa94e7669b902aa7285dd85bccea72781cf6cb9b3165941c6c2023491e2f945ed9b2a4f7cb87fa3f55e30c5fbb5b0857726eda1b859b3385d9370e641b9
@@ -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.5.3
8
+ - ruby: 2.7.8
9
9
  - homebrew:
10
10
  - clang-format
11
11
  - bundler
@@ -1,4 +1,5 @@
1
1
  #include "callsite.h"
2
+
2
3
  #include <ruby.h>
3
4
  #include <ruby/debug.h>
4
5
  #include <stdbool.h>
@@ -7,13 +8,14 @@ static VALUE caller_frame(int *line, bool ruby_call) {
7
8
  VALUE frames[2] = {Qnil, Qnil};
8
9
  int lines[2] = {0, 0};
9
10
 
10
- // At this point, the top ruby stack frame is for the method
11
- // being called, so we want to skip that frame and get
12
- // the caller location. This is why we use 1 for ruby calls.
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.
13
15
  //
14
- // However, rb_profile_frames also automatically skips over
15
- // non-ruby stack frames, so we don't want to have to skip
16
- // over any extra stack frames for a C call.
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.
17
19
  int frame_index = ruby_call ? 1 : 0;
18
20
 
19
21
  // There is currently a bug in rb_profile_frames that
@@ -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
 
@@ -61,7 +60,9 @@ static rs_method_desc_t called_method_desc(rb_trace_arg_t *trace_arg) {
61
60
  SYM2ID(method_id) != id_initialize;
62
61
 
63
62
  return (rs_method_desc_t){
64
- .receiver = receiver, .id = method_id, .singleton_p = singleton_p,
63
+ .receiver = receiver,
64
+ .id = method_id,
65
+ .singleton_p = singleton_p,
65
66
  };
66
67
  }
67
68
 
@@ -99,7 +100,7 @@ static void event_hook(VALUE tpval, void *data) {
99
100
  return;
100
101
  }
101
102
 
102
- if (config->tid != gettid()) return;
103
+ if (config->tid != current_thread_id()) return;
103
104
  if (in_fork(config)) {
104
105
  rb_tracepoint_disable(config->tracepoint);
105
106
  config->tracing = false;
@@ -131,29 +132,45 @@ static void event_hook(VALUE tpval, void *data) {
131
132
  rb_funcall(config->trace_proc, id_call, 1, config->self);
132
133
  }
133
134
 
134
- static void rs_gc_mark(Rotoscope *config) {
135
+ static void rs_gc_mark(void *data) {
136
+ Rotoscope *config = (Rotoscope *)data;
135
137
  rb_gc_mark(config->tracepoint);
136
138
  rb_gc_mark(config->trace_proc);
137
139
  rs_stack_mark(&config->stack);
138
140
  }
139
141
 
140
- void rs_dealloc(Rotoscope *config) {
142
+ static void rs_dealloc(void *data) {
143
+ Rotoscope *config = (Rotoscope *)data;
141
144
  stop_tracing_on_cleanup(config);
142
145
  rs_stack_free(&config->stack);
143
146
  xfree(config);
144
147
  }
145
148
 
149
+ static size_t rs_memsize(const void *data) { return sizeof(Rotoscope); }
150
+
151
+ static const rb_data_type_t rs_data_type = {
152
+ .wrap_struct_name = "Rotoscope",
153
+ .function =
154
+ {
155
+ .dmark = rs_gc_mark,
156
+ .dfree = rs_dealloc,
157
+ .dsize = rs_memsize,
158
+ },
159
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY};
160
+
146
161
  static VALUE rs_alloc(VALUE klass) {
147
162
  Rotoscope *config;
148
- VALUE self =
149
- Data_Make_Struct(klass, Rotoscope, rs_gc_mark, rs_dealloc, config);
163
+ VALUE self = TypedData_Make_Struct(klass, Rotoscope, &rs_data_type, config);
150
164
  config->self = self;
151
165
  config->pid = getpid();
152
- config->tid = gettid();
166
+ config->tid = current_thread_id();
153
167
  config->tracing = false;
154
168
  config->caller = NULL;
155
169
  config->callsite = (rs_callsite_t){
156
- .filepath = Qnil, .lineno = 0, .method_name = Qnil, .singleton_p = Qnil,
170
+ .filepath = Qnil,
171
+ .lineno = 0,
172
+ .method_name = Qnil,
173
+ .singleton_p = Qnil,
157
174
  };
158
175
  config->trace_proc = Qnil;
159
176
  rs_stack_init(&config->stack, STACK_CAPACITY);
@@ -164,7 +181,7 @@ static VALUE rs_alloc(VALUE klass) {
164
181
 
165
182
  static Rotoscope *get_config(VALUE self) {
166
183
  Rotoscope *config;
167
- Data_Get_Struct(self, Rotoscope, config);
184
+ TypedData_Get_Struct(self, Rotoscope, &rs_data_type, config);
168
185
  return config;
169
186
  }
170
187
 
@@ -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) {
@@ -60,8 +62,10 @@ void rs_stack_init(rs_stack_t *stack, unsigned int capacity) {
60
62
  }
61
63
 
62
64
  void rs_stack_mark(rs_stack_t *stack) {
63
- for (int i = 0; i <= stack->top; i++) {
64
- rs_stack_frame_t *frame = &stack->contents[i];
65
- rs_method_desc_mark(&frame->method);
65
+ if (stack->contents) {
66
+ for (int i = 0; i <= stack->top; i++) {
67
+ rs_stack_frame_t *frame = &stack->contents[i];
68
+ rs_method_desc_mark(&frame->method);
69
+ }
66
70
  }
67
71
  }
@@ -1,9 +1,12 @@
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
- typedef struct { rs_method_desc_t method; } rs_stack_frame_t;
7
+ typedef struct {
8
+ rs_method_desc_t method;
9
+ } rs_stack_frame_t;
7
10
 
8
11
  typedef struct {
9
12
  int capacity;
@@ -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
- caller_path = call.caller_path || ''
92
- return if blacklist.match?(caller_path)
108
+ caller_path = call.caller_path || ""
109
+ return if excludelist.match?(caller_path)
93
110
  return if self == call.receiver
94
111
 
95
- caller_class_name = call.caller_class_name || '<UNKNOWN>'
112
+ caller_class_name = call.caller_class_name || "<UNKNOWN>"
96
113
  if call.caller_method_name.nil?
97
- caller_method_name = '<UNKNOWN>'
98
- caller_method_level = '<UNKNOWN>'
114
+ caller_method_name = "<UNKNOWN>"
115
+ caller_method_level = "<UNKNOWN>"
99
116
  else
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
- '"' << 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.1.pre.1"
4
+ VERSION = "0.3.1.pre.3"
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.1.pre.1
4
+ version: 0.3.1.pre.3
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-12-08 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:
@@ -119,8 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
108
  - !ruby/object:Gem::Version
120
109
  version: 1.3.1
121
110
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.7.6
111
+ rubygems_version: 3.4.22
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.5.3
data/.travis.yml DELETED
@@ -1,2 +0,0 @@
1
- env:
2
- - ROTOSCOPE_COMPILE_ERROR=1