atatus 1.3.0 → 1.4.0

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.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +72 -22
  4. data/LICENSE +1 -1
  5. data/atatus.gemspec +2 -2
  6. data/lib/atatus.rb +76 -16
  7. data/lib/atatus/agent.rb +78 -29
  8. data/lib/atatus/central_config.rb +72 -27
  9. data/lib/atatus/central_config/cache_control.rb +18 -1
  10. data/lib/atatus/child_durations.rb +64 -0
  11. data/lib/atatus/collector/base.rb +61 -29
  12. data/lib/atatus/collector/builder.rb +46 -2
  13. data/lib/atatus/collector/hist.rb +54 -0
  14. data/lib/atatus/collector/transport.rb +41 -11
  15. data/lib/atatus/config.rb +129 -28
  16. data/lib/atatus/config/bytes.rb +17 -0
  17. data/lib/atatus/config/duration.rb +17 -0
  18. data/lib/atatus/config/options.rb +29 -9
  19. data/lib/atatus/config/regexp_list.rb +17 -0
  20. data/lib/atatus/config/wildcard_pattern_list.rb +64 -0
  21. data/lib/atatus/context.rb +32 -1
  22. data/lib/atatus/context/request.rb +17 -0
  23. data/lib/atatus/context/request/socket.rb +18 -1
  24. data/lib/atatus/context/request/url.rb +17 -0
  25. data/lib/atatus/context/response.rb +27 -2
  26. data/lib/atatus/context/user.rb +17 -0
  27. data/lib/atatus/context_builder.rb +19 -4
  28. data/lib/atatus/deprecations.rb +17 -0
  29. data/lib/atatus/error.rb +27 -0
  30. data/lib/atatus/error/exception.rb +24 -0
  31. data/lib/atatus/error/log.rb +17 -0
  32. data/lib/atatus/error_builder.rb +17 -2
  33. data/lib/atatus/grape.rb +62 -0
  34. data/lib/atatus/graphql.rb +91 -0
  35. data/lib/atatus/grpc.rb +99 -0
  36. data/lib/atatus/instrumenter.rb +135 -30
  37. data/lib/atatus/internal_error.rb +17 -0
  38. data/lib/atatus/logging.rb +17 -2
  39. data/lib/atatus/metadata.rb +17 -0
  40. data/lib/atatus/metadata/process_info.rb +17 -0
  41. data/lib/atatus/metadata/service_info.rb +21 -6
  42. data/lib/atatus/metadata/system_info.rb +22 -3
  43. data/lib/atatus/metadata/system_info/container_info.rb +49 -10
  44. data/lib/atatus/metadata/system_info/hw_info.rb +1 -1
  45. data/lib/atatus/metrics.rb +69 -27
  46. data/lib/atatus/metrics/breakdown_set.rb +31 -0
  47. data/lib/atatus/metrics/{cpu_mem.rb → cpu_mem_set.rb} +110 -63
  48. data/lib/atatus/metrics/metric.rb +140 -0
  49. data/lib/atatus/metrics/set.rb +123 -0
  50. data/lib/atatus/metrics/span_scoped_set.rb +56 -0
  51. data/lib/atatus/metrics/transaction_set.rb +26 -0
  52. data/lib/atatus/metrics/vm_set.rb +58 -0
  53. data/lib/atatus/metricset.rb +48 -4
  54. data/lib/atatus/middleware.rb +28 -8
  55. data/lib/atatus/naively_hashable.rb +17 -0
  56. data/lib/atatus/normalizers.rb +23 -9
  57. data/lib/atatus/normalizers/grape.rb +22 -0
  58. data/lib/atatus/normalizers/grape/endpoint_run.rb +65 -0
  59. data/lib/atatus/normalizers/rails.rb +27 -0
  60. data/lib/atatus/normalizers/rails/action_controller.rb +44 -0
  61. data/lib/atatus/normalizers/rails/action_mailer.rb +43 -0
  62. data/lib/atatus/normalizers/{action_view.rb → rails/action_view.rb} +17 -0
  63. data/lib/atatus/normalizers/rails/active_record.rb +80 -0
  64. data/lib/atatus/opentracing.rb +75 -42
  65. data/lib/atatus/rails.rb +29 -13
  66. data/lib/atatus/railtie.rb +19 -6
  67. data/lib/atatus/resque.rb +29 -0
  68. data/lib/atatus/sinatra.rb +53 -0
  69. data/lib/atatus/span.rb +44 -15
  70. data/lib/atatus/span/context.rb +43 -28
  71. data/lib/atatus/span/context/db.rb +43 -0
  72. data/lib/atatus/span/context/destination.rb +77 -0
  73. data/lib/atatus/span/context/http.rb +43 -0
  74. data/lib/atatus/span_helpers.rb +18 -1
  75. data/lib/atatus/spies.rb +33 -15
  76. data/lib/atatus/spies/action_dispatch.rb +27 -6
  77. data/lib/atatus/spies/delayed_job.rb +26 -5
  78. data/lib/atatus/spies/dynamo_db.rb +62 -0
  79. data/lib/atatus/spies/elasticsearch.rb +53 -7
  80. data/lib/atatus/spies/faraday.rb +54 -20
  81. data/lib/atatus/spies/http.rb +36 -6
  82. data/lib/atatus/spies/json.rb +18 -0
  83. data/lib/atatus/spies/mongo.rb +41 -10
  84. data/lib/atatus/spies/net_http.rb +52 -11
  85. data/lib/atatus/spies/rake.rb +42 -23
  86. data/lib/atatus/spies/redis.rb +17 -0
  87. data/lib/atatus/spies/resque.rb +57 -0
  88. data/lib/atatus/spies/sequel.rb +54 -17
  89. data/lib/atatus/spies/shoryuken.rb +69 -0
  90. data/lib/atatus/spies/sidekiq.rb +46 -25
  91. data/lib/atatus/spies/sinatra.rb +20 -4
  92. data/lib/atatus/spies/sneakers.rb +74 -0
  93. data/lib/atatus/spies/sucker_punch.rb +58 -0
  94. data/lib/atatus/spies/tilt.rb +20 -1
  95. data/lib/atatus/sql.rb +36 -0
  96. data/lib/atatus/sql/signature.rb +169 -0
  97. data/lib/atatus/sql/tokenizer.rb +264 -0
  98. data/lib/atatus/sql/tokens.rb +63 -0
  99. data/lib/atatus/sql_summarizer.rb +24 -6
  100. data/lib/atatus/stacktrace.rb +17 -0
  101. data/lib/atatus/stacktrace/frame.rb +17 -3
  102. data/lib/atatus/stacktrace_builder.rb +23 -3
  103. data/lib/atatus/subscriber.rb +23 -4
  104. data/lib/atatus/trace_context.rb +84 -51
  105. data/lib/atatus/trace_context/traceparent.rb +111 -0
  106. data/lib/atatus/trace_context/tracestate.rb +148 -0
  107. data/lib/atatus/transaction.rb +74 -18
  108. data/lib/atatus/transport/base.rb +44 -27
  109. data/lib/atatus/transport/connection.rb +28 -72
  110. data/lib/atatus/transport/connection/http.rb +58 -35
  111. data/lib/atatus/transport/connection/proxy_pipe.rb +24 -5
  112. data/lib/atatus/transport/filters.rb +18 -1
  113. data/lib/atatus/transport/filters/hash_sanitizer.rb +77 -0
  114. data/lib/atatus/transport/filters/secrets_filter.rb +30 -55
  115. data/lib/atatus/transport/headers.rb +83 -0
  116. data/lib/atatus/transport/serializers.rb +17 -5
  117. data/lib/atatus/transport/serializers/context_serializer.rb +30 -3
  118. data/lib/atatus/transport/serializers/error_serializer.rb +17 -2
  119. data/lib/atatus/transport/serializers/metadata_serializer.rb +44 -22
  120. data/lib/atatus/transport/serializers/metricset_serializer.rb +34 -6
  121. data/lib/atatus/transport/serializers/span_serializer.rb +47 -12
  122. data/lib/atatus/transport/serializers/transaction_serializer.rb +18 -2
  123. data/lib/atatus/transport/user_agent.rb +48 -0
  124. data/lib/atatus/transport/worker.rb +31 -7
  125. data/lib/atatus/util.rb +18 -1
  126. data/lib/atatus/util/inflector.rb +17 -0
  127. data/lib/atatus/util/lru_cache.rb +17 -0
  128. data/lib/atatus/util/throttle.rb +17 -0
  129. data/lib/atatus/version.rb +19 -1
  130. metadata +46 -26
  131. data/Rakefile +0 -19
  132. data/bench/.gitignore +0 -2
  133. data/bench/app.rb +0 -53
  134. data/bench/benchmark.rb +0 -36
  135. data/bench/report.rb +0 -55
  136. data/bench/rubyprof.rb +0 -39
  137. data/bench/stackprof.rb +0 -23
  138. data/bin/build_docs +0 -5
  139. data/bin/console +0 -15
  140. data/bin/setup +0 -8
  141. data/bin/with_framework +0 -7
  142. data/lib/atatus/metrics/vm.rb +0 -60
  143. data/lib/atatus/normalizers/action_controller.rb +0 -27
  144. data/lib/atatus/normalizers/action_mailer.rb +0 -26
  145. data/lib/atatus/normalizers/active_record.rb +0 -45
  146. data/lib/atatus/util/prefixed_logger.rb +0 -18
  147. data/vendor/.gitkeep +0 -0
@@ -0,0 +1,31 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module Atatus
21
+ module Metrics
22
+ # @api private
23
+ class BreakdownSet < SpanScopedSet
24
+ def initialize(config)
25
+ super
26
+
27
+ disable! unless config.breakdown_metrics?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,107 +1,161 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
1
18
  # frozen_string_literal: true
2
19
 
3
20
  module Atatus
4
21
  module Metrics
5
22
  # @api private
6
- class CpuMem
23
+ class CpuMemSet < Set
7
24
  include Logging
8
25
 
9
26
  # @api private
10
27
  class Sample
11
28
  # rubocop:disable Metrics/ParameterLists
12
29
  def initialize(
30
+ page_size:,
31
+ process_cpu_usage:,
32
+ process_memory_rss:,
33
+ process_memory_size:,
13
34
  system_cpu_total:,
14
35
  system_cpu_usage:,
15
- system_memory_total:,
16
36
  system_memory_free:,
17
- process_cpu_usage:,
18
- process_memory_size:,
19
- process_memory_rss:,
20
- page_size:
37
+ system_memory_total:
21
38
  )
39
+ @page_size = page_size
40
+ @process_cpu_usage = process_cpu_usage
41
+ @process_memory_rss = process_memory_rss
42
+ @process_memory_size = process_memory_size
22
43
  @system_cpu_total = system_cpu_total
23
44
  @system_cpu_usage = system_cpu_usage
24
- @system_memory_total = system_memory_total
25
45
  @system_memory_free = system_memory_free
26
- @process_cpu_usage = process_cpu_usage
27
- @process_memory_size = process_memory_size
28
- @process_memory_rss = process_memory_rss
29
- @page_size = page_size
46
+ @system_memory_total = system_memory_total
30
47
  end
31
48
  # rubocop:enable Metrics/ParameterLists
32
49
 
33
- attr_accessor :system_cpu_total, :system_cpu_usage,
34
- :system_memory_total, :system_memory_free, :process_cpu_usage,
35
- :process_memory_size, :process_memory_rss, :page_size
36
-
37
- def delta(previous)
38
- dup.tap do |sample|
39
- sample.system_cpu_total =
40
- system_cpu_total - previous.system_cpu_total
41
- sample.system_cpu_usage =
42
- system_cpu_usage - previous.system_cpu_usage
43
- sample.process_cpu_usage =
44
- process_cpu_usage - previous.process_cpu_usage
45
- end
46
- end
50
+ attr_accessor(
51
+ :page_size,
52
+ :process_cpu_usage,
53
+ :process_memory_rss,
54
+ :process_memory_size,
55
+ :system_cpu_total,
56
+ :system_cpu_usage,
57
+ :system_memory_free,
58
+ :system_memory_total
59
+ )
47
60
  end
48
61
 
49
62
  def initialize(config)
50
- @config = config
63
+ @vmset_disabled = false
64
+ super
65
+
51
66
  @sampler = sampler_for_platform(Metrics.platform)
67
+ read! # set @previous on boot
52
68
  end
53
69
 
54
- attr_reader :config, :sampler
70
+ attr_reader :config
55
71
 
56
- def sample
57
- @sampler.sample
72
+ def collect
73
+ read!
74
+ super
58
75
  end
59
76
 
60
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
61
- def collect
62
- return unless sampler
77
+ private
78
+
79
+ def vmset_disable!
80
+ @vmset_disabled = true
81
+ end
63
82
 
64
- current = sample
83
+ def vmset_disabled?
84
+ @vmset_disabled
85
+ end
86
+
87
+ def sampler_for_platform(platform)
88
+ case platform
89
+ when :linux then Linux.new
90
+ else
91
+ warn "Unsupported platform '#{platform}' - Disabling system metrics"
92
+ disable!
93
+ nil
94
+ end
95
+ end
96
+
97
+ def read!
98
+ return if disabled?
99
+
100
+ current = @sampler.sample
65
101
 
66
102
  unless @previous
67
103
  @previous = current
68
104
  return
69
105
  end
70
106
 
71
- delta = current.delta(@previous)
107
+ cpu_usage_pct, cpu_process_pct = calculate_deltas(current, @previous)
72
108
 
73
- cpu_usage_pct = delta.system_cpu_usage.to_f / delta.system_cpu_total
74
- cpu_process_pct = delta.process_cpu_usage.to_f / delta.system_cpu_total
109
+ gauge(:'system.cpu.total.norm.pct').value = cpu_usage_pct
110
+ gauge(:'system.memory.actual.free').value = current.system_memory_free
111
+ gauge(:'system.memory.total').value = current.system_memory_total
112
+ gauge(:'system.process.cpu.total.norm.pct').value = cpu_process_pct
113
+ gauge(:'system.process.memory.size').value = current.process_memory_size
114
+ gauge(:'system.process.memory.rss.bytes').value =
115
+ current.process_memory_rss * current.page_size
75
116
 
76
117
  @previous = current
77
118
 
78
- {
79
- 'system.cpu.total.norm.pct': cpu_usage_pct,
80
- 'system.memory.actual.free': current.system_memory_free,
81
- 'system.memory.total': current.system_memory_total,
82
- 'system.process.cpu.total.norm.pct': cpu_process_pct,
83
- 'system.process.memory.size': current.process_memory_size,
84
- 'system.process.memory.rss.bytes':
85
- current.process_memory_rss * current.page_size
86
- }
119
+ return if vmset_disabled?
120
+
121
+ stat = GC.stat
122
+
123
+ gauge(:'ruby.gc.count').value = stat[:count]
124
+ gauge(:'ruby.threads').value = Thread.list.count
125
+ gauge(:'ruby.heap.slots.live').value = stat[:heap_live_slots]
126
+
127
+ gauge(:'ruby.heap.slots.free').value = stat[:heap_free_slots]
128
+ gauge(:'ruby.heap.allocations.total').value =
129
+ stat[:total_allocated_objects]
130
+
131
+ return unless GC::Profiler.enabled?
132
+ @total_time ||= 0
133
+ @total_time += GC::Profiler.total_time
134
+ GC::Profiler.clear
135
+ gauge(:'ruby.gc.time').value = @total_time
136
+ rescue TypeError => e
137
+ error 'VM metrics encountered error: %s', e
138
+ debug('Backtrace:') { e.backtrace.join("\n") }
139
+
140
+ vmset_disable!
87
141
  end
88
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
89
142
 
90
- private
143
+ def calculate_deltas(current, previous)
144
+ system_cpu_total =
145
+ current.system_cpu_total - previous.system_cpu_total
146
+ system_cpu_usage =
147
+ current.system_cpu_usage - previous.system_cpu_usage
148
+ process_cpu_usage =
149
+ current.process_cpu_usage - previous.process_cpu_usage
91
150
 
92
- def sampler_for_platform(platform)
93
- case platform
94
- when :linux then Linux.new
95
- else
96
- warn "Unsupported platform '#{platform}' - Disabling metrics"
97
- @disabled = true
98
- nil
99
- end
151
+ cpu_usage_pct = system_cpu_usage.to_f / system_cpu_total
152
+ cpu_process_pct = process_cpu_usage.to_f / system_cpu_total
153
+
154
+ [cpu_usage_pct, cpu_process_pct]
100
155
  end
101
156
 
102
157
  # @api private
103
158
  class Linux
104
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
105
159
  def sample
106
160
  proc_stat = ProcStat.new.read!
107
161
  proc_self_stat = ProcSelfStat.new.read!
@@ -118,7 +172,6 @@ module Atatus
118
172
  page_size: meminfo.page_size
119
173
  )
120
174
  end
121
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
122
175
 
123
176
  # @api private
124
177
  class ProcStat
@@ -136,8 +189,6 @@ module Atatus
136
189
  guest
137
190
  guest_nice
138
191
  ].freeze
139
-
140
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
141
192
  def read!
142
193
  stat =
143
194
  IO.readlines('/proc/stat')
@@ -165,7 +216,6 @@ module Atatus
165
216
 
166
217
  self
167
218
  end
168
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
169
219
  end
170
220
 
171
221
  UTIME_POS = 13
@@ -196,8 +246,6 @@ module Atatus
196
246
  # @api private
197
247
  class Meminfo
198
248
  attr_reader :total, :available, :page_size
199
-
200
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
201
249
  # rubocop:disable Metrics/PerceivedComplexity
202
250
  # rubocop:disable Metrics/CyclomaticComplexity
203
251
  def read!
@@ -232,7 +280,6 @@ module Atatus
232
280
  end
233
281
  # rubocop:enable Metrics/CyclomaticComplexity
234
282
  # rubocop:enable Metrics/PerceivedComplexity
235
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
236
283
  end
237
284
  end
238
285
  end
@@ -0,0 +1,140 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module Atatus
21
+ module Metrics
22
+ # @api private
23
+ class Metric
24
+ def initialize(
25
+ key,
26
+ initial_value: nil,
27
+ tags: nil,
28
+ reset_on_collect: false
29
+ )
30
+ @key = key
31
+ @initial_value = initial_value
32
+ @value = initial_value
33
+ @tags = tags
34
+ @reset_on_collect = reset_on_collect
35
+ @mutex = Mutex.new
36
+ end
37
+
38
+ attr_reader :key, :initial_value, :tags, :value
39
+
40
+ def value=(value)
41
+ @mutex.synchronize { @value = value }
42
+ end
43
+
44
+ def reset!
45
+ self.value = initial_value
46
+ end
47
+
48
+ def tags?
49
+ !!tags&.any?
50
+ end
51
+
52
+ def reset_on_collect?
53
+ @reset_on_collect
54
+ end
55
+
56
+ def collect
57
+ @mutex.synchronize do
58
+ collected = @value
59
+
60
+ @value = initial_value if reset_on_collect?
61
+
62
+ return nil if reset_on_collect? && collected == 0
63
+
64
+ collected
65
+ end
66
+ end
67
+ end
68
+
69
+ # @api private
70
+ class NoopMetric
71
+ def value; end
72
+
73
+ def value=(_); end
74
+
75
+ def collect; end
76
+
77
+ def reset!; end
78
+
79
+ def tags?; end
80
+
81
+ def reset_on_collect?; end
82
+
83
+ def inc!; end
84
+
85
+ def dec!; end
86
+
87
+ def update(_, delta: nil); end
88
+ end
89
+
90
+ # @api private
91
+ class Counter < Metric
92
+ def initialize(key, initial_value: 0, **args)
93
+ super(key, initial_value: initial_value, **args)
94
+ end
95
+
96
+ def inc!
97
+ @mutex.synchronize do
98
+ @value += 1
99
+ end
100
+ end
101
+
102
+ def dec!
103
+ @mutex.synchronize do
104
+ @value -= 1
105
+ end
106
+ end
107
+ end
108
+
109
+ # @api private
110
+ class Gauge < Metric
111
+ def initialize(key, **args)
112
+ super(key, initial_value: 0, **args)
113
+ end
114
+ end
115
+
116
+ # @api private
117
+ class Timer < Metric
118
+ def initialize(key, **args)
119
+ super(key, initial_value: 0, **args)
120
+ @count = 0
121
+ end
122
+
123
+ attr_accessor :count
124
+
125
+ def update(duration, delta: 0)
126
+ @mutex.synchronize do
127
+ @value += duration
128
+ @count += delta
129
+ end
130
+ end
131
+
132
+ def reset!
133
+ @mutex.synchronize do
134
+ @value = 0
135
+ @count = 0
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,123 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module Atatus
21
+ # @api private
22
+ module Metrics
23
+ NOOP = NoopMetric.new
24
+
25
+ # @api private
26
+ class Set
27
+ include Logging
28
+
29
+ DISTINCT_LABEL_LIMIT = 1000
30
+
31
+ def initialize(config)
32
+ @config = config
33
+ @metrics = {}
34
+ @disabled = false
35
+ @lock = Mutex.new
36
+ end
37
+
38
+ attr_reader :metrics
39
+
40
+ def disable!
41
+ @disabled = true
42
+ end
43
+
44
+ def disabled?
45
+ @disabled
46
+ end
47
+
48
+ def counter(key, tags: nil, **args)
49
+ metric(Counter, key, tags: tags, **args)
50
+ end
51
+
52
+ def gauge(key, tags: nil, **args)
53
+ metric(Gauge, key, tags: tags, **args)
54
+ end
55
+
56
+ def timer(key, tags: nil, **args)
57
+ metric(Timer, key, tags: tags, **args)
58
+ end
59
+
60
+ def metric(kls, key, tags: nil, **args)
61
+ if @config.disable_metrics.any? { |p| p.match? key }
62
+ return NOOP
63
+ end
64
+
65
+ key = key_with_tags(key, tags)
66
+ return metrics[key] if metrics[key]
67
+
68
+ @lock.synchronize do
69
+ return metrics[key] if metrics[key]
70
+
71
+ metrics[key] =
72
+ if metrics.length < DISTINCT_LABEL_LIMIT
73
+ kls.new(key, tags: tags, **args)
74
+ else
75
+ unless @label_limit_logged
76
+ warn(
77
+ 'The limit of %d metricsets has been reached, no new ' \
78
+ 'metricsets will be created.', DISTINCT_LABEL_LIMIT
79
+ )
80
+ @label_limit_logged = true
81
+ end
82
+
83
+ NOOP
84
+ end
85
+ end
86
+ end
87
+
88
+ def collect
89
+ return if disabled?
90
+
91
+ @lock.synchronize do
92
+ metrics.each_with_object({}) do |(key, metric), sets|
93
+ next unless (value = metric.collect)
94
+
95
+ # metrics have a key of name and flat array of key-value pairs
96
+ # eg [name, key, value, key, value]
97
+ # they can be sent in the same metricset but not if they have
98
+ # differing tags. So, we split the resulting sets by tags first.
99
+ name, *tags = key
100
+ sets[tags] ||= Metricset.new
101
+
102
+ # then we set the `samples` value for the metricset
103
+ set = sets[tags]
104
+ set.samples[name] = value
105
+
106
+ # and finally we copy the tags from the Metric to the Metricset
107
+ set.merge_tags! metric.tags
108
+ end.values
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def key_with_tags(key, tags)
115
+ return key unless tags
116
+
117
+ tuple = tags.keys.zip(tags.values)
118
+ tuple.flatten!
119
+ tuple.unshift(key)
120
+ end
121
+ end
122
+ end
123
+ end