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.
Files changed (145) 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 +103 -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 +168 -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 +159 -0
  12. data/.gitignore +36 -0
  13. data/.rubocop.yml +29 -0
  14. data/.travis.yml +130 -0
  15. data/.yardopts +6 -0
  16. data/CHANGELOG.md +769 -0
  17. data/CONFIG.md +33 -0
  18. data/Gemfile +14 -0
  19. data/LICENSE +202 -0
  20. data/README.md +393 -0
  21. data/appoptics_apm.gemspec +70 -0
  22. data/bin/appoptics_apm_config +15 -0
  23. data/examples/prepend.rb +13 -0
  24. data/examples/sdk_examples.rb +158 -0
  25. data/ext/oboe_metal/README.md +69 -0
  26. data/ext/oboe_metal/extconf.rb +151 -0
  27. data/ext/oboe_metal/lib/.keep +0 -0
  28. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
  29. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
  30. data/ext/oboe_metal/noop/noop.c +8 -0
  31. data/ext/oboe_metal/src/README.md +6 -0
  32. data/ext/oboe_metal/src/VERSION +2 -0
  33. data/ext/oboe_metal/src/bson/bson.h +220 -0
  34. data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
  35. data/ext/oboe_metal/src/frames.cc +246 -0
  36. data/ext/oboe_metal/src/frames.h +40 -0
  37. data/ext/oboe_metal/src/init_appoptics_apm.cc +21 -0
  38. data/ext/oboe_metal/src/logging.cc +95 -0
  39. data/ext/oboe_metal/src/logging.h +35 -0
  40. data/ext/oboe_metal/src/oboe.h +1156 -0
  41. data/ext/oboe_metal/src/oboe_api.cpp +652 -0
  42. data/ext/oboe_metal/src/oboe_api.hpp +431 -0
  43. data/ext/oboe_metal/src/oboe_debug.h +59 -0
  44. data/ext/oboe_metal/src/oboe_swig_wrap.cc +7329 -0
  45. data/ext/oboe_metal/src/profiling.cc +435 -0
  46. data/ext/oboe_metal/src/profiling.h +78 -0
  47. data/ext/oboe_metal/test/CMakeLists.txt +53 -0
  48. data/ext/oboe_metal/test/FindGMock.cmake +43 -0
  49. data/ext/oboe_metal/test/README.md +56 -0
  50. data/ext/oboe_metal/test/frames_test.cc +164 -0
  51. data/ext/oboe_metal/test/profiling_test.cc +93 -0
  52. data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
  53. data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
  54. data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
  55. data/ext/oboe_metal/test/test.h +11 -0
  56. data/ext/oboe_metal/test/test_main.cc +32 -0
  57. data/init.rb +4 -0
  58. data/lib/appoptics_apm/api/layerinit.rb +41 -0
  59. data/lib/appoptics_apm/api/logging.rb +381 -0
  60. data/lib/appoptics_apm/api/memcache.rb +37 -0
  61. data/lib/appoptics_apm/api/metrics.rb +63 -0
  62. data/lib/appoptics_apm/api/tracing.rb +57 -0
  63. data/lib/appoptics_apm/api/util.rb +120 -0
  64. data/lib/appoptics_apm/api.rb +21 -0
  65. data/lib/appoptics_apm/base.rb +231 -0
  66. data/lib/appoptics_apm/config.rb +299 -0
  67. data/lib/appoptics_apm/frameworks/grape.rb +98 -0
  68. data/lib/appoptics_apm/frameworks/padrino.rb +78 -0
  69. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
  70. data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
  71. data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  72. data/lib/appoptics_apm/frameworks/rails/inst/action_controller6.rb +50 -0
  73. data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  74. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +88 -0
  75. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
  76. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
  77. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  78. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
  79. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
  80. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +114 -0
  81. data/lib/appoptics_apm/frameworks/rails/inst/logger_formatters.rb +27 -0
  82. data/lib/appoptics_apm/frameworks/rails.rb +100 -0
  83. data/lib/appoptics_apm/frameworks/sinatra.rb +96 -0
  84. data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
  85. data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
  86. data/lib/appoptics_apm/inst/curb.rb +332 -0
  87. data/lib/appoptics_apm/inst/dalli.rb +85 -0
  88. data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
  89. data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
  90. data/lib/appoptics_apm/inst/excon.rb +125 -0
  91. data/lib/appoptics_apm/inst/faraday.rb +106 -0
  92. data/lib/appoptics_apm/inst/graphql.rb +240 -0
  93. data/lib/appoptics_apm/inst/grpc_client.rb +159 -0
  94. data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
  95. data/lib/appoptics_apm/inst/http.rb +81 -0
  96. data/lib/appoptics_apm/inst/httpclient.rb +174 -0
  97. data/lib/appoptics_apm/inst/logger_formatter.rb +50 -0
  98. data/lib/appoptics_apm/inst/logging_log_event.rb +28 -0
  99. data/lib/appoptics_apm/inst/lumberjack_formatter.rb +13 -0
  100. data/lib/appoptics_apm/inst/memcached.rb +86 -0
  101. data/lib/appoptics_apm/inst/mongo.rb +246 -0
  102. data/lib/appoptics_apm/inst/mongo2.rb +225 -0
  103. data/lib/appoptics_apm/inst/moped.rb +466 -0
  104. data/lib/appoptics_apm/inst/rack.rb +182 -0
  105. data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
  106. data/lib/appoptics_apm/inst/redis.rb +274 -0
  107. data/lib/appoptics_apm/inst/resque.rb +151 -0
  108. data/lib/appoptics_apm/inst/rest-client.rb +48 -0
  109. data/lib/appoptics_apm/inst/sequel.rb +178 -0
  110. data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
  111. data/lib/appoptics_apm/inst/sidekiq-worker.rb +66 -0
  112. data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
  113. data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
  114. data/lib/appoptics_apm/instrumentation.rb +22 -0
  115. data/lib/appoptics_apm/loading.rb +65 -0
  116. data/lib/appoptics_apm/logger.rb +14 -0
  117. data/lib/appoptics_apm/noop/README.md +9 -0
  118. data/lib/appoptics_apm/noop/context.rb +27 -0
  119. data/lib/appoptics_apm/noop/metadata.rb +25 -0
  120. data/lib/appoptics_apm/noop/profiling.rb +21 -0
  121. data/lib/appoptics_apm/oboe_init_options.rb +211 -0
  122. data/lib/appoptics_apm/ruby.rb +35 -0
  123. data/lib/appoptics_apm/sdk/current_trace.rb +77 -0
  124. data/lib/appoptics_apm/sdk/custom_metrics.rb +94 -0
  125. data/lib/appoptics_apm/sdk/logging.rb +37 -0
  126. data/lib/appoptics_apm/sdk/tracing.rb +434 -0
  127. data/lib/appoptics_apm/support/profiling.rb +18 -0
  128. data/lib/appoptics_apm/support/transaction_metrics.rb +67 -0
  129. data/lib/appoptics_apm/support/transaction_settings.rb +219 -0
  130. data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
  131. data/lib/appoptics_apm/support_report.rb +119 -0
  132. data/lib/appoptics_apm/test.rb +95 -0
  133. data/lib/appoptics_apm/thread_local.rb +26 -0
  134. data/lib/appoptics_apm/util.rb +326 -0
  135. data/lib/appoptics_apm/version.rb +16 -0
  136. data/lib/appoptics_apm/xtrace.rb +115 -0
  137. data/lib/appoptics_apm.rb +77 -0
  138. data/lib/joboe_metal.rb +212 -0
  139. data/lib/oboe.rb +7 -0
  140. data/lib/oboe_metal.rb +172 -0
  141. data/lib/rails/generators/appoptics_apm/install_generator.rb +47 -0
  142. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +425 -0
  143. data/log/.keep +0 -0
  144. data/yardoc_frontpage.md +26 -0
  145. 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
+ ```