metrux 1.0.0
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/.ruby-version.sample +1 -0
- data/CHANGELOG.md +110 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +429 -0
- data/Rakefile +2 -0
- data/bin/console +9 -0
- data/bin/playground +38 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/rubocop +17 -0
- data/bin/setup +15 -0
- data/config/metrux.sample.yml +46 -0
- data/lib/metrux.rb +60 -0
- data/lib/metrux/client.rb +40 -0
- data/lib/metrux/commands.rb +12 -0
- data/lib/metrux/commands/base.rb +62 -0
- data/lib/metrux/commands/gauge.rb +21 -0
- data/lib/metrux/commands/meter.rb +15 -0
- data/lib/metrux/commands/notice_error.rb +38 -0
- data/lib/metrux/commands/periodic_gauge.rb +25 -0
- data/lib/metrux/commands/periodic_gauge/agent.rb +68 -0
- data/lib/metrux/commands/periodic_gauge/registry.rb +39 -0
- data/lib/metrux/commands/periodic_gauge/reporter.rb +33 -0
- data/lib/metrux/commands/periodic_gauge/supervisor.rb +53 -0
- data/lib/metrux/commands/timer.rb +30 -0
- data/lib/metrux/commands/write.rb +9 -0
- data/lib/metrux/config_builders.rb +11 -0
- data/lib/metrux/config_builders/common.rb +53 -0
- data/lib/metrux/config_builders/influx.rb +71 -0
- data/lib/metrux/config_builders/logger.rb +48 -0
- data/lib/metrux/config_builders/periodic_gauge.rb +21 -0
- data/lib/metrux/config_builders/yaml.rb +64 -0
- data/lib/metrux/configuration.rb +52 -0
- data/lib/metrux/connections.rb +7 -0
- data/lib/metrux/connections/influx_db.rb +17 -0
- data/lib/metrux/connections/null.rb +19 -0
- data/lib/metrux/constants.rb +19 -0
- data/lib/metrux/loggable.rb +24 -0
- data/lib/metrux/plugin_register.rb +57 -0
- data/lib/metrux/plugins.rb +10 -0
- data/lib/metrux/plugins/gc.rb +52 -0
- data/lib/metrux/plugins/periodic_gauge.rb +32 -0
- data/lib/metrux/plugins/process.rb +71 -0
- data/lib/metrux/plugins/thread.rb +13 -0
- data/lib/metrux/plugins/yarv.rb +26 -0
- data/lib/metrux/sleeper.rb +8 -0
- data/lib/metrux/version.rb +3 -0
- data/metrux.gemspec +40 -0
- metadata +250 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module Metrux
|
2
|
+
module Commands
|
3
|
+
class PeriodicGauge < Gauge
|
4
|
+
class Agent
|
5
|
+
include Loggable
|
6
|
+
include Sleeper
|
7
|
+
|
8
|
+
DEFAULT_INVERVAL = 60
|
9
|
+
|
10
|
+
def initialize(command, registry, config)
|
11
|
+
@command = command
|
12
|
+
@registry = registry
|
13
|
+
@interval = config.periodic_gauge_interval || DEFAULT_INVERVAL
|
14
|
+
@logger = config.logger
|
15
|
+
@thread = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
return false if alive?
|
20
|
+
|
21
|
+
log('Starting...', :info)
|
22
|
+
@thread = Thread.new do
|
23
|
+
loop do
|
24
|
+
log("sleeping for #{interval}s...")
|
25
|
+
wait(interval)
|
26
|
+
|
27
|
+
metrics.each(&method(:execute_metric))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
log('Stopping...', :info)
|
36
|
+
@thread.kill if @thread
|
37
|
+
@thread = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def alive?
|
41
|
+
@thread && @thread.alive?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :command, :interval, :registry
|
47
|
+
|
48
|
+
def metrics
|
49
|
+
registry.metrics
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute_metric(key, params)
|
53
|
+
log("Executing #{key}...", :info)
|
54
|
+
|
55
|
+
command.gauge(
|
56
|
+
params.fetch(:measurement), params.fetch(:options),
|
57
|
+
¶ms.fetch(:metric)
|
58
|
+
)
|
59
|
+
rescue => e
|
60
|
+
log(
|
61
|
+
"ERROR #{key}: #{e.class}: #{e.message} #{e.backtrace.take(2)}",
|
62
|
+
:error
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Metrux
|
2
|
+
module Commands
|
3
|
+
class PeriodicGauge < Gauge
|
4
|
+
class Registry
|
5
|
+
include Loggable
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@metrics = {}
|
10
|
+
@logger = config.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def metrics
|
14
|
+
mutex.synchronize { @metrics.dup }
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(measurement, options = {}, &metric_block)
|
18
|
+
tags = options.fetch(:tags, {})
|
19
|
+
key = "#{measurement}/#{tags.to_query}".freeze
|
20
|
+
|
21
|
+
log("Registering #{key}")
|
22
|
+
|
23
|
+
mutex.synchronize do
|
24
|
+
@metrics[key] = {
|
25
|
+
measurement: measurement, metric: metric_block,
|
26
|
+
options: options
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :mutex, :logger
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Metrux
|
2
|
+
module Commands
|
3
|
+
class PeriodicGauge < Gauge
|
4
|
+
class Reporter
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize(command, registry, config)
|
8
|
+
@agent = Agent.new(command, registry, config)
|
9
|
+
@supervisor = Supervisor.new(agent, config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
return false unless config.active?
|
15
|
+
|
16
|
+
agent.start
|
17
|
+
supervisor.start
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
supervisor.stop
|
24
|
+
agent.stop
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :agent, :supervisor, :config
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Metrux
|
2
|
+
module Commands
|
3
|
+
class PeriodicGauge < Gauge
|
4
|
+
class Supervisor
|
5
|
+
include Loggable
|
6
|
+
include Sleeper
|
7
|
+
|
8
|
+
INTERVAL_CHECK = 10
|
9
|
+
|
10
|
+
def initialize(agent, config)
|
11
|
+
@agent = agent
|
12
|
+
@logger = config.logger
|
13
|
+
@thread = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
unless alive?
|
18
|
+
log('Starting...', :info)
|
19
|
+
@thread = Thread.new { loop { check } }
|
20
|
+
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
log('Stopping...', :info)
|
28
|
+
@thread.kill if @thread
|
29
|
+
@thread = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def alive?
|
33
|
+
@thread && @thread.alive?
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :agent
|
39
|
+
|
40
|
+
def check
|
41
|
+
wait(INTERVAL_CHECK)
|
42
|
+
|
43
|
+
unless agent.alive?
|
44
|
+
log('Agent is dead. Restarting...', :info)
|
45
|
+
agent.start
|
46
|
+
end
|
47
|
+
rescue => e
|
48
|
+
log("ERROR: #{e.class}: #{e.message}", :error)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Metrux
|
2
|
+
module Commands
|
3
|
+
class Timer < Base
|
4
|
+
TIMER_MEASUREMENT_PREFIX_KEY = 'timers/'.freeze
|
5
|
+
|
6
|
+
def execute(key, params = {})
|
7
|
+
key = "#{TIMER_MEASUREMENT_PREFIX_KEY}#{key}"
|
8
|
+
|
9
|
+
result, duration = if block_given?
|
10
|
+
calculate_duration { yield }
|
11
|
+
else
|
12
|
+
[nil, params.fetch(:duration)]
|
13
|
+
end
|
14
|
+
|
15
|
+
write(key, format_data(duration, params), format_write_options(params))
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def calculate_duration
|
23
|
+
started_at = Time.now
|
24
|
+
result = yield
|
25
|
+
duration = ((Time.now - started_at) * 1_000).ceil
|
26
|
+
[result, duration]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Metrux
|
2
|
+
module ConfigBuilders
|
3
|
+
ConfigurationError = Class.new(RuntimeError)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'config_builders/yaml'
|
8
|
+
require_relative 'config_builders/common'
|
9
|
+
require_relative 'config_builders/periodic_gauge'
|
10
|
+
require_relative 'config_builders/influx'
|
11
|
+
require_relative 'config_builders/logger'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Metrux
|
2
|
+
module ConfigBuilders
|
3
|
+
class Common
|
4
|
+
APP_NAME_KEY = 'METRUX_APP_NAME'.freeze
|
5
|
+
ACTIVE_KEY = 'METRUX_ACTIVE'.freeze
|
6
|
+
|
7
|
+
AppNameNotFoundError = Class.new(ConfigurationError)
|
8
|
+
|
9
|
+
def initialize(yaml)
|
10
|
+
@yaml = yaml
|
11
|
+
end
|
12
|
+
|
13
|
+
def build
|
14
|
+
{ app_name: app_name, active: active, prefix: prefix }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :yaml
|
20
|
+
|
21
|
+
def app_name
|
22
|
+
ENV[APP_NAME_KEY] || yaml[:app_name] || raise(AppNameNotFoundError)
|
23
|
+
end
|
24
|
+
|
25
|
+
def active
|
26
|
+
return ENV[ACTIVE_KEY] == 'true' if ENV[ACTIVE_KEY].present?
|
27
|
+
|
28
|
+
yaml[:active].presence || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def prefix
|
32
|
+
without_accent(app_name.underscore)
|
33
|
+
.gsub(/\s/, '_') # {\s,_}
|
34
|
+
.gsub(/\W/, '') # non-chars
|
35
|
+
end
|
36
|
+
|
37
|
+
def without_accent(text)
|
38
|
+
# String#parameterize or ActiveSupport::Inflector.transliterate
|
39
|
+
# mess with i18n
|
40
|
+
#
|
41
|
+
# See https://github.com/rails/rails/issues/21939
|
42
|
+
text.tr(
|
43
|
+
'ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħ' \
|
44
|
+
'ÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕ' \
|
45
|
+
'ŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž',
|
46
|
+
'AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHh' \
|
47
|
+
'IIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRr' \
|
48
|
+
'RrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz'
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Metrux
|
2
|
+
module ConfigBuilders
|
3
|
+
class Influx
|
4
|
+
DEFAULT_TIME_PRECISION = 'ns'.freeze
|
5
|
+
DEFAULT_ASYNC = true
|
6
|
+
|
7
|
+
def initialize(yaml)
|
8
|
+
@yaml = yaml
|
9
|
+
end
|
10
|
+
|
11
|
+
def build
|
12
|
+
defaults.deep_merge(
|
13
|
+
config_from_yaml.deep_merge(config_from_env_var)
|
14
|
+
).freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :yaml
|
20
|
+
|
21
|
+
def config_from_env_var
|
22
|
+
fetch_from(ENV, 'METRUX_INFLUX_'.freeze)
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_from_yaml
|
26
|
+
fetch_from(yaml, 'influx_'.freeze)
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch_from(object, prefix)
|
30
|
+
object.each_with_object({}) do |(config_key, value), acc|
|
31
|
+
if config_key.start_with?(prefix)
|
32
|
+
acc[config_key.gsub(/^#{prefix}/, '').to_s.downcase.to_sym] =
|
33
|
+
cast_value(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cast_value(value)
|
39
|
+
string_value = value.to_s
|
40
|
+
|
41
|
+
unless (numeric_value = numeric_value(string_value)).nil?
|
42
|
+
return numeric_value
|
43
|
+
end
|
44
|
+
|
45
|
+
unless (boolean_value = boolean_value(string_value)).nil?
|
46
|
+
return boolean_value
|
47
|
+
end
|
48
|
+
|
49
|
+
value
|
50
|
+
end
|
51
|
+
|
52
|
+
def numeric_value(value)
|
53
|
+
return value.to_f if value.to_f.to_s == value
|
54
|
+
return value.to_i if value.to_i.to_s == value
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def boolean_value(value)
|
60
|
+
return true if value == true || value =~ /^(true)$/i
|
61
|
+
return false if value == false || value =~ /^(false)$/i
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def defaults
|
67
|
+
{ time_precision: DEFAULT_TIME_PRECISION, async: DEFAULT_ASYNC }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Metrux
|
2
|
+
module ConfigBuilders
|
3
|
+
class Logger
|
4
|
+
LOG_FILE_KEY = 'METRUX_LOG_FILE'.freeze
|
5
|
+
LOG_LEVEL_KEY = 'METRUX_LOG_LEVEL'.freeze
|
6
|
+
|
7
|
+
DEFAULT_LOG_PATH = STDOUT
|
8
|
+
DEFAULT_LOG_LEVEL = :info
|
9
|
+
|
10
|
+
def initialize(yaml)
|
11
|
+
@yaml = yaml
|
12
|
+
end
|
13
|
+
|
14
|
+
def build
|
15
|
+
::Logger.new(log_file).tap do |logger|
|
16
|
+
logger.level = log_level
|
17
|
+
logger.formatter = ::Logger::Formatter.new
|
18
|
+
end
|
19
|
+
rescue => e
|
20
|
+
Kernel.warn(
|
21
|
+
'[WARNING] Cound\'t configure Metrux\'s logger. '\
|
22
|
+
"#{e.class}: #{e.message}"
|
23
|
+
)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :yaml
|
30
|
+
|
31
|
+
def log_file
|
32
|
+
from_config = (ENV[LOG_FILE_KEY] || yaml[:log_file]).presence
|
33
|
+
|
34
|
+
return DEFAULT_LOG_PATH if from_config.blank?
|
35
|
+
|
36
|
+
from_config == 'STDOUT'.freeze ? STDOUT : from_config
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_level
|
40
|
+
::Logger.const_get(
|
41
|
+
(
|
42
|
+
ENV[LOG_LEVEL_KEY] || yaml[:log_level] || DEFAULT_LOG_LEVEL
|
43
|
+
).to_s.upcase
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Metrux
|
2
|
+
module ConfigBuilders
|
3
|
+
class PeriodicGauge
|
4
|
+
INTERVAL_KEY = 'METRUX_PERIODIC_GAUGE_INTERVAL'.freeze
|
5
|
+
|
6
|
+
def initialize(yaml)
|
7
|
+
@yaml = yaml
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
interval = (ENV[INTERVAL_KEY] || yaml[:periodic_gauge_interval]).to_i
|
12
|
+
|
13
|
+
(interval > 0 && interval).presence
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :yaml
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|