solarwinds_apm 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
  4. data/.github/workflows/build_and_release_gem.yml +112 -0
  5. data/.github/workflows/build_for_packagecloud.yml +70 -0
  6. data/.github/workflows/docker-images.yml +47 -0
  7. data/.github/workflows/run_cpluplus_tests.yml +73 -0
  8. data/.github/workflows/run_tests.yml +155 -0
  9. data/.github/workflows/scripts/test_install.rb +23 -0
  10. data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
  11. data/.github/workflows/test_on_4_linux.yml +161 -0
  12. data/.gitignore +39 -0
  13. data/.rubocop.yml +29 -0
  14. data/.yardopts +7 -0
  15. data/CHANGELOG.md +769 -0
  16. data/CONFIG.md +31 -0
  17. data/Gemfile +14 -0
  18. data/LICENSE +202 -0
  19. data/README.md +383 -0
  20. data/bin/solarwinds_apm_config +15 -0
  21. data/examples/prepend.rb +13 -0
  22. data/examples/sdk_examples.rb +158 -0
  23. data/ext/oboe_metal/README.md +69 -0
  24. data/ext/oboe_metal/extconf.rb +141 -0
  25. data/ext/oboe_metal/extconf_local.rb +75 -0
  26. data/ext/oboe_metal/lib/.keep +0 -0
  27. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
  28. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
  29. data/ext/oboe_metal/noop/noop.c +8 -0
  30. data/ext/oboe_metal/src/README.md +6 -0
  31. data/ext/oboe_metal/src/VERSION +2 -0
  32. data/ext/oboe_metal/src/bson/bson.h +220 -0
  33. data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
  34. data/ext/oboe_metal/src/frames.cc +247 -0
  35. data/ext/oboe_metal/src/frames.h +40 -0
  36. data/ext/oboe_metal/src/init_solarwinds_apm.cc +21 -0
  37. data/ext/oboe_metal/src/logging.cc +95 -0
  38. data/ext/oboe_metal/src/logging.h +35 -0
  39. data/ext/oboe_metal/src/oboe.h +1169 -0
  40. data/ext/oboe_metal/src/oboe_api.cpp +658 -0
  41. data/ext/oboe_metal/src/oboe_api.hpp +433 -0
  42. data/ext/oboe_metal/src/oboe_debug.h +59 -0
  43. data/ext/oboe_metal/src/oboe_swig_wrap.cc +7562 -0
  44. data/ext/oboe_metal/src/profiling.cc +435 -0
  45. data/ext/oboe_metal/src/profiling.h +78 -0
  46. data/ext/oboe_metal/test/CMakeLists.txt +53 -0
  47. data/ext/oboe_metal/test/FindGMock.cmake +43 -0
  48. data/ext/oboe_metal/test/README.md +56 -0
  49. data/ext/oboe_metal/test/frames_test.cc +164 -0
  50. data/ext/oboe_metal/test/profiling_test.cc +93 -0
  51. data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
  52. data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
  53. data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
  54. data/ext/oboe_metal/test/test.h +11 -0
  55. data/ext/oboe_metal/test/test_main.cc +32 -0
  56. data/init.rb +4 -0
  57. data/lib/oboe.rb +7 -0
  58. data/lib/oboe_metal.rb +172 -0
  59. data/lib/rails/generators/solarwinds_apm/install_generator.rb +47 -0
  60. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +424 -0
  61. data/lib/solarwinds_apm/api/layerinit.rb +41 -0
  62. data/lib/solarwinds_apm/api/logging.rb +356 -0
  63. data/lib/solarwinds_apm/api/memcache.rb +37 -0
  64. data/lib/solarwinds_apm/api/metrics.rb +63 -0
  65. data/lib/solarwinds_apm/api/util.rb +98 -0
  66. data/lib/solarwinds_apm/api.rb +21 -0
  67. data/lib/solarwinds_apm/base.rb +160 -0
  68. data/lib/solarwinds_apm/config.rb +301 -0
  69. data/lib/solarwinds_apm/frameworks/grape.rb +96 -0
  70. data/lib/solarwinds_apm/frameworks/padrino.rb +78 -0
  71. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +100 -0
  72. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  73. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  74. data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +88 -0
  75. data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +26 -0
  76. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  77. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +22 -0
  78. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +103 -0
  79. data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +14 -0
  80. data/lib/solarwinds_apm/frameworks/rails.rb +100 -0
  81. data/lib/solarwinds_apm/frameworks/sinatra.rb +96 -0
  82. data/lib/solarwinds_apm/inst/bunny-client.rb +157 -0
  83. data/lib/solarwinds_apm/inst/bunny-consumer.rb +102 -0
  84. data/lib/solarwinds_apm/inst/curb.rb +288 -0
  85. data/lib/solarwinds_apm/inst/dalli.rb +89 -0
  86. data/lib/solarwinds_apm/inst/delayed_job.rb +100 -0
  87. data/lib/solarwinds_apm/inst/excon.rb +113 -0
  88. data/lib/solarwinds_apm/inst/faraday.rb +96 -0
  89. data/lib/solarwinds_apm/inst/graphql.rb +206 -0
  90. data/lib/solarwinds_apm/inst/grpc_client.rb +147 -0
  91. data/lib/solarwinds_apm/inst/grpc_server.rb +119 -0
  92. data/lib/solarwinds_apm/inst/httpclient.rb +181 -0
  93. data/lib/solarwinds_apm/inst/logger_formatter.rb +46 -0
  94. data/lib/solarwinds_apm/inst/logging_log_event.rb +24 -0
  95. data/lib/solarwinds_apm/inst/lumberjack_formatter.rb +9 -0
  96. data/lib/solarwinds_apm/inst/memcached.rb +86 -0
  97. data/lib/solarwinds_apm/inst/mongo.rb +246 -0
  98. data/lib/solarwinds_apm/inst/mongo2.rb +225 -0
  99. data/lib/solarwinds_apm/inst/moped.rb +466 -0
  100. data/lib/solarwinds_apm/inst/net_http.rb +60 -0
  101. data/lib/solarwinds_apm/inst/rack.rb +217 -0
  102. data/lib/solarwinds_apm/inst/rack_cache.rb +35 -0
  103. data/lib/solarwinds_apm/inst/redis.rb +273 -0
  104. data/lib/solarwinds_apm/inst/resque.rb +129 -0
  105. data/lib/solarwinds_apm/inst/rest-client.rb +43 -0
  106. data/lib/solarwinds_apm/inst/sequel.rb +241 -0
  107. data/lib/solarwinds_apm/inst/sidekiq-client.rb +63 -0
  108. data/lib/solarwinds_apm/inst/sidekiq-worker.rb +64 -0
  109. data/lib/solarwinds_apm/inst/typhoeus.rb +90 -0
  110. data/lib/solarwinds_apm/instrumentation.rb +22 -0
  111. data/lib/solarwinds_apm/loading.rb +65 -0
  112. data/lib/solarwinds_apm/logger.rb +14 -0
  113. data/lib/solarwinds_apm/noop/README.md +9 -0
  114. data/lib/solarwinds_apm/noop/context.rb +26 -0
  115. data/lib/solarwinds_apm/noop/metadata.rb +25 -0
  116. data/lib/solarwinds_apm/noop/profiling.rb +21 -0
  117. data/lib/solarwinds_apm/oboe_init_options.rb +191 -0
  118. data/lib/solarwinds_apm/ruby.rb +35 -0
  119. data/lib/solarwinds_apm/sdk/current_trace_info.rb +123 -0
  120. data/lib/solarwinds_apm/sdk/custom_metrics.rb +94 -0
  121. data/lib/solarwinds_apm/sdk/logging.rb +37 -0
  122. data/lib/solarwinds_apm/sdk/trace_context_headers.rb +69 -0
  123. data/lib/solarwinds_apm/sdk/tracing.rb +432 -0
  124. data/lib/solarwinds_apm/support/profiling.rb +22 -0
  125. data/lib/solarwinds_apm/support/trace_context.rb +53 -0
  126. data/lib/solarwinds_apm/support/trace_state.rb +69 -0
  127. data/lib/solarwinds_apm/support/trace_string.rb +89 -0
  128. data/lib/solarwinds_apm/support/transaction_metrics.rb +67 -0
  129. data/lib/solarwinds_apm/support/transaction_settings.rb +233 -0
  130. data/lib/solarwinds_apm/support/x_trace_options.rb +113 -0
  131. data/lib/solarwinds_apm/support.rb +12 -0
  132. data/lib/solarwinds_apm/support_report.rb +113 -0
  133. data/lib/solarwinds_apm/test.rb +165 -0
  134. data/lib/solarwinds_apm/thread_local.rb +26 -0
  135. data/lib/solarwinds_apm/util.rb +334 -0
  136. data/lib/solarwinds_apm/version.rb +17 -0
  137. data/lib/solarwinds_apm.rb +72 -0
  138. data/log/.keep +0 -0
  139. data/log/postgresql/.keep +0 -0
  140. data/solarwinds_apm.gemspec +52 -0
  141. data/yardoc_frontpage.md +24 -0
  142. 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
+ ```