scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -27,6 +27,14 @@ module ScoutApm
27
27
  end
28
28
  end
29
29
 
30
+ def model
31
+ parts.first
32
+ end
33
+
34
+ def normalized_operation
35
+ parse_operation
36
+ end
37
+
30
38
  # For the layer lookup.
31
39
  def hash
32
40
  h = name.downcase.hash
@@ -44,10 +52,6 @@ module ScoutApm
44
52
 
45
53
  private
46
54
 
47
- def model
48
- parts.first
49
- end
50
-
51
55
  def operation
52
56
  if parts.length >= 2
53
57
  parts[1].downcase
@@ -6,8 +6,8 @@ require 'scout_apm/environment'
6
6
  module ScoutApm
7
7
  module Utils
8
8
  class BacktraceParser
9
-
10
- APP_FRAMES = 3 # will return up to 3 frames from the app stack.
9
+ # will return this many backtrace frames from the app stack.
10
+ APP_FRAMES = 8
11
11
 
12
12
  attr_reader :call_stack
13
13
 
@@ -24,7 +24,7 @@ module ScoutApm
24
24
  stack = []
25
25
  call_stack.each do |c|
26
26
  if m = c.match(@@app_dir_regex)
27
- stack << m[1]
27
+ stack << ScoutApm::Utils::Scm.relative_scm_path(m[1])
28
28
  break if stack.size == APP_FRAMES
29
29
  end
30
30
  end
@@ -0,0 +1,24 @@
1
+ module ScoutApm
2
+ module Utils
3
+ # A simple wrapper around Ruby's built-in gzip support.
4
+ class GzipHelper
5
+ DEFAULT_GZIP_LEVEL = 5
6
+
7
+ attr_reader :level
8
+
9
+ def initialize(level = DEFAULT_GZIP_LEVEL)
10
+ @level = level
11
+ end
12
+
13
+ def deflate(str)
14
+ strio = StringIO.new
15
+
16
+ gz = Zlib::GzipWriter.new(strio, level)
17
+ gz.write str
18
+ gz.close
19
+
20
+ strio.string
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module ScoutApm
2
+ module Utils
3
+ class Numbers
4
+
5
+ # Round a float to a certain number of decimal places
6
+ def self.round(number, decimals)
7
+ factor = 10 ** decimals
8
+
9
+ (number * factor).round / factor.to_f
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # Module for helping to deal with Source Code Management settings
2
+ module ScoutApm
3
+ module Utils
4
+ class Scm
5
+ # Takes an *already relative* path +path+
6
+ # Returns a relative path, prepending the configured +scm_subdirectory+ environment string
7
+ def self.relative_scm_path(path, scm_subdirectory = ScoutApm::Environment.instance.scm_subdirectory)
8
+ @@scm_subdirectory ||= scm_subdirectory.sub(/^\//, '')
9
+ @@scm_subdirectoy_blank ||= @@scm_subdirectory.empty?
10
+ @@scm_subdirectoy_blank ? path : File.join(@@scm_subdirectory, path)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.2.0.pre3"
2
+ VERSION = "2.3.0.pre"
3
3
  end
4
4
 
data/scout_apm.gemspec CHANGED
@@ -19,9 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib","data"]
21
21
  s.extensions << 'ext/allocations/extconf.rb'
22
- s.extensions << 'ext/stacks/extconf.rb'
23
-
24
- s.add_runtime_dependency "rusage", '~> 0.2.0'
22
+ s.extensions << 'ext/rusage/extconf.rb'
25
23
 
26
24
  s.add_development_dependency "minitest"
27
25
  s.add_development_dependency 'mocha'
@@ -29,4 +27,7 @@ Gem::Specification.new do |s|
29
27
  s.add_development_dependency "m"
30
28
  s.add_development_dependency "simplecov"
31
29
  s.add_development_dependency "rake-compiler"
32
- end
30
+ s.add_development_dependency "addressable"
31
+ s.add_development_dependency "guard"
32
+ s.add_development_dependency "guard-minitest"
33
+ end
data/test/test_helper.rb CHANGED
@@ -16,6 +16,14 @@ Kernel.module_eval do
16
16
  def self.const_unset(const)
17
17
  self.instance_eval { remove_const(const) }
18
18
  end
19
+
20
+ def silence_warnings(&block)
21
+ warn_level = $VERBOSE
22
+ $VERBOSE = nil
23
+ result = block.call
24
+ $VERBOSE = warn_level
25
+ result
26
+ end
19
27
  end
20
28
 
21
29
  # A test helper class to create a temporary "configuration" we can control entirely purposes
@@ -83,3 +91,13 @@ class Minitest::Test
83
91
  DATA_FILE_PATH = "#{DATA_FILE_DIR}/scout_apm.db"
84
92
  end
85
93
 
94
+
95
+ module CustomAsserts
96
+ def assert_false(thing)
97
+ assert !thing
98
+ end
99
+ end
100
+
101
+ class Minitest::Test
102
+ include CustomAsserts
103
+ end
@@ -7,24 +7,75 @@ class ConfigTest < Minitest::Test
7
7
  conf = ScoutApm::Config.without_file
8
8
 
9
9
  # nil for random keys
10
- assert_nil conf.value("log_file_path")
10
+ assert_nil conf.value('log_file_path')
11
11
 
12
12
  # but has values for defaulted keys
13
- assert conf.value("host")
13
+ assert conf.value('host')
14
14
 
15
15
  # and still reads from ENV
16
16
  ENV['SCOUT_CONFIG_TEST_KEY'] = 'testval'
17
- assert_equal 'testval', conf.value("config_test_key")
17
+ assert_equal 'testval', conf.value('config_test_key')
18
18
  end
19
19
 
20
20
  def test_loading_a_file
21
- set_rack_env("production")
21
+ set_rack_env('production')
22
22
 
23
- conf_file = File.expand_path("../../data/config_test_1.yml", __FILE__)
23
+ conf_file = File.expand_path('../../data/config_test_1.yml', __FILE__)
24
24
  conf = ScoutApm::Config.with_file(conf_file)
25
25
 
26
- assert_equal "debug", conf.value('log_level')
27
- assert_equal "APM Test Conf (Production)", conf.value('name')
26
+ assert_equal 'debug', conf.value('log_level')
27
+ assert_equal 'APM Test Conf (Production)', conf.value('name')
28
28
  end
29
- end
30
29
 
30
+ def test_loading_file_without_env_in_file
31
+ conf_file = File.expand_path("../../data/config_test_1.yml", __FILE__)
32
+ conf = ScoutApm::Config.with_file(conf_file, environment: "staging")
33
+
34
+ assert_equal "info", conf.value('log_level') # the default value
35
+ assert_nil nil, conf.value('name') # the default value
36
+ end
37
+
38
+ def test_boolean_coercion
39
+ coercion = ScoutApm::Config::BooleanCoercion.new
40
+ assert_equal true, coercion.coerce("true")
41
+ assert_equal true, coercion.coerce("t")
42
+ assert_equal false, coercion.coerce("false")
43
+ assert_equal false, coercion.coerce("f")
44
+ assert_equal false, coercion.coerce("")
45
+
46
+ assert_equal true, coercion.coerce(true)
47
+ assert_equal false, coercion.coerce(false)
48
+ assert_equal false, coercion.coerce(nil)
49
+
50
+ assert_equal true, coercion.coerce(1)
51
+ assert_equal true, coercion.coerce(20)
52
+ assert_equal true, coercion.coerce(-1)
53
+ assert_equal false, coercion.coerce(0)
54
+
55
+ # for any other unknown class, there is no clear answer, so be safe and say false.
56
+ assert_equal false, coercion.coerce([])
57
+ end
58
+
59
+ def test_json_coersion
60
+ coercion = ScoutApm::Config::JsonCoercion.new
61
+ assert_equal [1,2,3], coercion.coerce('[1,2,3]')
62
+ assert_equal ['foo/bar','baz/quux'], coercion.coerce('["foo/bar", "baz/quux"]')
63
+
64
+ assert_equal({"foo" => "bar"}, coercion.coerce('{"foo": "bar"}'))
65
+
66
+ assert_equal true, coercion.coerce(true)
67
+ assert_equal 10, coercion.coerce(10)
68
+ assert_equal ["a"], coercion.coerce(["a"])
69
+ end
70
+
71
+ def test_any_keys_found
72
+ ENV.stubs(:has_key?).returns(nil)
73
+
74
+ conf = ScoutApm::Config.with_file("a_file_that_doesnt_exist.yml")
75
+ assert ! conf.any_keys_found?
76
+
77
+ ENV.stubs(:has_key?).with("SCOUT_MONITOR").returns("true")
78
+ conf = ScoutApm::Config.with_file("a_file_that_doesnt_exist.yml")
79
+ assert conf.any_keys_found?
80
+ end
81
+ end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/db_query_metric_set'
4
+
5
+ module ScoutApm
6
+ class DbQueryMetricSetTest < Minitest::Test
7
+ def test_hard_limit
8
+ config = make_fake_config(
9
+ 'database_metric_limit' => 5, # The hard limit on db metrics
10
+ 'database_metric_report_limit' => 2,)
11
+ set = DbQueryMetricSet.new(config)
12
+ set << fake_stat("a", 10)
13
+ set << fake_stat("b", 20)
14
+ set << fake_stat("c", 30)
15
+ set << fake_stat("d", 40)
16
+ set << fake_stat("e", 50)
17
+ set << fake_stat("f", 60)
18
+
19
+ assert_equal 5, set.metrics.size
20
+ end
21
+
22
+ def test_report_limit
23
+ config = make_fake_config(
24
+ 'database_metric_limit' => 50, # much larger max, uninterested in hitting it.
25
+ 'database_metric_report_limit' => 2,)
26
+ set = DbQueryMetricSet.new(config)
27
+ set << fake_stat("a", 10)
28
+ set << fake_stat("b", 20)
29
+ set << fake_stat("c", 30)
30
+ set << fake_stat("d", 40)
31
+ set << fake_stat("e", 50)
32
+ set << fake_stat("f", 60)
33
+
34
+ assert_equal 2, set.metrics_to_report.size
35
+ assert_equal ["f","e"], set.metrics_to_report.map{|m| m.key}
36
+ end
37
+
38
+ def test_combine
39
+ set1 = DbQueryMetricSet.new
40
+ set1 << fake_stat("a", 10)
41
+ set1 << fake_stat("b", 20)
42
+ set2 = DbQueryMetricSet.new
43
+ set2 << fake_stat("c", 10)
44
+ set2 << fake_stat("d", 20)
45
+
46
+ combined = set1.combine!(set2)
47
+ assert_equal ["a", "b", "c", "d"], combined.metrics.map{|_k, m| m.key}
48
+ end
49
+
50
+ def fake_stat(key, call_time)
51
+ OpenStruct.new(
52
+ key: key,
53
+ call_time: call_time)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/db_query_metric_stats'
4
+
5
+ module ScoutApm
6
+ class DbQueryMetricStatsTest < Minitest::Test
7
+ def test_as_json_empty_stats
8
+ stat = build("table", "op", "Controller/public/index", 1, 10, 20)
9
+
10
+ assert_equal({
11
+ model_name: "table",
12
+ operation: "op",
13
+ call_count: 1,
14
+ transaction_count: 0,
15
+ scope: "Controller/public/index",
16
+ histogram: [[10.0, 1]],
17
+
18
+ max_call_time: 10.0,
19
+ min_call_time: 10.0,
20
+ call_time: 10.0,
21
+
22
+ max_rows_returned: 20,
23
+ min_rows_returned: 20,
24
+ rows_returned: 20,
25
+ }, stat.as_json)
26
+ end
27
+
28
+ def test_increment_transaction_count
29
+ stat = build()
30
+ assert_equal 0, stat.transaction_count
31
+
32
+ stat.increment_transaction_count!
33
+
34
+ assert_equal 1, stat.transaction_count
35
+ end
36
+
37
+ def test_key_name
38
+ stat = build("User", "find", "Controller/public/index")
39
+ assert_equal ["User", "find", "Controller/public/index"], stat.key
40
+ end
41
+
42
+ def test_combine_min_call_time_picks_smallest
43
+ stat1, stat2 = build_pair
44
+ assert_equal 5.1, stat1.combine!(stat2).min_call_time
45
+ end
46
+
47
+ def test_combine_max_call_time_picks_largest
48
+ stat1, stat2 = build_pair
49
+ assert_equal 8.2, stat1.combine!(stat2).max_call_time
50
+ end
51
+
52
+ def test_combine_call_counts_adds
53
+ stat1, stat2 = build_pair
54
+ assert_equal 5, stat1.combine!(stat2).call_count
55
+ end
56
+
57
+ def test_combine_transaction_count_adds
58
+ stat1, stat2 = build_pair
59
+ 2.times { stat1.increment_transaction_count! }
60
+ 3.times { stat2.increment_transaction_count! }
61
+
62
+ assert_equal 5, stat1.combine!(stat2).call_count
63
+ end
64
+
65
+ def test_combine_doesnt_merge_with_self
66
+ stat = build
67
+ merged = stat.combine!(stat)
68
+
69
+ assert_equal DEFAULTS[:call_count], merged.call_count
70
+ assert_equal DEFAULTS[:call_time], merged.call_time
71
+ assert_equal DEFAULTS[:rows_returned], merged.rows_returned
72
+ end
73
+
74
+ # A.combine!(B) should be the the same as B.combine!(A)
75
+ # Have to be a bit careful, since combine! is destructive, so make two pairs
76
+ # with same data to do both sides, then check that they result in the same
77
+ # answer
78
+ [:transaction_count, :call_count, :rows_returned, :min_rows_returned, :max_rows_returned, :max_call_time, :min_call_time].each do |attr|
79
+ define_method :"test_combine_#{attr}_is_symmetric" do
80
+ stat1_a, stat2_a = build_pair
81
+ stat1_b, stat2_b = build_pair
82
+ merged_a = stat1_a.combine!(stat2_a)
83
+ merged_b = stat2_b.combine!(stat1_b)
84
+
85
+ assert_equal merged_a.send(attr), merged_b.send(attr)
86
+ end
87
+ end
88
+
89
+ #############
90
+ # Helpers #
91
+ #############
92
+ DEFAULTS = {
93
+ call_count: 1,
94
+ call_time: 10.0,
95
+ rows_returned: 20,
96
+ }
97
+
98
+ def build(model_name="User",
99
+ operation="find",
100
+ scope="Controller/public/index",
101
+ call_count=DEFAULTS[:call_count],
102
+ call_time=DEFAULTS[:call_time],
103
+ rows_returned=DEFAULTS[:rows_returned])
104
+ DbQueryMetricStats.new(model_name, operation, scope, call_count, call_time, rows_returned)
105
+ end
106
+
107
+ def build_pair
108
+ stat1 = build("table", "op", "Controller/public/index", 2, 5.1, 10)
109
+ stat2 = build("table", "op", "Controller/public/index", 3, 8.2, 20)
110
+ [stat1, stat2]
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/git_revision'
4
+
5
+ class GitRevisionTest < Minitest::Test
6
+ # TODO - other tests that would be nice:
7
+ # * ensure we only detect once, on initialize.
8
+ # * tests for reading cap files
9
+
10
+ def test_sha_from_heroku
11
+ ENV['HEROKU_SLUG_COMMIT'] = 'heroku_slug'
12
+ revision = ScoutApm::GitRevision.new
13
+ assert_equal 'heroku_slug', revision.sha
14
+ end
15
+ end
@@ -79,6 +79,20 @@ class HistogramTest < Minitest::Test
79
79
  assert combined.quantile(0) < combined.quantile(100)
80
80
  end
81
81
 
82
+ def test_combine_dedups_identicals
83
+ hist1 = ScoutApm::NumericHistogram.new(5)
84
+ hist2 = ScoutApm::NumericHistogram.new(5)
85
+ hist1.add(1)
86
+ hist1.add(2)
87
+ hist2.add(2)
88
+ hist2.add(3)
89
+
90
+ combined = hist1.combine!(hist2)
91
+ assert_equal 4, combined.total
92
+ assert_equal [[1, 1], [2, 2], [1, 3]],
93
+ combined.bins.map{|bin| [bin.count, bin.value.to_i] }
94
+ end
95
+
82
96
  def test_mean
83
97
  hist = ScoutApm::NumericHistogram.new(5)
84
98
  10.times {