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 +7 -0
- data/.clang-format +2 -0
- data/.github/PULL_REQUEST_TEMPLATE +3 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +15 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +215 -0
- data/Rakefile +36 -0
- data/bin/fmt +9 -0
- data/dev.yml +12 -0
- data/ext/rotoscope/callsite.c +78 -0
- data/ext/rotoscope/callsite.h +17 -0
- data/ext/rotoscope/extconf.rb +10 -0
- data/ext/rotoscope/rotoscope.c +391 -0
- data/ext/rotoscope/rotoscope.h +65 -0
- data/ext/rotoscope/stack.c +92 -0
- data/ext/rotoscope/stack.h +29 -0
- data/ext/rotoscope/strmemo.c +33 -0
- data/ext/rotoscope/strmemo.h +14 -0
- data/ext/rotoscope/tracepoint.c +8 -0
- data/ext/rotoscope/tracepoint.h +17 -0
- data/lib/rotoscope.rb +71 -0
- data/lib/uthash/uthash.h +1107 -0
- data/rotoscope.gemspec +23 -0
- data/test/fixture_inner.rb +10 -0
- data/test/fixture_outer.rb +10 -0
- data/test/monadify.rb +16 -0
- data/test/rotoscope_test.rb +482 -0
- metadata +129 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "mkmf"
|
3
|
+
|
4
|
+
$CFLAGS << ' -std=c99 -Wall -Werror -Wno-declaration-after-statement'
|
5
|
+
$defs << "-D_POSIX_SOURCE"
|
6
|
+
|
7
|
+
uthash_path = File.expand_path('../../../lib/uthash', __FILE__)
|
8
|
+
find_header('uthash.h', uthash_path) || raise
|
9
|
+
|
10
|
+
create_makefile('rotoscope/rotoscope')
|
@@ -0,0 +1,391 @@
|
|
1
|
+
#include <errno.h>
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <ruby/debug.h>
|
4
|
+
#include <ruby/intern.h>
|
5
|
+
#include <ruby/version.h>
|
6
|
+
#include <stdbool.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <sys/file.h>
|
9
|
+
|
10
|
+
#include "callsite.h"
|
11
|
+
#include "rotoscope.h"
|
12
|
+
#include "stack.h"
|
13
|
+
#include "strmemo.h"
|
14
|
+
#include "tracepoint.h"
|
15
|
+
|
16
|
+
VALUE cRotoscope, cTracePoint;
|
17
|
+
|
18
|
+
// recursive with singleton2str
|
19
|
+
static rs_class_desc_t class2str(VALUE klass);
|
20
|
+
|
21
|
+
static unsigned long gettid() {
|
22
|
+
return NUM2ULONG(rb_obj_id(rb_thread_current()));
|
23
|
+
}
|
24
|
+
|
25
|
+
static int write_csv_header(FILE *log, const char *header) {
|
26
|
+
return fprintf(log, "%s\n", header);
|
27
|
+
}
|
28
|
+
|
29
|
+
static const char *evflag2name(rb_event_flag_t evflag) {
|
30
|
+
switch (evflag) {
|
31
|
+
case RUBY_EVENT_CALL:
|
32
|
+
case RUBY_EVENT_C_CALL:
|
33
|
+
return "call";
|
34
|
+
case RUBY_EVENT_RETURN:
|
35
|
+
case RUBY_EVENT_C_RETURN:
|
36
|
+
return "return";
|
37
|
+
default:
|
38
|
+
return "unknown";
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
static bool rejected_path(VALUE path, Rotoscope *config) {
|
43
|
+
for (unsigned long i = 0; i < config->blacklist_size; i++) {
|
44
|
+
if (strstr(StringValueCStr(path), config->blacklist[i])) return true;
|
45
|
+
}
|
46
|
+
|
47
|
+
return false;
|
48
|
+
}
|
49
|
+
|
50
|
+
static VALUE class_of_singleton(VALUE klass) {
|
51
|
+
return rb_iv_get(klass, "__attached__");
|
52
|
+
}
|
53
|
+
|
54
|
+
static bool is_class_singleton(VALUE klass) {
|
55
|
+
VALUE obj = class_of_singleton(klass);
|
56
|
+
return (RB_TYPE_P(obj, T_MODULE) || RB_TYPE_P(obj, T_CLASS));
|
57
|
+
}
|
58
|
+
|
59
|
+
static VALUE singleton2str(VALUE klass) {
|
60
|
+
if (is_class_singleton(klass)) {
|
61
|
+
VALUE obj = class_of_singleton(klass);
|
62
|
+
VALUE cached_lookup = rb_class_path_cached(obj);
|
63
|
+
VALUE name = (NIL_P(cached_lookup)) ? rb_class_name(obj) : cached_lookup;
|
64
|
+
return name;
|
65
|
+
} else // singleton of an instance
|
66
|
+
{
|
67
|
+
return singleton2str(CLASS_OF(klass));
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
static rs_class_desc_t class2str(VALUE klass) {
|
72
|
+
rs_class_desc_t real_class;
|
73
|
+
real_class.method_level = INSTANCE_METHOD;
|
74
|
+
|
75
|
+
VALUE cached_lookup = rb_class_path_cached(klass);
|
76
|
+
if (RTEST(cached_lookup)) {
|
77
|
+
real_class.name = cached_lookup;
|
78
|
+
} else {
|
79
|
+
if (FL_TEST(klass, FL_SINGLETON)) {
|
80
|
+
real_class.name = singleton2str(klass);
|
81
|
+
if (is_class_singleton(klass)) {
|
82
|
+
real_class.method_level = CLASS_METHOD;
|
83
|
+
}
|
84
|
+
} else {
|
85
|
+
real_class.name = rb_class_path(klass);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
return real_class;
|
90
|
+
}
|
91
|
+
|
92
|
+
static rs_callsite_t tracearg_path(rb_trace_arg_t *trace_arg) {
|
93
|
+
switch (rb_tracearg_event_flag(trace_arg)) {
|
94
|
+
case RUBY_EVENT_C_RETURN:
|
95
|
+
case RUBY_EVENT_C_CALL:
|
96
|
+
return c_callsite(trace_arg);
|
97
|
+
default:
|
98
|
+
return ruby_callsite(trace_arg);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
static rs_class_desc_t tracearg_class(rb_trace_arg_t *trace_arg) {
|
103
|
+
VALUE klass;
|
104
|
+
VALUE self = rb_tracearg_self(trace_arg);
|
105
|
+
|
106
|
+
if (RB_TYPE_P(self, T_MODULE) || RB_TYPE_P(self, T_OBJECT)) {
|
107
|
+
klass = CLASS_OF(self);
|
108
|
+
} else if (RB_TYPE_P(self, T_CLASS)) {
|
109
|
+
// Does the object have an attached singleton?
|
110
|
+
// If not, name based on self instead of its singleton
|
111
|
+
klass = (FL_TEST(CLASS_OF(self), FL_SINGLETON)) ? CLASS_OF(self) : self;
|
112
|
+
} else {
|
113
|
+
klass = rb_tracearg_defined_class(trace_arg);
|
114
|
+
}
|
115
|
+
|
116
|
+
return class2str(klass);
|
117
|
+
}
|
118
|
+
|
119
|
+
static VALUE tracearg_method_name(rb_trace_arg_t *trace_arg) {
|
120
|
+
return rb_sym2str(rb_tracearg_method_id(trace_arg));
|
121
|
+
}
|
122
|
+
|
123
|
+
static rs_tracepoint_t extract_full_tracevals(rb_trace_arg_t *trace_arg,
|
124
|
+
const rs_callsite_t *callsite) {
|
125
|
+
rs_class_desc_t method_owner = tracearg_class(trace_arg);
|
126
|
+
rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
|
127
|
+
|
128
|
+
VALUE method_name = tracearg_method_name(trace_arg);
|
129
|
+
VALUE filepath = callsite->filepath;
|
130
|
+
|
131
|
+
return (rs_tracepoint_t){.event = evflag2name(event_flag),
|
132
|
+
.entity = method_owner.name,
|
133
|
+
.filepath = filepath,
|
134
|
+
.method_name = method_name,
|
135
|
+
.method_level = method_owner.method_level,
|
136
|
+
.lineno = callsite->lineno};
|
137
|
+
}
|
138
|
+
|
139
|
+
static bool in_fork(Rotoscope *config) { return config->pid != getpid(); }
|
140
|
+
|
141
|
+
static bool tracecmp(rs_tracepoint_t *a, rs_tracepoint_t *b) {
|
142
|
+
return (!rb_str_cmp(a->method_name, b->method_name) &&
|
143
|
+
!rb_str_cmp(a->entity, b->entity) &&
|
144
|
+
a->method_level == b->method_level);
|
145
|
+
}
|
146
|
+
|
147
|
+
static void log_raw_trace(FILE *stream, rs_tracepoint_t trace) {
|
148
|
+
fprintf(stream, RS_CSV_FORMAT "\n", RS_CSV_VALUES(trace));
|
149
|
+
}
|
150
|
+
|
151
|
+
unsigned char output_buffer[500];
|
152
|
+
static void log_stack_frame(FILE *stream, rs_stack_t *stack,
|
153
|
+
rs_strmemo_t **call_memo, rs_tracepoint_t trace,
|
154
|
+
rb_event_flag_t event) {
|
155
|
+
if (event & EVENT_CALL) {
|
156
|
+
rs_stack_frame_t frame = rs_stack_push(stack, trace);
|
157
|
+
sprintf((char *)output_buffer, RS_FLATTENED_CSV_FORMAT "\n",
|
158
|
+
RS_FLATTENED_CSV_VALUES(frame));
|
159
|
+
|
160
|
+
if (rs_strmemo_uniq(call_memo, output_buffer)) {
|
161
|
+
fputs((char *)output_buffer, stream);
|
162
|
+
}
|
163
|
+
} else if (event & EVENT_RETURN) {
|
164
|
+
if (tracecmp(&trace, &rs_stack_peek(stack)->tp)) rs_stack_pop(stack);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
static void event_hook(VALUE tpval, void *data) {
|
169
|
+
Rotoscope *config = (Rotoscope *)data;
|
170
|
+
|
171
|
+
if (config->tid != gettid()) return;
|
172
|
+
if (in_fork(config)) {
|
173
|
+
rb_tracepoint_disable(config->tracepoint);
|
174
|
+
config->state = RS_OPEN;
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
|
178
|
+
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval);
|
179
|
+
rs_callsite_t trace_path = tracearg_path(trace_arg);
|
180
|
+
|
181
|
+
if (rejected_path(trace_path.filepath, config)) return;
|
182
|
+
|
183
|
+
rs_tracepoint_t trace = extract_full_tracevals(trace_arg, &trace_path);
|
184
|
+
if (!strcmp("Rotoscope", StringValueCStr(trace.entity))) return;
|
185
|
+
|
186
|
+
if (config->flatten_output) {
|
187
|
+
rb_event_flag_t event_flag = rb_tracearg_event_flag(trace_arg);
|
188
|
+
log_stack_frame(config->log, &config->stack, &config->call_memo, trace,
|
189
|
+
event_flag);
|
190
|
+
} else {
|
191
|
+
log_raw_trace(config->log, trace);
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
195
|
+
static void close_log_handle(Rotoscope *config) {
|
196
|
+
if (config->log) {
|
197
|
+
// Stop tracing so the event hook isn't called with a NULL log
|
198
|
+
if (config->state == RS_TRACING) {
|
199
|
+
// During process cleanup, event hooks are removed and tracepoint may have
|
200
|
+
// already have been GCed, so we need a sanity check before disabling the
|
201
|
+
// tracepoint.
|
202
|
+
if (RB_TYPE_P(config->tracepoint, T_DATA) &&
|
203
|
+
CLASS_OF(config->tracepoint) == cTracePoint) {
|
204
|
+
rb_tracepoint_disable(config->tracepoint);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
if (in_fork(config)) {
|
209
|
+
close(fileno(config->log));
|
210
|
+
} else {
|
211
|
+
fclose(config->log);
|
212
|
+
}
|
213
|
+
config->log = NULL;
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
static void rs_gc_mark(Rotoscope *config) {
|
218
|
+
rb_gc_mark(config->log_path);
|
219
|
+
rb_gc_mark(config->tracepoint);
|
220
|
+
rs_stack_mark(&config->stack);
|
221
|
+
}
|
222
|
+
|
223
|
+
void rs_dealloc(Rotoscope *config) {
|
224
|
+
close_log_handle(config);
|
225
|
+
rs_stack_free(&config->stack);
|
226
|
+
rs_strmemo_free(config->call_memo);
|
227
|
+
xfree(config->blacklist);
|
228
|
+
xfree(config);
|
229
|
+
}
|
230
|
+
|
231
|
+
static VALUE rs_alloc(VALUE klass) {
|
232
|
+
Rotoscope *config;
|
233
|
+
VALUE self =
|
234
|
+
Data_Make_Struct(klass, Rotoscope, rs_gc_mark, rs_dealloc, config);
|
235
|
+
config->log_path = Qnil;
|
236
|
+
config->tracepoint = rb_tracepoint_new(Qnil, EVENT_CALL | EVENT_RETURN,
|
237
|
+
event_hook, (void *)config);
|
238
|
+
config->pid = getpid();
|
239
|
+
config->tid = gettid();
|
240
|
+
return self;
|
241
|
+
}
|
242
|
+
|
243
|
+
static Rotoscope *get_config(VALUE self) {
|
244
|
+
Rotoscope *config;
|
245
|
+
Data_Get_Struct(self, Rotoscope, config);
|
246
|
+
return config;
|
247
|
+
}
|
248
|
+
|
249
|
+
void copy_blacklist(Rotoscope *config, VALUE blacklist) {
|
250
|
+
Check_Type(blacklist, T_ARRAY);
|
251
|
+
|
252
|
+
size_t blacklist_malloc_size =
|
253
|
+
RARRAY_LEN(blacklist) * sizeof(*config->blacklist);
|
254
|
+
|
255
|
+
for (long i = 0; i < RARRAY_LEN(blacklist); i++) {
|
256
|
+
VALUE ruby_string = RARRAY_AREF(blacklist, i);
|
257
|
+
Check_Type(ruby_string, T_STRING);
|
258
|
+
blacklist_malloc_size += RSTRING_LEN(ruby_string) + 1;
|
259
|
+
}
|
260
|
+
|
261
|
+
config->blacklist = ruby_xmalloc(blacklist_malloc_size);
|
262
|
+
config->blacklist_size = RARRAY_LEN(blacklist);
|
263
|
+
char *str = (char *)(config->blacklist + config->blacklist_size);
|
264
|
+
|
265
|
+
for (unsigned long i = 0; i < config->blacklist_size; i++) {
|
266
|
+
VALUE ruby_string = RARRAY_AREF(blacklist, i);
|
267
|
+
|
268
|
+
config->blacklist[i] = str;
|
269
|
+
memcpy(str, RSTRING_PTR(ruby_string), RSTRING_LEN(ruby_string));
|
270
|
+
str += RSTRING_LEN(ruby_string);
|
271
|
+
*str = '\0';
|
272
|
+
str++;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
VALUE initialize(int argc, VALUE *argv, VALUE self) {
|
277
|
+
Rotoscope *config = get_config(self);
|
278
|
+
VALUE output_path, blacklist, flatten;
|
279
|
+
|
280
|
+
rb_scan_args(argc, argv, "12", &output_path, &blacklist, &flatten);
|
281
|
+
Check_Type(output_path, T_STRING);
|
282
|
+
|
283
|
+
if (!NIL_P(blacklist)) {
|
284
|
+
copy_blacklist(config, blacklist);
|
285
|
+
}
|
286
|
+
|
287
|
+
config->flatten_output = RTEST(flatten);
|
288
|
+
config->log_path = output_path;
|
289
|
+
config->log = fopen(StringValueCStr(config->log_path), "w");
|
290
|
+
|
291
|
+
if (config->log == NULL) {
|
292
|
+
fprintf(stderr, "\nERROR: Failed to open file handle at %s (%s)\n",
|
293
|
+
StringValueCStr(config->log_path), strerror(errno));
|
294
|
+
exit(1);
|
295
|
+
}
|
296
|
+
|
297
|
+
if (config->flatten_output)
|
298
|
+
write_csv_header(config->log, RS_FLATTENED_CSV_HEADER);
|
299
|
+
else
|
300
|
+
write_csv_header(config->log, RS_CSV_HEADER);
|
301
|
+
|
302
|
+
rs_stack_init(&config->stack, STACK_CAPACITY);
|
303
|
+
config->call_memo = NULL;
|
304
|
+
config->state = RS_OPEN;
|
305
|
+
return self;
|
306
|
+
}
|
307
|
+
|
308
|
+
VALUE rotoscope_start_trace(VALUE self) {
|
309
|
+
Rotoscope *config = get_config(self);
|
310
|
+
rb_tracepoint_enable(config->tracepoint);
|
311
|
+
config->state = RS_TRACING;
|
312
|
+
return Qnil;
|
313
|
+
}
|
314
|
+
|
315
|
+
VALUE rotoscope_stop_trace(VALUE self) {
|
316
|
+
Rotoscope *config = get_config(self);
|
317
|
+
if (rb_tracepoint_enabled_p(config->tracepoint)) {
|
318
|
+
rb_tracepoint_disable(config->tracepoint);
|
319
|
+
config->state = RS_OPEN;
|
320
|
+
}
|
321
|
+
|
322
|
+
return Qnil;
|
323
|
+
}
|
324
|
+
|
325
|
+
VALUE rotoscope_log_path(VALUE self) {
|
326
|
+
Rotoscope *config = get_config(self);
|
327
|
+
return config->log_path;
|
328
|
+
}
|
329
|
+
|
330
|
+
VALUE rotoscope_mark(int argc, VALUE *argv, VALUE self) {
|
331
|
+
VALUE str;
|
332
|
+
rb_scan_args(argc, argv, "01", &str);
|
333
|
+
|
334
|
+
if (NIL_P(str)) str = rb_str_new2("");
|
335
|
+
Check_Type(str, T_STRING);
|
336
|
+
|
337
|
+
Rotoscope *config = get_config(self);
|
338
|
+
if (config->log != NULL && !in_fork(config)) {
|
339
|
+
rs_stack_reset(&config->stack, STACK_CAPACITY);
|
340
|
+
rs_strmemo_free(config->call_memo);
|
341
|
+
fprintf(config->log, "--- %s\n", StringValueCStr(str));
|
342
|
+
}
|
343
|
+
return Qnil;
|
344
|
+
}
|
345
|
+
|
346
|
+
VALUE rotoscope_close(VALUE self) {
|
347
|
+
Rotoscope *config = get_config(self);
|
348
|
+
if (config->state == RS_CLOSED) {
|
349
|
+
return Qtrue;
|
350
|
+
}
|
351
|
+
close_log_handle(config);
|
352
|
+
config->state = RS_CLOSED;
|
353
|
+
return Qtrue;
|
354
|
+
}
|
355
|
+
|
356
|
+
VALUE rotoscope_trace(VALUE self) {
|
357
|
+
rotoscope_start_trace(self);
|
358
|
+
return rb_ensure(rb_yield, Qundef, rotoscope_stop_trace, self);
|
359
|
+
}
|
360
|
+
|
361
|
+
VALUE rotoscope_state(VALUE self) {
|
362
|
+
Rotoscope *config = get_config(self);
|
363
|
+
switch (config->state) {
|
364
|
+
case RS_OPEN:
|
365
|
+
return ID2SYM(rb_intern("open"));
|
366
|
+
case RS_TRACING:
|
367
|
+
return ID2SYM(rb_intern("tracing"));
|
368
|
+
default:
|
369
|
+
return ID2SYM(rb_intern("closed"));
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
void Init_rotoscope(void) {
|
374
|
+
cTracePoint = rb_const_get(rb_cObject, rb_intern("TracePoint"));
|
375
|
+
|
376
|
+
cRotoscope = rb_define_class("Rotoscope", rb_cObject);
|
377
|
+
rb_define_alloc_func(cRotoscope, rs_alloc);
|
378
|
+
rb_define_method(cRotoscope, "initialize", initialize, -1);
|
379
|
+
rb_define_method(cRotoscope, "trace", (VALUE(*)(ANYARGS))rotoscope_trace, 0);
|
380
|
+
rb_define_method(cRotoscope, "mark", (VALUE(*)(ANYARGS))rotoscope_mark, -1);
|
381
|
+
rb_define_method(cRotoscope, "close", (VALUE(*)(ANYARGS))rotoscope_close, 0);
|
382
|
+
rb_define_method(cRotoscope, "start_trace",
|
383
|
+
(VALUE(*)(ANYARGS))rotoscope_start_trace, 0);
|
384
|
+
rb_define_method(cRotoscope, "stop_trace",
|
385
|
+
(VALUE(*)(ANYARGS))rotoscope_stop_trace, 0);
|
386
|
+
rb_define_method(cRotoscope, "log_path",
|
387
|
+
(VALUE(*)(ANYARGS))rotoscope_log_path, 0);
|
388
|
+
rb_define_method(cRotoscope, "state", (VALUE(*)(ANYARGS))rotoscope_state, 0);
|
389
|
+
|
390
|
+
init_callsite();
|
391
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#ifndef _INC_ROTOSCOPE_H_
|
2
|
+
#define _INC_ROTOSCOPE_H_
|
3
|
+
|
4
|
+
#include <unistd.h>
|
5
|
+
#include "stack.h"
|
6
|
+
#include "strmemo.h"
|
7
|
+
|
8
|
+
#define EVENT_CALL (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL)
|
9
|
+
#define EVENT_RETURN (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN)
|
10
|
+
|
11
|
+
#define CLASS_METHOD "class"
|
12
|
+
#define INSTANCE_METHOD "instance"
|
13
|
+
|
14
|
+
#define STACK_CAPACITY 500
|
15
|
+
|
16
|
+
// clang-format off
|
17
|
+
#define _RS_COMMON_CSV_HEADER "entity,method_name,method_level,filepath,lineno"
|
18
|
+
#define _RS_COMMON_CSV_FORMAT "\"%s\",\"%s\",%s,\"%s\",%d"
|
19
|
+
#define _RS_COMMON_CSV_VALUES(trace) \
|
20
|
+
StringValueCStr(trace.entity), \
|
21
|
+
StringValueCStr(trace.method_name), \
|
22
|
+
trace.method_level, \
|
23
|
+
StringValueCStr(trace.filepath), \
|
24
|
+
trace.lineno
|
25
|
+
|
26
|
+
#define RS_CSV_HEADER "event," _RS_COMMON_CSV_HEADER
|
27
|
+
#define RS_CSV_FORMAT "%s," _RS_COMMON_CSV_FORMAT
|
28
|
+
#define RS_CSV_VALUES(trace) trace.event, _RS_COMMON_CSV_VALUES(trace)
|
29
|
+
|
30
|
+
#define RS_FLATTENED_CSV_HEADER \
|
31
|
+
_RS_COMMON_CSV_HEADER ",caller_entity,caller_method_name,caller_method_level"
|
32
|
+
#define RS_FLATTENED_CSV_FORMAT _RS_COMMON_CSV_FORMAT ",\"%s\",\"%s\",%s"
|
33
|
+
#define RS_FLATTENED_CSV_VALUES(frame) \
|
34
|
+
_RS_COMMON_CSV_VALUES(frame.tp), \
|
35
|
+
StringValueCStr(frame.caller->tp.entity), \
|
36
|
+
StringValueCStr(frame.caller->tp.method_name), \
|
37
|
+
frame.caller->tp.method_level
|
38
|
+
// clang-format on
|
39
|
+
|
40
|
+
typedef enum {
|
41
|
+
RS_CLOSED = 0,
|
42
|
+
RS_OPEN,
|
43
|
+
RS_TRACING,
|
44
|
+
} rs_state;
|
45
|
+
|
46
|
+
typedef struct {
|
47
|
+
FILE *log;
|
48
|
+
VALUE log_path;
|
49
|
+
VALUE tracepoint;
|
50
|
+
const char **blacklist;
|
51
|
+
unsigned long blacklist_size;
|
52
|
+
bool flatten_output;
|
53
|
+
pid_t pid;
|
54
|
+
unsigned long tid;
|
55
|
+
rs_state state;
|
56
|
+
rs_stack_t stack;
|
57
|
+
rs_strmemo_t *call_memo;
|
58
|
+
} Rotoscope;
|
59
|
+
|
60
|
+
typedef struct {
|
61
|
+
VALUE name;
|
62
|
+
const char *method_level;
|
63
|
+
} rs_class_desc_t;
|
64
|
+
|
65
|
+
#endif
|