atatus 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +57 -0
- data/LICENSE +65 -0
- data/LICENSE-THIRD-PARTY +205 -0
- data/README.md +13 -0
- data/Rakefile +19 -0
- data/atatus.gemspec +36 -0
- data/atatus.yml +2 -0
- data/bench/.gitignore +2 -0
- data/bench/app.rb +53 -0
- data/bench/benchmark.rb +36 -0
- data/bench/report.rb +55 -0
- data/bench/rubyprof.rb +39 -0
- data/bench/stackprof.rb +23 -0
- data/bin/build_docs +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/lib/atatus.rb +325 -0
- data/lib/atatus/agent.rb +260 -0
- data/lib/atatus/central_config.rb +141 -0
- data/lib/atatus/central_config/cache_control.rb +34 -0
- data/lib/atatus/collector/base.rb +329 -0
- data/lib/atatus/collector/builder.rb +317 -0
- data/lib/atatus/collector/transport.rb +72 -0
- data/lib/atatus/config.rb +248 -0
- data/lib/atatus/config/bytes.rb +25 -0
- data/lib/atatus/config/duration.rb +23 -0
- data/lib/atatus/config/options.rb +134 -0
- data/lib/atatus/config/regexp_list.rb +13 -0
- data/lib/atatus/context.rb +33 -0
- data/lib/atatus/context/request.rb +11 -0
- data/lib/atatus/context/request/socket.rb +19 -0
- data/lib/atatus/context/request/url.rb +42 -0
- data/lib/atatus/context/response.rb +22 -0
- data/lib/atatus/context/user.rb +42 -0
- data/lib/atatus/context_builder.rb +97 -0
- data/lib/atatus/deprecations.rb +22 -0
- data/lib/atatus/error.rb +22 -0
- data/lib/atatus/error/exception.rb +46 -0
- data/lib/atatus/error/log.rb +24 -0
- data/lib/atatus/error_builder.rb +76 -0
- data/lib/atatus/instrumenter.rb +224 -0
- data/lib/atatus/internal_error.rb +6 -0
- data/lib/atatus/logging.rb +55 -0
- data/lib/atatus/metadata.rb +19 -0
- data/lib/atatus/metadata/process_info.rb +18 -0
- data/lib/atatus/metadata/service_info.rb +61 -0
- data/lib/atatus/metadata/system_info.rb +35 -0
- data/lib/atatus/metadata/system_info/container_info.rb +121 -0
- data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
- data/lib/atatus/metadata/system_info/os_info.rb +31 -0
- data/lib/atatus/metrics.rb +98 -0
- data/lib/atatus/metrics/cpu_mem.rb +240 -0
- data/lib/atatus/metrics/vm.rb +60 -0
- data/lib/atatus/metricset.rb +19 -0
- data/lib/atatus/middleware.rb +76 -0
- data/lib/atatus/naively_hashable.rb +21 -0
- data/lib/atatus/normalizers.rb +68 -0
- data/lib/atatus/normalizers/action_controller.rb +27 -0
- data/lib/atatus/normalizers/action_mailer.rb +26 -0
- data/lib/atatus/normalizers/action_view.rb +77 -0
- data/lib/atatus/normalizers/active_record.rb +45 -0
- data/lib/atatus/opentracing.rb +346 -0
- data/lib/atatus/rails.rb +61 -0
- data/lib/atatus/railtie.rb +30 -0
- data/lib/atatus/span.rb +125 -0
- data/lib/atatus/span/context.rb +40 -0
- data/lib/atatus/span_helpers.rb +44 -0
- data/lib/atatus/spies.rb +86 -0
- data/lib/atatus/spies/action_dispatch.rb +28 -0
- data/lib/atatus/spies/delayed_job.rb +68 -0
- data/lib/atatus/spies/elasticsearch.rb +36 -0
- data/lib/atatus/spies/faraday.rb +70 -0
- data/lib/atatus/spies/http.rb +44 -0
- data/lib/atatus/spies/json.rb +22 -0
- data/lib/atatus/spies/mongo.rb +87 -0
- data/lib/atatus/spies/net_http.rb +70 -0
- data/lib/atatus/spies/rake.rb +45 -0
- data/lib/atatus/spies/redis.rb +27 -0
- data/lib/atatus/spies/sequel.rb +47 -0
- data/lib/atatus/spies/sidekiq.rb +89 -0
- data/lib/atatus/spies/sinatra.rb +41 -0
- data/lib/atatus/spies/tilt.rb +27 -0
- data/lib/atatus/sql_summarizer.rb +35 -0
- data/lib/atatus/stacktrace.rb +16 -0
- data/lib/atatus/stacktrace/frame.rb +52 -0
- data/lib/atatus/stacktrace_builder.rb +104 -0
- data/lib/atatus/subscriber.rb +77 -0
- data/lib/atatus/trace_context.rb +85 -0
- data/lib/atatus/transaction.rb +100 -0
- data/lib/atatus/transport/base.rb +174 -0
- data/lib/atatus/transport/connection.rb +156 -0
- data/lib/atatus/transport/connection/http.rb +116 -0
- data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
- data/lib/atatus/transport/filters.rb +43 -0
- data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
- data/lib/atatus/transport/serializers.rb +93 -0
- data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
- data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
- data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
- data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
- data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
- data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
- data/lib/atatus/transport/worker.rb +73 -0
- data/lib/atatus/util.rb +42 -0
- data/lib/atatus/util/inflector.rb +93 -0
- data/lib/atatus/util/lru_cache.rb +48 -0
- data/lib/atatus/util/prefixed_logger.rb +18 -0
- data/lib/atatus/util/throttle.rb +35 -0
- data/lib/atatus/version.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +190 -0
data/lib/atatus/agent.rb
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'atatus/error'
|
|
4
|
+
|
|
5
|
+
require 'atatus/context_builder'
|
|
6
|
+
require 'atatus/error_builder'
|
|
7
|
+
require 'atatus/stacktrace_builder'
|
|
8
|
+
require 'atatus/collector/base'
|
|
9
|
+
|
|
10
|
+
require 'atatus/central_config'
|
|
11
|
+
# require 'atatus/transport/base'
|
|
12
|
+
require 'atatus/metrics'
|
|
13
|
+
|
|
14
|
+
require 'atatus/spies'
|
|
15
|
+
|
|
16
|
+
module Atatus
|
|
17
|
+
# rubocop:disable Metrics/ClassLength
|
|
18
|
+
# @api private
|
|
19
|
+
class Agent
|
|
20
|
+
include Logging
|
|
21
|
+
|
|
22
|
+
LOCK = Mutex.new
|
|
23
|
+
|
|
24
|
+
# life cycle
|
|
25
|
+
|
|
26
|
+
def self.instance # rubocop:disable Style/TrivialAccessors
|
|
27
|
+
@instance
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
|
31
|
+
def self.start(config)
|
|
32
|
+
return @instance if @instance
|
|
33
|
+
|
|
34
|
+
config = Config.new(config) unless config.is_a?(Config)
|
|
35
|
+
|
|
36
|
+
LOCK.synchronize do
|
|
37
|
+
return @instance if @instance
|
|
38
|
+
|
|
39
|
+
unless config.active?
|
|
40
|
+
config.logger.debug format(
|
|
41
|
+
"%sAgent disabled with `active: false'",
|
|
42
|
+
Logging::PREFIX
|
|
43
|
+
)
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@instance = new(config).start
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
# rubocop:enable Metrics/MethodLength
|
|
51
|
+
|
|
52
|
+
def self.stop
|
|
53
|
+
LOCK.synchronize do
|
|
54
|
+
return unless @instance
|
|
55
|
+
|
|
56
|
+
@instance.stop
|
|
57
|
+
@instance = nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.running?
|
|
62
|
+
!!@instance
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# rubocop:disable Metrics/MethodLength
|
|
66
|
+
def initialize(config)
|
|
67
|
+
@config = config
|
|
68
|
+
|
|
69
|
+
@stacktrace_builder = StacktraceBuilder.new(config)
|
|
70
|
+
@context_builder = ContextBuilder.new(config)
|
|
71
|
+
@error_builder = ErrorBuilder.new(self)
|
|
72
|
+
|
|
73
|
+
@central_config = CentralConfig.new(config)
|
|
74
|
+
# @transport = Transport::Base.new(config)
|
|
75
|
+
@instrumenter = Instrumenter.new(
|
|
76
|
+
config,
|
|
77
|
+
stacktrace_builder: stacktrace_builder
|
|
78
|
+
) { |event| enqueue event }
|
|
79
|
+
@metrics = Metrics.new(config) { |event| enqueue event }
|
|
80
|
+
@collector = Collector::Base.new(config)
|
|
81
|
+
end
|
|
82
|
+
# rubocop:enable Metrics/MethodLength
|
|
83
|
+
|
|
84
|
+
attr_reader(
|
|
85
|
+
:central_config,
|
|
86
|
+
:config,
|
|
87
|
+
:context_builder,
|
|
88
|
+
:error_builder,
|
|
89
|
+
:instrumenter,
|
|
90
|
+
:metrics,
|
|
91
|
+
:stacktrace_builder,
|
|
92
|
+
# :transport,
|
|
93
|
+
:collector
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
97
|
+
def start
|
|
98
|
+
unless config.disable_start_message
|
|
99
|
+
info '[%s] Starting atatus ruby agent', VERSION
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if @config.license_key.nil? || @config.app_name.nil?
|
|
103
|
+
if @config.license_key.nil? && @config.app_name.nil?
|
|
104
|
+
error 'Atatus configuration license_key and app_name are missing'
|
|
105
|
+
elsif @config.license_key.nil?
|
|
106
|
+
error 'Atatus configuration license_key is missing'
|
|
107
|
+
elsif @config.app_name.nil?
|
|
108
|
+
error 'Atatus configuration app_name is missing'
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
collector.start
|
|
113
|
+
central_config.start
|
|
114
|
+
# transport.start
|
|
115
|
+
instrumenter.start
|
|
116
|
+
metrics.start
|
|
117
|
+
|
|
118
|
+
config.enabled_instrumentations.each do |lib|
|
|
119
|
+
debug "Requiring spy: #{lib}"
|
|
120
|
+
require "atatus/spies/#{lib}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
126
|
+
|
|
127
|
+
def stop
|
|
128
|
+
debug 'Stopping agent'
|
|
129
|
+
|
|
130
|
+
central_config.stop
|
|
131
|
+
metrics.stop
|
|
132
|
+
instrumenter.stop
|
|
133
|
+
# transport.stop
|
|
134
|
+
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
at_exit do
|
|
139
|
+
stop
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# transport
|
|
143
|
+
|
|
144
|
+
def enqueue(obj)
|
|
145
|
+
# transport.submit obj
|
|
146
|
+
case obj
|
|
147
|
+
when Atatus::Transaction
|
|
148
|
+
collector.add_txn(obj)
|
|
149
|
+
when Atatus::Span
|
|
150
|
+
collector.add_span(obj)
|
|
151
|
+
when Atatus::Error
|
|
152
|
+
collector.add_error(obj)
|
|
153
|
+
when Atatus::Metricset
|
|
154
|
+
collector.add_metrics(obj)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# instrumentation
|
|
159
|
+
|
|
160
|
+
def current_transaction
|
|
161
|
+
instrumenter.current_transaction
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def current_span
|
|
165
|
+
instrumenter.current_span
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def start_transaction(
|
|
169
|
+
name = nil,
|
|
170
|
+
type = nil,
|
|
171
|
+
context: nil,
|
|
172
|
+
trace_context: nil
|
|
173
|
+
)
|
|
174
|
+
instrumenter.start_transaction(
|
|
175
|
+
name,
|
|
176
|
+
type,
|
|
177
|
+
context: context,
|
|
178
|
+
trace_context: trace_context
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def end_transaction(result = nil)
|
|
183
|
+
instrumenter.end_transaction(result)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# rubocop:disable Metrics/ParameterLists
|
|
187
|
+
def start_span(
|
|
188
|
+
name = nil,
|
|
189
|
+
type = nil,
|
|
190
|
+
subtype: nil,
|
|
191
|
+
action: nil,
|
|
192
|
+
backtrace: nil,
|
|
193
|
+
context: nil,
|
|
194
|
+
trace_context: nil
|
|
195
|
+
)
|
|
196
|
+
instrumenter.start_span(
|
|
197
|
+
name,
|
|
198
|
+
type,
|
|
199
|
+
subtype: subtype,
|
|
200
|
+
action: action,
|
|
201
|
+
backtrace: backtrace,
|
|
202
|
+
context: context,
|
|
203
|
+
trace_context: trace_context
|
|
204
|
+
)
|
|
205
|
+
end
|
|
206
|
+
# rubocop:enable Metrics/ParameterLists
|
|
207
|
+
|
|
208
|
+
def end_span
|
|
209
|
+
instrumenter.end_span
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def set_label(key, value)
|
|
213
|
+
instrumenter.set_label(key, value)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def set_custom_context(context)
|
|
217
|
+
instrumenter.set_custom_context(context)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def set_user(user)
|
|
221
|
+
instrumenter.set_user(user)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def build_context(rack_env:, for_type:)
|
|
225
|
+
@context_builder.build(rack_env: rack_env, for_type: for_type)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# errors
|
|
229
|
+
|
|
230
|
+
def report(exception, context: nil, handled: true)
|
|
231
|
+
return if config.filter_exception_types.include?(exception.class.to_s)
|
|
232
|
+
|
|
233
|
+
error = @error_builder.build_exception(
|
|
234
|
+
exception,
|
|
235
|
+
context: context,
|
|
236
|
+
handled: handled
|
|
237
|
+
)
|
|
238
|
+
enqueue error
|
|
239
|
+
error.id
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def report_message(message, context: nil, backtrace: nil, **attrs)
|
|
243
|
+
error = @error_builder.build_log(
|
|
244
|
+
message,
|
|
245
|
+
context: context,
|
|
246
|
+
backtrace: backtrace,
|
|
247
|
+
**attrs
|
|
248
|
+
)
|
|
249
|
+
enqueue error
|
|
250
|
+
error.id
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# filters
|
|
254
|
+
|
|
255
|
+
def add_filter(key, callback)
|
|
256
|
+
# transport.add_filter(key, callback)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
# rubocop:enable Metrics/ClassLength
|
|
260
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'atatus/central_config/cache_control'
|
|
4
|
+
|
|
5
|
+
module Atatus
|
|
6
|
+
# @api private
|
|
7
|
+
class CentralConfig
|
|
8
|
+
include Logging
|
|
9
|
+
|
|
10
|
+
# @api private
|
|
11
|
+
class ResponseError < InternalError
|
|
12
|
+
def initialize(response)
|
|
13
|
+
@response = response
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :response
|
|
17
|
+
end
|
|
18
|
+
class ClientError < ResponseError; end
|
|
19
|
+
class ServerError < ResponseError; end
|
|
20
|
+
|
|
21
|
+
DEFAULT_MAX_AGE = 300
|
|
22
|
+
|
|
23
|
+
def initialize(config)
|
|
24
|
+
@config = config
|
|
25
|
+
@modified_options = {}
|
|
26
|
+
@service_info = {
|
|
27
|
+
'service.name': config.service_name,
|
|
28
|
+
'service.environment': config.environment
|
|
29
|
+
}.to_json
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :config
|
|
33
|
+
attr_reader :scheduled_task, :promise # for specs
|
|
34
|
+
|
|
35
|
+
def start
|
|
36
|
+
return unless config.central_config?
|
|
37
|
+
|
|
38
|
+
fetch_and_apply_config
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fetch_and_apply_config
|
|
42
|
+
@promise =
|
|
43
|
+
Concurrent::Promise
|
|
44
|
+
.execute(&method(:fetch_config))
|
|
45
|
+
.on_success(&method(:handle_success))
|
|
46
|
+
.rescue(&method(:handle_error))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def stop
|
|
50
|
+
@scheduled_task&.cancel
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# rubocop:disable Metrics/MethodLength
|
|
54
|
+
def fetch_config
|
|
55
|
+
resp = perform_request
|
|
56
|
+
|
|
57
|
+
case resp.status
|
|
58
|
+
when 200..299
|
|
59
|
+
resp
|
|
60
|
+
when 300..399
|
|
61
|
+
resp
|
|
62
|
+
when 400..499
|
|
63
|
+
raise ClientError, resp
|
|
64
|
+
when 500..599
|
|
65
|
+
raise ServerError, resp
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
# rubocop:enable Metrics/MethodLength
|
|
69
|
+
|
|
70
|
+
def assign(update)
|
|
71
|
+
# For each updated option, store the original value,
|
|
72
|
+
# unless already stored
|
|
73
|
+
update.each_key do |key|
|
|
74
|
+
@modified_options[key] ||= config.get(key.to_sym)&.value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# If the new update doesn't set a previously modified option,
|
|
78
|
+
# revert it to the original
|
|
79
|
+
@modified_options.each_key do |key|
|
|
80
|
+
next if update.key?(key)
|
|
81
|
+
update[key] = @modified_options.delete(key)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
config.assign(update)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# rubocop:disable Metrics/MethodLength
|
|
90
|
+
def handle_success(resp)
|
|
91
|
+
if resp.status == 304
|
|
92
|
+
info 'Received 304 Not Modified'
|
|
93
|
+
else
|
|
94
|
+
update = JSON.parse(resp.body.to_s)
|
|
95
|
+
assign(update)
|
|
96
|
+
|
|
97
|
+
info 'Updated config from Kibana'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
schedule_next_fetch(resp)
|
|
101
|
+
|
|
102
|
+
true
|
|
103
|
+
rescue Exception => e
|
|
104
|
+
error 'Failed to apply remote config, %s', e.inspect
|
|
105
|
+
debug { e.backtrace.join('\n') }
|
|
106
|
+
end
|
|
107
|
+
# rubocop:enable Metrics/MethodLength
|
|
108
|
+
|
|
109
|
+
def handle_error(error)
|
|
110
|
+
error(
|
|
111
|
+
'Failed fetching config: %s, trying again in %d seconds',
|
|
112
|
+
error.response.body, DEFAULT_MAX_AGE
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assign({})
|
|
116
|
+
|
|
117
|
+
schedule_next_fetch(error.response)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def perform_request
|
|
121
|
+
Http.post(
|
|
122
|
+
config.server_url + '/agent/v1/config/',
|
|
123
|
+
body: @service_info,
|
|
124
|
+
headers: { etag: 1, content_type: 'application/json' }
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def schedule_next_fetch(resp)
|
|
129
|
+
seconds =
|
|
130
|
+
if (cache_header = resp.headers['Cache-Control'])
|
|
131
|
+
CacheControl.new(cache_header).max_age
|
|
132
|
+
else
|
|
133
|
+
DEFAULT_MAX_AGE
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@scheduled_task =
|
|
137
|
+
Concurrent::ScheduledTask
|
|
138
|
+
.execute(seconds, &method(:fetch_and_apply_config))
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class CentralConfig
|
|
5
|
+
# @api private
|
|
6
|
+
class CacheControl
|
|
7
|
+
def initialize(value)
|
|
8
|
+
@header = value
|
|
9
|
+
parse!(value)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader(
|
|
13
|
+
:must_revalidate,
|
|
14
|
+
:no_cache,
|
|
15
|
+
:no_store,
|
|
16
|
+
:no_transform,
|
|
17
|
+
:public,
|
|
18
|
+
:private,
|
|
19
|
+
:proxy_revalidate,
|
|
20
|
+
:max_age,
|
|
21
|
+
:s_maxage
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def parse!(value)
|
|
27
|
+
value.split(',').each do |token|
|
|
28
|
+
k, v = token.split('=').map(&:strip)
|
|
29
|
+
instance_variable_set(:"@#{k.gsub('-', '_')}", v ? v.to_i : true)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
require 'atatus'
|
|
2
|
+
require 'atatus/transaction'
|
|
3
|
+
require 'atatus/config'
|
|
4
|
+
# require 'atatus/logger'
|
|
5
|
+
require 'atatus/collector/transport'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Atatus
|
|
9
|
+
module Collector
|
|
10
|
+
class Layer
|
|
11
|
+
attr_reader :count, :min, :max, :total, :type, :kind, :id, :pid
|
|
12
|
+
attr_writer :id, :pid
|
|
13
|
+
|
|
14
|
+
def initialize(type, kind, duration)
|
|
15
|
+
@type = type
|
|
16
|
+
@kind = kind
|
|
17
|
+
@count = 1
|
|
18
|
+
@min = duration
|
|
19
|
+
@max = duration
|
|
20
|
+
@total = duration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def aggregate!(duration)
|
|
24
|
+
@count += 1
|
|
25
|
+
@min = duration if duration < @min
|
|
26
|
+
@max = duration if duration > @max
|
|
27
|
+
@total += duration
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.span_kind(value)
|
|
31
|
+
return KINDS.fetch(value, value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.span_type(value)
|
|
35
|
+
return TYPES.fetch(value, value)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
KINDS = {
|
|
40
|
+
'db' => 'Database',
|
|
41
|
+
'cache' => 'Database',
|
|
42
|
+
'ext' => 'Remote',
|
|
43
|
+
'websocket' => 'Remote',
|
|
44
|
+
'template' => 'Template'
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
TYPES = {
|
|
48
|
+
'mysql' => 'MySQL',
|
|
49
|
+
'mysql2' => 'MySQL',
|
|
50
|
+
'postgresql' => 'Postgres',
|
|
51
|
+
'mssql' => 'MS SQL',
|
|
52
|
+
'mongodb' => 'MongoDB',
|
|
53
|
+
'redis' => 'Redis',
|
|
54
|
+
'graphql' => 'GraphQL',
|
|
55
|
+
'elasticsearch' => 'Elasticsearch',
|
|
56
|
+
'cassandra' => 'Cassandra',
|
|
57
|
+
'http' => 'External Requests',
|
|
58
|
+
'http2' => 'External Requests',
|
|
59
|
+
'http_rb' => 'External Requests',
|
|
60
|
+
'net_http' => 'External Requests'
|
|
61
|
+
}.freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Txn < Layer
|
|
65
|
+
attr_reader :spans
|
|
66
|
+
|
|
67
|
+
def initialize(type, kind, duration)
|
|
68
|
+
super(type, kind, duration)
|
|
69
|
+
@spans = Hash.new {}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_span(span_name, type, kind, duration)
|
|
73
|
+
if !@spans.has_key?(span_name)
|
|
74
|
+
@spans[key] = Layer.new(type, kind, duration)
|
|
75
|
+
else
|
|
76
|
+
@spans[key].aggregate! duration
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class Base
|
|
82
|
+
include Logging
|
|
83
|
+
|
|
84
|
+
def initialize(config)
|
|
85
|
+
# info 'Initializing Collector'
|
|
86
|
+
@config = config
|
|
87
|
+
@spans = Hash.new {|h,k| h[k]=[]}
|
|
88
|
+
@running = false
|
|
89
|
+
uri = URI("https://apm-rx.atatus.com/")
|
|
90
|
+
@transport = Atatus::BaseTransport.new(config, uri.host, uri.port)
|
|
91
|
+
@txn_lock = Mutex.new
|
|
92
|
+
@txn_aggs = Hash.new {}
|
|
93
|
+
@trace_aggs = Array.new()
|
|
94
|
+
@error_metrics = Hash.new {}
|
|
95
|
+
@error_requests = Array.new()
|
|
96
|
+
|
|
97
|
+
@error_lock = Mutex.new
|
|
98
|
+
@error_aggs = Array.new()
|
|
99
|
+
|
|
100
|
+
@metrics_lock = Mutex.new
|
|
101
|
+
@metrics = Array.new()
|
|
102
|
+
@collect_counter = 0
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
attr_reader :config
|
|
106
|
+
|
|
107
|
+
def pid_str
|
|
108
|
+
format('[PID:%s]', Process.pid)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def start
|
|
112
|
+
debug '%s: Starting Collector', pid_str
|
|
113
|
+
|
|
114
|
+
ensure_worker_running
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def add_error(error)
|
|
118
|
+
ensure_worker_running
|
|
119
|
+
|
|
120
|
+
@error_lock.synchronize do
|
|
121
|
+
if @error_aggs.length < 20
|
|
122
|
+
@error_aggs.push(error)
|
|
123
|
+
else
|
|
124
|
+
i = rand(20)
|
|
125
|
+
@error_aggs[i] = error
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
# puts error.to_yaml
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def add_metrics(metricset)
|
|
132
|
+
ensure_worker_running
|
|
133
|
+
metric = {}
|
|
134
|
+
if metricset.samples.key?(:'system.memory.total')
|
|
135
|
+
metric[:'system.cpu.total.norm.pct'] = metricset.samples[:'system.cpu.total.norm.pct']
|
|
136
|
+
metric[:'system.memory.actual.free'] = metricset.samples[:'system.memory.actual.free']
|
|
137
|
+
metric[:'system.memory.total'] = metricset.samples[:'system.memory.total']
|
|
138
|
+
metric[:'system.process.cpu.total.norm.pct'] = metricset.samples[:'system.process.cpu.total.norm.pct']
|
|
139
|
+
metric[:'system.process.memory.size'] = metricset.samples[:'system.process.memory.size']
|
|
140
|
+
metric[:'system.process.memory.rss.bytes'] = metricset.samples[:'system.process.memory.rss.bytes']
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if metricset.samples.key?(:'ruby.gc.count')
|
|
144
|
+
metric[:'ruby.gc.count'] = metricset.samples[:'ruby.gc.count']
|
|
145
|
+
metric[:'ruby.threads'] = metricset.samples[:'ruby.threads']
|
|
146
|
+
metric[:'ruby.heap.slots.live'] = metricset.samples[:'ruby.heap.slots.live']
|
|
147
|
+
metric[:'ruby.heap.slots.free'] = metricset.samples[:'ruby.heap.slots.free']
|
|
148
|
+
metric[:'ruby.heap.allocations.total'] = metricset.samples[:'ruby.heap.allocations.total']
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@metrics_lock.synchronize do
|
|
152
|
+
@metrics << metric
|
|
153
|
+
end
|
|
154
|
+
# puts metric.to_yaml
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def add_span(span)
|
|
158
|
+
ensure_worker_running
|
|
159
|
+
|
|
160
|
+
@spans[span.transaction_id] << span if span.transaction_id
|
|
161
|
+
# puts span.to_yaml
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def add_txn(txn)
|
|
165
|
+
ensure_worker_running
|
|
166
|
+
|
|
167
|
+
# puts txn.to_yaml
|
|
168
|
+
txn_name = txn.name
|
|
169
|
+
return unless txn_name
|
|
170
|
+
@txn_lock.synchronize do
|
|
171
|
+
if !@txn_aggs.has_key?(txn_name)
|
|
172
|
+
@txn_aggs[txn_name] = Txn.new(@config.framework_name, "Ruby", txn.duration)
|
|
173
|
+
@txn_aggs[txn_name].id = txn.id
|
|
174
|
+
@txn_aggs[txn_name].pid = txn.id
|
|
175
|
+
else
|
|
176
|
+
@txn_aggs[txn_name].aggregate! txn.duration
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
spans_present = false
|
|
180
|
+
if @spans.has_key?(txn.id)
|
|
181
|
+
@spans[txn.id].each do |span|
|
|
182
|
+
span_name = span.name
|
|
183
|
+
if !@txn_aggs[txn_name].spans.has_key?(span_name)
|
|
184
|
+
kind = Layer.span_kind(span.type)
|
|
185
|
+
type = Layer.span_type(span.subtype)
|
|
186
|
+
@txn_aggs[txn_name].spans[span_name] = Layer.new(type, kind, span.duration)
|
|
187
|
+
@txn_aggs[txn_name].spans[span_name].id = span.id
|
|
188
|
+
@txn_aggs[txn_name].spans[span_name].pid = span.transaction_id
|
|
189
|
+
else
|
|
190
|
+
@txn_aggs[txn_name].spans[span_name].aggregate! span.duration
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
spans_present = true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if spans_present
|
|
197
|
+
if Util.ms(txn.duration) >= @config.trace_threshold
|
|
198
|
+
trace_txn = txn
|
|
199
|
+
trace_txn.spans = @spans[txn.id]
|
|
200
|
+
|
|
201
|
+
if @trace_aggs.length < 5
|
|
202
|
+
@trace_aggs.push(trace_txn)
|
|
203
|
+
else
|
|
204
|
+
i = rand(5)
|
|
205
|
+
@trace_aggs[i] = trace_txn
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if spans_present
|
|
211
|
+
@spans.delete(txn.id)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
status_code = txn.context.response.status_code
|
|
215
|
+
|
|
216
|
+
if status_code >= 400 && status_code != 404
|
|
217
|
+
if !@error_metrics.has_key?(txn_name)
|
|
218
|
+
@error_metrics[txn_name] = {status_code => 1}
|
|
219
|
+
else
|
|
220
|
+
if !@error_metrics[txn_name].has_key?(status_code)
|
|
221
|
+
@error_metrics[txn_name][status_code] = 1
|
|
222
|
+
else
|
|
223
|
+
@error_metrics[txn_name][status_code] += 1
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# puts txn.context.to_yaml
|
|
228
|
+
if @error_requests.length < 20
|
|
229
|
+
@error_requests.push({'name' => txn_name, 'context' => txn.context})
|
|
230
|
+
else
|
|
231
|
+
i = rand(20)
|
|
232
|
+
@error_requests[i] = {'name' => txn_name, 'context' => txn.context}
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
private
|
|
240
|
+
def ensure_worker_running
|
|
241
|
+
return if worker_active?
|
|
242
|
+
@running = true
|
|
243
|
+
@worker = Thread.new() do
|
|
244
|
+
debug '%s: Starting Collector Worker', pid_str
|
|
245
|
+
|
|
246
|
+
while @running
|
|
247
|
+
start_time = Time.now
|
|
248
|
+
sleep(60)
|
|
249
|
+
collect start_time
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
at_exit do
|
|
254
|
+
debug '%s: Waiting for Collector Worker to exit', pid_str
|
|
255
|
+
@running = false
|
|
256
|
+
@worker.run
|
|
257
|
+
@worker.join(10)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def worker_active?
|
|
262
|
+
@worker && @worker.alive?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def collect(start_time)
|
|
266
|
+
# puts @config.to_yaml
|
|
267
|
+
# return
|
|
268
|
+
if @config.license_key.nil? || @config.app_name.nil?
|
|
269
|
+
if @config.license_key.nil? && @config.app_name.nil?
|
|
270
|
+
error '%s: Atatus configuration license_key and app_name are missing', pid_str
|
|
271
|
+
return
|
|
272
|
+
elsif @config.license_key.nil?
|
|
273
|
+
error '%s: Atatus configuration license_key is missing', pid_str
|
|
274
|
+
return
|
|
275
|
+
elsif @config.app_name.nil?
|
|
276
|
+
error '%s: Atatus configuration app_name is missing', pid_str
|
|
277
|
+
return
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
if @collect_counter % 30 == 0
|
|
282
|
+
@transport.hostinfo(start_time)
|
|
283
|
+
@collect_counter = 0
|
|
284
|
+
end
|
|
285
|
+
@collect_counter += 1
|
|
286
|
+
|
|
287
|
+
end_time = Time.now
|
|
288
|
+
debug '%s: Collecting transactions', pid_str
|
|
289
|
+
|
|
290
|
+
txn_data = nil
|
|
291
|
+
trace_data = nil
|
|
292
|
+
error_data = nil
|
|
293
|
+
error_metrics_data = nil
|
|
294
|
+
error_requests_data = nil
|
|
295
|
+
metrics_data = nil
|
|
296
|
+
|
|
297
|
+
@txn_lock.synchronize do
|
|
298
|
+
txn_data = @txn_aggs
|
|
299
|
+
@txn_aggs = Hash.new {}
|
|
300
|
+
|
|
301
|
+
trace_data = @trace_aggs
|
|
302
|
+
@trace_aggs = Array.new()
|
|
303
|
+
|
|
304
|
+
error_metrics_data = @error_metrics
|
|
305
|
+
@error_metrics = Hash.new {}
|
|
306
|
+
|
|
307
|
+
error_requests_data = @error_requests
|
|
308
|
+
@error_requests = Array.new()
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
@error_lock.synchronize do
|
|
312
|
+
error_data = @error_aggs
|
|
313
|
+
@error_aggs = Array.new()
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
@metrics_lock.synchronize do
|
|
317
|
+
metrics_data = @metrics
|
|
318
|
+
@metrics = Array.new()
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
@transport.txn(start_time, end_time, txn_data) unless txn_data.empty?
|
|
322
|
+
@transport.trace(start_time, end_time, trace_data) unless trace_data.empty?
|
|
323
|
+
@transport.error_metrics(start_time, end_time, error_metrics_data, error_requests_data) unless error_metrics_data.empty?
|
|
324
|
+
@transport.errors(start_time, end_time, error_data) unless error_data.empty?
|
|
325
|
+
@transport.metrics(start_time, end_time, metrics_data) unless metrics_data.empty?
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|