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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +14 -0
  5. data/.ruby-version.sample +1 -0
  6. data/CHANGELOG.md +110 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +429 -0
  10. data/Rakefile +2 -0
  11. data/bin/console +9 -0
  12. data/bin/playground +38 -0
  13. data/bin/rake +17 -0
  14. data/bin/rspec +17 -0
  15. data/bin/rubocop +17 -0
  16. data/bin/setup +15 -0
  17. data/config/metrux.sample.yml +46 -0
  18. data/lib/metrux.rb +60 -0
  19. data/lib/metrux/client.rb +40 -0
  20. data/lib/metrux/commands.rb +12 -0
  21. data/lib/metrux/commands/base.rb +62 -0
  22. data/lib/metrux/commands/gauge.rb +21 -0
  23. data/lib/metrux/commands/meter.rb +15 -0
  24. data/lib/metrux/commands/notice_error.rb +38 -0
  25. data/lib/metrux/commands/periodic_gauge.rb +25 -0
  26. data/lib/metrux/commands/periodic_gauge/agent.rb +68 -0
  27. data/lib/metrux/commands/periodic_gauge/registry.rb +39 -0
  28. data/lib/metrux/commands/periodic_gauge/reporter.rb +33 -0
  29. data/lib/metrux/commands/periodic_gauge/supervisor.rb +53 -0
  30. data/lib/metrux/commands/timer.rb +30 -0
  31. data/lib/metrux/commands/write.rb +9 -0
  32. data/lib/metrux/config_builders.rb +11 -0
  33. data/lib/metrux/config_builders/common.rb +53 -0
  34. data/lib/metrux/config_builders/influx.rb +71 -0
  35. data/lib/metrux/config_builders/logger.rb +48 -0
  36. data/lib/metrux/config_builders/periodic_gauge.rb +21 -0
  37. data/lib/metrux/config_builders/yaml.rb +64 -0
  38. data/lib/metrux/configuration.rb +52 -0
  39. data/lib/metrux/connections.rb +7 -0
  40. data/lib/metrux/connections/influx_db.rb +17 -0
  41. data/lib/metrux/connections/null.rb +19 -0
  42. data/lib/metrux/constants.rb +19 -0
  43. data/lib/metrux/loggable.rb +24 -0
  44. data/lib/metrux/plugin_register.rb +57 -0
  45. data/lib/metrux/plugins.rb +10 -0
  46. data/lib/metrux/plugins/gc.rb +52 -0
  47. data/lib/metrux/plugins/periodic_gauge.rb +32 -0
  48. data/lib/metrux/plugins/process.rb +71 -0
  49. data/lib/metrux/plugins/thread.rb +13 -0
  50. data/lib/metrux/plugins/yarv.rb +26 -0
  51. data/lib/metrux/sleeper.rb +8 -0
  52. data/lib/metrux/version.rb +3 -0
  53. data/metrux.gemspec +40 -0
  54. 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
+ &params.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,9 @@
1
+ module Metrux
2
+ module Commands
3
+ class Write < Base
4
+ def execute(key, data, options = {})
5
+ write(key, format_data(data, options), format_write_options(options))
6
+ end
7
+ end
8
+ end
9
+ 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