appoptics_apm-zearn 4.13.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/.dockerignore +5 -0
- data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
- 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 +36 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +130 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +33 -0
- data/Gemfile +14 -0
- data/LICENSE +202 -0
- data/README.md +393 -0
- data/appoptics_apm.gemspec +70 -0
- data/bin/appoptics_apm_config +15 -0
- data/examples/prepend.rb +13 -0
- data/examples/sdk_examples.rb +158 -0
- data/ext/oboe_metal/README.md +69 -0
- data/ext/oboe_metal/extconf.rb +151 -0
- data/ext/oboe_metal/lib/.keep +0 -0
- 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/noop/noop.c +8 -0
- data/ext/oboe_metal/src/README.md +6 -0
- data/ext/oboe_metal/src/VERSION +2 -0
- data/ext/oboe_metal/src/bson/bson.h +220 -0
- data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
- 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 +21 -0
- 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 +1156 -0
- data/ext/oboe_metal/src/oboe_api.cpp +652 -0
- data/ext/oboe_metal/src/oboe_api.hpp +431 -0
- data/ext/oboe_metal/src/oboe_debug.h +59 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +7329 -0
- 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/init.rb +4 -0
- data/lib/appoptics_apm/api/layerinit.rb +41 -0
- data/lib/appoptics_apm/api/logging.rb +381 -0
- data/lib/appoptics_apm/api/memcache.rb +37 -0
- data/lib/appoptics_apm/api/metrics.rb +63 -0
- data/lib/appoptics_apm/api/tracing.rb +57 -0
- data/lib/appoptics_apm/api/util.rb +120 -0
- data/lib/appoptics_apm/api.rb +21 -0
- data/lib/appoptics_apm/base.rb +231 -0
- data/lib/appoptics_apm/config.rb +299 -0
- data/lib/appoptics_apm/frameworks/grape.rb +98 -0
- data/lib/appoptics_apm/frameworks/padrino.rb +78 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller6.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +88 -0
- data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +114 -0
- data/lib/appoptics_apm/frameworks/rails/inst/logger_formatters.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails.rb +100 -0
- data/lib/appoptics_apm/frameworks/sinatra.rb +96 -0
- data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
- data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
- data/lib/appoptics_apm/inst/curb.rb +332 -0
- data/lib/appoptics_apm/inst/dalli.rb +85 -0
- data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
- data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
- data/lib/appoptics_apm/inst/excon.rb +125 -0
- data/lib/appoptics_apm/inst/faraday.rb +106 -0
- data/lib/appoptics_apm/inst/graphql.rb +240 -0
- data/lib/appoptics_apm/inst/grpc_client.rb +159 -0
- data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
- data/lib/appoptics_apm/inst/http.rb +81 -0
- data/lib/appoptics_apm/inst/httpclient.rb +174 -0
- data/lib/appoptics_apm/inst/logger_formatter.rb +50 -0
- data/lib/appoptics_apm/inst/logging_log_event.rb +28 -0
- data/lib/appoptics_apm/inst/lumberjack_formatter.rb +13 -0
- data/lib/appoptics_apm/inst/memcached.rb +86 -0
- data/lib/appoptics_apm/inst/mongo.rb +246 -0
- data/lib/appoptics_apm/inst/mongo2.rb +225 -0
- data/lib/appoptics_apm/inst/moped.rb +466 -0
- data/lib/appoptics_apm/inst/rack.rb +182 -0
- data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
- data/lib/appoptics_apm/inst/redis.rb +274 -0
- data/lib/appoptics_apm/inst/resque.rb +151 -0
- data/lib/appoptics_apm/inst/rest-client.rb +48 -0
- data/lib/appoptics_apm/inst/sequel.rb +178 -0
- data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
- data/lib/appoptics_apm/inst/sidekiq-worker.rb +66 -0
- data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
- data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
- data/lib/appoptics_apm/instrumentation.rb +22 -0
- data/lib/appoptics_apm/loading.rb +65 -0
- data/lib/appoptics_apm/logger.rb +14 -0
- data/lib/appoptics_apm/noop/README.md +9 -0
- data/lib/appoptics_apm/noop/context.rb +27 -0
- data/lib/appoptics_apm/noop/metadata.rb +25 -0
- data/lib/appoptics_apm/noop/profiling.rb +21 -0
- data/lib/appoptics_apm/oboe_init_options.rb +211 -0
- data/lib/appoptics_apm/ruby.rb +35 -0
- data/lib/appoptics_apm/sdk/current_trace.rb +77 -0
- data/lib/appoptics_apm/sdk/custom_metrics.rb +94 -0
- data/lib/appoptics_apm/sdk/logging.rb +37 -0
- data/lib/appoptics_apm/sdk/tracing.rb +434 -0
- data/lib/appoptics_apm/support/profiling.rb +18 -0
- data/lib/appoptics_apm/support/transaction_metrics.rb +67 -0
- data/lib/appoptics_apm/support/transaction_settings.rb +219 -0
- data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
- data/lib/appoptics_apm/support_report.rb +119 -0
- data/lib/appoptics_apm/test.rb +95 -0
- data/lib/appoptics_apm/thread_local.rb +26 -0
- data/lib/appoptics_apm/util.rb +326 -0
- data/lib/appoptics_apm/version.rb +16 -0
- data/lib/appoptics_apm/xtrace.rb +115 -0
- data/lib/appoptics_apm.rb +77 -0
- data/lib/joboe_metal.rb +212 -0
- data/lib/oboe.rb +7 -0
- data/lib/oboe_metal.rb +172 -0
- data/lib/rails/generators/appoptics_apm/install_generator.rb +47 -0
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +425 -0
- data/log/.keep +0 -0
- data/yardoc_frontpage.md +26 -0
- metadata +231 -0
@@ -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
|
@@ -0,0 +1,53 @@
|
|
1
|
+
cmake_minimum_required(VERSION 3.13)
|
2
|
+
project(test)
|
3
|
+
|
4
|
+
# specify the C++ standard
|
5
|
+
set(CMAKE_CXX_STANDARD 11)
|
6
|
+
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
7
|
+
# set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/FindGMock.cmake)
|
8
|
+
|
9
|
+
include(FetchContent)
|
10
|
+
FetchContent_Declare(
|
11
|
+
googletest
|
12
|
+
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
|
13
|
+
# URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
|
14
|
+
)
|
15
|
+
|
16
|
+
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
17
|
+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
18
|
+
FetchContent_MakeAvailable(googletest)
|
19
|
+
|
20
|
+
include_directories(
|
21
|
+
${gtest_SOURCE_DIR}/include
|
22
|
+
../src/
|
23
|
+
$ENV{RUBY_INC_DIR}
|
24
|
+
$ENV{RUBY_INC_DIR}/x86_64-linux/
|
25
|
+
)
|
26
|
+
|
27
|
+
link_directories(
|
28
|
+
# /usr/lib/
|
29
|
+
$ENV{RUBY_PREFIX}/lib/
|
30
|
+
../../../lib/
|
31
|
+
../lib
|
32
|
+
)
|
33
|
+
|
34
|
+
enable_testing()
|
35
|
+
set (sources
|
36
|
+
test_main.cc
|
37
|
+
frames_test.cc
|
38
|
+
profiling_test.cc
|
39
|
+
)
|
40
|
+
|
41
|
+
## Link runTests with what we want to test and the GTest and pthread library
|
42
|
+
add_executable(runTests ${sources})
|
43
|
+
target_link_libraries(runTests
|
44
|
+
# ${GTEST_LIBRARIES}
|
45
|
+
gtest
|
46
|
+
appoptics_apm.so
|
47
|
+
liboboe.so
|
48
|
+
libruby.so
|
49
|
+
pthread
|
50
|
+
)
|
51
|
+
|
52
|
+
include(GoogleTest)
|
53
|
+
gtest_discover_tests(runTests)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This content copied from
|
2
|
+
# https://git.simply-life.net/simply-life.net/talltower/-/blob/639293a366da43eb94a72d2e7596242314c9809c/cmake/FindGMock.cmake
|
3
|
+
|
4
|
+
|
5
|
+
# Try to find GMock
|
6
|
+
find_package(GTest)
|
7
|
+
|
8
|
+
# the following issues a warning, but it works nonetheless
|
9
|
+
find_package(PkgConfig)
|
10
|
+
pkg_check_modules(PC_GMOCK QUIET gmock)
|
11
|
+
set(GMOCK_DEFINITIONS ${PC_GMOCK_CFLAGS_OTHER})
|
12
|
+
|
13
|
+
find_path(GMOCK_INCLUDE_DIR gmock.h
|
14
|
+
HINTS ${PC_GMOCK_INCLUDEDIR} ${PC_GMOCK_INCLUDE_DIRS}
|
15
|
+
PATH_SUFFIXES gmock)
|
16
|
+
|
17
|
+
find_library(GMOCK_LIBRARY NAMES gmock libgmock
|
18
|
+
HINTS ${PC_GMOCK_LIBDIR} ${PC_GMOCK_LIBRARY_DIRS} )
|
19
|
+
|
20
|
+
find_library(GMOCK_MAIN_LIBRARY NAMES gmock_main libgmock_main
|
21
|
+
HINTS ${PC_GMOCK_LIBDIR} ${PC_GMOCK_LIBRARY_DIRS} )
|
22
|
+
|
23
|
+
include(FindPackageHandleStandardArgs)
|
24
|
+
# handle the QUIETLY and REQUIRED arguments and set GMOCK_FOUND to TRUE
|
25
|
+
# if all listed variables are TRUE
|
26
|
+
find_package_handle_standard_args(GMock DEFAULT_MSG
|
27
|
+
GMOCK_LIBRARY GMOCK_INCLUDE_DIR GTEST_FOUND)
|
28
|
+
|
29
|
+
mark_as_advanced(GMOCK_INCLUDE_DIR GMOCK_LIBRARY GMOCK_MAIN_LIBRARY)
|
30
|
+
|
31
|
+
set(GMOCK_LIBRARIES ${GMOCK_LIBRARY} )
|
32
|
+
set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR} )
|
33
|
+
set(GMOCK_MAIN_LIBRARIES ${GMOCK_MAIN_LIBRARY} )
|
34
|
+
|
35
|
+
if (NOT TARGET GMock)
|
36
|
+
add_library(GMock IMPORTED SHARED)
|
37
|
+
set_property(TARGET GMock PROPERTY IMPORTED_LOCATION ${GMOCK_LIBRARY})
|
38
|
+
set_property(TARGET GMock PROPERTY INTERFACE_INCLUDE_DIRECTORY ${GMOCK_INCLUDE_DIR})
|
39
|
+
|
40
|
+
add_library(GMockMain IMPORTED SHARED)
|
41
|
+
set_property(TARGET GMockMain PROPERTY IMPORTED_LOCATION ${GMOCK_MAIN_LIBRARY})
|
42
|
+
set_property(TARGET GMockMain PROPERTY INTERFACE_LINK_LIBRARIES GMock GTest)
|
43
|
+
endif()
|
@@ -0,0 +1,56 @@
|
|
1
|
+
C-code tests:
|
2
|
+
|
3
|
+
CMakeLists.txt includes downloading and compiling googletest if necessary
|
4
|
+
|
5
|
+
In the ext/oboe_metal/test directory:
|
6
|
+
|
7
|
+
Set an environment variable for the current path:
|
8
|
+
```
|
9
|
+
export TEST_DIR=`pwd`
|
10
|
+
```
|
11
|
+
|
12
|
+
Every time the ruby version changes the appoptics_apm gem needs to be
|
13
|
+
re-installed or its c++-code recompiled and relinked
|
14
|
+
|
15
|
+
These environment variables need to be set every time the ruby version is set:
|
16
|
+
```
|
17
|
+
export RUBY_INC_DIR=$(ruby ruby_inc_dir.rb)
|
18
|
+
export RUBY_PREFIX=$(ruby ruby_prefix.rb)
|
19
|
+
```
|
20
|
+
|
21
|
+
create the Makefile (needs to be remade when the ruby version changes)
|
22
|
+
```
|
23
|
+
cmake -S . -B build
|
24
|
+
```
|
25
|
+
build
|
26
|
+
```
|
27
|
+
cmake --build build
|
28
|
+
```
|
29
|
+
run
|
30
|
+
```
|
31
|
+
cd build && ctest && cd -
|
32
|
+
```
|
33
|
+
|
34
|
+
Most testing of profiling is done via Ruby integration tests
|
35
|
+
|
36
|
+
For example logging is tested in Ruby tests that verify the different
|
37
|
+
KVs and values in the resulting traces, using the same approach as
|
38
|
+
for traces without profiling.
|
39
|
+
|
40
|
+
Gotchas:
|
41
|
+
|
42
|
+
- In alpine the `ruby/config.h` file is in an architecture specific folder and needs
|
43
|
+
to be symlinked to the location set via RUBY_INC_DIR (see: Dockerfile_alpine)
|
44
|
+
|
45
|
+
TODO:
|
46
|
+
|
47
|
+
- write a script for this
|
48
|
+
|
49
|
+
```
|
50
|
+
export TEST_DIR=`pwd`
|
51
|
+
export RUBY_INC_DIR=$(ruby ruby_inc_dir.rb)
|
52
|
+
export RUBY_PREFIX=$(ruby ruby_prefix.rb)
|
53
|
+
cmake -S . -B build
|
54
|
+
cmake --build build
|
55
|
+
cd build && ctest && cd -
|
56
|
+
```
|