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

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
  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?