Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add insights instrumentation - events and metrics #539

Merged
merged 76 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
d7e4a22
Add Honeybadger::Instrumentation class
roelbondoc May 2, 2024
d8e9c46
Add new configuration options for Insights
roelbondoc May 2, 2024
a556188
Add a CollectorWorker and collect block
roelbondoc May 2, 2024
21750be
Add Insight events for Rails via ActiveSupport::Notifictions
roelbondoc May 2, 2024
762da65
Add Insight events for ActiveJob notifications
roelbondoc May 2, 2024
53a1741
Add Insights for Sidekiq
roelbondoc May 2, 2024
ff5990a
Add Insights events for karafka
roelbondoc May 2, 2024
997e05e
Add Insights metrics gathering for solid_queue
roelbondoc May 2, 2024
b15d02e
Add a Puma plugin for Honeybadger
roelbondoc May 2, 2024
b06a6dc
Add Insights metrics for autotuner
roelbondoc May 2, 2024
c11c398
Add Insights events for net_http requests
roelbondoc May 2, 2024
da33856
Add spec for net_http plugin
roelbondoc May 2, 2024
3f48394
Add flushing of collector worker
roelbondoc May 3, 2024
820d6e5
Add spec for CollectorWorker
roelbondoc May 3, 2024
60dd43a
Add spec for agent
roelbondoc May 3, 2024
e940a76
Add some api documentation
roelbondoc May 3, 2024
51880d7
Report autotuner reports as events
roelbondoc May 6, 2024
acb7181
Enable cluster collection by default
roelbondoc May 6, 2024
1e5b671
Simplify configuration so there is only one main insights flag
roelbondoc May 9, 2024
5e0228b
Return nil if collection_interval is not configured
roelbondoc May 9, 2024
7bc1a83
Safe guard interval tick if for some reason it skips 0
roelbondoc May 9, 2024
5dc0626
Add some examples in the comments
roelbondoc May 10, 2024
78559c5
Apply suggestions from code review
roelbondoc May 10, 2024
c23f426
Simplify net_http payload
roelbondoc May 10, 2024
c4dc963
Make return value clear for nil config
roelbondoc May 10, 2024
40343cb
Simplify sidekiq utilization code
roelbondoc May 10, 2024
f2d9429
Update lib/honeybadger/config.rb
roelbondoc May 13, 2024
fcc4ef3
Rename histogram to measure
roelbondoc May 15, 2024
9e91cd3
Merge remote-tracking branch 'origin/master' into insights-events-ins…
roelbondoc May 15, 2024
b83aee1
Rename measure to time
roelbondoc May 17, 2024
ea0101d
Rename key to worker instead of index
roelbondoc May 17, 2024
00892cc
Update lib/honeybadger/agent.rb
roelbondoc May 21, 2024
85feb1f
Record request_id for action controller notifications
roelbondoc May 21, 2024
94cd569
Delegate metric methods to the module
roelbondoc May 21, 2024
200154d
Add request_id to context manager
roelbondoc May 22, 2024
ed41492
Add defaults for cluster_collection for sidekiq and solid_queue
roelbondoc May 22, 2024
16da78f
Prefix events with plugin name
roelbondoc May 22, 2024
a18cd47
Reverse it
roelbondoc May 22, 2024
adfb7ca
Generate timestamp with miliseconds
roelbondoc May 22, 2024
46b6c06
feat: implement metric registry for aggregation (#548)
roelbondoc May 22, 2024
d91e175
Do not attempt to record falsey values
roelbondoc May 22, 2024
67d3e36
Add infinity bin, and always send zero value bins
roelbondoc May 23, 2024
15a2f01
Fix typo
roelbondoc May 23, 2024
2e744f2
Add unit specs for metric types
roelbondoc May 23, 2024
a2077fc
Update version to beta string
roelbondoc May 23, 2024
76523b0
Update autotuner report event name
roelbondoc May 23, 2024
e6446b8
Merge branch 'master' into insights-events-instrumentation
stympy May 24, 2024
dae6bd3
Update default collection interval to 60 seconds
roelbondoc May 24, 2024
36530ec
Add specs for InstrumentationHelper module
roelbondoc May 24, 2024
874c43b
Refactor metrics api to be more uniform. Added specs
roelbondoc May 24, 2024
981e6f5
Switch from interval checking, to timed checking.
roelbondoc May 27, 2024
35bde80
Rename CollectorWorker to MetricsWorker
roelbondoc May 27, 2024
a8784d0
Better code organization.
roelbondoc May 27, 2024
95bbb40
Document 3 metric variants, and add specs
roelbondoc May 27, 2024
121746e
Remove unused files
roelbondoc May 27, 2024
521f895
Add hostname to all event payloads. Configurable.
roelbondoc May 28, 2024
9ad5e70
Add default config values for collection_interval
roelbondoc May 28, 2024
ee6f434
Report system information (mem/cpu load).
roelbondoc May 29, 2024
24b79be
Just send one event for all the system information.
roelbondoc May 29, 2024
803b7c9
Fix config context
roelbondoc May 29, 2024
dccbb7f
Add config for ignoring event types
roelbondoc May 29, 2024
07efc0d
Update lib/honeybadger/version.rb
roelbondoc May 30, 2024
c568495
Generate a hash for the metric signature
roelbondoc May 30, 2024
8326a19
Handle nil event_type
roelbondoc May 30, 2024
5d84e66
Update lib/honeybadger/agent.rb
roelbondoc May 30, 2024
8b7210c
Update description for events.ignore config
roelbondoc May 31, 2024
0b7cc47
Rename sampled to samples
roelbondoc May 31, 2024
7b5c8ba
Add more ActiveSupport::Notifications subscriptions
roelbondoc May 31, 2024
fff154d
Disable Net::HTTP instrumentation by default.
roelbondoc May 31, 2024
e09d15e
Fix spec
roelbondoc May 31, 2024
4a120f6
Use assignment instead of merge!
roelbondoc May 31, 2024
5f90555
Isolate metric calls to a specific agent
roelbondoc May 31, 2024
2676d6e
Fix call to monotonic_timer
roelbondoc May 31, 2024
9157106
Enable net_http with domain logging by default.
roelbondoc May 31, 2024
66fd331
Add config for separate max queue size for events. Increase batch size
roelbondoc Jun 3, 2024
0ec8957
Attempt to fix flakey spec in jruby
roelbondoc Jun 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 70 additions & 3 deletions lib/honeybadger/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
require 'honeybadger/logging'
require 'honeybadger/worker'
require 'honeybadger/events_worker'
require 'honeybadger/metrics_worker'
require 'honeybadger/breadcrumbs'
require 'honeybadger/registry'
require 'honeybadger/registry_execution'

module Honeybadger
# The Honeybadger agent contains all the methods for interacting with the
Expand Down Expand Up @@ -358,6 +361,7 @@ def flush
ensure
worker.flush
events_worker&.flush
metrics_worker&.flush
end

# Stops the Honeybadger service.
Expand All @@ -367,6 +371,7 @@ def flush
def stop(force = false)
worker.shutdown(force)
events_worker&.shutdown(force)
metrics_worker&.shutdown(force)
true
end

Expand All @@ -387,20 +392,46 @@ def stop(force = false)
def event(event_type, payload = {})
init_events_worker

ts = DateTime.now.new_offset(0).rfc3339
ts = Time.now.utc.strftime("%FT%T.%LZ")
merged = {ts: ts}

if event_type.is_a?(String)
merged.merge!(event_type: event_type)
merged[:event_type] = event_type
else
merged.merge!(Hash(event_type))
end

if (request_id = context_manager.get_request_id)
merged[:request_id] = request_id
end

if config[:'events.attach_hostname']
merged[:hostname] = config[:hostname].to_s
end

merged.merge!(Hash(payload))

return if config.ignored_events.any? { |check| merged[:event_type]&.match?(check) }

events_worker.push(merged)
end

# @api private
def collect(collector)
roelbondoc marked this conversation as resolved.
Show resolved Hide resolved
return unless config.insights_enabled?

init_metrics_worker
metrics_worker.push(collector)
end

# @api private
def registry
return @registry if defined?(@registry)
@registry = Honeybadger::Registry.new.tap do |r|
collect(Honeybadger::RegistryExecution.new(r, config, {}))
end
end

# @api private
attr_reader :config

Expand Down Expand Up @@ -467,13 +498,15 @@ def event(event_type, payload = {})
# @api private
def with_rack_env(rack_env, &block)
context_manager.set_rack_env(rack_env)
context_manager.set_request_id(rack_env["action_dispatch.request_id"] || SecureRandom.uuid)
yield
ensure
context_manager.set_rack_env(nil)
context_manager.set_request_id(nil)
end

# @api private
attr_reader :worker, :events_worker
attr_reader :worker, :events_worker, :metrics_worker

# @api private
# @!method init!(...)
Expand All @@ -485,6 +518,35 @@ def with_rack_env(rack_env, &block)
# @see Config#backend
def_delegators :config, :backend

# @api private
# @!method time
# @see Honeybadger::Instrumentation#time
def_delegator :instrumentation, :time

# @api private
# @!method histogram
# @see Honeybadger::Instrumentation#histogram
def_delegator :instrumentation, :histogram

# @api private
# @!method gauge
# @see Honeybadger::Instrumentation#gauge
def_delegator :instrumentation, :gauge

# @api private
# @!method increment_counter
# @see Honeybadger::Instrumentation#increment_counter
def_delegator :instrumentation, :increment_counter

# @api private
# @!method decrement_counter
# @see Honeybadger::Instrumentation#decrement_counter
def_delegator :instrumentation, :decrement_counter

def instrumentation
@instrumentation ||= Honeybadger::Instrumentation.new(self)
end

private

def validate_notify_opts!(opts)
Expand Down Expand Up @@ -520,6 +582,11 @@ def init_events_worker
@events_worker = EventsWorker.new(config)
end

def init_metrics_worker
return if @metrics_worker
@metrics_worker = MetricsWorker.new(config)
end

def with_error_handling
yield
rescue => ex
Expand Down
32 changes: 32 additions & 0 deletions lib/honeybadger/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ def ignored_classes
DEFAULTS[:'exceptions.ignore'] | Array(ignore)
end

def ignored_events
self[:'events.ignore'].map do |check|
check.is_a?(String) ? /^#{check}$/ : check
end
end

def ca_bundle_path
if self[:'connection.system_ssl_cert_chain'] && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
OpenSSL::X509::DEFAULT_CERT_FILE
Expand Down Expand Up @@ -224,6 +230,10 @@ def max_queue_size
self[:max_queue_size]
end

def events_max_queue_size
self[:'events.max_queue_size']
end

def events_batch_size
self[:'events.batch_size']
end
Expand Down Expand Up @@ -262,6 +272,28 @@ def load_plugin?(name)
includes_token?(self[:plugins], name)
end

def insights_enabled?
return false if defined?(::Rails.application) && ::Rails.const_defined?("Console")
!!self[:'insights.enabled']
end

def cluster_collection?(name)
return false unless insights_enabled?
return true if self[:"#{name}.insights.cluster_collection"].nil?
!!self[:"#{name}.insights.cluster_collection"]
end

def collection_interval(name)
return nil unless insights_enabled?
self[:"#{name}.insights.collection_interval"]
end

def load_plugin_insights?(name)
return false unless insights_enabled?
return true if self[:"#{name}.insights.enabled"].nil?
!!self[:"#{name}.insights.enabled"]
end

def root_regexp
return @root_regexp if @root_regexp
return nil if @no_root
Expand Down
57 changes: 56 additions & 1 deletion lib/honeybadger/config/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,31 @@ class Boolean; end
default: 100,
type: Integer
},
:'events.max_queue_size' => {
description: 'Maximum number of event for the event worker queue.',
default: 10000,
type: Integer
},
:'events.batch_size' => {
description: 'Send events batch if n events have accumulated',
default: 100,
default: 1000,
type: Integer
},
:'events.timeout' => {
description: 'Timeout after which the events batch will be sent regardless (in milliseconds)',
default: 30_000,
type: Integer
},
:'events.attach_hostname' => {
description: 'Add the hostname to all event paylaods.',
default: true,
type: Boolean
},
:'events.ignore' => {
description: 'A list of events to ignore. Use a string to specify exact matches, or regex for more flexibility.',
default: [],
type: Array
},
plugins: {
description: 'An optional list of plugins to load. Default is to load all plugins.',
default: nil,
Expand Down Expand Up @@ -311,6 +326,36 @@ class Boolean; end
default: true,
type: Boolean
},
:'sidekiq.insights.cluster_collection' => {
description: 'Collect cluster based metrics for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.collection_interval' => {
description: 'The frequency in which Sidekiq cluster metrics are sampled.',
default: 60,
type: Integer
},
:'solid_queue.insights.cluster_collection' => {
description: 'Collect cluster based metrics for SolidQueue.',
default: true,
type: Boolean
},
:'solid_queue.insights.collection_interval' => {
description: 'The frequency in which SolidQueue cluster metrics are sampled.',
default: 60,
type: Integer
},
:'net_http.insights.enabled' => {
description: 'Allow automatic instrumentation of Net::HTTP requests.',
default: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this supposed to be false by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a conversation about disabling it by default, but ultimately we decided to enable it by default, but only log the domain, with an optional configuration to log the full path.

type: Boolean
},
:'net_http.insights.full_url' => {
description: 'Record the full request url during instrumentation.',
default: false,
type: Boolean
},
:'sinatra.enabled' => {
description: 'Enable Sinatra auto-initialization.',
default: true,
Expand Down Expand Up @@ -343,6 +388,16 @@ class Boolean; end
description: 'Enable/Disable automatic breadcrumbs from log messages.',
default: true,
type: Boolean
},
:'insights.enabled' => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to keep in mind for the extra optional plugin config—if it's not explicitly defined in the defaults, people won't be able to configure the plugin values via environment variables, since the env config uses the defaults to know what to look for.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow, thank you. This was in the back of the mind, to figure out how things are configured through ENV's.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some defaults for the plugin specific configurations.

description: "Enable/Disable Honeybadger Insights built-in instrumentation.",
default: false,
type: Boolean
},
:'insights.registry_flush_interval' => {
description: "Number of seconds between registry flushes.",
default: 60,
type: Integer
}
}.freeze

Expand Down
11 changes: 10 additions & 1 deletion lib/honeybadger/context_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,24 @@ def get_rack_env
@mutex.synchronize { @rack_env }
end

def set_request_id(request_id)
@mutex.synchronize { @request_id = request_id }
end

def get_request_id
@mutex.synchronize { @request_id }
end

private

attr_accessor :custom, :rack_env
attr_accessor :custom, :rack_env, :request_id

def _initialize
@mutex.synchronize do
@global_context = nil
@local_context = nil
@rack_env = nil
@request_id = nil
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions lib/honeybadger/counter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'honeybadger/metric'

module Honeybadger
class Counter < Metric
def count(by=1)
return unless by

@samples += 1

@counter ||= 0
@counter = @counter + by
end

def payloads
[{ counter: @counter }]
end
end
end
2 changes: 1 addition & 1 deletion lib/honeybadger/events_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def initialize(config)
def push(msg)
return false unless start

if queue.size >= config.max_queue_size
if queue.size >= config.events_max_queue_size
warn { sprintf('Unable to send event; reached max queue size of %s.', queue.size) }
return false
end
Expand Down
30 changes: 30 additions & 0 deletions lib/honeybadger/gauge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'honeybadger/metric'

module Honeybadger
class Gauge < Metric
def record(value)
return unless value

@samples += 1

@total ||= 0
@total = @total + value

@min = value if @min.nil? || @min > value
@max = value if @max.nil? || @max < value
@avg = @total.to_f / @samples
@latest = value
end

def payloads
[
{
min: @min,
max: @max,
avg: @avg,
latest: @latest
}
]
end
end
end
32 changes: 32 additions & 0 deletions lib/honeybadger/histogram.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'honeybadger/metric'

module Honeybadger
class Histogram < Metric
DEFAULT_BINS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
INFINITY = 1e20.to_f # not quite, but pretty much

def record(value)
return unless value

@samples += 1
@bin_counts ||= Hash.new(0)
@bin_counts[find_bin(value)] += 1
end

def find_bin(value)
bin = bins.find {|b| b >= value }
bin = INFINITY if bin.nil?
bin
end

def bins
@attributes.fetch(:bins, DEFAULT_BINS).sort
end

def payloads
[{
bins: (bins + [INFINITY]).map { |bin| [bin.to_f, @bin_counts[bin]] }
}]
end
end
end
Loading