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 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