plain_apm 0.9.1 → 0.9.3

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 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