plain_apm 0.9.1 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41210e3cc29f6e3645782734560b34029d5b4add1b1037d1f58aaccad4d862e1
4
- data.tar.gz: f34874b032c24d198306605b7cb97c86a6c302028fb964e1bff3510e22996daf
3
+ metadata.gz: a2950f7d2e02a813b67a5bfb4c2ff7cc522acb8770404e3ef3dcef4e13aa4c4c
4
+ data.tar.gz: 4af16bf90b9904db8f5adf9efc6438aec2a6f31954a481f1a3384fbdadddf1d4
5
5
  SHA512:
6
- metadata.gz: 6f13aa7ab1389bc50e92ec060f8efd370c00355b871c347d74696d362d5212a53c0a6606b78cbf3c763d18b1d9dd0a1783add010897c201af38371f762af6be8
7
- data.tar.gz: d657c5a1c56158c94c7f1b5d366b2d52aa38e9d29fc46e820af96e695ee69494de0464228f4fad6eb2788f410b88c00d55d30092c8effb00d0da5b5609d6c7bd
6
+ metadata.gz: 6ceba3781efda4b4b60dc66f85ed07a065b47c624d7c6da2fbd42d52bc525cf2b95204d9030aa6e5540e7f6e5e6112b387a40008b29c69236cedcbeb9ae62e69
7
+ data.tar.gz: '08dcda87123a5e006442cfe574da4ee41c011a28880b559a19bb65871785eb06154ba19f86c00d159c7d0c3e40b22c9758b6a373150de1344a001062fab0cd96'
@@ -1,62 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
- require "json"
5
4
 
6
5
  module PlainApm
7
6
  class Agent
8
7
  include Singleton
9
8
 
10
- def self.collect(event)
11
- instance.collect(event)
9
+ def enabled?
10
+ @config && @config.enabled
12
11
  end
13
12
 
14
- def self.start
15
- instance.start
16
- end
13
+ def collect(event)
14
+ return unless enabled?
17
15
 
18
- def self.stop
19
- instance.stop
20
- end
16
+ # stop accepting events when shutting down / shutdown.
17
+ return if @status != :running
21
18
 
22
- def collect(event)
23
- return unless @config.enabled
19
+ publisher_start if @pid != $$
24
20
 
25
21
  @events << event
26
22
  end
27
23
 
28
24
  def start
29
- # Already running
30
- return unless @publisher.nil?
25
+ if !defined?(@started)
26
+ @started = true
27
+ else
28
+ return
29
+ end
31
30
 
32
31
  configure
33
32
 
34
- return unless @config.enabled
33
+ return unless enabled?
34
+
35
+ warn("PlainAPM agent enabled.")
36
+
37
+ setup_at_exit_hooks
38
+ publisher_start
39
+ install_hooks
40
+ end
41
+
42
+ private
43
+
44
+ def stop
45
+ uninstall_hooks
46
+ publisher_shutdown
47
+ end
48
+
49
+ def publisher_start
50
+ # Store PID for fork detection
51
+ @pid = $$
52
+
53
+ # Already running
54
+ return if @publisher&.alive?
35
55
 
36
- # TODO: sized queue w/ a timeout.
37
- @events = Thread::Queue.new
56
+ # TODO: sized queue.
57
+ @events = PlainApm::Queue.new
38
58
 
39
59
  # TODO: Multiple threads
40
60
  @publisher = Thread.new { publisher_loop }
41
61
 
42
- install_hooks
62
+ @status = :running
63
+ end
43
64
 
44
- # TODO: add a cleaner shutdown.
65
+ def setup_at_exit_hooks
45
66
  at_exit { stop }
46
67
  end
47
68
 
48
- def stop
69
+ def publisher_shutdown
49
70
  return if @publisher.nil?
50
71
 
51
- uninstall_hooks
52
-
72
+ # FIXME: raise in / kill the threads after a pre-determined timeout not
73
+ # to block
74
+ @status = :shutting_down
53
75
  @events << nil
54
76
  @publisher.join
55
77
  @publisher = nil
56
78
  end
57
79
 
58
- private
59
-
60
80
  def configure
61
81
  @config = Config.new
62
82
  end
@@ -92,16 +112,38 @@ module PlainApm
92
112
  app_key: @config.app_key
93
113
  )
94
114
 
95
- loop do
96
- event = @events.pop
97
-
98
- break if event.nil?
115
+ buf = []
116
+ timeout = 1.0
99
117
 
100
- # TODO: event serialization
101
- _response, _error, _retriable = transport.deliver(JSON.generate(event))
102
-
103
- # TODO: retries / drops
118
+ loop do
119
+ event = @events.pop(timeout: timeout)
120
+
121
+ buf << event if event
122
+
123
+ case @status
124
+ when :running
125
+ # not a timeout or full buffer
126
+ next if !event.nil? && buf.size < 128
127
+ send(transport, buf)
128
+ buf = []
129
+ when :shutting_down
130
+ send(transport, buf)
131
+ buf = []
132
+ @status = :shutdown
133
+ break
134
+ when :shutdown
135
+ nil
136
+ else
137
+ # ?
138
+ end
104
139
  end
105
140
  end
141
+
142
+ # TODO: retries / drops
143
+ def send(transport, buf)
144
+ return if buf.empty?
145
+ meta = { queue: @events.size, pid: $$, thread: Thread.current.object_id, sent_at: Time.now.to_f }
146
+ _response, _error, _retriable = transport.deliver(buf, meta)
147
+ end
106
148
  end
107
149
  end
@@ -10,8 +10,6 @@ module PlainApm
10
10
  @enabled = enabled? && key_present?
11
11
  @endpoint = ENV["PLAIN_APM_ENDPOINT"] || DEFAULT_EVENT_ENDPOINT
12
12
  @app_key = ENV["PLAIN_APM_APP_KEY"]
13
-
14
- warn("PlainAPM agent enabled.") if enabled
15
13
  end
16
14
 
17
15
  private
@@ -21,34 +21,45 @@ module PlainApm
21
21
 
22
22
  return [source, nil] if IGNORED_EXCEPTIONS.include?(e.class.name)
23
23
 
24
- attrs = {
25
- "class" => e.class.name,
26
- "message" => e.message,
27
- "backtrace" => e.backtrace
28
- }
24
+ attrs = context_attributes
25
+
26
+ attrs[:class] = e.class.name
27
+ attrs[:message] = e.message
28
+ attrs[:backtrace] = e.backtrace
29
29
 
30
30
  if error_source
31
- attrs["event_source"] = error_source
31
+ attrs[:event_source] = error_source
32
32
  end
33
33
 
34
34
  if context[:env]
35
- attrs["params"] = context[:env]["action_dispatch.request.parameters"]
35
+ attrs[:params] = context[:env]["action_dispatch.request.parameters"]
36
36
  end
37
37
 
38
38
  if context[:job]&.is_a?(ActiveJob::Base)
39
- attrs["job_class"] = context[:job].class.name
40
- attrs["queue_name"] = context[:job].queue_name
39
+ attrs[:job_class] = context[:job].class.name
40
+ attrs[:queue_name] = context[:job].queue_name
41
+ end
42
+
43
+ # https://bugs.ruby-lang.org/issues/19197
44
+ root_cause = e
45
+ root_cause = root_cause.cause while root_cause.cause
46
+
47
+ if root_cause != e
48
+ attrs[:root_cause_class] = root_cause.class.name
49
+ attrs[:root_cause_message] = root_cause.message
50
+ attrs[:root_cause_backtrace] = root_cause.backtrace
51
+ loc = source_location(root_cause.backtrace)
52
+ if !loc.nil?
53
+ attrs[:root_cause_location] = loc
54
+ end
41
55
  end
42
56
 
43
- if e.cause
44
- attrs.merge!({
45
- "cause_class" => e.cause.class.name,
46
- "cause_message" => e.cause.message,
47
- "cause_backtrace" => e.cause.backtrace
48
- })
57
+ loc = source_location(e.backtrace)
58
+ if !loc.nil?
59
+ attrs[:source_location] = loc
49
60
  end
50
61
 
51
- attrs.merge!(trace_attributes(source))
62
+ add_trace_attributes(attrs)
52
63
 
53
64
  [source, attrs]
54
65
  end
@@ -57,70 +68,73 @@ module PlainApm
57
68
  name, source = *event.name.split(".")
58
69
  loc = source_location
59
70
 
60
- attrs = {
61
- "source" => source,
62
- "name" => name,
63
- "allocations" => event.allocations,
64
- "event_time" => event.time,
65
- "duration" => event.duration
66
- }
71
+ attrs = context_attributes
67
72
 
68
- attrs["thread_allocations"] = event.thread_allocations if event.respond_to?(:thread_allocations)
69
- attrs["source_location"] = loc if !loc.nil?
73
+ attrs[:source] = source
74
+ attrs[:name] = name
75
+ attrs[:allocations] = event.allocations
76
+ attrs[:event_time] = event.time
77
+ attrs[:duration] = event.duration
70
78
 
71
- attrs.merge!(trace_attributes(source))
79
+ if event.respond_to?(:thread_allocations)
80
+ attrs[:thread_allocations] = event.thread_allocations
81
+ end
82
+
83
+ if !loc.nil?
84
+ attrs[:source_location] = loc
85
+ end
86
+
87
+ add_trace_attributes(attrs)
72
88
 
73
89
  [name, attrs]
74
90
  end
75
91
 
76
92
  private
77
93
 
78
- def trace_attributes(source)
79
- process_attributes.merge(context_attributes)
80
- end
81
-
82
- def process_attributes
83
- {
84
- "thread_id" => Thread.current.object_id,
85
- "collected_at" => Time.now.iso8601(9),
86
- # TODO: There is a perf cost in Rubies until 3.3, so it might be a good
87
- # idea to cache this. See https://bugs.ruby-lang.org/issues/19443 for
88
- # the feature.
89
- "pid" => Process.pid,
90
- "version" => PlainApm::VERSION
91
- }.merge(
92
- cached_attributes
93
- )
94
+ # TODO: There is a perf cost in Rubies for Process.pid until 3.3, so it
95
+ # might be a good idea to cache this. See
96
+ # https://bugs.ruby-lang.org/issues/19443 for the feature.
97
+ def add_trace_attributes(attrs)
98
+ attrs[:thread_id] = Thread.current.object_id
99
+ attrs[:collected_at] = Time.now.to_f
100
+ attrs[:pid] = Process.pid
101
+ attrs[:version] = PlainApm::VERSION
102
+ attrs[:hostname] = cached_attributes[:hostname]
103
+ attrs[:revision] = cached_attributes[:revision]
94
104
  end
95
105
 
96
106
  def cached_attributes
97
107
  @cached_attributes ||= {
98
- "hostname" => Socket.gethostname,
99
- "revision" => PlainApm::DeployTracking.revision
108
+ hostname: Socket.gethostname,
109
+ revision: PlainApm::DeployTracking.revision
100
110
  }
101
111
  end
102
112
 
113
+ ##
114
+ # Context contains the trace ID (which comes from either
115
+ # HTTP_X_REQUEST_ID header, the deserialized job,
116
+ # or is generated by the trace_id middleware).
117
+ # It can also carry user inserted app data.
103
118
  def context_attributes
104
- ##
105
- # Context contains the trace ID (which comes from either
106
- # HTTP_X_REQUEST_ID header, the deserialized job,
107
- # or is generated by the trace_id middleware).
108
- # It can also carry user inserted app data.
109
119
  if defined?(PlainApm::Extensions::Context)
110
- PlainApm::Extensions::Context.current.transform_keys(&:to_s)
120
+ PlainApm::Extensions::Context.current.dup
111
121
  else
112
122
  {}
113
123
  end
114
124
  end
115
125
 
116
- def source_location
117
- filtered_backtrace&.first
126
+ def source_location(backtrace = nil)
127
+ return if self.class.rails_root.nil?
128
+ call = (backtrace || caller).find { |frame| frame.start_with?(self.class.rails_root) } || return
129
+ call[(self.class.rails_root.size + 1)..-1]
118
130
  end
119
131
 
120
- def filtered_backtrace
121
- if defined?(Rails) && defined?(Rails::BacktraceCleaner)
122
- @cleaner ||= Rails::BacktraceCleaner.new
123
- @cleaner.clean(caller)
132
+ def self.included(other)
133
+ other.class_eval do
134
+ def self.rails_root
135
+ return @rails_root if defined?(@rails_root)
136
+ @rails_root = (defined?(Rails) && Rails.root.to_s.present?) ? Rails.root.to_s.freeze : nil
137
+ end
124
138
  end
125
139
  end
126
140
  end
@@ -7,27 +7,21 @@
7
7
  #
8
8
  # See LICENSE.txt in the current directory for the license.
9
9
 
10
- begin
11
- require "rails/railtie"
12
- rescue LoadError
13
- nil
14
- end
15
-
16
10
  module PlainApm
17
11
  module Extensions
18
12
  module Context
19
- if defined?(Rails::Railtie)
20
- class Railtie < Rails::Railtie
21
- initializer "plain_apm.initialize_context" do |app|
22
- ActiveSupport.on_load(:active_job) do |klass|
23
- klass.prepend(PlainApm::Extensions::Context::ActiveJob)
24
- end
13
+ class Railtie < Rails::Railtie
14
+ initializer(:plain_apm_thread_context, after: :plain_apm_agent_start) do |app|
15
+ next if !PlainApm.agent.enabled?
16
+
17
+ ActiveSupport.on_load(:active_job, run_once: true) do |klass|
18
+ klass.prepend(PlainApm::Extensions::Context::ActiveJob)
19
+ end
25
20
 
26
- if defined?(ActionDispatch::RequestId)
27
- app.config.middleware.insert_after ActionDispatch::RequestId, PlainApm::Extensions::Context::Rack
28
- else
29
- app.config.middleware.insert_after Rack::MethodOverride, PlainApm::Extensions::Context::Rack
30
- end
21
+ if defined?(ActionDispatch::RequestId)
22
+ app.config.middleware.insert_after ActionDispatch::RequestId, PlainApm::Extensions::Context::Rack
23
+ else
24
+ app.config.middleware.insert_after Rack::MethodOverride, PlainApm::Extensions::Context::Rack
31
25
  end
32
26
  end
33
27
  end
@@ -1,3 +1,7 @@
1
+ require_relative "context/rack"
2
+ require_relative "context/active_job"
3
+ require_relative "context/railtie" if defined?(Rails::Railtie)
4
+
1
5
  module PlainApm
2
6
  module Extensions
3
7
  module Context
@@ -26,7 +26,7 @@ module PlainApm
26
26
 
27
27
  ##
28
28
  # Allow tracing request ID through jobs
29
- ActiveSupport.on_load(:active_job) do |klass|
29
+ ActiveSupport.on_load(:active_job, run_once: true) do |klass|
30
30
  klass.prepend(ActiveJob)
31
31
  end
32
32
  end
@@ -29,8 +29,8 @@ module PlainApm
29
29
 
30
30
  return if event.nil?
31
31
 
32
- event["source"] = source
33
- event["name"] = "rack_middleware"
32
+ event[:source] = source
33
+ event[:name] = "rack_middleware"
34
34
 
35
35
  PlainApm.agent.collect(event)
36
36
  end
@@ -1,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "rails/railtie"
5
- rescue LoadError
6
- nil
7
- end
8
-
9
3
  module PlainApm
10
4
  module Extensions
11
5
  module Exceptions
12
- if defined?(Rails::Railtie)
13
- class Railtie < Rails::Railtie
14
- initializer "plain_apm.insert_exceptions_middleware" do |app|
15
- app.config.middleware.insert(0, PlainApm::Extensions::Exceptions::Rack)
16
- end
6
+ class Railtie < Rails::Railtie
7
+ initializer(:plain_apm_exceptions_middleware, after: :plain_apm_agent_start) do |app|
8
+ next if !PlainApm.agent.enabled?
9
+
10
+ app.config.middleware.insert(0, PlainApm::Extensions::Exceptions::Rack)
17
11
  end
18
12
  end
19
13
  end
@@ -0,0 +1,3 @@
1
+ require_relative "exceptions/rack"
2
+ require_relative "exceptions/railtie" if defined?(Rails::Railtie)
3
+
@@ -16,14 +16,8 @@ module PlainApm
16
16
  @thread_allocation_count_finish - @thread_allocation_count_start
17
17
  end
18
18
 
19
- if defined?(PlainApm::ObjectTracing)
20
- def now_thread_allocations
21
- PlainApm::ObjectTracing.total_thread_allocated_objects
22
- end
23
- else
24
- def now_thread_allocations
25
- 0
26
- end
19
+ def now_thread_allocations
20
+ PlainApm::ObjectTracing.total_thread_allocated_objects
27
21
  end
28
22
  end
29
23
  end
@@ -1,21 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "rails/railtie"
5
- rescue LoadError
6
- nil
7
- end
8
-
9
3
  module PlainApm
10
4
  module Extensions
11
5
  module ThreadAllocations
12
- if defined?(Rails::Railtie)
13
- class Railtie < Rails::Railtie
14
- initializer "plain_apm.thread_allocations_in_active_support_events" do
15
- ActiveSupport::Notifications::Event.prepend(
16
- PlainApm::Extensions::ThreadAllocations::ActiveSupportEvent
17
- )
18
- end
6
+ class Railtie < Rails::Railtie
7
+ initializer(:plain_apm_thread_allocationss, after: :plain_apm_agent_start) do
8
+ next if !PlainApm.agent.enabled?
9
+
10
+ require "object_tracing"
11
+
12
+ ActiveSupport::Notifications::Event.prepend(
13
+ PlainApm::Extensions::ThreadAllocations::ActiveSupportEvent
14
+ )
19
15
  end
20
16
  end
21
17
  end
@@ -0,0 +1,2 @@
1
+ require_relative "thread_allocations/active_support_event"
2
+ require_relative "thread_allocations/railtie" if defined?(Rails::Railtie)
@@ -1,12 +1,13 @@
1
1
  module PlainApm
2
2
  module Helpers
3
3
  def plain_apm_context(context = {})
4
+ return unless PlainApm.agent.enabled?
4
5
  PlainApm::Extensions::Context.context.merge!(context)
5
6
  end
6
7
 
7
8
  def plain_apm_instrument(name, context = {}, &block)
9
+ return unless PlainApm.agent.enabled? && defined?(ActiveSupport::Notifications)
8
10
  sanitized_name = name.gsub(/\W/, "_").gsub(/(?!^)([A-Z])/) { |m| "_#{m}" }.squeeze("_").downcase
9
- return unless defined?(ActiveSupport::Notifications)
10
11
  ActiveSupport::Notifications.instrument("#{sanitized_name}.manual_plain_apm", **context, &block)
11
12
  end
12
13
  end
@@ -17,16 +17,18 @@ module PlainApm
17
17
 
18
18
  case name
19
19
  when "deliver"
20
- base.merge({
21
- "message_id" => payload[:message_id],
22
- "mailer" => payload[:mailer],
23
- "perform_deliveries" => payload[:perform_deliveries]
24
- })
20
+ base.tap do |o|
21
+ o[:message_id] = payload[:message_id]
22
+ o[:mailer] = payload[:mailer]
23
+ o[:perform_deliveries] = payload[:perform_deliveries]
24
+ end
25
25
  when "process"
26
- base.merge({
27
- "mailer" => payload[:mailer],
28
- "action" => payload[:action]
29
- })
26
+ base.tap do |o|
27
+ o[:mailer] = payload[:mailer]
28
+ o[:action] = payload[:action]
29
+ end
30
+ else
31
+ nil
30
32
  end
31
33
  end
32
34
  end
@@ -17,20 +17,22 @@ module PlainApm
17
17
 
18
18
  case name
19
19
  when "process_action"
20
- base.merge({
21
- "controller" => payload[:controller],
22
- "action" => payload[:action],
23
- "params" => payload[:params],
24
- "format" => payload[:format],
25
- "method" => payload[:method],
26
- "path" => payload[:path],
27
- "status" => payload[:status]
28
- })
20
+ base.tap do |o|
21
+ o[:controller] = payload[:controller]
22
+ o[:action] = payload[:action]
23
+ o[:params] = payload[:params]
24
+ o[:format] = payload[:format]
25
+ o[:method] = payload[:method]
26
+ o[:path] = payload[:path]
27
+ o[:status] = payload[:status]
28
+ end
29
29
  when "redirect_to", "start_processing", "halted_callback", "send_file", "send_data"
30
30
  nil
31
31
  when "read_fragment", "write_fragment", "exist_fragment?", "expire_fragment"
32
32
  # controller, action, key
33
33
  nil
34
+ else
35
+ nil
34
36
  end
35
37
  end
36
38
  end
@@ -17,27 +17,29 @@ module PlainApm
17
17
 
18
18
  case name
19
19
  when "render_collection"
20
- base.merge({
21
- "identifier" => identifier(payload[:identifier]),
22
- "layout" => payload[:layout],
23
- "count" => payload[:count],
24
- "cache_hits" => payload[:cache_hits]
25
- })
20
+ base.tap do |o|
21
+ o[:identifier] = identifier(payload[:identifier])
22
+ o[:layout] = payload[:layout]
23
+ o[:count] = payload[:count]
24
+ o[:cache_hits] = payload[:cache_hits]
25
+ end
26
26
  when "render_layout"
27
- base.merge({
28
- "identifier" => identifier(payload[:identifier])
29
- })
27
+ base.tap do |o|
28
+ o[:identifier] = identifier(payload[:identifier])
29
+ end
30
30
  when "render_template"
31
- base.merge({
32
- "identifier" => identifier(payload[:identifier]),
33
- "layout" => payload[:layout]
34
- })
31
+ base.tap do |o|
32
+ o[:identifier] = identifier(payload[:identifier])
33
+ o[:layout] = payload[:layout]
34
+ end
35
35
  when "render_partial"
36
- base.merge({
37
- "identifier" => identifier(payload[:identifier]),
38
- "layout" => payload[:layout],
39
- "cache_hit" => payload[:cache_hit]
40
- })
36
+ base.tap do |o|
37
+ o[:identifier] = identifier(payload[:identifier])
38
+ o[:layout] = payload[:layout]
39
+ o[:cache_hit] = payload[:cache_hit]
40
+ end
41
+ else
42
+ nil
41
43
  end
42
44
  end
43
45
 
@@ -16,33 +16,35 @@ module PlainApm
16
16
  payload = event.payload
17
17
  job = payload[:job]
18
18
 
19
- base.merge!({
20
- "queue_name" => job.queue_name,
21
- "job_id" => job.job_id,
22
- "job_class" => job.class.name,
23
- "job_arguments" => job.arguments,
24
- "executions" => job.executions,
25
- "enqueued_at" => enqueued_at(job),
26
- "dequeued_at" => dequeued_at(job),
27
- "scheduled_at" => job.scheduled_at,
28
- "adapter" => payload[:adapter].class.name,
29
- "aborted" => payload[:aborted]
30
- })
19
+ base.tap do |o|
20
+ o[:queue_name] = job.queue_name
21
+ o[:job_id] = job.job_id
22
+ o[:job_class] = job.class.name
23
+ o[:job_arguments] = job.arguments
24
+ o[:executions] = job.executions
25
+ o[:enqueued_at] = enqueued_at(job)
26
+ o[:dequeued_at] = dequeued_at(job)
27
+ o[:scheduled_at] = job.scheduled_at
28
+ o[:adapter] = payload[:adapter].class.name
29
+ o[:aborted] = payload[:aborted]
30
+ end
31
31
 
32
32
  case name
33
33
  when "enqueue", "enqueue_at", "perform"
34
34
  base
35
35
  when "enqueue_retry"
36
- base.merge({
37
- "error" => payload[:error],
38
- "wait" => payload[:wait]
39
- })
36
+ base.tap do |o|
37
+ o[:error] = payload[:error]
38
+ o[:wait] = payload[:wait]
39
+ end
40
40
  when "retry_stopped", "discard"
41
- base.merge({
42
- "error" => payload[:error]
43
- })
41
+ base.tap do |o|
42
+ o[:error] = payload[:error]
43
+ end
44
44
  when "perform_start"
45
45
  nil
46
+ else
47
+ nil
46
48
  end
47
49
  end
48
50
 
@@ -22,9 +22,17 @@ module PlainApm
22
22
 
23
23
  case name
24
24
  when "sql"
25
- base.merge({"sql" => payload[:sql], "sql_name" => payload[:name]})
25
+ base.tap do |o|
26
+ o[:sql] = payload[:sql]
27
+ o[:sql_name] = payload[:name]
28
+ end
26
29
  when "instantiation"
27
- base.merge({"class_name" => payload[:class_name], "record_count" => payload[:record_count]})
30
+ base.tap do |o|
31
+ o[:class_name] = payload[:class_name]
32
+ o[:record_count] = payload[:record_count]
33
+ end
34
+ else
35
+ nil
28
36
  end
29
37
  end
30
38
  end
@@ -15,34 +15,36 @@ module PlainApm
15
15
  name, base = attributes_from_notification(event)
16
16
  payload = event.payload
17
17
 
18
- base["store"] = payload[:store]
18
+ base[:store] = payload[:store]
19
19
 
20
20
  case name
21
21
  when "cache_read"
22
- base.merge({
23
- "key" => payload[:key],
24
- "hit" => payload[:hit],
25
- "trigger" => payload[:super_operation]
26
- })
22
+ base.tap do |o|
23
+ o[:key] = payload[:key]
24
+ o[:hit] = payload[:hit]
25
+ o[:trigger] = payload[:super_operation]
26
+ end
27
27
  when "cache_read_multi"
28
- base.merge({
29
- "keys" => payload[:key],
30
- "hits" => payload[:hits]
31
- })
28
+ base.tap do |o|
29
+ o[:keys] = payload[:key]
30
+ o[:hits] = payload[:hits]
31
+ end
32
32
  when "cache_fetch_hit"
33
- base.merge({
34
- "key" => payload[:key],
35
- "hit" => true
36
- })
33
+ base.tap do |o|
34
+ o[:key] = payload[:key]
35
+ o[:hit] = true
36
+ end
37
37
  when "cache_write", "cache_write_multi", "cache_generate", "cache_delete", "cache_delete_matched", "cache_exist?"
38
- base.merge({
39
- "key" => payload[:key]
40
- })
38
+ base.tap do |o|
39
+ o[:key] = payload[:key]
40
+ end
41
41
  when "cache_increment", "cache_decrement"
42
- base.merge({
43
- "key" => payload[:key],
44
- "amount" => payload[:amount]
45
- })
42
+ base.tap do |o|
43
+ o[:key] = payload[:key]
44
+ o[:amount] = payload[:amount]
45
+ end
46
+ else
47
+ nil
46
48
  end
47
49
  end
48
50
  end
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "active_support"
5
- require "active_support/notifications"
6
- rescue LoadError
7
- nil
8
- end
9
-
10
3
  module PlainApm
11
4
  module Hooks
12
5
  class ActiveSupportSubscriber
@@ -27,8 +27,8 @@ module PlainApm
27
27
 
28
28
  return if event.nil?
29
29
 
30
- event["source"] = event_source
31
- event["name"] = "error_reporter"
30
+ event[:source] = event_source
31
+ event[:name] = "error_reporter"
32
32
 
33
33
  ::PlainApm.agent.collect(event)
34
34
  end
@@ -14,12 +14,12 @@ module PlainApm
14
14
  def payload(event)
15
15
  name, base = attributes_from_notification(event)
16
16
 
17
- base.merge({
18
- "source" => "user",
19
- "name" => "manual",
20
- "payload_name" => name,
21
- "payload" => event.payload
22
- })
17
+ base.tap do |o|
18
+ o[:source] = "user"
19
+ o[:name] = "manual"
20
+ o[:payload_name] = name
21
+ o[:payload] = event.payload
22
+ end
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,123 @@
1
+ module PlainApm
2
+ if RUBY_VERSION >= "3.2.0"
3
+ Queue = Thread::Queue
4
+ else
5
+ # The queue is made to conform to the ruby/spec
6
+ class Queue
7
+ ClosedQueueError = Class.new(StopIteration)
8
+
9
+ def initialize(initial = nil)
10
+ @data = []
11
+ @num_waiting = 0
12
+ @closed = false
13
+ @mutex = Mutex.new
14
+ @cv = ConditionVariable.new
15
+
16
+ return if initial.nil?
17
+
18
+ raise TypeError, "can't convert #{initial.class} into Array" unless initial.respond_to?(:to_a)
19
+
20
+ elems = initial.to_a
21
+ raise TypeError, "can't convert #{initial.class} into Array (#{initial.class}#to_a gives #{elems.class})" unless elems.is_a?(Array)
22
+
23
+ @data.concat(elems)
24
+ end
25
+
26
+ def close
27
+ @mutex.synchronize do
28
+ return if @closed
29
+ @closed = true
30
+ # Wake up everyone waiting on this.
31
+ @cv.broadcast
32
+ end
33
+ end
34
+
35
+ def closed?
36
+ @closed
37
+ end
38
+
39
+ def clear
40
+ @data = []
41
+ end
42
+
43
+ def num_waiting
44
+ @num_waiting
45
+ end
46
+
47
+ def empty?
48
+ @data.empty?
49
+ end
50
+
51
+ def length
52
+ @data.length
53
+ end
54
+
55
+ alias_method :size, :length
56
+
57
+ def push(obj)
58
+ @mutex.synchronize do
59
+ raise ClosedQueueError if closed?
60
+ @data << obj
61
+ @cv.signal
62
+ end
63
+ end
64
+
65
+ alias_method :<<, :push
66
+ alias_method :enq, :push
67
+
68
+ def pop(non_block = false, timeout: nil)
69
+ if non_block && timeout
70
+ raise ArgumentError, "can't set a timeout if non_block is enabled"
71
+ end
72
+
73
+ if !timeout.nil? && !timeout.is_a?(Numeric)
74
+ raise TypeError, "no implicit conversion to float from #{timeout.class.name.downcase}"
75
+ end
76
+
77
+ @mutex.synchronize do
78
+ # The data is there.
79
+ return @data.shift if !@data.empty?
80
+
81
+ # Non block raises on empty queue
82
+ raise ThreadError, "queue empty" if non_block
83
+
84
+ # 0 means immediate timeout. Closed empty queue also immediately returns a nil
85
+ return nil if timeout == 0 || @closed
86
+
87
+ # Blocking and open. Let's wait.
88
+ timeout_at = timeout.nil? ? nil : now + timeout.to_f
89
+
90
+ begin
91
+ # We could keep the threads in an array, but a counter should do as well.
92
+ @num_waiting += 1
93
+
94
+ while @data.empty? && !@closed
95
+ if timeout_at.nil?
96
+ # Wait indefinitely.
97
+ @cv.wait(@mutex)
98
+ else
99
+ # Wait for what's left of the deadline
100
+ break if (left = timeout_at - now) <= 0.0
101
+ @cv.wait(@mutex, left)
102
+ end
103
+ end
104
+ ensure
105
+ @num_waiting -= 1
106
+ end
107
+
108
+ # Return whatever is there now, or nil (if timed out)
109
+ @data.shift
110
+ end
111
+ end
112
+
113
+ alias_method :deq, :pop
114
+ alias_method :shift, :pop
115
+
116
+ private
117
+
118
+ def now
119
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "net/http"
4
4
  require "json"
5
+ require "zlib"
5
6
 
6
7
  module PlainApm
7
8
  class Transport
@@ -16,7 +17,7 @@ module PlainApm
16
17
  HTTP_TIMEOUTS = [
17
18
  Net::OpenTimeout,
18
19
  Net::ReadTimeout,
19
- RUBY_VERSION >= "2.6.0" ? Net::WriteTimeout : nil
20
+ Net::WriteTimeout
20
21
  ].compact.freeze
21
22
 
22
23
  ERRNO_ERRORS = [
@@ -47,10 +48,7 @@ module PlainApm
47
48
 
48
49
  http.open_timeout = HTTP_OPEN_TIMEOUT_SECONDS
49
50
  http.read_timeout = HTTP_READ_TIMEOUT_SECONDS
50
-
51
- if RUBY_VERSION >= "2.6.0"
52
- http.write_timeout = HTTP_WRITE_TIMEOUT_SECONDS
53
- end
51
+ http.write_timeout = HTTP_WRITE_TIMEOUT_SECONDS
54
52
 
55
53
  at_exit { shutdown }
56
54
  end
@@ -60,9 +58,9 @@ module PlainApm
60
58
  #
61
59
  # @param data [String] serialized payload to POST
62
60
  # @return [Array] [response, error, retriable]
63
- def deliver(data)
61
+ def deliver(data, meta = {})
64
62
  http_response do
65
- http_request(http, uri.path, data)
63
+ http_request(http, uri.path, data, meta)
66
64
  end
67
65
  end
68
66
 
@@ -82,16 +80,21 @@ module PlainApm
82
80
  http.finish if http.started?
83
81
  end
84
82
 
85
- def http_request(http, path, body)
86
- request = Net::HTTP::Post.new(path, http_headers)
87
- http.request(request, body)
83
+ def http_request(http, path, body, meta)
84
+ request = Net::HTTP::Post.new(path, http_headers(meta))
85
+ http.request(request, Zlib::Deflate.deflate(JSON.generate(body)))
88
86
  end
89
87
 
90
- def http_headers
88
+ def http_headers(meta)
89
+ meta_headers = meta.map do |k, v|
90
+ ["X-PlainApm-#{k.to_s.split("_").map(&:capitalize).join("-")}", v.to_s]
91
+ end.to_h
92
+
91
93
  {
92
94
  "Content-Type" => "application/json, charset=UTF-8",
95
+ "Content-Encoding" => "gzip",
93
96
  "X-PlainApm-Key" => app_key
94
- }
97
+ }.merge(meta_headers)
95
98
  end
96
99
 
97
100
  def http_response
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlainApm
4
- VERSION = "0.9.1"
4
+ VERSION = "0.9.3"
5
5
  end
data/lib/plain_apm.rb CHANGED
@@ -1,13 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Rails deps
4
+ begin
5
+ require "active_support"
6
+ require "active_support/notifications"
7
+ require "rails/railtie"
8
+ rescue LoadError
9
+ nil
10
+ end
11
+
3
12
  require_relative "plain_apm/version"
4
13
  require_relative "plain_apm/transport"
14
+ require_relative "plain_apm/queue"
5
15
  require_relative "plain_apm/config"
6
16
  require_relative "plain_apm/agent"
7
17
  require_relative "plain_apm/deploy_tracking"
8
18
  require_relative "plain_apm/event_attributes"
19
+ require_relative "plain_apm/helpers"
20
+
21
+ # Per thread context for request IDs / job IDs.
22
+ require_relative "plain_apm/extensions/context"
9
23
 
10
- require "object_tracing"
24
+ # Rack exceptions. Activate the middleware if in Rails.
25
+ require_relative "plain_apm/extensions/exceptions"
26
+
27
+ # Per thread allocations in ASN events
28
+ require_relative "plain_apm/extensions/thread_allocations"
11
29
 
12
30
  # Rails instrumentation. The hooks won't install unless
13
31
  # ActiveSupport::Notifications is loaded.
@@ -21,23 +39,6 @@ require_relative "plain_apm/hooks/active_support"
21
39
  require_relative "plain_apm/hooks/manual"
22
40
  require_relative "plain_apm/hooks/error_reporter"
23
41
 
24
- # Per thread context for request IDs / job IDs.
25
- require_relative "plain_apm/extensions/context"
26
- require_relative "plain_apm/extensions/context/rack"
27
- require_relative "plain_apm/extensions/context/active_job"
28
- require_relative "plain_apm/extensions/context/railtie"
29
-
30
- # Per thread allocations in ASN events
31
- require_relative "plain_apm/extensions/thread_allocations/active_support_event"
32
- require_relative "plain_apm/extensions/thread_allocations/railtie"
33
-
34
- # Helpers du jour.
35
- require_relative "plain_apm/helpers"
36
-
37
- # Rack exceptions. Activate the middleware if in Rails.
38
- require_relative "plain_apm/extensions/exceptions/rack"
39
- require_relative "plain_apm/extensions/exceptions/railtie"
40
-
41
42
  module PlainApm
42
43
  # Allow swapping out the Agent for a synchronous, in-memory implementation in
43
44
  # the tests.
@@ -49,16 +50,12 @@ module PlainApm
49
50
  @@agent ||= Agent.instance
50
51
  end
51
52
 
52
- begin
53
- require "rails/railtie"
54
- rescue LoadError
55
- nil
56
- end
57
-
58
- # after_initialize allows reading settings from ENV on app start.
59
53
  if defined?(Rails::Railtie)
60
54
  class Railtie < Rails::Railtie
61
- config.after_initialize { PlainApm.agent.start }
55
+ # allows reading settings from ENV vars set in config/initializers.
56
+ initializer(:plain_apm_agent_start, after: :load_config_initializers) do
57
+ PlainApm.agent.start
58
+ end
62
59
  end
63
60
  else
64
61
  PlainApm.agent.start
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plain_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - PlainAPM Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-04 00:00:00.000000000 Z
11
+ date: 2024-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -107,9 +107,11 @@ files:
107
107
  - lib/plain_apm/extensions/context/middleware.rb
108
108
  - lib/plain_apm/extensions/context/rack.rb
109
109
  - lib/plain_apm/extensions/context/railtie.rb
110
+ - lib/plain_apm/extensions/exceptions.rb
110
111
  - lib/plain_apm/extensions/exceptions/active_job.rb
111
112
  - lib/plain_apm/extensions/exceptions/rack.rb
112
113
  - lib/plain_apm/extensions/exceptions/railtie.rb
114
+ - lib/plain_apm/extensions/thread_allocations.rb
113
115
  - lib/plain_apm/extensions/thread_allocations/active_support_event.rb
114
116
  - lib/plain_apm/extensions/thread_allocations/railtie.rb
115
117
  - lib/plain_apm/helpers.rb
@@ -122,6 +124,7 @@ files:
122
124
  - lib/plain_apm/hooks/active_support_subscriber.rb
123
125
  - lib/plain_apm/hooks/error_reporter.rb
124
126
  - lib/plain_apm/hooks/manual.rb
127
+ - lib/plain_apm/queue.rb
125
128
  - lib/plain_apm/transport.rb
126
129
  - lib/plain_apm/version.rb
127
130
  homepage: https://plainapm.com