appoptics_apm 4.12.2 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build_and_release_gem.yml +103 -0
  3. data/.github/workflows/build_for_packagecloud.yml +70 -0
  4. data/.github/workflows/docker-images.yml +47 -0
  5. data/.github/workflows/run_cpluplus_tests.yml +73 -0
  6. data/.github/workflows/run_tests.yml +168 -0
  7. data/.github/workflows/scripts/test_install.rb +23 -0
  8. data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
  9. data/.github/workflows/test_on_4_linux.yml +159 -0
  10. data/.gitignore +17 -25
  11. data/.travis.yml +17 -14
  12. data/Gemfile +1 -25
  13. data/README.md +4 -6
  14. data/appoptics_apm.gemspec +11 -5
  15. data/examples/prepend.rb +13 -0
  16. data/examples/sdk_examples.rb +16 -0
  17. data/ext/oboe_metal/extconf.rb +25 -31
  18. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
  19. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
  20. data/ext/oboe_metal/src/README.md +6 -0
  21. data/ext/oboe_metal/src/VERSION +2 -1
  22. data/ext/oboe_metal/src/frames.cc +246 -0
  23. data/ext/oboe_metal/src/frames.h +40 -0
  24. data/ext/oboe_metal/src/init_appoptics_apm.cc +5 -4
  25. data/ext/oboe_metal/src/logging.cc +95 -0
  26. data/ext/oboe_metal/src/logging.h +35 -0
  27. data/ext/oboe_metal/src/oboe.h +8 -5
  28. data/ext/oboe_metal/src/oboe_api.cpp +40 -14
  29. data/ext/oboe_metal/src/oboe_api.hpp +29 -8
  30. data/ext/oboe_metal/src/oboe_debug.h +1 -0
  31. data/ext/oboe_metal/src/oboe_swig_wrap.cc +85 -21
  32. data/ext/oboe_metal/src/profiling.cc +435 -0
  33. data/ext/oboe_metal/src/profiling.h +78 -0
  34. data/ext/oboe_metal/test/CMakeLists.txt +53 -0
  35. data/ext/oboe_metal/test/FindGMock.cmake +43 -0
  36. data/ext/oboe_metal/test/README.md +56 -0
  37. data/ext/oboe_metal/test/frames_test.cc +164 -0
  38. data/ext/oboe_metal/test/profiling_test.cc +93 -0
  39. data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
  40. data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
  41. data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
  42. data/ext/oboe_metal/test/test.h +11 -0
  43. data/ext/oboe_metal/test/test_main.cc +32 -0
  44. data/lib/appoptics_apm/api/metrics.rb +3 -0
  45. data/lib/appoptics_apm/base.rb +1 -1
  46. data/lib/appoptics_apm/config.rb +11 -2
  47. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +7 -1
  48. data/lib/appoptics_apm/inst/rack.rb +13 -6
  49. data/lib/appoptics_apm/inst/redis.rb +1 -2
  50. data/lib/appoptics_apm/noop/context.rb +3 -0
  51. data/lib/appoptics_apm/noop/metadata.rb +4 -1
  52. data/lib/appoptics_apm/noop/profiling.rb +21 -0
  53. data/lib/appoptics_apm/oboe_init_options.rb +26 -22
  54. data/lib/appoptics_apm/support/profiling.rb +18 -0
  55. data/lib/appoptics_apm/support/transaction_metrics.rb +1 -1
  56. data/lib/appoptics_apm/support/transaction_settings.rb +2 -2
  57. data/lib/appoptics_apm/support/x_trace_options.rb +2 -2
  58. data/lib/appoptics_apm/support_report.rb +2 -2
  59. data/lib/appoptics_apm/test.rb +4 -3
  60. data/lib/appoptics_apm/util.rb +1 -1
  61. data/lib/appoptics_apm/version.rb +3 -3
  62. data/lib/appoptics_apm/xtrace.rb +1 -1
  63. data/lib/appoptics_apm.rb +3 -1
  64. data/lib/oboe_metal.rb +2 -2
  65. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +24 -0
  66. data/log/.keep +0 -0
  67. metadata +46 -16
  68. data/.travis/bundle.sh +0 -9
@@ -0,0 +1,435 @@
1
+ // Copyright (c) 2021 SolarWinds, LLC.
2
+ // All rights reserved.
3
+
4
+ #include "profiling.h"
5
+
6
+ #include <ruby/debug.h>
7
+ #include <signal.h>
8
+ #include <time.h>
9
+
10
+ #include <atomic>
11
+ #include <unordered_map>
12
+ #include <vector>
13
+
14
+ #include "frames.h"
15
+ #include "logging.h"
16
+ #include "oboe_api.hpp"
17
+
18
+
19
+ #define TIMER_SIG SIGRTMAX // the timer notification signal
20
+
21
+ using namespace std;
22
+
23
+ static atomic_bool running;
24
+ atomic_bool profiling_shut_down; // !! can't be static because of tests
25
+
26
+ // need to initialize here, hangs if it is done inside the signal handler
27
+ // these are reused for every snapshot
28
+ static VALUE frames_buffer[BUF_SIZE];
29
+ static int lines_buffer[BUF_SIZE];
30
+
31
+
32
+ static long configured_interval = 10; // in milliseconds, initializing in case Ruby forgets to
33
+ static long current_interval = 10;
34
+ timer_t timerid;
35
+
36
+ typedef struct prof_data {
37
+ bool running_p = false;
38
+ Metadata md = Metadata(Context::get());
39
+ string prof_op_id;
40
+
41
+ VALUE prev_frames_buffer[BUF_SIZE];
42
+ int prev_num = 0;
43
+ long omitted[BUF_SIZE];
44
+ int omitted_num = 0;
45
+ } prof_data_t;
46
+
47
+ unordered_map<pid_t, prof_data_t> prof_data_map;
48
+
49
+ const string Profiling::string_job_handler = "Profiling::profiler_job_handler()";
50
+ const string Profiling::string_gc_handler = "Profiling::profiler_gc_handler()";
51
+ const string Profiling::string_signal_handler = "Profiling::profiler_signal_handler()";
52
+ const string Profiling::string_stop = "Profiling::profiling_stop()";
53
+
54
+ // for debugging only
55
+ void print_prof_data_map() {
56
+ pid_t tid = AO_GETTID;
57
+ Metadata md_str(prof_data_map[tid].md);
58
+ cout << tid << ", " << prof_data_map[tid].running_p << ", " << prof_data_map[tid].prof_op_id << ", ";
59
+ cout << md_str.toString() << ", " << prof_data_map[tid].prev_num << ", " << prof_data_map[tid].omitted_num << endl;
60
+ }
61
+
62
+ long ts_now() {
63
+ struct timeval tv;
64
+
65
+ oboe_gettimeofday(&tv);
66
+ return (long)tv.tv_sec * 1000000 + (long)tv.tv_usec;
67
+ }
68
+
69
+ // try catch block to be used inside functions that return an int
70
+ // shuts down profiling and returns -1 on error
71
+ int Profiling::try_catch_shutdown(std::function<int()> f, const string& fun_name) {
72
+ try {
73
+ return f();
74
+ } catch (const std::exception &e) {
75
+ string msg = "Exception in " + fun_name + ", can't recover, profiling shutting down";
76
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, e.what());
77
+ OBOE_DEBUG_LOG_HIGH(OBOE_MODULE_RUBY, msg.c_str());
78
+ Profiling::shut_down();
79
+ return -1;
80
+ } catch (...) {
81
+ string msg = "Exception in " + fun_name + ", can't recover, profiling shutting down";
82
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, msg.c_str());
83
+ Profiling::shut_down();
84
+ return -1;
85
+ }
86
+ }
87
+
88
+ void Profiling::profiler_record_frames() {
89
+ pid_t tid = AO_GETTID;
90
+ long ts = ts_now();
91
+
92
+ // check if this thread is being profiled
93
+ if (prof_data_map[tid].running_p) {
94
+ // executes in the same thread as rb_postponed_job was called from
95
+
96
+ // get the frames
97
+ // won't overrun frames buffer, because size is set in arg 2
98
+ int num = rb_profile_frames(0, sizeof(frames_buffer) / sizeof(VALUE), frames_buffer, lines_buffer);
99
+
100
+ Profiling::process_snapshot(frames_buffer, num, tid, ts);
101
+ }
102
+
103
+ // add this timestamp as omitted to other running threads that are profiled
104
+ for (pair<const pid_t, prof_data_t> &ele : prof_data_map) {
105
+ if (ele.second.running_p && ele.first != tid) {
106
+ frames_buffer[0] = PR_OTHER_THREAD;
107
+ Profiling::process_snapshot(frames_buffer, 1, ele.first, ts);
108
+ }
109
+ }
110
+ }
111
+
112
+ void Profiling::profiler_record_gc() {
113
+ pid_t tid = AO_GETTID;
114
+ long ts = ts_now();
115
+
116
+ // check if this thread is being profiled
117
+ if (prof_data_map[tid].running_p) {
118
+ frames_buffer[0] = PR_IN_GC;
119
+ Profiling::process_snapshot(frames_buffer, 1, tid, ts);
120
+ }
121
+
122
+ // add this timestamp as omitted to other running threads that are profiled
123
+ for (pair<const pid_t, prof_data_t> &ele : prof_data_map) {
124
+ if (ele.second.running_p && ele.first != tid) {
125
+ frames_buffer[0] = PR_OTHER_THREAD;
126
+ Profiling::process_snapshot(frames_buffer, 1, ele.first, ts);
127
+ }
128
+ }
129
+ }
130
+
131
+ void Profiling::send_omitted(pid_t tid, long ts) {
132
+ static vector<FrameData> empty;
133
+ Logging::log_profile_snapshot(prof_data_map[tid].md,
134
+ prof_data_map[tid].prof_op_id,
135
+ ts, // timestamp
136
+ empty, // <vector> new frames
137
+ 0, // number of exited frames
138
+ prof_data_map[tid].prev_num, // total number of frames
139
+ prof_data_map[tid].omitted, // array of timestamps of omitted snapshots
140
+ prof_data_map[tid].omitted_num, // number of omitted snapshots
141
+ tid); // thread id
142
+
143
+ prof_data_map[tid].omitted_num = 0;
144
+ }
145
+
146
+ void Profiling::process_snapshot(VALUE *frames_buffer, int num, pid_t tid, long ts) {
147
+ int num_new = 0;
148
+ int num_exited = 0;
149
+ vector<FrameData> new_frames;
150
+
151
+ num = Frames::remove_garbage(frames_buffer, num);
152
+
153
+ // find the number of matching frames from the top
154
+ int num_match = Frames::num_matching(frames_buffer,
155
+ num,
156
+ prof_data_map[tid].prev_frames_buffer,
157
+ prof_data_map[tid].prev_num);
158
+ num_new = num - num_match;
159
+ num_exited = prof_data_map[tid].prev_num - num_match;
160
+
161
+ if (num_new == 0 && num_exited == 0) {
162
+ prof_data_map[tid].omitted[prof_data_map[tid].omitted_num] = ts;
163
+ prof_data_map[tid].omitted_num++;
164
+
165
+ // the omitted buffer can fill up if the interval is small
166
+ // and the stack doesn't change
167
+ // We need to send a profiling event with the timestamps when it is full
168
+ if (prof_data_map[tid].omitted_num >= BUF_SIZE) {
169
+ Profiling::send_omitted(tid, ts);
170
+ }
171
+ return;
172
+ }
173
+
174
+ Frames::collect_frame_data(frames_buffer, num_new, new_frames);
175
+
176
+ Logging::log_profile_snapshot(prof_data_map[tid].md,
177
+ prof_data_map[tid].prof_op_id,
178
+ ts, // timestamp
179
+ new_frames, // <vector> new frames
180
+ num_exited, // number of exited frames
181
+ num, // total number of frames
182
+ prof_data_map[tid].omitted, // array of timestamps of omitted snapshots
183
+ prof_data_map[tid].omitted_num, // number of omitted snapshots
184
+ tid); // thread id
185
+
186
+ prof_data_map[tid].omitted_num = 0;
187
+ prof_data_map[tid].prev_num = num;
188
+ for (int i = 0; i < num; ++i)
189
+ prof_data_map[tid].prev_frames_buffer[i] = frames_buffer[i];
190
+ }
191
+
192
+ void Profiling::profiler_job_handler(void *data) {
193
+ static atomic_bool in_job_handler{false};
194
+
195
+ // atomically replaces the value of the object, returns the value held previously
196
+ if (in_job_handler.exchange(true)) return;
197
+
198
+ try_catch_shutdown([&]() {
199
+ Profiling::profiler_record_frames();
200
+ return 0; // block needs an int returned
201
+ }, Profiling::string_job_handler);
202
+
203
+ in_job_handler = false;
204
+ }
205
+
206
+ void Profiling::profiler_gc_handler(void *data) {
207
+ static atomic_bool in_gc_handler{false};
208
+
209
+ // atomically replaces the value of the object, returns the value held previously
210
+ if (in_gc_handler.exchange(true)) return;
211
+
212
+ try_catch_shutdown([]() {
213
+ Profiling::profiler_record_gc();
214
+ return 0; // block needs an int returned
215
+ }, Profiling::string_gc_handler);
216
+
217
+ in_gc_handler = false;
218
+ }
219
+
220
+ ////////////////////////////////////////////////////////////////////////////////
221
+ // THIS IS THE SIGNAL HANDLER FUNCTION
222
+ // ONLY ASYNC-SAFE FUNCTIONS ALLOWED IN HERE (no exception handling !!!)
223
+ ////////////////////////////////////////////////////////////////////////////////
224
+ extern "C" void profiler_signal_handler(int sigint, siginfo_t *siginfo, void *ucontext) {
225
+ if (!ruby_native_thread_p()) return;
226
+ static std::atomic_bool in_signal_handler{false};
227
+
228
+ // atomically replaces the value of the object, returns the value held previously
229
+ // also keeps in_signal_handler lock_free -> async-safe
230
+ if (in_signal_handler.exchange(true)) return;
231
+
232
+ // the following two ruby c-functions are async safe
233
+ if (rb_during_gc())
234
+ {
235
+ rb_postponed_job_register(0, Profiling::profiler_gc_handler, (void *)0);
236
+ } else {
237
+ rb_postponed_job_register(0, Profiling::profiler_job_handler, (void *)0);
238
+ }
239
+
240
+ in_signal_handler = false;
241
+ }
242
+
243
+ void Profiling::profiling_start(pid_t tid) {
244
+ prof_data_map[tid].md = Metadata(Context::get());
245
+ prof_data_map[tid].prev_num = 0;
246
+ prof_data_map[tid].omitted_num = 0;
247
+ prof_data_map[tid].running_p = true;
248
+
249
+ Logging::log_profile_entry(prof_data_map[tid].md,
250
+ prof_data_map[tid].prof_op_id,
251
+ tid,
252
+ current_interval);
253
+
254
+ if (!running.exchange(true)) {
255
+ // start timer with interval timer spec
256
+ struct itimerspec ts;
257
+ ts.it_interval.tv_sec = 0;
258
+ ts.it_interval.tv_nsec = current_interval * 1000000;
259
+ ts.it_value.tv_sec = 0;
260
+ ts.it_value.tv_nsec = ts.it_interval.tv_nsec;
261
+
262
+ // global timer_t timerid points to timer created in Init_profiling
263
+ if (timer_settime(timerid, 0, &ts, NULL) == -1) {
264
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, "timer_settime() failed");
265
+ shut_down();
266
+ }
267
+ }
268
+ }
269
+
270
+ VALUE Profiling::profiling_stop(pid_t tid) {
271
+ if (!running.exchange(false)) return Qfalse;
272
+
273
+ int result = try_catch_shutdown([&]() {
274
+ // stop the timer, needs both (value and interval) set to 0
275
+ struct itimerspec ts;
276
+ ts.it_value.tv_sec = 0;
277
+ ts.it_value.tv_nsec = 0;
278
+ ts.it_interval.tv_sec = 0;
279
+ ts.it_interval.tv_nsec = 0;
280
+
281
+ if (timer_settime(timerid, 0, &ts, NULL) == -1) {
282
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, "timer_settime() failed");
283
+ shut_down();
284
+ }
285
+
286
+ Logging::log_profile_exit(prof_data_map[tid].md,
287
+ prof_data_map[tid].prof_op_id,
288
+ tid,
289
+ prof_data_map[tid].omitted,
290
+ prof_data_map[tid].omitted_num);
291
+
292
+ prof_data_map[tid].running_p = false;
293
+ return 0; // block needs an int returned
294
+ }, Profiling::string_stop);
295
+
296
+ return (result == 0) ? Qtrue : Qfalse;
297
+ }
298
+
299
+ VALUE Profiling::set_interval(VALUE self, VALUE val) {
300
+ if (!FIXNUM_P(val)) return Qfalse;
301
+
302
+ configured_interval = FIX2INT(val);
303
+
304
+ return INT2FIX(configured_interval);
305
+ }
306
+
307
+ VALUE Profiling::get_interval() {
308
+ return INT2FIX(current_interval);
309
+ }
310
+
311
+ VALUE Profiling::profiling_run(VALUE self, VALUE rb_thread_val, VALUE interval) {
312
+ rb_need_block(); // checks if function is called with a block in Ruby
313
+ if (profiling_shut_down || OboeProfiling::get_interval() == 0) {
314
+ return rb_yield(Qundef);
315
+ }
316
+
317
+ if (FIXNUM_P(interval)) configured_interval = FIX2INT(interval);
318
+ current_interval = max(configured_interval, (long)OboeProfiling::get_interval());
319
+
320
+ // !!!!! Can't use try_catch_shutdown() here, MAKES rb_ensure cause a memory leak !!!!!
321
+ try {
322
+ pid_t tid = AO_GETTID;
323
+ profiling_start(tid);
324
+ rb_ensure(reinterpret_cast<VALUE (*)(...)>(rb_yield), Qundef,
325
+ reinterpret_cast<VALUE (*)(...)>(profiling_stop), tid);
326
+ return Qtrue;
327
+ } catch (const std::exception &e) {
328
+ string msg = "Exception in Profiling::profiling_run(), can't recover, profiling shutting down";
329
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, e.what());
330
+ OBOE_DEBUG_LOG_HIGH(OBOE_MODULE_RUBY, msg.c_str());
331
+ shut_down();
332
+ return Qfalse;
333
+ } catch (...) {
334
+ string msg = "Exception in Profiling::profiling_run(), can't recover, profiling shutting down";
335
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, msg.c_str());
336
+ shut_down();
337
+ return Qfalse;
338
+ }
339
+
340
+ return Qfalse;
341
+ }
342
+
343
+ // in case C++ misbehaves we will stop profiling
344
+ // to be used when catching exceptions
345
+ void Profiling::shut_down() {
346
+ static atomic_bool ending{false};
347
+
348
+ if (ending.exchange(true)) return;
349
+
350
+ // avoid running any more profiling
351
+ profiling_shut_down = true;
352
+
353
+ // stop all profiling, the last one also stops the timer/signals
354
+ for (pair<const pid_t, prof_data_t> &ele : prof_data_map) {
355
+ profiling_stop(ele.first);
356
+ }
357
+ }
358
+
359
+ VALUE Profiling::getTid() {
360
+ pid_t tid = AO_GETTID;
361
+
362
+ return INT2NUM(tid);
363
+ }
364
+
365
+ static void
366
+ prof_atfork_prepare(void) {
367
+ // cout << "Parent getting ready" << endl;
368
+ }
369
+
370
+ static void
371
+ prof_atfork_parent(void) {
372
+ // cout << "Parent let child loose" << endl;
373
+ }
374
+
375
+ // make sure new processes have a clean slate for profiling
376
+ static void
377
+ prof_atfork_child(void) {
378
+ // cout << "A child is born" << endl;
379
+ Frames::clear_cached_frames();
380
+ prof_data_map.clear();
381
+ running = false;
382
+
383
+ // make sure it has a timer ready, it is a per-process-timer
384
+ Profiling::create_timer();
385
+ }
386
+
387
+ void Profiling::create_sigaction() {
388
+ struct sigaction sa;
389
+ // what happens if there is another action for the same signal?
390
+ // => last one defined wins!
391
+ sa.sa_sigaction = profiler_signal_handler;
392
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
393
+ sigemptyset(&sa.sa_mask);
394
+ if (sigaction(TIMER_SIG, &sa, NULL) == -1) {
395
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, "sigaction() failed");
396
+ profiling_shut_down = true; // no profiling without sigaction
397
+ }
398
+ }
399
+
400
+ void Profiling::create_timer() {
401
+ struct sigevent sev;
402
+
403
+ sev.sigev_value.sival_ptr = &timerid;
404
+ sev.sigev_notify = SIGEV_SIGNAL; /* Notify via signal */
405
+ sev.sigev_signo = TIMER_SIG; /* Notify using this signal */
406
+
407
+ if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
408
+ OBOE_DEBUG_LOG_ERROR(OBOE_MODULE_RUBY, "timer_create() failed");
409
+ profiling_shut_down = true; // no profiling without clock
410
+ }
411
+ }
412
+
413
+ extern "C" void Init_profiling(void) {
414
+ // assign values to global atomic vars that know about state of profiling
415
+ running = false;
416
+ profiling_shut_down = false;
417
+
418
+ // prep data structures
419
+ Profiling::create_sigaction();
420
+ Profiling::create_timer();
421
+ Frames::reserve_cached_frames();
422
+
423
+ // create Ruby Module: AppOpticsAPM::CProfiler
424
+ static VALUE rb_mAppOpticsAPM = rb_define_module("AppOpticsAPM");
425
+ static VALUE rb_mCProfiler = rb_define_module_under(rb_mAppOpticsAPM, "CProfiler");
426
+
427
+ rb_define_singleton_method(rb_mCProfiler, "get_interval", reinterpret_cast<VALUE (*)(...)>(Profiling::get_interval), 0);
428
+ rb_define_singleton_method(rb_mCProfiler, "set_interval", reinterpret_cast<VALUE (*)(...)>(Profiling::set_interval), 1);
429
+ rb_define_singleton_method(rb_mCProfiler, "run", reinterpret_cast<VALUE (*)(...)>(Profiling::profiling_run), 2);
430
+ rb_define_singleton_method(rb_mCProfiler, "get_tid", reinterpret_cast<VALUE (*)(...)>(Profiling::getTid), 0);
431
+
432
+ pthread_atfork(prof_atfork_prepare,
433
+ prof_atfork_parent,
434
+ prof_atfork_child);
435
+ }
@@ -0,0 +1,78 @@
1
+ // Copyright (c) 2021 SolarWinds, LLC.
2
+ // All rights reserved.
3
+
4
+ #ifndef PROFILING_H
5
+ #define PROFILING_H
6
+
7
+ #include <ruby/ruby.h>
8
+ #include <ruby/debug.h>
9
+ #include <signal.h>
10
+ #include <time.h>
11
+
12
+ #include <atomic>
13
+ #include <functional>
14
+ #include <unordered_map>
15
+ #include <vector>
16
+
17
+ #include "frames.h"
18
+ #include "logging.h"
19
+ #include "oboe_api.hpp"
20
+
21
+ #define BUF_SIZE 2048
22
+
23
+ // these definitions are based on the assumption that there are no
24
+ // frames with VALUE == 1 or VALUE == 2 in Ruby
25
+ // profiling won't blow up if there are, because there is also a check to see
26
+ // if the stack has size == 1 when assuming what these frames refer to
27
+ #define PR_OTHER_THREAD 1
28
+ #define PR_IN_GC 2
29
+
30
+ #if !defined(AO_GETTID)
31
+ #if defined(_WIN32)
32
+ #define AO_GETTID GetCurrentThreadId
33
+ #else
34
+ #include <unistd.h>
35
+ #include <sys/syscall.h>
36
+ #ifdef SYS_gettid
37
+ #define AO_GETTID syscall(SYS_gettid);
38
+ #endif
39
+ #endif
40
+ #endif
41
+
42
+ class Profiling {
43
+ public:
44
+ static const string string_job_handler, string_gc_handler, string_signal_handler, string_stop;
45
+
46
+ static void create_sigaction();
47
+ static void create_timer();
48
+
49
+ static int try_catch_shutdown(std::function<int()>, const string& fun_name);
50
+ static void profiler_job_handler(void* data);
51
+ static void profiler_gc_handler(void* data);
52
+ // This is used when catching an exception
53
+ static void shut_down();
54
+
55
+ // The following are made available to Ruby and have to return VALUE
56
+ static VALUE profiling_run(VALUE self, VALUE rb_thread_val, VALUE interval);
57
+ static VALUE get_interval();
58
+ static VALUE set_interval(VALUE self, VALUE interval);
59
+ static VALUE getTid();
60
+
61
+ private:
62
+ static void profiling_start(pid_t tid);
63
+
64
+ // This is used via rb_ensure and therefore needs VALUE as a return type
65
+ static VALUE profiling_stop(pid_t tid);
66
+
67
+ static void process_snapshot(VALUE* frames_buffer,
68
+ int num,
69
+ pid_t tid,
70
+ long ts);
71
+ static void profiler_record_frames();
72
+ static void profiler_record_gc();
73
+ static void send_omitted(pid_t tid, long ts);
74
+ };
75
+
76
+ extern "C" void Init_profiling(void);
77
+
78
+ #endif // PROFILING_H