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.
- checksums.yaml +4 -4
- data/.github/workflows/build_and_release_gem.yml +103 -0
- data/.github/workflows/build_for_packagecloud.yml +70 -0
- data/.github/workflows/docker-images.yml +47 -0
- data/.github/workflows/run_cpluplus_tests.yml +73 -0
- data/.github/workflows/run_tests.yml +168 -0
- data/.github/workflows/scripts/test_install.rb +23 -0
- data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
- data/.github/workflows/test_on_4_linux.yml +159 -0
- data/.gitignore +17 -25
- data/.travis.yml +17 -14
- data/Gemfile +1 -25
- data/README.md +4 -6
- data/appoptics_apm.gemspec +11 -5
- data/examples/prepend.rb +13 -0
- data/examples/sdk_examples.rb +16 -0
- data/ext/oboe_metal/extconf.rb +25 -31
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/src/README.md +6 -0
- data/ext/oboe_metal/src/VERSION +2 -1
- data/ext/oboe_metal/src/frames.cc +246 -0
- data/ext/oboe_metal/src/frames.h +40 -0
- data/ext/oboe_metal/src/init_appoptics_apm.cc +5 -4
- data/ext/oboe_metal/src/logging.cc +95 -0
- data/ext/oboe_metal/src/logging.h +35 -0
- data/ext/oboe_metal/src/oboe.h +8 -5
- data/ext/oboe_metal/src/oboe_api.cpp +40 -14
- data/ext/oboe_metal/src/oboe_api.hpp +29 -8
- data/ext/oboe_metal/src/oboe_debug.h +1 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +85 -21
- data/ext/oboe_metal/src/profiling.cc +435 -0
- data/ext/oboe_metal/src/profiling.h +78 -0
- data/ext/oboe_metal/test/CMakeLists.txt +53 -0
- data/ext/oboe_metal/test/FindGMock.cmake +43 -0
- data/ext/oboe_metal/test/README.md +56 -0
- data/ext/oboe_metal/test/frames_test.cc +164 -0
- data/ext/oboe_metal/test/profiling_test.cc +93 -0
- data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
- data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
- data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
- data/ext/oboe_metal/test/test.h +11 -0
- data/ext/oboe_metal/test/test_main.cc +32 -0
- data/lib/appoptics_apm/api/metrics.rb +3 -0
- data/lib/appoptics_apm/base.rb +1 -1
- data/lib/appoptics_apm/config.rb +11 -2
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +7 -1
- data/lib/appoptics_apm/inst/rack.rb +13 -6
- data/lib/appoptics_apm/inst/redis.rb +1 -2
- data/lib/appoptics_apm/noop/context.rb +3 -0
- data/lib/appoptics_apm/noop/metadata.rb +4 -1
- data/lib/appoptics_apm/noop/profiling.rb +21 -0
- data/lib/appoptics_apm/oboe_init_options.rb +26 -22
- data/lib/appoptics_apm/support/profiling.rb +18 -0
- data/lib/appoptics_apm/support/transaction_metrics.rb +1 -1
- data/lib/appoptics_apm/support/transaction_settings.rb +2 -2
- data/lib/appoptics_apm/support/x_trace_options.rb +2 -2
- data/lib/appoptics_apm/support_report.rb +2 -2
- data/lib/appoptics_apm/test.rb +4 -3
- data/lib/appoptics_apm/util.rb +1 -1
- data/lib/appoptics_apm/version.rb +3 -3
- data/lib/appoptics_apm/xtrace.rb +1 -1
- data/lib/appoptics_apm.rb +3 -1
- data/lib/oboe_metal.rb +2 -2
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +24 -0
- data/log/.keep +0 -0
- metadata +46 -16
- 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
|