solarwinds_apm 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +112 -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 +155 -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 +161 -0
- data/.gitignore +39 -0
- data/.rubocop.yml +29 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +31 -0
- data/Gemfile +14 -0
- data/LICENSE +202 -0
- data/README.md +383 -0
- data/bin/solarwinds_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 +141 -0
- data/ext/oboe_metal/extconf_local.rb +75 -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 +247 -0
- data/ext/oboe_metal/src/frames.h +40 -0
- data/ext/oboe_metal/src/init_solarwinds_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 +1169 -0
- data/ext/oboe_metal/src/oboe_api.cpp +658 -0
- data/ext/oboe_metal/src/oboe_api.hpp +433 -0
- data/ext/oboe_metal/src/oboe_debug.h +59 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +7562 -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/oboe.rb +7 -0
- data/lib/oboe_metal.rb +172 -0
- data/lib/rails/generators/solarwinds_apm/install_generator.rb +47 -0
- data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +424 -0
- data/lib/solarwinds_apm/api/layerinit.rb +41 -0
- data/lib/solarwinds_apm/api/logging.rb +356 -0
- data/lib/solarwinds_apm/api/memcache.rb +37 -0
- data/lib/solarwinds_apm/api/metrics.rb +63 -0
- data/lib/solarwinds_apm/api/util.rb +98 -0
- data/lib/solarwinds_apm/api.rb +21 -0
- data/lib/solarwinds_apm/base.rb +160 -0
- data/lib/solarwinds_apm/config.rb +301 -0
- data/lib/solarwinds_apm/frameworks/grape.rb +96 -0
- data/lib/solarwinds_apm/frameworks/padrino.rb +78 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +100 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +88 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +26 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +22 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +103 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +14 -0
- data/lib/solarwinds_apm/frameworks/rails.rb +100 -0
- data/lib/solarwinds_apm/frameworks/sinatra.rb +96 -0
- data/lib/solarwinds_apm/inst/bunny-client.rb +157 -0
- data/lib/solarwinds_apm/inst/bunny-consumer.rb +102 -0
- data/lib/solarwinds_apm/inst/curb.rb +288 -0
- data/lib/solarwinds_apm/inst/dalli.rb +89 -0
- data/lib/solarwinds_apm/inst/delayed_job.rb +100 -0
- data/lib/solarwinds_apm/inst/excon.rb +113 -0
- data/lib/solarwinds_apm/inst/faraday.rb +96 -0
- data/lib/solarwinds_apm/inst/graphql.rb +206 -0
- data/lib/solarwinds_apm/inst/grpc_client.rb +147 -0
- data/lib/solarwinds_apm/inst/grpc_server.rb +119 -0
- data/lib/solarwinds_apm/inst/httpclient.rb +181 -0
- data/lib/solarwinds_apm/inst/logger_formatter.rb +46 -0
- data/lib/solarwinds_apm/inst/logging_log_event.rb +24 -0
- data/lib/solarwinds_apm/inst/lumberjack_formatter.rb +9 -0
- data/lib/solarwinds_apm/inst/memcached.rb +86 -0
- data/lib/solarwinds_apm/inst/mongo.rb +246 -0
- data/lib/solarwinds_apm/inst/mongo2.rb +225 -0
- data/lib/solarwinds_apm/inst/moped.rb +466 -0
- data/lib/solarwinds_apm/inst/net_http.rb +60 -0
- data/lib/solarwinds_apm/inst/rack.rb +217 -0
- data/lib/solarwinds_apm/inst/rack_cache.rb +35 -0
- data/lib/solarwinds_apm/inst/redis.rb +273 -0
- data/lib/solarwinds_apm/inst/resque.rb +129 -0
- data/lib/solarwinds_apm/inst/rest-client.rb +43 -0
- data/lib/solarwinds_apm/inst/sequel.rb +241 -0
- data/lib/solarwinds_apm/inst/sidekiq-client.rb +63 -0
- data/lib/solarwinds_apm/inst/sidekiq-worker.rb +64 -0
- data/lib/solarwinds_apm/inst/typhoeus.rb +90 -0
- data/lib/solarwinds_apm/instrumentation.rb +22 -0
- data/lib/solarwinds_apm/loading.rb +65 -0
- data/lib/solarwinds_apm/logger.rb +14 -0
- data/lib/solarwinds_apm/noop/README.md +9 -0
- data/lib/solarwinds_apm/noop/context.rb +26 -0
- data/lib/solarwinds_apm/noop/metadata.rb +25 -0
- data/lib/solarwinds_apm/noop/profiling.rb +21 -0
- data/lib/solarwinds_apm/oboe_init_options.rb +191 -0
- data/lib/solarwinds_apm/ruby.rb +35 -0
- data/lib/solarwinds_apm/sdk/current_trace_info.rb +123 -0
- data/lib/solarwinds_apm/sdk/custom_metrics.rb +94 -0
- data/lib/solarwinds_apm/sdk/logging.rb +37 -0
- data/lib/solarwinds_apm/sdk/trace_context_headers.rb +69 -0
- data/lib/solarwinds_apm/sdk/tracing.rb +432 -0
- data/lib/solarwinds_apm/support/profiling.rb +22 -0
- data/lib/solarwinds_apm/support/trace_context.rb +53 -0
- data/lib/solarwinds_apm/support/trace_state.rb +69 -0
- data/lib/solarwinds_apm/support/trace_string.rb +89 -0
- data/lib/solarwinds_apm/support/transaction_metrics.rb +67 -0
- data/lib/solarwinds_apm/support/transaction_settings.rb +233 -0
- data/lib/solarwinds_apm/support/x_trace_options.rb +113 -0
- data/lib/solarwinds_apm/support.rb +12 -0
- data/lib/solarwinds_apm/support_report.rb +113 -0
- data/lib/solarwinds_apm/test.rb +165 -0
- data/lib/solarwinds_apm/thread_local.rb +26 -0
- data/lib/solarwinds_apm/util.rb +334 -0
- data/lib/solarwinds_apm/version.rb +17 -0
- data/lib/solarwinds_apm.rb +72 -0
- data/log/.keep +0 -0
- data/log/postgresql/.keep +0 -0
- data/solarwinds_apm.gemspec +52 -0
- data/yardoc_frontpage.md +24 -0
- metadata +228 -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: SolarWindsAPM::CProfiler
|
424
|
+
static VALUE rb_mSolarWindsAPM = rb_define_module("SolarWindsAPM");
|
425
|
+
static VALUE rb_mCProfiler = rb_define_module_under(rb_mSolarWindsAPM, "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
|
+
solarwinds_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 solarwinds_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
|
+
```
|