scout_apm 2.2.0.pre0 → 2.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|