scout_apm 2.2.0.pre0 → 2.2.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +6 -0
- data/ext/stacks/extconf.rb +5 -1
- data/ext/stacks/stacks.c +79 -18
- data/lib/scout_apm/agent.rb +1 -1
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +7 -3
- data/lib/scout_apm/config.rb +8 -2
- data/lib/scout_apm/instant/middleware.rb +2 -2
- data/lib/scout_apm/instruments/active_record.rb +2 -2
- data/lib/scout_apm/layaway.rb +9 -5
- data/lib/scout_apm/layer.rb +1 -0
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +1 -1
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +1 -1
- data/lib/scout_apm/trace_compactor.rb +1 -0
- data/lib/scout_apm/utils/fake_stacks.rb +4 -0
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +0 -1
- data/test/test_helper.rb +38 -0
- data/test/unit/config_test.rb +2 -2
- data/test/unit/layaway_test.rb +11 -16
- data/test/unit/serializers/payload_serializer_test.rb +0 -102
- data/test/unit/slow_job_policy_test.rb +0 -49
- data/test/unit/slow_request_policy_test.rb +4 -5
- metadata +2 -4
- data/lib/scout_apm/slow_item_set.rb +0 -80
- data/test/unit/slow_item_set_test.rb +0 -94
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04b5ba56e4b5d547ab50ca2c7bf9de7ad51175b4
|
4
|
+
data.tar.gz: d8a85c252b959e001d5b92498de94ab844a76f6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49365dc262bd4065eb827c3affc362d02e82b25e1638d67912f2d32ed2ac75bd08c490cb6a5071d9d352ce0f0b9a0531d8adea657c39f42913f28291f961eed3
|
7
|
+
data.tar.gz: e931e86d96b7baf0a7fc329e17ce0683f953085b95cc6a01ca2ef67d13d1ba556d346a675fc96e9738ab65f5f2771fb90e49a2539952246f417873a3eebf16c1
|
data/CHANGELOG.markdown
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
* ScoutProf BETA
|
4
4
|
|
5
|
+
# 2.1.1
|
6
|
+
|
7
|
+
* Fix an issue with AR instrumentation and complex queries
|
8
|
+
* Fix use of configuration option `data_file`
|
9
|
+
* Update unit tests
|
10
|
+
|
5
11
|
# 2.1.0
|
6
12
|
|
7
13
|
* Added ignore key to configuration to entirely ignore an endpoint. No traces
|
data/ext/stacks/extconf.rb
CHANGED
@@ -11,7 +11,11 @@ can_compile &&= have_func('rb_profile_frames')
|
|
11
11
|
can_compile &&= have_func('rb_profile_frame_absolute_path')
|
12
12
|
can_compile &&= have_func('rb_profile_frame_label')
|
13
13
|
can_compile &&= have_func('rb_profile_frame_classpath')
|
14
|
-
|
14
|
+
|
15
|
+
# Explicitly link against librt
|
16
|
+
if have_macro('__linux__')
|
17
|
+
can_compile &&= have_library('rt') # for timer_create, timer_settime
|
18
|
+
end
|
15
19
|
|
16
20
|
# Pick the atomics implementation
|
17
21
|
has_atomics_header = have_header("stdatomic.h")
|
data/ext/stacks/stacks.c
CHANGED
@@ -24,15 +24,15 @@
|
|
24
24
|
#include <errno.h>
|
25
25
|
#include <inttypes.h>
|
26
26
|
#include <pthread.h>
|
27
|
-
#include <semaphore.h>
|
28
|
-
#include <setjmp.h>
|
29
27
|
#include <signal.h>
|
30
28
|
#include <stdbool.h>
|
31
29
|
|
32
30
|
/*
|
33
31
|
* System
|
34
32
|
*/
|
33
|
+
#ifdef __linux__
|
35
34
|
#include <sys/syscall.h>
|
35
|
+
#endif
|
36
36
|
#include <sys/time.h>
|
37
37
|
|
38
38
|
#include "scout_atomics.h"
|
@@ -57,12 +57,20 @@ VALUE interval;
|
|
57
57
|
#define BUF_SIZE 512
|
58
58
|
#define MAX_TRACES 2000
|
59
59
|
|
60
|
+
#ifdef __linux__
|
60
61
|
#define NANO_SECOND_MULTIPLIER 1000000 // 1 millisecond = 1,000,000 Nanoseconds
|
61
62
|
const long INTERVAL = 1 * NANO_SECOND_MULTIPLIER; // milliseconds * NANO_SECOND_MULTIPLIER
|
62
|
-
|
63
63
|
// For support of thread id in timer_create
|
64
64
|
#define sigev_notify_thread_id _sigev_un._tid
|
65
65
|
|
66
|
+
#else // __linux__
|
67
|
+
|
68
|
+
const long INTERVAL = 1000; // 1ms
|
69
|
+
|
70
|
+
#endif
|
71
|
+
|
72
|
+
|
73
|
+
|
66
74
|
#ifdef RUBY_INTERNAL_EVENT_NEWOBJ
|
67
75
|
|
68
76
|
// Forward Declarations
|
@@ -95,14 +103,17 @@ static __thread atomic_uint16_t _cur_traces_num = ATOMIC_INIT(0);
|
|
95
103
|
static __thread atomic_uint32_t _skipped_in_gc = ATOMIC_INIT(0);
|
96
104
|
static __thread atomic_uint32_t _skipped_in_signal_handler = ATOMIC_INIT(0);
|
97
105
|
static __thread atomic_uint32_t _skipped_in_job_registered = ATOMIC_INIT(0);
|
106
|
+
static __thread atomic_uint32_t _skipped_in_not_running = ATOMIC_INIT(0);
|
98
107
|
|
99
108
|
static __thread VALUE _gc_hook;
|
109
|
+
static __thread VALUE _ruby_thread;
|
100
110
|
|
101
111
|
static __thread atomic_bool_t _job_registered = ATOMIC_INIT(false);
|
102
112
|
|
113
|
+
#ifdef __linux__
|
103
114
|
static __thread timer_t _timerid;
|
104
115
|
static __thread struct sigevent _sev;
|
105
|
-
|
116
|
+
#endif
|
106
117
|
|
107
118
|
////////////////////////////////////////////////////////////////////////////////////////
|
108
119
|
// Global variables
|
@@ -116,7 +127,6 @@ scout_add_profiled_thread(pthread_t th)
|
|
116
127
|
init_thread_vars();
|
117
128
|
ATOMIC_STORE_BOOL(&_thread_registered, true);
|
118
129
|
|
119
|
-
fprintf(stderr, "APM DEBUG: Added thread id: %li\n", (unsigned long int)pthread_self());
|
120
130
|
return 1;
|
121
131
|
}
|
122
132
|
|
@@ -146,14 +156,14 @@ remove_profiled_thread(pthread_t th)
|
|
146
156
|
|
147
157
|
ATOMIC_STORE_BOOL(&_ok_to_sample, false);
|
148
158
|
|
149
|
-
fprintf(stderr, "APM DEBUG: Would remove thread id: %li\n", (unsigned long int)th);
|
150
|
-
|
151
159
|
// Unregister the _gc_hook from Ruby ObjectSpace, then free it as well as the _traces struct it wrapped.
|
152
160
|
rb_gc_unregister_address(&_gc_hook);
|
153
161
|
xfree(&_gc_hook);
|
154
162
|
xfree(&_traces);
|
155
163
|
|
164
|
+
#ifdef __linux__
|
156
165
|
timer_delete(_timerid);
|
166
|
+
#endif
|
157
167
|
|
158
168
|
ATOMIC_STORE_BOOL(&_thread_registered, false);
|
159
169
|
return 0;
|
@@ -196,6 +206,9 @@ rb_scout_uninstall_profiling(VALUE self)
|
|
196
206
|
static VALUE
|
197
207
|
rb_scout_install_profiling(VALUE self)
|
198
208
|
{
|
209
|
+
#ifndef __linux__
|
210
|
+
struct itimerval timer;
|
211
|
+
#endif
|
199
212
|
struct sigaction new_vtaction, old_vtaction;
|
200
213
|
|
201
214
|
// We can only install once. If uninstall is called, we will NOT be able to call install again.
|
@@ -210,6 +223,13 @@ rb_scout_install_profiling(VALUE self)
|
|
210
223
|
sigemptyset(&new_vtaction.sa_mask);
|
211
224
|
sigaction(SIGALRM, &new_vtaction, &old_vtaction);
|
212
225
|
|
226
|
+
#ifndef __linux__
|
227
|
+
timer.it_interval.tv_sec = 0;
|
228
|
+
timer.it_interval.tv_usec = INTERVAL; //FIX2INT(interval);
|
229
|
+
timer.it_value = timer.it_interval;
|
230
|
+
setitimer(ITIMER_REAL, &timer, 0);
|
231
|
+
#endif
|
232
|
+
|
213
233
|
rb_define_const(cStacks, "INSTALLED", Qtrue);
|
214
234
|
scout_profiling_installed = 1;
|
215
235
|
|
@@ -260,6 +280,8 @@ init_thread_vars()
|
|
260
280
|
ATOMIC_STORE_INT16(&_start_trace_index, 0);
|
261
281
|
ATOMIC_STORE_INT16(&_cur_traces_num, 0);
|
262
282
|
|
283
|
+
_ruby_thread = rb_thread_current(); // Used as a check to avoid any Fiber switching silliness
|
284
|
+
|
263
285
|
_traces = ALLOC_N(struct c_trace, MAX_TRACES); // TODO Check return
|
264
286
|
|
265
287
|
_gc_hook = Data_Wrap_Struct(rb_cObject, &scoutprof_gc_mark, NULL, &_traces);
|
@@ -267,17 +289,19 @@ init_thread_vars()
|
|
267
289
|
|
268
290
|
res = pthread_atfork(scout_parent_atfork_prepare, scout_parent_atfork_finish, NULL);
|
269
291
|
if (res != 0) {
|
270
|
-
fprintf(stderr, "Pthread_atfork failed: %d\n", res);
|
292
|
+
fprintf(stderr, "APM-DEBUG: Pthread_atfork failed: %d\n", res);
|
271
293
|
}
|
272
294
|
|
295
|
+
#ifdef __linux__
|
273
296
|
// Create timer to target this thread
|
274
297
|
_sev.sigev_notify = SIGEV_THREAD_ID;
|
275
298
|
_sev.sigev_signo = SIGALRM;
|
276
299
|
_sev.sigev_notify_thread_id = syscall(SYS_gettid);
|
277
300
|
_sev.sigev_value.sival_ptr = &_timerid;
|
278
301
|
if (timer_create(CLOCK_MONOTONIC, &_sev, &_timerid) == -1) {
|
279
|
-
fprintf(stderr, "Time create failed: %d\n", errno);
|
302
|
+
fprintf(stderr, "APM-DEBUG: Time create failed: %d\n", errno);
|
280
303
|
}
|
304
|
+
#endif
|
281
305
|
|
282
306
|
return;
|
283
307
|
}
|
@@ -302,6 +326,8 @@ scout_profile_broadcast_signal_handler(int sig)
|
|
302
326
|
|
303
327
|
if (rb_during_gc()) {
|
304
328
|
ATOMIC_ADD(&_skipped_in_gc, 1);
|
329
|
+
} else if (rb_thread_current() != _ruby_thread) {
|
330
|
+
ATOMIC_ADD(&_skipped_in_not_running, 1);
|
305
331
|
} else {
|
306
332
|
if (ATOMIC_LOAD(&_job_registered) == false){
|
307
333
|
register_result = rb_postponed_job_register(0, scout_record_sample, 0);
|
@@ -334,6 +360,10 @@ scout_record_sample()
|
|
334
360
|
ATOMIC_ADD(&_skipped_in_gc, 1);
|
335
361
|
return;
|
336
362
|
}
|
363
|
+
if (rb_thread_current() != _ruby_thread) {
|
364
|
+
ATOMIC_ADD(&_skipped_in_not_running, 1);
|
365
|
+
return;
|
366
|
+
}
|
337
367
|
|
338
368
|
cur_traces_num = ATOMIC_LOAD(&_cur_traces_num);
|
339
369
|
start_frame_index = ATOMIC_LOAD(&_start_frame_index);
|
@@ -359,7 +389,7 @@ static VALUE rb_scout_profile_frames(VALUE self)
|
|
359
389
|
VALUE traces, trace, trace_line;
|
360
390
|
|
361
391
|
if (ATOMIC_LOAD(&_thread_registered) == false) {
|
362
|
-
fprintf(stderr, "Error: trying to get profiled frames on a non-profiled thread!\n");
|
392
|
+
fprintf(stderr, "APM-DEBUG: Error: trying to get profiled frames on a non-profiled thread!\n");
|
363
393
|
ATOMIC_STORE_INT16(&_cur_traces_num, 0);
|
364
394
|
return rb_ary_new();
|
365
395
|
}
|
@@ -373,10 +403,14 @@ static VALUE rb_scout_profile_frames(VALUE self)
|
|
373
403
|
if (_traces[i].num_tracelines > 0) {
|
374
404
|
trace = rb_ary_new2(_traces[i].num_tracelines);
|
375
405
|
for(n = 0; n < _traces[i].num_tracelines; n++) {
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
406
|
+
if (TYPE(_traces[i].frames_buf[n]) == RUBY_T_DATA) { // We should always get valid frames from rb_profile_frames, but that doesn't always seem to be the case
|
407
|
+
trace_line = rb_ary_new2(2);
|
408
|
+
rb_ary_store(trace_line, 0, _traces[i].frames_buf[n]);
|
409
|
+
rb_ary_store(trace_line, 1, INT2FIX(_traces[i].lines_buf[n]));
|
410
|
+
rb_ary_push(trace, trace_line);
|
411
|
+
} else {
|
412
|
+
fprintf(stderr, "APM-DEBUG: Non-data frame is: 0x%04x\n", TYPE(_traces[i].frames_buf[n]));
|
413
|
+
}
|
380
414
|
}
|
381
415
|
rb_ary_push(traces, trace);
|
382
416
|
}
|
@@ -397,15 +431,18 @@ static VALUE rb_scout_profile_frames(VALUE self)
|
|
397
431
|
static void
|
398
432
|
scout_start_thread_timer()
|
399
433
|
{
|
434
|
+
#ifdef __linux__
|
400
435
|
struct itimerspec its;
|
401
436
|
sigset_t mask;
|
437
|
+
#endif
|
402
438
|
|
439
|
+
#ifdef __linux__
|
403
440
|
if (ATOMIC_LOAD(&_thread_registered) == false) return;
|
404
441
|
|
405
442
|
sigemptyset(&mask);
|
406
443
|
sigaddset(&mask, SIGALRM);
|
407
444
|
if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1) {
|
408
|
-
fprintf(stderr, "Block mask failed: %d\n", errno);
|
445
|
+
fprintf(stderr, "APM-DEBUG: Block mask failed: %d\n", errno);
|
409
446
|
}
|
410
447
|
|
411
448
|
its.it_value.tv_sec = 0;
|
@@ -414,25 +451,30 @@ scout_start_thread_timer()
|
|
414
451
|
its.it_interval.tv_nsec = its.it_value.tv_nsec;
|
415
452
|
|
416
453
|
if (timer_settime(_timerid, 0, &its, NULL) == -1) {
|
417
|
-
fprintf(stderr, "Timer set failed in start sampling: %d\n", errno);
|
454
|
+
fprintf(stderr, "APM-DEBUG: Timer set failed in start sampling: %d\n", errno);
|
418
455
|
}
|
419
456
|
|
420
457
|
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) {
|
421
|
-
fprintf(stderr, "UNBlock mask failed: %d\n", errno);
|
458
|
+
fprintf(stderr, "APM-DEBUG: UNBlock mask failed: %d\n", errno);
|
422
459
|
}
|
460
|
+
#endif
|
423
461
|
}
|
424
462
|
|
425
463
|
static void
|
426
464
|
scout_stop_thread_timer()
|
427
465
|
{
|
466
|
+
#ifdef __linux__
|
428
467
|
struct itimerspec its;
|
468
|
+
#endif
|
429
469
|
|
430
470
|
if (ATOMIC_LOAD(&_thread_registered) == false) return;
|
431
471
|
|
472
|
+
#ifdef __linux__
|
432
473
|
memset((void*)&its, 0, sizeof(its));
|
433
474
|
if (timer_settime(_timerid, 0, &its, NULL) == -1 ) {
|
434
|
-
fprintf(stderr, "Timer set failed: %d\n", errno);
|
475
|
+
fprintf(stderr, "APM-DEBUG: Timer set failed: %d\n", errno);
|
435
476
|
}
|
477
|
+
#endif
|
436
478
|
}
|
437
479
|
|
438
480
|
/* Per thread start sampling */
|
@@ -441,7 +483,9 @@ rb_scout_start_sampling(VALUE self)
|
|
441
483
|
{
|
442
484
|
scout_add_profiled_thread(pthread_self());
|
443
485
|
ATOMIC_STORE_BOOL(&_ok_to_sample, true);
|
486
|
+
#ifdef __linux__
|
444
487
|
scout_start_thread_timer();
|
488
|
+
#endif
|
445
489
|
return Qtrue;
|
446
490
|
}
|
447
491
|
|
@@ -450,7 +494,9 @@ static VALUE
|
|
450
494
|
rb_scout_stop_sampling(VALUE self, VALUE reset)
|
451
495
|
{
|
452
496
|
if(ATOMIC_LOAD(&_ok_to_sample) == true ) {
|
497
|
+
#ifdef __linux__
|
453
498
|
scout_stop_thread_timer();
|
499
|
+
#endif
|
454
500
|
}
|
455
501
|
|
456
502
|
ATOMIC_STORE_BOOL(&_ok_to_sample, false);
|
@@ -465,6 +511,7 @@ rb_scout_stop_sampling(VALUE self, VALUE reset)
|
|
465
511
|
ATOMIC_STORE_INT32(&_skipped_in_gc, 0);
|
466
512
|
ATOMIC_STORE_INT32(&_skipped_in_signal_handler, 0);
|
467
513
|
ATOMIC_STORE_INT32(&_skipped_in_job_registered, 0);
|
514
|
+
ATOMIC_STORE_INT32(&_skipped_in_not_running, 0);
|
468
515
|
}
|
469
516
|
return Qtrue;
|
470
517
|
}
|
@@ -520,6 +567,12 @@ rb_scout_skipped_in_job_registered(VALUE self)
|
|
520
567
|
return INT2NUM(ATOMIC_LOAD(&_skipped_in_job_registered));
|
521
568
|
}
|
522
569
|
|
570
|
+
static VALUE
|
571
|
+
rb_scout_skipped_in_not_running(VALUE self)
|
572
|
+
{
|
573
|
+
return INT2NUM(ATOMIC_LOAD(&_skipped_in_not_running));
|
574
|
+
}
|
575
|
+
|
523
576
|
////////////////////////////////////////////////////////////////
|
524
577
|
// Fetch details from a frame
|
525
578
|
////////////////////////////////////////////////////////////////
|
@@ -587,6 +640,7 @@ void Init_stacks()
|
|
587
640
|
rb_define_singleton_method(cStacks, "skipped_in_gc", rb_scout_skipped_in_gc, 0);
|
588
641
|
rb_define_singleton_method(cStacks, "skipped_in_handler", rb_scout_skipped_in_handler, 0);
|
589
642
|
rb_define_singleton_method(cStacks, "skipped_in_job_registered", rb_scout_skipped_in_job_registered, 0);
|
643
|
+
rb_define_singleton_method(cStacks, "skipped_in_not_running", rb_scout_skipped_in_not_running, 0);
|
590
644
|
|
591
645
|
rb_define_const(cStacks, "ENABLED", Qtrue);
|
592
646
|
rb_warning("Finished Initializing ScoutProf Native Extension");
|
@@ -679,6 +733,12 @@ rb_scout_skipped_in_job_registered(VALUE self)
|
|
679
733
|
return INT2NUM(0);
|
680
734
|
}
|
681
735
|
|
736
|
+
static VALUE
|
737
|
+
rb_scout_skipped_in_not_running(VALUE self)
|
738
|
+
{
|
739
|
+
return INT2NUM(0);
|
740
|
+
}
|
741
|
+
|
682
742
|
static VALUE
|
683
743
|
rb_scout_frame_klass(VALUE self, VALUE frame)
|
684
744
|
{
|
@@ -735,6 +795,7 @@ void Init_stacks()
|
|
735
795
|
rb_define_singleton_method(cStacks, "skipped_in_gc", rb_scout_skipped_in_gc, 0);
|
736
796
|
rb_define_singleton_method(cStacks, "skipped_in_handler", rb_scout_skipped_in_handler, 0);
|
737
797
|
rb_define_singleton_method(cStacks, "skipped_in_job_registered", rb_scout_skipped_in_job_registered, 0);
|
798
|
+
rb_define_singleton_method(cStacks, "skipped_in_not_running", rb_scout_skipped_in_not_running, 0);
|
738
799
|
|
739
800
|
rb_define_const(cStacks, "ENABLED", Qfalse);
|
740
801
|
rb_define_const(cStacks, "INSTALLED", Qfalse);
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -55,7 +55,7 @@ module ScoutApm
|
|
55
55
|
@request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
|
56
56
|
|
57
57
|
@store = ScoutApm::Store.new
|
58
|
-
@layaway = ScoutApm::Layaway.new
|
58
|
+
@layaway = ScoutApm::Layaway.new(config, environment)
|
59
59
|
@metric_lookup = Hash.new
|
60
60
|
|
61
61
|
@capacity = ScoutApm::Capacity.new
|
@@ -57,12 +57,12 @@ module ScoutApm
|
|
57
57
|
queue_layer = ScoutApm::Layer.new("Queue", queue)
|
58
58
|
job_layer = ScoutApm::Layer.new("Job", job_class)
|
59
59
|
|
60
|
-
|
60
|
+
if ScoutApm::Agent.instance.config.value('profile') && SidekiqMiddleware.version_supports_profiling?
|
61
61
|
# Capture ScoutProf if we can
|
62
62
|
#req.enable_profiled_thread!
|
63
63
|
#job_layer.set_root_class(job_class)
|
64
64
|
#job_layer.traced!
|
65
|
-
|
65
|
+
end
|
66
66
|
|
67
67
|
req.start_layer(queue_layer)
|
68
68
|
req.start_layer(job_layer)
|
@@ -77,6 +77,10 @@ module ScoutApm
|
|
77
77
|
req.stop_layer # Job
|
78
78
|
req.stop_layer # Queue
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
|
+
def self.version_supports_profiling?
|
82
|
+
@@sidekiq_supports_profling ||= defined?(::Sidekiq::VERSION) && Gem::Dependency.new('', '~> 4.0').match?('', ::Sidekiq::VERSION.to_s)
|
83
|
+
end
|
84
|
+
end # SidekiqMiddleware
|
81
85
|
end
|
82
86
|
end
|
data/lib/scout_apm/config.rb
CHANGED
@@ -137,11 +137,17 @@ module ScoutApm
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def initialize(overlays)
|
140
|
-
@overlays = overlays
|
140
|
+
@overlays = Array(overlays)
|
141
141
|
end
|
142
142
|
|
143
143
|
def value(key)
|
144
|
-
|
144
|
+
o = @overlays.detect{ |overlay| overlay.has_key?(key) }
|
145
|
+
raw_value = if o
|
146
|
+
o.value(key)
|
147
|
+
else
|
148
|
+
# No overlay said it could handle this key, bail out with nil.
|
149
|
+
nil
|
150
|
+
end
|
145
151
|
|
146
152
|
coercion = SETTING_COERCIONS[key] || NullCoercion.new
|
147
153
|
coercion.coerce(raw_value)
|
@@ -61,7 +61,7 @@ module ScoutApm
|
|
61
61
|
:platform => "ruby",
|
62
62
|
}
|
63
63
|
hash = ScoutApm::Serializers::PayloadSerializerToJson.rearrange_slow_transaction(trace)
|
64
|
-
hash.merge!(
|
64
|
+
hash.merge!(:metadata => metadata)
|
65
65
|
payload = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
|
66
66
|
|
67
67
|
if env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
|
@@ -105,4 +105,4 @@ module ScoutApm
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
|
-
end
|
108
|
+
end
|
@@ -173,7 +173,7 @@ module ScoutApm
|
|
173
173
|
req.start_layer(layer)
|
174
174
|
req.ignore_children!
|
175
175
|
begin
|
176
|
-
find_by_sql_without_scout_instruments(*args)
|
176
|
+
find_by_sql_without_scout_instruments(*args, &block)
|
177
177
|
ensure
|
178
178
|
req.acknowledge_children!
|
179
179
|
req.stop_layer
|
@@ -199,7 +199,7 @@ module ScoutApm
|
|
199
199
|
req.start_layer(layer)
|
200
200
|
req.ignore_children!
|
201
201
|
begin
|
202
|
-
find_with_associations_without_scout_instruments(*args)
|
202
|
+
find_with_associations_without_scout_instruments(*args, &block)
|
203
203
|
ensure
|
204
204
|
req.acknowledge_children!
|
205
205
|
req.stop_layer
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -18,8 +18,12 @@ module ScoutApm
|
|
18
18
|
# Must be sortable as an integer
|
19
19
|
TIME_FORMAT = "%Y%m%d%H%M"
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
attr_reader :config
|
22
|
+
attr_reader :environment
|
23
|
+
|
24
|
+
def initialize(config, environment)
|
25
|
+
@config = config
|
26
|
+
@environment = environment
|
23
27
|
end
|
24
28
|
|
25
29
|
# Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
|
@@ -30,12 +34,12 @@ module ScoutApm
|
|
30
34
|
def directory
|
31
35
|
return @directory if @directory
|
32
36
|
|
33
|
-
data_file =
|
34
|
-
data_file = File.dirname(data_file) if data_file && !File.directory?
|
37
|
+
data_file = config.value("data_file")
|
38
|
+
data_file = File.dirname(data_file) if data_file && !File.directory?(data_file)
|
35
39
|
|
36
40
|
candidates = [
|
37
41
|
data_file,
|
38
|
-
"#{
|
42
|
+
"#{environment.root}/tmp",
|
39
43
|
"/tmp"
|
40
44
|
].compact
|
41
45
|
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -154,6 +154,7 @@ module ScoutApm
|
|
154
154
|
traces.skipped_in_gc = ScoutApm::Instruments::Stacks.skipped_in_gc
|
155
155
|
traces.skipped_in_handler = ScoutApm::Instruments::Stacks.skipped_in_handler
|
156
156
|
traces.skipped_in_job_registered = ScoutApm::Instruments::Stacks.skipped_in_job_registered
|
157
|
+
traces.skipped_in_not_running = ScoutApm::Instruments::Stacks.skipped_in_not_running
|
157
158
|
end
|
158
159
|
end
|
159
160
|
end
|
@@ -139,7 +139,7 @@ module ScoutApm
|
|
139
139
|
if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
|
140
140
|
ScoutApm::Agent.instance.logger.debug do
|
141
141
|
traces_inspect = layer.traces.inspect
|
142
|
-
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}:\n#{traces_inspect}"
|
142
|
+
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
|
143
143
|
end
|
144
144
|
end
|
145
145
|
else
|
@@ -133,7 +133,7 @@ module ScoutApm
|
|
133
133
|
if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
|
134
134
|
ScoutApm::Agent.instance.logger.debug do
|
135
135
|
traces_inspect = layer.traces.inspect
|
136
|
-
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}:\n#{traces_inspect}"
|
136
|
+
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
|
137
137
|
end
|
138
138
|
end
|
139
139
|
else
|
data/lib/scout_apm/version.rb
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -128,7 +128,6 @@ require 'scout_apm/metric_meta'
|
|
128
128
|
require 'scout_apm/metric_stats'
|
129
129
|
require 'scout_apm/slow_transaction'
|
130
130
|
require 'scout_apm/slow_job_record'
|
131
|
-
require 'scout_apm/slow_item_set'
|
132
131
|
require 'scout_apm/scored_item_set'
|
133
132
|
require 'scout_apm/slow_request_policy'
|
134
133
|
require 'scout_apm/slow_job_policy'
|
data/test/test_helper.rb
CHANGED
@@ -17,6 +17,34 @@ Kernel.module_eval do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
# A test helper class to create a temporary "configuration" we can control entirely purposes
|
21
|
+
class FakeConfigOverlay
|
22
|
+
def initialize(values)
|
23
|
+
@values = values
|
24
|
+
end
|
25
|
+
|
26
|
+
def value(key)
|
27
|
+
@values[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_key?(key)
|
31
|
+
@values.has_key?(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class FakeEnvironment
|
36
|
+
def initialize(values)
|
37
|
+
@values = values
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(sym)
|
41
|
+
if @values.has_key?(sym)
|
42
|
+
@values[sym]
|
43
|
+
else
|
44
|
+
raise "#{sym} not found in FakeEnvironment"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
20
48
|
|
21
49
|
# Helpers available to all tests
|
22
50
|
class Minitest::Test
|
@@ -42,6 +70,16 @@ class Minitest::Test
|
|
42
70
|
ScoutApm::Agent.instance.instance_variable_set("@logger", @logger)
|
43
71
|
end
|
44
72
|
|
73
|
+
def make_fake_environment(values)
|
74
|
+
FakeEnvironment.new(values)
|
75
|
+
end
|
76
|
+
|
77
|
+
def make_fake_config(values)
|
78
|
+
ScoutApm::Config.new(FakeConfigOverlay.new(values))
|
79
|
+
end
|
80
|
+
|
45
81
|
DATA_FILE_DIR = File.dirname(__FILE__) + '/tmp'
|
46
82
|
DATA_FILE_PATH = "#{DATA_FILE_DIR}/scout_apm.db"
|
47
83
|
end
|
84
|
+
|
85
|
+
|
data/test/unit/config_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'scout_apm/config'
|
|
4
4
|
|
5
5
|
class ConfigTest < Minitest::Test
|
6
6
|
def test_initalize_without_a_config
|
7
|
-
conf = ScoutApm::Config.
|
7
|
+
conf = ScoutApm::Config.without_file
|
8
8
|
|
9
9
|
# nil for random keys
|
10
10
|
assert_nil conf.value("log_file_path")
|
@@ -21,7 +21,7 @@ class ConfigTest < Minitest::Test
|
|
21
21
|
set_rack_env("production")
|
22
22
|
|
23
23
|
conf_file = File.expand_path("../../data/config_test_1.yml", __FILE__)
|
24
|
-
conf = ScoutApm::Config.
|
24
|
+
conf = ScoutApm::Config.with_file(conf_file)
|
25
25
|
|
26
26
|
assert_equal "debug", conf.value('log_level')
|
27
27
|
assert_equal "APM Test Conf (Production)", conf.value('name')
|
data/test/unit/layaway_test.rb
CHANGED
@@ -5,25 +5,20 @@ require 'scout_apm/metric_stats'
|
|
5
5
|
require 'scout_apm/context'
|
6
6
|
require 'scout_apm/store'
|
7
7
|
|
8
|
+
require 'fileutils'
|
8
9
|
class LayawayTest < Minitest::Test
|
9
|
-
def
|
10
|
-
|
11
|
-
|
10
|
+
def test_directory_uses_DATA_FILE_option
|
11
|
+
FileUtils.mkdir_p '/tmp/scout_apm_test/data_file_option'
|
12
|
+
config = make_fake_config("data_file" => "/tmp/scout_apm_test/data_file_option")
|
12
13
|
|
13
|
-
|
14
|
-
t = ScoutApm::StoreReportingPeriodTimestamp.new
|
15
|
-
data.add_reporting_period(t,ScoutApm::StoreReportingPeriod.new(t))
|
16
|
-
assert_equal [TIMESTAMP,t].sort_by(&:timestamp), Marshal.load(File.read(DATA_FILE_PATH)).keys.sort_by(&:timestamp)
|
14
|
+
assert_equal Pathname.new("/tmp/scout_apm_test/data_file_option"), ScoutApm::Layaway.new(config, ScoutApm::Agent.instance.environment).directory
|
17
15
|
end
|
18
16
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
unmarshalled = Marshal.load(File.read(DATA_FILE_PATH))
|
24
|
-
assert_equal [TIMESTAMP], unmarshalled.keys
|
25
|
-
end
|
17
|
+
def test_directory_looks_for_root_slash_tmp
|
18
|
+
FileUtils.mkdir_p '/tmp/scout_apm_test/tmp'
|
19
|
+
config = make_fake_config({})
|
20
|
+
env = make_fake_environment(:root => "/tmp/scout_apm_test")
|
26
21
|
|
27
|
-
|
28
|
-
|
22
|
+
assert_equal Pathname.new("/tmp/scout_apm_test/tmp"), ScoutApm::Layaway.new(config, env).directory
|
23
|
+
end
|
29
24
|
end
|
@@ -6,26 +6,11 @@ require 'scout_apm/serializers/payload_serializer_to_json'
|
|
6
6
|
require 'scout_apm/slow_transaction'
|
7
7
|
require 'scout_apm/metric_meta'
|
8
8
|
require 'scout_apm/metric_stats'
|
9
|
-
require 'scout_apm/utils/fake_stack_prof'
|
10
9
|
require 'scout_apm/context'
|
11
10
|
require 'ostruct'
|
12
11
|
require 'json' # to deserialize what has been manually serialized by the production code
|
13
12
|
|
14
|
-
# stub the report_format value
|
15
|
-
# class ScoutApm::Agent
|
16
|
-
# module Config
|
17
|
-
# def self.value(key)
|
18
|
-
# 'json'
|
19
|
-
# end
|
20
|
-
# end
|
21
|
-
|
22
|
-
# def self.instance
|
23
|
-
# OpenStruct.new(:config => Config)
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
|
27
13
|
class PayloadSerializerTest < Minitest::Test
|
28
|
-
|
29
14
|
def test_serializes_metadata_as_json
|
30
15
|
metadata = {
|
31
16
|
:app_root => "/srv/app/rootz",
|
@@ -112,93 +97,6 @@ class PayloadSerializerTest < Minitest::Test
|
|
112
97
|
assert_equal formatted_metrics, JSON.parse(payload)["metrics"]
|
113
98
|
end
|
114
99
|
|
115
|
-
def test_serializes_slow_transactions_as_json
|
116
|
-
slow_transaction_metrics = {
|
117
|
-
ScoutApm::MetricMeta.new('ActiveRecord/all').tap { |meta|
|
118
|
-
meta.desc = "SELECT *\nfrom users where filter=?"
|
119
|
-
meta.extra = {:user => 'cooluser'}
|
120
|
-
meta.metric_id = nil
|
121
|
-
meta.scope = "Controller/apps/checkin"
|
122
|
-
} => ScoutApm::MetricStats.new.tap { |stats|
|
123
|
-
stats.call_count = 16
|
124
|
-
stats.max_call_time = 0.005338062
|
125
|
-
stats.min_call_time = 0.000613518
|
126
|
-
stats.sum_of_squares = 9.8040860751126e-05
|
127
|
-
stats.total_call_time = 0.033245704
|
128
|
-
stats.total_exclusive_time = 0.033245704
|
129
|
-
},
|
130
|
-
ScoutApm::MetricMeta.new("Controller/apps/checkin").tap { |meta|
|
131
|
-
meta.desc = nil
|
132
|
-
meta.extra = {}
|
133
|
-
meta.metric_id = nil
|
134
|
-
meta.scope = nil
|
135
|
-
} => ScoutApm::MetricStats.new.tap { |stats|
|
136
|
-
stats.call_count = 2
|
137
|
-
stats.max_call_time = 0.078521419
|
138
|
-
stats.min_call_time = 0.034881757
|
139
|
-
stats.sum_of_squares = 0.007382350213180609
|
140
|
-
stats.total_call_time = 0.113403176
|
141
|
-
stats.total_exclusive_time = 0.07813208899999999
|
142
|
-
}
|
143
|
-
}
|
144
|
-
context = ScoutApm::Context.new
|
145
|
-
context.add({"this" => "that"})
|
146
|
-
context.add_user({"hello" => "goodbye"})
|
147
|
-
slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), [], 10)
|
148
|
-
payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, {}, [slow_t], [], [])
|
149
|
-
formatted_slow_transactions = [
|
150
|
-
{
|
151
|
-
"key" => {
|
152
|
-
"bucket" => "Buckethead",
|
153
|
-
"name" => "something/else"
|
154
|
-
},
|
155
|
-
"time" => "2015-11-22 06:26:28 -0700",
|
156
|
-
"total_call_time" => 1.23,
|
157
|
-
"uri" => "http://example.com/blabla",
|
158
|
-
"context" => {"this"=>"that", "user"=>{"hello"=>"goodbye"}},
|
159
|
-
"prof" => [],
|
160
|
-
"score" => 10,
|
161
|
-
"metrics" => [
|
162
|
-
{
|
163
|
-
"key" => {
|
164
|
-
"bucket" => "ActiveRecord",
|
165
|
-
"name" => "all",
|
166
|
-
"desc" => "SELECT *\nfrom users where filter=?",
|
167
|
-
"extra" => {
|
168
|
-
"user" => "cooluser",
|
169
|
-
},
|
170
|
-
"scope" => {
|
171
|
-
"bucket" => "Controller",
|
172
|
-
"name" => "apps/checkin",
|
173
|
-
},
|
174
|
-
},
|
175
|
-
"call_count" => 16,
|
176
|
-
"max_call_time" => 0.005338062,
|
177
|
-
"min_call_time" => 0.000613518,
|
178
|
-
"total_call_time" => 0.033245704,
|
179
|
-
"total_exclusive_time" => 0.033245704,
|
180
|
-
},
|
181
|
-
{
|
182
|
-
"key" => {
|
183
|
-
"bucket" => "Controller",
|
184
|
-
"name" => "apps/checkin",
|
185
|
-
"desc" => nil,
|
186
|
-
"extra" => {},
|
187
|
-
"scope" => nil,
|
188
|
-
},
|
189
|
-
"call_count" => 2,
|
190
|
-
"max_call_time" => 0.078521419,
|
191
|
-
"min_call_time" => 0.034881757,
|
192
|
-
"total_call_time" => 0.113403176,
|
193
|
-
"total_exclusive_time" => 0.07813208899999999,
|
194
|
-
}
|
195
|
-
]
|
196
|
-
}
|
197
|
-
]
|
198
|
-
|
199
|
-
assert_equal formatted_slow_transactions, JSON.parse(payload)["slow_transactions"]
|
200
|
-
end
|
201
|
-
|
202
100
|
def test_escapes_json_quotes
|
203
101
|
metadata = {
|
204
102
|
:quotie => "here are some \"quotes\"",
|
@@ -3,53 +3,4 @@ require 'test_helper'
|
|
3
3
|
require 'scout_apm/slow_job_policy'
|
4
4
|
|
5
5
|
class SlowJobPolicyTest < Minitest::Test
|
6
|
-
def test_first_call_is_not_slow
|
7
|
-
policy = ScoutApm::SlowJobPolicy.new
|
8
|
-
assert !policy.slow?("TestWorker", 10)
|
9
|
-
end
|
10
|
-
|
11
|
-
# All of these get faster and faster, so none are marked as slow.
|
12
|
-
def test_fast_calls_are_not_slow
|
13
|
-
policy = ScoutApm::SlowJobPolicy.new
|
14
|
-
assert !policy.slow?("TestWorker", 10)
|
15
|
-
assert !policy.slow?("TestWorker", 8)
|
16
|
-
assert !policy.slow?("TestWorker", 6)
|
17
|
-
assert !policy.slow?("TestWorker", 4)
|
18
|
-
assert !policy.slow?("TestWorker", 2)
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_slow_calls_are_marked_as_slow
|
22
|
-
policy = ScoutApm::SlowJobPolicy.new
|
23
|
-
policy.slow?("TestWorker", 10) # Prime it with a not-slow
|
24
|
-
|
25
|
-
assert policy.slow?("TestWorker", 12)
|
26
|
-
assert policy.slow?("TestWorker", 14)
|
27
|
-
assert policy.slow?("TestWorker", 16)
|
28
|
-
assert policy.slow?("TestWorker", 18)
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_mix_of_fast_and_slow
|
32
|
-
policy = ScoutApm::SlowJobPolicy.new
|
33
|
-
policy.slow?("TestWorker", 10) # Prime it with a not-slow
|
34
|
-
|
35
|
-
assert policy.slow?("TestWorker", 12)
|
36
|
-
assert !policy.slow?("TestWorker", 8)
|
37
|
-
assert policy.slow?("TestWorker", 13)
|
38
|
-
assert !policy.slow?("TestWorker", 6)
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_different_workers_dont_interfere
|
42
|
-
policy = ScoutApm::SlowJobPolicy.new
|
43
|
-
policy.slow?("TestWorker", 10) # Prime it with a not-slow
|
44
|
-
policy.slow?("OtherWorker", 1.0) # Prime it with a not-slow
|
45
|
-
|
46
|
-
assert !policy.slow?("TestWorker", 8)
|
47
|
-
assert policy.slow?("OtherWorker", 2)
|
48
|
-
assert !policy.slow?("TestWorker", 1)
|
49
|
-
assert policy.slow?("OtherWorker", 3)
|
50
|
-
assert policy.slow?("TestWorker", 12)
|
51
|
-
assert !policy.slow?("OtherWorker", 1)
|
52
|
-
assert policy.slow?("TestWorker", 12)
|
53
|
-
assert policy.slow?("OtherWorker", 4)
|
54
|
-
end
|
55
6
|
end
|
@@ -32,11 +32,10 @@ class SlowRequestPolicyTest < Minitest::Test
|
|
32
32
|
|
33
33
|
request.set_duration(10) # 10 seconds
|
34
34
|
policy.last_seen[request.unique_name] = Time.now - 120 # 2 minutes since last seen
|
35
|
-
|
36
|
-
|
37
|
-
# Actual value I have in console is 1.599
|
38
|
-
assert policy.score(request) > 1.5
|
39
|
-
assert policy.score(request) < 2.0
|
35
|
+
ScoutApm::Agent.instance.request_histograms.add(request.unique_name, 1)
|
40
36
|
|
37
|
+
# Actual value I have in console is 1.499
|
38
|
+
assert policy.score(request) > 1.45
|
39
|
+
assert policy.score(request) < 1.55
|
41
40
|
end
|
42
41
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.0.
|
4
|
+
version: 2.2.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-08-
|
12
|
+
date: 2016-08-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rusage
|
@@ -204,7 +204,6 @@ files:
|
|
204
204
|
- lib/scout_apm/server_integrations/thin.rb
|
205
205
|
- lib/scout_apm/server_integrations/unicorn.rb
|
206
206
|
- lib/scout_apm/server_integrations/webrick.rb
|
207
|
-
- lib/scout_apm/slow_item_set.rb
|
208
207
|
- lib/scout_apm/slow_job_policy.rb
|
209
208
|
- lib/scout_apm/slow_job_record.rb
|
210
209
|
- lib/scout_apm/slow_request_policy.rb
|
@@ -239,7 +238,6 @@ files:
|
|
239
238
|
- test/unit/metric_set_test.rb
|
240
239
|
- test/unit/scored_item_set_test.rb
|
241
240
|
- test/unit/serializers/payload_serializer_test.rb
|
242
|
-
- test/unit/slow_item_set_test.rb
|
243
241
|
- test/unit/slow_job_policy_test.rb
|
244
242
|
- test/unit/slow_request_policy_test.rb
|
245
243
|
- test/unit/sql_sanitizer_test.rb
|
@@ -1,80 +0,0 @@
|
|
1
|
-
# In order to keep load down, only record a sample of Slow Items (Transactions
|
2
|
-
# or Jobs). In order to make that sampling as fair as possible, follow a basic
|
3
|
-
# algorithm:
|
4
|
-
#
|
5
|
-
# When adding a new Slow Item:
|
6
|
-
# * Just add it if there is an open spot
|
7
|
-
# * If there isn't an open spot, attempt to remove an over-represented
|
8
|
-
# item instead ("attempt_to_evict"). Overrepresented is simply "has more
|
9
|
-
# than @fair number of Matching Items in the set". The fastest of the
|
10
|
-
# overrepresented items is removed.
|
11
|
-
# * If there isn't an open spot, and no Item is valid to evict, drop the
|
12
|
-
# incoming Item without adding.
|
13
|
-
#
|
14
|
-
# There is no way to remove Items from this set, create a new object
|
15
|
-
# for each reporting period.
|
16
|
-
#
|
17
|
-
# Item must respond to:
|
18
|
-
# #metric_name - string - grouping key to see if one kind of thing is overrepresented
|
19
|
-
# #total_call_time - float - duration of the item
|
20
|
-
|
21
|
-
module ScoutApm
|
22
|
-
class SlowItemSet
|
23
|
-
include Enumerable
|
24
|
-
|
25
|
-
DEFAULT_TOTAL = 10
|
26
|
-
DEFAULT_FAIR = 1
|
27
|
-
|
28
|
-
attr_reader :total
|
29
|
-
attr_reader :fair
|
30
|
-
|
31
|
-
def initialize(total=DEFAULT_TOTAL, fair=DEFAULT_FAIR)
|
32
|
-
@total = total
|
33
|
-
@fair = fair
|
34
|
-
@items = []
|
35
|
-
end
|
36
|
-
|
37
|
-
def each
|
38
|
-
@items.each { |s| yield s }
|
39
|
-
end
|
40
|
-
|
41
|
-
def <<(item)
|
42
|
-
return if attempt_append(item)
|
43
|
-
attempt_to_evict
|
44
|
-
attempt_append(item)
|
45
|
-
end
|
46
|
-
|
47
|
-
def empty_slot?
|
48
|
-
@items.length < total
|
49
|
-
end
|
50
|
-
|
51
|
-
def attempt_append(item)
|
52
|
-
if empty_slot?
|
53
|
-
@items.push(item)
|
54
|
-
true
|
55
|
-
else
|
56
|
-
false
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def attempt_to_evict
|
61
|
-
return if @items.length == 0
|
62
|
-
|
63
|
-
overrepresented = @items.
|
64
|
-
group_by { |item| unique_name_for(item) }.
|
65
|
-
to_a.
|
66
|
-
sort_by { |(_, items)| items.length }.
|
67
|
-
last
|
68
|
-
|
69
|
-
if overrepresented[1].length > fair
|
70
|
-
fastest = overrepresented[1].sort_by { |item| item.total_call_time }.first
|
71
|
-
@items.delete(fastest)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Determine this items' "hash key"
|
76
|
-
def unique_name_for(item)
|
77
|
-
item.metric_name
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
require 'scout_apm/slow_item_set'
|
4
|
-
require 'scout_apm/slow_transaction'
|
5
|
-
|
6
|
-
class SlowItemSetTest < Minitest::Test
|
7
|
-
def test_adding_to_empty_set
|
8
|
-
set = ScoutApm::SlowItemSet.new(3, 1)
|
9
|
-
set << make_slow("Controller/Foo")
|
10
|
-
assert_equal 1, set.count
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_adding_to_partially_full_set
|
14
|
-
set = ScoutApm::SlowItemSet.new(3, 1)
|
15
|
-
set << make_slow("Controller/Foo")
|
16
|
-
set << make_slow("Controller/Foo")
|
17
|
-
assert_equal 2, set.count
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_overflow_of_one_type
|
21
|
-
max_size = 3
|
22
|
-
set = ScoutApm::SlowItemSet.new(max_size, 1)
|
23
|
-
set << make_slow("Controller/Foo")
|
24
|
-
set << make_slow("Controller/Foo")
|
25
|
-
set << make_slow("Controller/Foo")
|
26
|
-
set << make_slow("Controller/Foo")
|
27
|
-
set << make_slow("Controller/Foo")
|
28
|
-
set << make_slow("Controller/Foo")
|
29
|
-
assert_equal max_size, set.count
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_eviction_of_overrepresented
|
33
|
-
max_size = 3
|
34
|
-
set = ScoutApm::SlowItemSet.new(max_size, 1)
|
35
|
-
set << make_slow("Controller/Foo")
|
36
|
-
set << make_slow("Controller/Foo")
|
37
|
-
set << make_slow("Controller/Foo")
|
38
|
-
set << make_slow("Controller/Foo")
|
39
|
-
set << make_slow("Controller/Foo")
|
40
|
-
set << make_slow("Controller/Bar")
|
41
|
-
|
42
|
-
# 3 total
|
43
|
-
assert_equal max_size, set.count
|
44
|
-
assert_equal 1, set.select{|sl| sl.metric_name == "Controller/Bar"}.length
|
45
|
-
assert_equal 2, set.select{|sl| sl.metric_name == "Controller/Foo"}.length
|
46
|
-
end
|
47
|
-
|
48
|
-
# Fill the set with /Foo records, then add a /Bar to evict. Check that the
|
49
|
-
# evicted one was the fastest of the Foos
|
50
|
-
def test_eviction_of_fastest
|
51
|
-
max_size = 3
|
52
|
-
set = ScoutApm::SlowItemSet.new(max_size, 1)
|
53
|
-
|
54
|
-
[1,2,3].shuffle.each do |seconds| # Shuffle to remove any assumptions on order
|
55
|
-
set << make_slow("Controller/Foo", seconds)
|
56
|
-
end
|
57
|
-
set << make_slow("Controller/Bar", 8)
|
58
|
-
|
59
|
-
# The foo taking 1 second should be evicted
|
60
|
-
assert_equal 2, set.select{|sl| sl.metric_name == "Controller/Foo"}.map{ |sl| sl.total_call_time}.min
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_eviction_when_no_overrepresented
|
64
|
-
max_size = 4
|
65
|
-
fair = 2
|
66
|
-
set = ScoutApm::SlowItemSet.new(max_size, fair)
|
67
|
-
|
68
|
-
# Full, but each is at fair level
|
69
|
-
set << make_slow("Controller/Bar")
|
70
|
-
set << make_slow("Controller/Bar")
|
71
|
-
set << make_slow("Controller/Foo")
|
72
|
-
set << make_slow("Controller/Foo")
|
73
|
-
|
74
|
-
set << make_slow("Controller/Quux")
|
75
|
-
assert_equal max_size, set.count
|
76
|
-
assert_equal 0, set.select{|sl| sl.metric_name == "Controller/Quux" }.length
|
77
|
-
end
|
78
|
-
|
79
|
-
##############
|
80
|
-
#### Helpers
|
81
|
-
##############
|
82
|
-
|
83
|
-
def make_slow(metric, time=5)
|
84
|
-
ScoutApm::SlowTransaction.new(
|
85
|
-
"http://foo.app/#{metric}",
|
86
|
-
metric,
|
87
|
-
time,
|
88
|
-
{}, # metrics
|
89
|
-
{}, # context
|
90
|
-
Time.now, # end time
|
91
|
-
[], # stackprof
|
92
|
-
0)
|
93
|
-
end
|
94
|
-
end
|