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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a1a02cb2156a16b3ddaa054eb34065653ed2abb7
4
- data.tar.gz: 8023afd5daf499a2441ee54b9d7c5914ec00ac73
3
+ metadata.gz: 04b5ba56e4b5d547ab50ca2c7bf9de7ad51175b4
4
+ data.tar.gz: d8a85c252b959e001d5b92498de94ab844a76f6b
5
5
  SHA512:
6
- metadata.gz: 2483b8a46a365e9568f36f7d2c46b8271b295b9c12c950694318d4dd09668d91d30a39cd3c0b7c03396ce07e9a5bd9f1f77710d9cc3175cfb09d57e41aea8d28
7
- data.tar.gz: 979cae85d0a830fe3c4ff4be8640f7bc503e1bceb72bb096ae731d7679bf93b2d17de266dd300a57b23029014ccabc61d20863fcd6d8b104b3c82e4b632c91d5
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
@@ -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
- can_compile &&= have_library('rt') # for timer_create, timer_settime
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
- trace_line = rb_ary_new2(2);
377
- rb_ary_store(trace_line, 0, _traces[i].frames_buf[n]);
378
- rb_ary_store(trace_line, 1, INT2FIX(_traces[i].lines_buf[n]));
379
- rb_ary_push(trace, trace_line);
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);
@@ -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
- #if ScoutApm::Agent.instance.config.value('profile')
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
- #end
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
- end
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
@@ -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
- raw_value = @overlays.detect{ |overlay| overlay.has_key?(key) }.value(key)
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!(metadata:metadata)
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
@@ -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
- def initialize(directory=nil)
22
- @directory = directory
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 = ScoutApm::Agent.instance.config.value("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
- "#{ScoutApm::Agent.instance.environment.root}/tmp",
42
+ "#{environment.root}/tmp",
39
43
  "/tmp"
40
44
  ].compact
41
45
 
@@ -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
@@ -19,6 +19,7 @@ class TraceSet
19
19
  attr_accessor :skipped_in_gc
20
20
  attr_accessor :skipped_in_handler
21
21
  attr_accessor :skipped_in_job_registered
22
+ attr_accessor :skipped_in_not_running
22
23
 
23
24
  def initialize
24
25
  @raw_traces = []
@@ -77,6 +77,10 @@ module ScoutApm
77
77
  def skipped_in_job_registered(*args)
78
78
  0
79
79
  end
80
+
81
+ def skipped_in_not_running(*args)
82
+ 0
83
+ end
80
84
  end
81
85
  end
82
86
  end
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.2.0.pre0"
2
+ VERSION = "2.2.0.pre1"
3
3
  end
4
4
 
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
+
@@ -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.new(nil)
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.new(conf_file)
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')
@@ -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 test_add_reporting_period
10
- File.open(DATA_FILE_PATH, 'w') { |file| file.write(Marshal.dump(NEW_FORMAT)) }
11
- ScoutApm::Agent.instance.start
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
- data = ScoutApm::Layaway.new
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 test_merge_reporting_period
20
- File.open(DATA_FILE_PATH, 'w') { |file| file.write(Marshal.dump(NEW_FORMAT)) }
21
- layaway = ScoutApm::Layaway.new
22
- layaway.add_reporting_period(TIMESTAMP, ScoutApm::StoreReportingPeriod.new(TIMESTAMP))
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
- TIMESTAMP = ScoutApm::StoreReportingPeriodTimestamp.new(Time.parse("2015-01-01"))
28
- NEW_FORMAT = {TIMESTAMP => ScoutApm::StoreReportingPeriod.new(TIMESTAMP)} # Format for 1.2+ agents
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
- agent.request_histograms.add(request.unique_name, 1)
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.pre0
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-15 00:00:00.000000000 Z
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