atatus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|