rotoscope 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9e3e40d016f9db6e000338a2324aa7e841596075
4
+ data.tar.gz: b09d90253edea8dadcef671c33e11fb482e6e505
5
+ SHA512:
6
+ metadata.gz: 5ff87cb9a2d13fc25ad797d14e4f38d92132de716ee164d201032c953f96ca47d1e268413cc5c5041b50e6cdf0f7e1477a41ead4ccea7e40da5190675bc99761
7
+ data.tar.gz: 6021e1a40637d82234fc87da1a66d93e2917fd5379da67692420c502b92f7fac1b81df3beffa24896c9492ce6cfdcd0d1c72d5223f3e6f4ddd9e174070df521f
data/.clang-format ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BasedOnStyle: Google
@@ -0,0 +1,3 @@
1
+ Make sure these are checked before submitting your PR — thank you!
2
+
3
+ - [ ] `bin/fmt` was successfully run
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ tmp
2
+ lib/rotoscope/rotoscope.bundle
3
+ lib/rotoscope/rotoscope.so
4
+ .vscode
5
+ *.gem
6
+ .rubocop-*
7
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ inherit_from:
2
+ - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'tmp/**/*'
7
+ TargetRubyVersion: 2.2
8
+
9
+ Metrics/LineLength:
10
+ Exclude:
11
+ - 'test/**/*'
12
+
13
+ Style/GlobalVars:
14
+ Exclude:
15
+ - 'ext/rotoscope/extconf.rb'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ source 'https://rubygems.org'
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright 2017 Shopify
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # Rotoscope
2
+
3
+ Rotoscope performs introspection of method calls in Ruby programs.
4
+
5
+ ## Status   [![status](https://circleci.com/gh/Shopify/rotoscope/tree/master.svg?style=shield&circle-token=cddbd315df7a81ab944adf4dfc14a5800cd589fc)](https://circleci.com/gh/Shopify/rotoscope/tree/master)
6
+
7
+ Alpha!
8
+
9
+ ## Example
10
+
11
+ ```ruby
12
+ require 'rotoscope'
13
+
14
+ class Dog
15
+ def bark
16
+ Noisemaker.speak('woof!')
17
+ end
18
+ end
19
+
20
+ class Noisemaker
21
+ def self.speak(str)
22
+ puts(str)
23
+ end
24
+ end
25
+
26
+ log_file = File.expand_path('dog_trace.log')
27
+ puts "Writing to #{log_file}..."
28
+
29
+ Rotoscope.trace(log_file) do
30
+ dog1 = Dog.new
31
+ dog1.bark
32
+ end
33
+ ```
34
+
35
+ The resulting method calls are saved in the specified `dest` in the order they were received.
36
+
37
+ Sample output:
38
+
39
+ ```
40
+ event,entity,method_name,method_level,filepath,lineno
41
+ call,"Dog","new",class,"example/dog.rb",19
42
+ call,"Dog","initialize",instance,"example/dog.rb",19
43
+ return,"Dog","initialize",instance,"example/dog.rb",19
44
+ return,"Dog","new",class,"example/dog.rb",19
45
+ call,"Dog","bark",instance,"example/dog.rb",4
46
+ call,"Noisemaker","speak",class,"example/dog.rb",10
47
+ call,"Noisemaker","puts",class,"example/dog.rb",11
48
+ call,"IO","puts",instance,"example/dog.rb",11
49
+ call,"IO","write",instance,"example/dog.rb",11
50
+ return,"IO","write",instance,"example/dog.rb",11
51
+ call,"IO","write",instance,"example/dog.rb",11
52
+ return,"IO","write",instance,"example/dog.rb",11
53
+ return,"IO","puts",instance,"example/dog.rb",11
54
+ return,"Noisemaker","puts",class,"example/dog.rb",11
55
+ return,"Noisemaker","speak",class,"example/dog.rb",12
56
+ return,"Dog","bark",instance,"example/dog.rb",6
57
+ ```
58
+
59
+ If you're interested solely in the flattened caller/callee list, you can pass the `flatten` option to retrieve that instead. This step will also remove all duplicate lines, which can produce significantly smaller output on large codebases.
60
+
61
+ ```ruby
62
+ # ... same code as above
63
+
64
+ Rotoscope.trace(log_file, flatten: true) do
65
+ dog1 = Dog.new
66
+ dog1.bark
67
+ end
68
+ ```
69
+
70
+ Sample output:
71
+
72
+ ```
73
+ entity,method_name,method_level,filepath,lineno,caller_entity,caller_method_name,caller_method_level
74
+ Dog,new,class,example/flattened_dog.rb,19,<ROOT>,<UNKNOWN>,<UNKNOWN>
75
+ Dog,initialize,instance,example/flattened_dog.rb,19,Dog,new,class
76
+ Dog,bark,instance,example/flattened_dog.rb,20,<ROOT>,<UNKNOWN>,<UNKNOWN>
77
+ Noisemaker,speak,class,example/flattened_dog.rb,5,Dog,bark,instance
78
+ Noisemaker,puts,class,example/flattened_dog.rb,11,Noisemaker,speak,class
79
+ IO,puts,instance,example/flattened_dog.rb,11,Noisemaker,puts,class
80
+ IO,write,instance,example/flattened_dog.rb,11,IO,puts,instance
81
+ ```
82
+
83
+ ## API
84
+
85
+ - [Public Class Methods](#public-class-methods)
86
+ - [`trace`](#rotoscopetracedest-blacklist--flatten-false)
87
+ - [`new`](#rotoscopenewdest-blacklist)
88
+ - [Public Instance Methods](#public-instance-methods)
89
+ - [`trace`](#rotoscopetraceblock)
90
+ - [`start_trace`](#rotoscopestart_trace)
91
+ - [`stop_trace`](#rotoscopestop_trace)
92
+ - [`mark`](#rotoscopemarkstr--)
93
+ - [`close`](#rotoscopeclose)
94
+ - [`state`](#rotoscopestate)
95
+ - [`closed?`](#rotoscopeclosed)
96
+ - [`log_path`](#rotoscopelog_path)
97
+
98
+ ---
99
+
100
+ ### Public Class Methods
101
+
102
+ #### `Rotoscope::trace(dest, blacklist: [], flatten: false)`
103
+
104
+ Writes all calls and returns of methods to `dest`, except for those whose filepath contains any entry in `blacklist`. `dest` is either a filename or an `IO`. The `flatten` option reduces the output data to a deduplicated list of method invocations and their caller, instead of all `call` and `return` events. Methods invoked at the top of the trace will have a caller entity of `<ROOT>` and a caller method name of `<UNKNOWN>`.
105
+
106
+ ```ruby
107
+ Rotoscope.trace(dest) { |rs| ... }
108
+ # or...
109
+ Rotoscope.trace(dest, blacklist: ["/.gem/"], flatten: true) { |rs| ... }
110
+ ```
111
+
112
+ #### `Rotoscope::new(dest, blacklist: [], flatten: false)`
113
+
114
+ Same interface as `Rotoscope::trace`, but returns a `Rotoscope` instance, allowing fine-grain control via `Rotoscope#start_trace` and `Rotoscope#stop_trace`.
115
+ ```ruby
116
+ rs = Rotoscope.new(dest)
117
+ # or...
118
+ rs = Rotoscope.new(dest, blacklist: ["/.gem/"], flatten: true)
119
+ ```
120
+
121
+ ---
122
+
123
+ ### Public Instance Methods
124
+
125
+ #### `Rotoscope#trace(&block)`
126
+
127
+ Similar to `Rotoscope::trace`, but does not need to create a file handle on invocation.
128
+
129
+ ```ruby
130
+ rs = Rotoscope.new(dest)
131
+ rs.trace do |rotoscope|
132
+ # code to trace...
133
+ end
134
+ ```
135
+
136
+ #### `Rotoscope#start_trace`
137
+
138
+ Begins writing method calls and returns to the `dest` specified in the initializer.
139
+
140
+ ```ruby
141
+ rs = Rotoscope.new(dest)
142
+ rs.start_trace
143
+ # code to trace...
144
+ rs.stop_trace
145
+ ```
146
+
147
+ #### `Rotoscope#stop_trace`
148
+
149
+ Stops writing method invocations to the `dest`. Subsequent calls to `Rotoscope#start_trace` may be invoked to resume tracing.
150
+
151
+ ```ruby
152
+ rs = Rotoscope.new(dest)
153
+ rs.start_trace
154
+ # code to trace...
155
+ rs.stop_trace
156
+ ```
157
+
158
+ #### `Rotoscope#mark(str = "")`
159
+
160
+ Inserts a marker '--- ' to divide output. Useful for segmenting multiple blocks of code that are being profiled. If `str` is provided, the line will be prefixed by '--- ', followed by the string passed.
161
+
162
+ ```ruby
163
+ rs = Rotoscope.new(dest)
164
+ rs.start_trace
165
+ # code to trace...
166
+ rs.mark('Something goes wrong here') # produces `--- Something goes wrong here` in the output
167
+ # more code ...
168
+ rs.stop_trace
169
+ ```
170
+
171
+ #### `Rotoscope#close`
172
+
173
+ Flushes the buffer and closes the file handle. Once this is invoked, no more writes can be performed on the `Rotoscope` object. Sets `state` to `:closed`.
174
+
175
+ ```ruby
176
+ rs = Rotoscope.new(dest)
177
+ rs.trace { |rotoscope| ... }
178
+ rs.close
179
+ ```
180
+
181
+ #### `Rotoscope#state`
182
+
183
+ Returns the current state of the Rotoscope object. Valid values are `:open`, `:tracing` and `:closed`.
184
+
185
+ ```ruby
186
+ rs = Rotoscope.new(dest)
187
+ rs.state # :open
188
+ rs.trace do
189
+ rs.state # :tracing
190
+ end
191
+ rs.close
192
+ rs.state # :closed
193
+ ```
194
+
195
+ #### `Rotoscope#closed?`
196
+
197
+ Shorthand to check if the `state` is set to `:closed`.
198
+
199
+ ```ruby
200
+ rs = Rotoscope.new(dest)
201
+ rs.closed? # false
202
+ rs.close
203
+ rs.closed? # true
204
+ ```
205
+
206
+
207
+ #### `Rotoscope#log_path`
208
+
209
+ Returns the output filepath set in the Rotoscope constructor.
210
+
211
+ ```ruby
212
+ dest = '/foo/bar/rotoscope.csv'
213
+ rs = Rotoscope.new(dest)
214
+ rs.log_path # "/foo/bar/rotoscope.csv"
215
+ ```
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # ==========================================================
3
+ # Packaging
4
+ # ==========================================================
5
+ GEMSPEC = Gem::Specification.load('rotoscope.gemspec')
6
+
7
+ require 'rubygems/package_task'
8
+ Gem::PackageTask.new(GEMSPEC) do |pkg|
9
+ end
10
+
11
+ # ==========================================================
12
+ # Ruby Extension
13
+ # ==========================================================
14
+
15
+ require 'rake/extensiontask'
16
+ Rake::ExtensionTask.new('rotoscope', GEMSPEC) do |ext|
17
+ ext.lib_dir = 'lib/rotoscope'
18
+ end
19
+
20
+ task build: :compile
21
+
22
+ task install: [:build] do |_t|
23
+ sh "gem build rotoscope.gemspec && gem install rotoscope-*.gem"
24
+ end
25
+
26
+ # ==========================================================
27
+ # Testing
28
+ # ==========================================================
29
+
30
+ require 'rake/testtask'
31
+ Rake::TestTask.new 'test' do |t|
32
+ t.test_files = FileList['test/*_test.rb']
33
+ end
34
+ task test: :build
35
+
36
+ task default: :test
data/bin/fmt ADDED
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env bash
2
+
3
+ echo "Formatting C code..."
4
+ find "$PWD" -iname lib/rotoscope/*.h -o -iname *.c | xargs clang-format -i -style=file
5
+
6
+ echo "Formatting Ruby code..."
7
+ bundle exec rubocop -a
8
+
9
+ echo "Formatting complete."
data/dev.yml ADDED
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: rotoscope
3
+ up:
4
+ - ruby: 2.3.3
5
+ - homebrew:
6
+ - clang-format
7
+ - bundler
8
+
9
+ commands:
10
+ build: bundle exec rake build
11
+ test: bundle exec rake test
12
+ fmt: bin/fmt
@@ -0,0 +1,78 @@
1
+ #include "callsite.h"
2
+ #include <ruby.h>
3
+ #include <ruby/debug.h>
4
+
5
+ VALUE empty_ruby_string;
6
+
7
+ // Need the cfp field from this internal ruby structure.
8
+ struct rb_trace_arg_struct {
9
+ // unused fields needed to make sure the cfp is at the
10
+ // correct offset
11
+ rb_event_flag_t unused1;
12
+ void *unused2;
13
+ void *cfp;
14
+ // rest of fields are unused
15
+ };
16
+
17
+ size_t ruby_control_frame_size;
18
+
19
+ // We depend on MRI to store ruby control frames as an array
20
+ // to determine the control frame size, which is used here to
21
+ // get the caller's control frame
22
+ static void *caller_cfp(void *cfp) {
23
+ return ((char *)cfp) + ruby_control_frame_size;
24
+ }
25
+
26
+ static VALUE dummy(VALUE self, VALUE first) {
27
+ if (first == Qtrue) {
28
+ rb_funcall(self, rb_intern("dummy"), 1, Qfalse);
29
+ }
30
+ return Qnil;
31
+ }
32
+
33
+ static void trace_control_frame_size(VALUE tpval, void *data) {
34
+ void **cfps = data;
35
+ rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval);
36
+
37
+ if (cfps[0] == NULL) {
38
+ cfps[0] = trace_arg->cfp;
39
+ } else if (cfps[1] == NULL) {
40
+ cfps[1] = trace_arg->cfp;
41
+ }
42
+ }
43
+
44
+ rs_callsite_t c_callsite(rb_trace_arg_t *trace_arg) {
45
+ VALUE path = rb_tracearg_path(trace_arg);
46
+ return (rs_callsite_t){
47
+ .filepath = NIL_P(path) ? empty_ruby_string : path,
48
+ .lineno = FIX2INT(rb_tracearg_lineno(trace_arg)),
49
+ };
50
+ }
51
+
52
+ rs_callsite_t ruby_callsite(rb_trace_arg_t *trace_arg) {
53
+ void *old_cfp = trace_arg->cfp;
54
+
55
+ // Ruby uses trace_arg->cfp to get the path and line number
56
+ trace_arg->cfp = caller_cfp(trace_arg->cfp);
57
+ rs_callsite_t callsite = c_callsite(trace_arg);
58
+ trace_arg->cfp = old_cfp;
59
+
60
+ return callsite;
61
+ }
62
+
63
+ void init_callsite() {
64
+ empty_ruby_string = rb_str_new_literal("");
65
+ RB_OBJ_FREEZE(empty_ruby_string);
66
+ rb_global_variable(&empty_ruby_string);
67
+
68
+ VALUE tmp_obj = rb_funcall(rb_cObject, rb_intern("new"), 0);
69
+ rb_define_singleton_method(tmp_obj, "dummy", dummy, 1);
70
+
71
+ char *cfps[2] = {NULL, NULL};
72
+ VALUE tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_C_CALL,
73
+ trace_control_frame_size, &cfps);
74
+ rb_tracepoint_enable(tracepoint);
75
+ rb_funcall(tmp_obj, rb_intern("dummy"), 1, Qtrue);
76
+ rb_tracepoint_disable(tracepoint);
77
+ ruby_control_frame_size = (size_t)cfps[0] - (size_t)cfps[1];
78
+ }
@@ -0,0 +1,17 @@
1
+ #ifndef _INC_CALLSITE_H_
2
+ #define _INC_CALLSITE_H_
3
+
4
+ #include <ruby.h>
5
+ #include <ruby/debug.h>
6
+
7
+ typedef struct {
8
+ VALUE filepath;
9
+ unsigned int lineno;
10
+ } rs_callsite_t;
11
+
12
+ void init_callsite();
13
+
14
+ rs_callsite_t c_callsite(rb_trace_arg_t *trace_arg);
15
+ rs_callsite_t ruby_callsite(rb_trace_arg_t *trace_arg);
16
+
17
+ #endif