metrux 1.0.0

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