rotoscope 0.3.0.pre.6 → 0.3.0.pre.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e1efb6a5e9839ddf8219a7cfe6287195f0efbc5
4
- data.tar.gz: 91bb41de1a6c892ccd9bb0f6e9b437c67fe90c68
3
+ metadata.gz: dc57c07b922488935b47e583d422a36d9fa32096
4
+ data.tar.gz: cd70207ab9f61bcd490506a5defc6c293dbec11d
5
5
  SHA512:
6
- metadata.gz: 436314656fd8c5e6ebabfd3b89b3ad2951b7073f530b3ac1381c04ad0a972fe3e434595c8e0a07e0c13e6ef1c5f1177bcc270b5995a15538466a7c2553637a26
7
- data.tar.gz: fae7fbf6c7c84b337579b1270f0620860dde11c0f88f8a526d2bfca87cf3eae8b2eee15de56c2ca3aca631538233bb351770f3c30cfda77fd0b4f1c3d73096d8
6
+ metadata.gz: 7c512251290df7ee37e72e0f130ca879da887f3ecdd206b60e9e4a361e422bf68daf6b6c42acbb1c20a40efaa4c7166334fc2e21d0df2e3dc1f9c722267b31c0
7
+ data.tar.gz: a5e1d3bdd7ae63147097154630aae6bb9c4153fa110be4fc1873cbce38f7d684a3e0f87cc1c80f4dfd811c04e21d21c8dcc5adad7de4afc91a977dc266ac0d41
@@ -4,7 +4,4 @@ require "mkmf"
4
4
  $CFLAGS << ' -std=c99 -Wall -Werror -Wno-declaration-after-statement'
5
5
  $defs << "-D_POSIX_SOURCE"
6
6
 
7
- uthash_path = File.expand_path('../../../lib/uthash', __FILE__)
8
- find_header('uthash.h', uthash_path) || raise
9
-
10
7
  create_makefile('rotoscope/rotoscope')
@@ -2,6 +2,7 @@
2
2
  #include <ruby.h>
3
3
  #include <ruby/debug.h>
4
4
  #include <ruby/intern.h>
5
+ #include <ruby/io.h>
5
6
  #include <ruby/version.h>
6
7
  #include <stdbool.h>
7
8
  #include <stdio.h>
@@ -10,21 +11,16 @@
10
11
  #include "callsite.h"
11
12
  #include "rotoscope.h"
12
13
  #include "stack.h"
13
- #include "strmemo.h"
14
14
  #include "tracepoint.h"
15
15
 
16
16
  VALUE cRotoscope, cTracePoint;
17
- ID id_initialize, id_gsub;
18
- VALUE str_quote, str_escaped_quote;
17
+ ID id_initialize, id_gsub, id_close;
18
+ VALUE str_quote, str_escaped_quote, str_header;
19
19
 
20
20
  static unsigned long gettid() {
21
21
  return NUM2ULONG(rb_obj_id(rb_thread_current()));
22
22
  }
23
23
 
24
- static int write_csv_header(FILE *log, const char *header) {
25
- return fprintf(log, "%s\n", header);
26
- }
27
-
28
24
  static const char *evflag2name(rb_event_flag_t evflag) {
29
25
  switch (evflag) {
30
26
  case RUBY_EVENT_CALL:
@@ -120,6 +116,17 @@ static rs_tracepoint_t extract_full_tracevals(rb_trace_arg_t *trace_arg,
120
116
 
121
117
  static bool in_fork(Rotoscope *config) { return config->pid != getpid(); }
122
118
 
119
+ // The GC sweep step will turn objects with finalizers (e.g. rs_dealloc)
120
+ // to zombie objects until their finalizer is run. In this state, any
121
+ // ruby objects in the Rotoscope struct may have already been collected
122
+ // so they can't safely be used. If tracing isn't stopped before the
123
+ // Rotoscope object has been garbage collected, then we still may receive
124
+ // trace events for method calls in finalizers that run before the one
125
+ // for the Rotoscope object.
126
+ bool rotoscope_marked_for_garbage_collection(Rotoscope *config) {
127
+ return RB_BUILTIN_TYPE(config->self) == RUBY_T_ZOMBIE;
128
+ }
129
+
123
130
  VALUE escape_csv_string(VALUE string) {
124
131
  if (!memchr(RSTRING_PTR(string), '"', RSTRING_LEN(string))) {
125
132
  return string;
@@ -127,36 +134,55 @@ VALUE escape_csv_string(VALUE string) {
127
134
  return rb_funcall(string, id_gsub, 2, str_quote, str_escaped_quote);
128
135
  }
129
136
 
130
- static void log_trace_event(FILE *stream, rs_tracepoint_t *trace) {
131
- VALUE escaped_method_name = escape_csv_string(trace->method_name);
132
- fprintf(stream, RS_CSV_FORMAT "\n",
133
- RS_CSV_VALUES(trace, escaped_method_name));
134
- RB_GC_GUARD(escaped_method_name);
135
- }
136
-
137
- unsigned char output_buffer[LOG_BUFFER_SIZE];
138
- static void log_trace_event_with_caller(FILE *stream,
137
+ static void log_trace_event_with_caller(VALUE output_buffer, VALUE io,
139
138
  rs_stack_frame_t *stack_frame,
140
- rs_stack_frame_t *caller_frame,
141
- rs_strmemo_t **call_memo) {
139
+ rs_stack_frame_t *caller_frame) {
142
140
  VALUE escaped_method_name = escape_csv_string(stack_frame->tp.method_name);
143
141
  VALUE escaped_caller_method_name =
144
142
  escape_csv_string(caller_frame->tp.method_name);
145
- snprintf(
146
- (char *)output_buffer, LOG_BUFFER_SIZE, RS_FLATTENED_CSV_FORMAT "\n",
147
- RS_FLATTENED_CSV_VALUES(&stack_frame->tp, &caller_frame->tp,
148
- escaped_method_name, escaped_caller_method_name));
143
+
144
+ while (true) {
145
+ rb_str_modify(output_buffer);
146
+ long out_len = snprintf(
147
+ RSTRING_PTR(output_buffer), rb_str_capacity(output_buffer),
148
+ RS_CSV_FORMAT "\n",
149
+ RS_CSV_VALUES(&stack_frame->tp, &caller_frame->tp, escaped_method_name,
150
+ escaped_caller_method_name));
151
+
152
+ if (out_len < RSTRING_LEN(output_buffer)) {
153
+ rb_str_set_len(output_buffer, out_len);
154
+ break;
155
+ }
156
+ rb_str_resize(output_buffer, out_len + 1);
157
+ }
158
+
149
159
  RB_GC_GUARD(escaped_method_name);
150
160
  RB_GC_GUARD(escaped_caller_method_name);
151
161
 
152
- if (rs_strmemo_uniq(call_memo, output_buffer)) {
153
- fputs((char *)output_buffer, stream);
162
+ rb_io_write(io, output_buffer);
163
+ }
164
+
165
+ static void stop_tracing_on_cleanup(Rotoscope *config) {
166
+ if (config->state == RS_TRACING) {
167
+ // During process cleanup, event hooks are removed and tracepoint may have
168
+ // already have been GCed, so we need a sanity check before disabling the
169
+ // tracepoint.
170
+ if (RB_TYPE_P(config->tracepoint, T_DATA) &&
171
+ CLASS_OF(config->tracepoint) == cTracePoint) {
172
+ rb_tracepoint_disable(config->tracepoint);
173
+ }
174
+ config->state = RS_OPEN;
154
175
  }
155
176
  }
156
177
 
157
178
  static void event_hook(VALUE tpval, void *data) {
158
179
  Rotoscope *config = (Rotoscope *)data;
159
180
 
181
+ if (rotoscope_marked_for_garbage_collection(config)) {
182
+ stop_tracing_on_cleanup(config);
183
+ return;
184
+ }
185
+
160
186
  if (config->tid != gettid()) return;
161
187
  if (in_fork(config)) {
162
188
  rb_tracepoint_disable(config->tracepoint);
@@ -165,77 +191,45 @@ static void event_hook(VALUE tpval, void *data) {
165
191
  }
166
192
 
167
193
  rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval);
168
- rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
169
194
 
170
- rs_callsite_t trace_path;
171
- bool blacklisted;
172
- if (event_flag & EVENT_CALL) {
173
- trace_path = tracearg_path(trace_arg);
174
- blacklisted = rejected_path(trace_path.filepath, config);
175
- } else {
176
- if (rs_stack_empty(&config->stack)) return;
177
- rs_stack_frame_t *call_frame = rs_stack_peek(&config->stack);
178
- trace_path = (rs_callsite_t){
179
- .filepath = call_frame->tp.filepath, .lineno = call_frame->tp.lineno,
180
- };
181
- blacklisted = call_frame->blacklisted;
195
+ if (rb_tracearg_defined_class(trace_arg) == cRotoscope) {
196
+ return;
182
197
  }
183
198
 
184
- rs_tracepoint_t trace = extract_full_tracevals(trace_arg, &trace_path);
185
- if (!strcmp("Rotoscope", StringValueCStr(trace.entity))) return;
186
-
187
- if (event_flag & EVENT_CALL) {
188
- rs_stack_push(&config->stack, trace, blacklisted);
189
- } else {
190
- rs_stack_pop(&config->stack);
191
- }
192
- if (blacklisted) return;
199
+ rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
193
200
 
194
- if (config->flatten_output) {
195
- if (event_flag & EVENT_CALL) {
196
- rs_stack_frame_t *stack_frame = rs_stack_peek(&config->stack);
197
- rs_stack_frame_t *caller_frame =
198
- rs_stack_below(&config->stack, stack_frame);
199
- log_trace_event_with_caller(config->log, stack_frame, caller_frame,
200
- &config->call_memo);
201
+ if (event_flag & EVENT_RETURN) {
202
+ if (!rs_stack_empty(&config->stack)) {
203
+ rs_stack_pop(&config->stack);
201
204
  }
202
- } else {
203
- log_trace_event(config->log, &trace);
205
+ return;
204
206
  }
205
- }
206
207
 
207
- static void close_log_handle(Rotoscope *config) {
208
- if (config->log) {
209
- // Stop tracing so the event hook isn't called with a NULL log
210
- if (config->state == RS_TRACING) {
211
- // During process cleanup, event hooks are removed and tracepoint may have
212
- // already have been GCed, so we need a sanity check before disabling the
213
- // tracepoint.
214
- if (RB_TYPE_P(config->tracepoint, T_DATA) &&
215
- CLASS_OF(config->tracepoint) == cTracePoint) {
216
- rb_tracepoint_disable(config->tracepoint);
217
- }
218
- }
208
+ rs_callsite_t trace_path = tracearg_path(trace_arg);
209
+ bool blacklisted = rejected_path(trace_path.filepath, config);
219
210
 
220
- if (in_fork(config)) {
221
- close(fileno(config->log));
222
- } else {
223
- fclose(config->log);
224
- }
225
- config->log = NULL;
226
- }
211
+ rs_tracepoint_t trace = extract_full_tracevals(trace_arg, &trace_path);
212
+
213
+ rs_stack_push(&config->stack, trace, blacklisted);
214
+
215
+ if (blacklisted) return;
216
+
217
+ rs_stack_frame_t *stack_frame = rs_stack_peek(&config->stack);
218
+ rs_stack_frame_t *caller_frame = rs_stack_below(&config->stack, stack_frame);
219
+ log_trace_event_with_caller(config->output_buffer, config->log, stack_frame,
220
+ caller_frame);
227
221
  }
228
222
 
229
223
  static void rs_gc_mark(Rotoscope *config) {
230
- rb_gc_mark(config->log_path);
224
+ rb_gc_mark(config->log);
231
225
  rb_gc_mark(config->tracepoint);
226
+ rb_gc_mark(config->output_buffer);
232
227
  rs_stack_mark(&config->stack);
233
228
  }
234
229
 
235
230
  void rs_dealloc(Rotoscope *config) {
236
- close_log_handle(config);
231
+ stop_tracing_on_cleanup(config);
237
232
  rs_stack_free(&config->stack);
238
- rs_strmemo_free(config->call_memo);
239
233
  xfree(config->blacklist);
240
234
  xfree(config);
241
235
  }
@@ -244,11 +238,13 @@ static VALUE rs_alloc(VALUE klass) {
244
238
  Rotoscope *config;
245
239
  VALUE self =
246
240
  Data_Make_Struct(klass, Rotoscope, rs_gc_mark, rs_dealloc, config);
247
- config->log_path = Qnil;
241
+ config->self = self;
242
+ config->log = Qnil;
248
243
  config->tracepoint = rb_tracepoint_new(Qnil, EVENT_CALL | EVENT_RETURN,
249
244
  event_hook, (void *)config);
250
245
  config->pid = getpid();
251
246
  config->tid = gettid();
247
+ config->output_buffer = Qnil;
252
248
  return self;
253
249
  }
254
250
 
@@ -287,33 +283,21 @@ void copy_blacklist(Rotoscope *config, VALUE blacklist) {
287
283
 
288
284
  VALUE initialize(int argc, VALUE *argv, VALUE self) {
289
285
  Rotoscope *config = get_config(self);
290
- VALUE output_path, blacklist, flatten;
286
+ VALUE output, blacklist;
291
287
 
292
- rb_scan_args(argc, argv, "12", &output_path, &blacklist, &flatten);
293
- Check_Type(output_path, T_STRING);
288
+ rb_scan_args(argc, argv, "11", &output, &blacklist);
294
289
 
295
290
  if (!NIL_P(blacklist)) {
296
291
  copy_blacklist(config, blacklist);
297
292
  }
298
293
 
299
- config->flatten_output = RTEST(flatten);
300
- config->log_path = output_path;
301
- config->log = fopen(StringValueCStr(config->log_path), "w");
294
+ config->log = output;
302
295
 
303
- if (config->log == NULL) {
304
- fprintf(stderr, "\nERROR: Failed to open file handle at %s (%s)\n",
305
- StringValueCStr(config->log_path), strerror(errno));
306
- exit(1);
307
- }
308
-
309
- if (config->flatten_output)
310
- write_csv_header(config->log, RS_FLATTENED_CSV_HEADER);
311
- else
312
- write_csv_header(config->log, RS_CSV_HEADER);
296
+ rb_io_write(config->log, str_header);
313
297
 
314
298
  rs_stack_init(&config->stack, STACK_CAPACITY);
315
- config->call_memo = NULL;
316
299
  config->state = RS_OPEN;
300
+ config->output_buffer = rb_str_buf_new(LOG_BUFFER_SIZE);
317
301
  return self;
318
302
  }
319
303
 
@@ -335,36 +319,25 @@ VALUE rotoscope_stop_trace(VALUE self) {
335
319
  return Qnil;
336
320
  }
337
321
 
338
- VALUE rotoscope_log_path(VALUE self) {
339
- Rotoscope *config = get_config(self);
340
- return config->log_path;
341
- }
342
-
343
- VALUE rotoscope_mark(int argc, VALUE *argv, VALUE self) {
344
- VALUE str;
345
- rb_scan_args(argc, argv, "01", &str);
346
-
347
- if (NIL_P(str)) str = rb_str_new2("");
348
- Check_Type(str, T_STRING);
349
-
350
- Rotoscope *config = get_config(self);
351
- if (config->log != NULL && !in_fork(config)) {
352
- rs_strmemo_free(config->call_memo);
353
- fprintf(config->log, "--- %s\n", StringValueCStr(str));
354
- }
355
- return Qnil;
356
- }
357
-
358
322
  VALUE rotoscope_close(VALUE self) {
359
323
  Rotoscope *config = get_config(self);
360
324
  if (config->state == RS_CLOSED) {
361
325
  return Qtrue;
362
326
  }
363
- close_log_handle(config);
327
+ rb_tracepoint_disable(config->tracepoint);
328
+ config->state = RS_OPEN;
329
+ if (!in_fork(config)) {
330
+ rb_funcall(config->log, id_close, 0);
331
+ }
364
332
  config->state = RS_CLOSED;
365
333
  return Qtrue;
366
334
  }
367
335
 
336
+ VALUE rotoscope_io(VALUE self) {
337
+ Rotoscope *config = get_config(self);
338
+ return config->log;
339
+ }
340
+
368
341
  VALUE rotoscope_trace(VALUE self) {
369
342
  rotoscope_start_trace(self);
370
343
  return rb_ensure(rb_yield, Qundef, rotoscope_stop_trace, self);
@@ -387,24 +360,26 @@ void Init_rotoscope(void) {
387
360
 
388
361
  id_initialize = rb_intern("initialize");
389
362
  id_gsub = rb_intern("gsub");
363
+ id_close = rb_intern("close");
390
364
 
391
365
  str_quote = rb_str_new_literal("\"");
392
366
  rb_global_variable(&str_quote);
393
367
  str_escaped_quote = rb_str_new_literal("\"\"");
394
368
  rb_global_variable(&str_escaped_quote);
395
369
 
370
+ str_header = rb_str_new_literal(RS_CSV_HEADER "\n");
371
+ rb_global_variable(&str_header);
372
+
396
373
  cRotoscope = rb_define_class("Rotoscope", rb_cObject);
397
374
  rb_define_alloc_func(cRotoscope, rs_alloc);
398
375
  rb_define_method(cRotoscope, "initialize", initialize, -1);
399
376
  rb_define_method(cRotoscope, "trace", (VALUE(*)(ANYARGS))rotoscope_trace, 0);
400
- rb_define_method(cRotoscope, "mark", (VALUE(*)(ANYARGS))rotoscope_mark, -1);
401
377
  rb_define_method(cRotoscope, "close", (VALUE(*)(ANYARGS))rotoscope_close, 0);
378
+ rb_define_method(cRotoscope, "io", rotoscope_io, 0);
402
379
  rb_define_method(cRotoscope, "start_trace",
403
380
  (VALUE(*)(ANYARGS))rotoscope_start_trace, 0);
404
381
  rb_define_method(cRotoscope, "stop_trace",
405
382
  (VALUE(*)(ANYARGS))rotoscope_stop_trace, 0);
406
- rb_define_method(cRotoscope, "log_path",
407
- (VALUE(*)(ANYARGS))rotoscope_log_path, 0);
408
383
  rb_define_method(cRotoscope, "state", (VALUE(*)(ANYARGS))rotoscope_state, 0);
409
384
 
410
385
  init_callsite();
@@ -3,7 +3,6 @@
3
3
 
4
4
  #include <unistd.h>
5
5
  #include "stack.h"
6
- #include "strmemo.h"
7
6
 
8
7
  #define EVENT_CALL (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL)
9
8
  #define EVENT_RETURN (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN)
@@ -16,20 +15,10 @@
16
15
 
17
16
  // clang-format off
18
17
 
19
- #define RS_CSV_HEADER "event,entity,filepath,lineno,method_name,method_level"
20
- #define RS_CSV_FORMAT "%s,\"%s\",\"%s\",%d,\"%s\",%s"
21
- #define RS_CSV_VALUES(trace, method_name) \
22
- trace->event, \
23
- StringValueCStr((trace)->entity), \
24
- StringValueCStr((trace)->filepath), \
25
- (trace)->lineno, \
26
- StringValueCStr(method_name), \
27
- (trace)->method_level
28
-
29
- #define RS_FLATTENED_CSV_HEADER \
18
+ #define RS_CSV_HEADER \
30
19
  "entity,caller_entity,filepath,lineno,method_name,method_level,caller_method_name,caller_method_level"
31
- #define RS_FLATTENED_CSV_FORMAT "\"%s\",\"%s\",\"%s\",%d,\"%s\",%s,\"%s\",%s"
32
- #define RS_FLATTENED_CSV_VALUES(trace, caller_trace, method_name, caller_method_name) \
20
+ #define RS_CSV_FORMAT "\"%s\",\"%s\",\"%s\",%d,\"%s\",%s,\"%s\",%s"
21
+ #define RS_CSV_VALUES(trace, caller_trace, method_name, caller_method_name) \
33
22
  StringValueCStr((trace)->entity), \
34
23
  StringValueCStr((caller_trace)->entity), \
35
24
  StringValueCStr((trace)->filepath), \
@@ -48,17 +37,16 @@ typedef enum {
48
37
  } rs_state;
49
38
 
50
39
  typedef struct {
51
- FILE *log;
52
- VALUE log_path;
40
+ VALUE self;
41
+ VALUE log;
53
42
  VALUE tracepoint;
54
43
  const char **blacklist;
55
44
  unsigned long blacklist_size;
56
- bool flatten_output;
57
45
  pid_t pid;
58
46
  unsigned long tid;
59
47
  rs_state state;
60
48
  rs_stack_t stack;
61
- rs_strmemo_t *call_memo;
49
+ VALUE output_buffer;
62
50
  } Rotoscope;
63
51
 
64
52
  typedef struct {
@@ -1,56 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rotoscope/rotoscope'
3
- require 'fileutils'
4
- require 'tempfile'
5
3
  require 'csv'
6
4
 
7
5
  class Rotoscope
8
6
  class << self
9
- def new(output_path, blacklist: [], flatten: false)
10
- super(output_path, blacklist, flatten)
11
- end
12
-
13
- def trace(dest, blacklist: [], flatten: false, &block)
14
- config = { blacklist: blacklist, flatten: flatten }
15
- if dest.is_a?(String)
16
- event_trace(dest, config, &block)
7
+ def new(output, blacklist: [])
8
+ if output.is_a?(String)
9
+ io = File.open(output, 'w')
10
+ prevent_flush_from_finalizer_in_fork(io)
11
+ obj = super(io, blacklist)
12
+ obj.log_path = output
13
+ obj
17
14
  else
18
- io_event_trace(dest, config, &block)
15
+ super(output, blacklist)
19
16
  end
20
17
  end
21
18
 
22
- private
23
-
24
- def with_temp_file(name)
25
- temp_file = Tempfile.new(name)
26
- yield temp_file
19
+ def trace(dest, blacklist: [])
20
+ rs = new(dest, blacklist: blacklist)
21
+ rs.trace { yield rs }
22
+ rs
27
23
  ensure
28
- temp_file.close! if temp_file
24
+ rs.close if rs && dest.is_a?(String)
29
25
  end
30
26
 
31
- def temp_event_trace(config, block)
32
- with_temp_file("rotoscope_output") do |temp_file|
33
- rs = event_trace(temp_file.path, config, &block)
34
- yield rs
35
- rs
36
- end
37
- end
27
+ private
38
28
 
39
- def io_event_trace(dest_io, config, &block)
40
- temp_event_trace(config, block) do |rs|
41
- File.open(rs.log_path) do |rs_file|
42
- IO.copy_stream(rs_file, dest_io)
43
- end
29
+ def prevent_flush_from_finalizer_in_fork(io)
30
+ pid = Process.pid
31
+ finalizer = lambda do |_|
32
+ next if Process.pid == pid
33
+ # close the file descriptor from another IO object so
34
+ # buffered writes aren't flushed
35
+ IO.for_fd(io.fileno).close
44
36
  end
37
+ ObjectSpace.define_finalizer(io, finalizer)
45
38
  end
39
+ end
46
40
 
47
- def event_trace(dest_path, config)
48
- rs = Rotoscope.new(dest_path, blacklist: config[:blacklist], flatten: config[:flatten])
49
- rs.trace { yield rs }
50
- rs
51
- ensure
52
- rs.close if rs
41
+ attr_accessor :log_path
42
+
43
+ def mark(message = "")
44
+ state = self.state
45
+ if state == :tracing
46
+ # stop tracing to avoid logging these io method calls
47
+ stop_trace
53
48
  end
49
+ io.write("--- ")
50
+ io.puts(message)
51
+ ensure
52
+ start_trace if state == :tracing
54
53
  end
55
54
 
56
55
  def closed?