metrux 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|