atatus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +57 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-THIRD-PARTY +205 -0
  7. data/README.md +13 -0
  8. data/Rakefile +19 -0
  9. data/atatus.gemspec +36 -0
  10. data/atatus.yml +2 -0
  11. data/bench/.gitignore +2 -0
  12. data/bench/app.rb +53 -0
  13. data/bench/benchmark.rb +36 -0
  14. data/bench/report.rb +55 -0
  15. data/bench/rubyprof.rb +39 -0
  16. data/bench/stackprof.rb +23 -0
  17. data/bin/build_docs +5 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/bin/with_framework +7 -0
  21. data/lib/atatus.rb +325 -0
  22. data/lib/atatus/agent.rb +260 -0
  23. data/lib/atatus/central_config.rb +141 -0
  24. data/lib/atatus/central_config/cache_control.rb +34 -0
  25. data/lib/atatus/collector/base.rb +329 -0
  26. data/lib/atatus/collector/builder.rb +317 -0
  27. data/lib/atatus/collector/transport.rb +72 -0
  28. data/lib/atatus/config.rb +248 -0
  29. data/lib/atatus/config/bytes.rb +25 -0
  30. data/lib/atatus/config/duration.rb +23 -0
  31. data/lib/atatus/config/options.rb +134 -0
  32. data/lib/atatus/config/regexp_list.rb +13 -0
  33. data/lib/atatus/context.rb +33 -0
  34. data/lib/atatus/context/request.rb +11 -0
  35. data/lib/atatus/context/request/socket.rb +19 -0
  36. data/lib/atatus/context/request/url.rb +42 -0
  37. data/lib/atatus/context/response.rb +22 -0
  38. data/lib/atatus/context/user.rb +42 -0
  39. data/lib/atatus/context_builder.rb +97 -0
  40. data/lib/atatus/deprecations.rb +22 -0
  41. data/lib/atatus/error.rb +22 -0
  42. data/lib/atatus/error/exception.rb +46 -0
  43. data/lib/atatus/error/log.rb +24 -0
  44. data/lib/atatus/error_builder.rb +76 -0
  45. data/lib/atatus/instrumenter.rb +224 -0
  46. data/lib/atatus/internal_error.rb +6 -0
  47. data/lib/atatus/logging.rb +55 -0
  48. data/lib/atatus/metadata.rb +19 -0
  49. data/lib/atatus/metadata/process_info.rb +18 -0
  50. data/lib/atatus/metadata/service_info.rb +61 -0
  51. data/lib/atatus/metadata/system_info.rb +35 -0
  52. data/lib/atatus/metadata/system_info/container_info.rb +121 -0
  53. data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
  54. data/lib/atatus/metadata/system_info/os_info.rb +31 -0
  55. data/lib/atatus/metrics.rb +98 -0
  56. data/lib/atatus/metrics/cpu_mem.rb +240 -0
  57. data/lib/atatus/metrics/vm.rb +60 -0
  58. data/lib/atatus/metricset.rb +19 -0
  59. data/lib/atatus/middleware.rb +76 -0
  60. data/lib/atatus/naively_hashable.rb +21 -0
  61. data/lib/atatus/normalizers.rb +68 -0
  62. data/lib/atatus/normalizers/action_controller.rb +27 -0
  63. data/lib/atatus/normalizers/action_mailer.rb +26 -0
  64. data/lib/atatus/normalizers/action_view.rb +77 -0
  65. data/lib/atatus/normalizers/active_record.rb +45 -0
  66. data/lib/atatus/opentracing.rb +346 -0
  67. data/lib/atatus/rails.rb +61 -0
  68. data/lib/atatus/railtie.rb +30 -0
  69. data/lib/atatus/span.rb +125 -0
  70. data/lib/atatus/span/context.rb +40 -0
  71. data/lib/atatus/span_helpers.rb +44 -0
  72. data/lib/atatus/spies.rb +86 -0
  73. data/lib/atatus/spies/action_dispatch.rb +28 -0
  74. data/lib/atatus/spies/delayed_job.rb +68 -0
  75. data/lib/atatus/spies/elasticsearch.rb +36 -0
  76. data/lib/atatus/spies/faraday.rb +70 -0
  77. data/lib/atatus/spies/http.rb +44 -0
  78. data/lib/atatus/spies/json.rb +22 -0
  79. data/lib/atatus/spies/mongo.rb +87 -0
  80. data/lib/atatus/spies/net_http.rb +70 -0
  81. data/lib/atatus/spies/rake.rb +45 -0
  82. data/lib/atatus/spies/redis.rb +27 -0
  83. data/lib/atatus/spies/sequel.rb +47 -0
  84. data/lib/atatus/spies/sidekiq.rb +89 -0
  85. data/lib/atatus/spies/sinatra.rb +41 -0
  86. data/lib/atatus/spies/tilt.rb +27 -0
  87. data/lib/atatus/sql_summarizer.rb +35 -0
  88. data/lib/atatus/stacktrace.rb +16 -0
  89. data/lib/atatus/stacktrace/frame.rb +52 -0
  90. data/lib/atatus/stacktrace_builder.rb +104 -0
  91. data/lib/atatus/subscriber.rb +77 -0
  92. data/lib/atatus/trace_context.rb +85 -0
  93. data/lib/atatus/transaction.rb +100 -0
  94. data/lib/atatus/transport/base.rb +174 -0
  95. data/lib/atatus/transport/connection.rb +156 -0
  96. data/lib/atatus/transport/connection/http.rb +116 -0
  97. data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
  98. data/lib/atatus/transport/filters.rb +43 -0
  99. data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
  100. data/lib/atatus/transport/serializers.rb +93 -0
  101. data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
  102. data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
  103. data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
  104. data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
  105. data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
  106. data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
  107. data/lib/atatus/transport/worker.rb +73 -0
  108. data/lib/atatus/util.rb +42 -0
  109. data/lib/atatus/util/inflector.rb +93 -0
  110. data/lib/atatus/util/lru_cache.rb +48 -0
  111. data/lib/atatus/util/prefixed_logger.rb +18 -0
  112. data/lib/atatus/util/throttle.rb +35 -0
  113. data/lib/atatus/version.rb +5 -0
  114. data/vendor/.gitkeep +0 -0
  115. metadata +190 -0
@@ -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