-
Notifications
You must be signed in to change notification settings - Fork 25
/
yabeda.rb
152 lines (125 loc) · 4.37 KB
/
yabeda.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# frozen_string_literal: true
require "concurrent"
require "forwardable"
require "yabeda/version"
require "yabeda/config"
require "yabeda/dsl"
require "yabeda/tags"
require "yabeda/errors"
require "yabeda/railtie" if defined?(Rails)
# Extendable framework for collecting and exporting metrics from Ruby apps
module Yabeda
include DSL
class << self
extend Forwardable
# @return [Hash<String, Yabeda::Metric>] All registered metrics
def metrics
@metrics ||= Concurrent::Hash.new
end
# @return [Hash<String, Yabeda::Group>] All registered metrics
def groups
@groups ||= Concurrent::Hash.new.tap do |hash|
hash[nil] = Yabeda::GlobalGroup.new(nil)
end
end
# @return [Hash<String, Yabeda::BaseAdapter>] All loaded adapters
def adapters
@adapters ||= Concurrent::Hash.new
end
# @return [Array<Proc>] All collectors for periodical retrieving of metrics
def collectors
@collectors ||= Concurrent::Array.new
end
def config
@config ||= Config.new
end
def_delegators :config, :debug?
# Execute all collector blocks for periodical retrieval of metrics
#
# This method is intended to be used by monitoring systems adapters
def collect!
collectors.each do |collector|
if config.debug?
yabeda.collect_duration.measure({ location: collector.source_location.join(":") }, &collector)
else
collector.call
end
end
end
# @return [Hash<Symbol, Symbol>] All added global default tags
def default_tags
@default_tags ||= Concurrent::Hash.new
end
# @param [Symbol] name
# @param [BaseAdapter] instance
def register_adapter(name, instance)
adapters[name] = instance
# NOTE: Pretty sure there is race condition
metrics.each do |_, metric|
instance.register!(metric)
end
end
# @return [Array<Proc>] All configuration blocks for postponed setup
def configurators
@configurators ||= Concurrent::Array.new
end
# @return [Boolean] Whether +Yabeda.configure!+ has been already called
def configured?
!@configured_by.nil?
end
alias already_configured? configured?
# Perform configuration: registration of metrics and collector blocks
# @return [void]
# rubocop: disable Metrics/MethodLength, Metrics/AbcSize
def configure!
raise(AlreadyConfiguredError, @configured_by) if already_configured?
debug! if config.debug?
configurators.each do |(group, block)|
group group
class_eval(&block)
group nil
end
# Register metrics in adapters after evaluating all configuration blocks
# to ensure that all global settings (like default tags) will be applied.
adapters.each_value do |adapter|
metrics.each_value do |metric|
adapter.register!(metric)
end
end
@configured_by = caller_locations(1, 1)[0].to_s
end
# Enable and setup service metrics to monitor yabeda performance
def debug!
return false if @debug_was_enabled_by # Prevent multiple calls
config.debug ||= true # Enable debug mode in config if it wasn't enabled from other sources
@debug_was_enabled_by = caller_locations(1, 1)[0].to_s
configure do
group :yabeda
histogram :collect_duration,
tags: %i[location], unit: :seconds,
buckets: [0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60].freeze,
comment: "A histogram for the time required to evaluate collect blocks"
end
adapters.each_value(&:debug!)
true
end
# rubocop: enable Metrics/MethodLength, Metrics/AbcSize
# Forget all the configuration.
# For testing purposes as it doesn't rollback changes in adapters.
# @api private
# rubocop: disable Metrics/AbcSize
def reset!
default_tags.clear
adapters.clear
groups.each_key { |group| singleton_class.send(:remove_method, group) if group && respond_to?(group) }
@groups = nil
metrics.each_key { |metric| singleton_class.send(:remove_method, metric) if respond_to?(metric) }
@metrics = nil
collectors.clear
configurators.clear
instance_variable_set(:@configured_by, nil)
instance_variable_set(:@debug_was_enabled_by, nil)
end
# rubocop: enable Metrics/AbcSize
end
end