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,2 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "metrux"
5
+
6
+ Metrux.setup
7
+
8
+ require "pry"
9
+ Pry.start
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV['METRUX_ACTIVE'] = 'true'
4
+ ENV['METRUX_APP_NAME'] = 'My application'
5
+ ENV['METRUX_LOG_LEVEL'] = 'debug'
6
+
7
+ require "bundler/setup"
8
+ require "metrux"
9
+
10
+ puts 'Starting playground...'
11
+ puts "Ctrl+C to stop\n\n"
12
+
13
+ Metrux.register(Metrux::Plugins::Thread)
14
+ Metrux.register(Metrux::Plugins::Gc)
15
+ Metrux.register(Metrux::Plugins::Process)
16
+ Metrux.register(Metrux::Plugins::Yarv)
17
+
18
+ Metrux.periodic_gauge('threads_count') { Thread.list.count }
19
+ Metrux.periodic_gauge(
20
+ 'threads_count', tags: { type: :domain }
21
+ ) { Thread.list.count }
22
+
23
+ timer = Proc.new { sleep(5); Metrux.timer('sleep') { sleep(rand + rand(2)); } }
24
+ error = Proc.new do
25
+ sleep(5)
26
+ Metrux.notice_error(
27
+ [StandardError, RuntimeError, ArgumentError, Exception].sample.new
28
+ )
29
+ end
30
+ meter = Proc.new { sleep(rand + rand(5)); Metrux.meter('sleep') }
31
+ gauge = Proc.new { sleep(5); Metrux.gauge('rand') { rand(200) } }
32
+
33
+ [
34
+ Thread.new { loop { timer.call } },
35
+ Thread.new { loop { error.call } },
36
+ Thread.new { loop { meter.call } },
37
+ Thread.new { loop { gauge.call } }
38
+ ].each(&:join)
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rake' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rspec' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rubocop' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rubocop", "rubocop")
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ echo "Creating log dir..."
6
+ mkdir -p log
7
+
8
+ if [ ! -f "config/metrux.yml" ]
9
+ then
10
+ echo "Copying config/metrux{.sample.yml,.yml}..."
11
+ cp config/metrux{.sample.yml,.yml}
12
+ fi
13
+
14
+ bundle install
15
+ gem install geminabox --no-ri --no-rdoc
@@ -0,0 +1,46 @@
1
+ default: &defaults
2
+ #
3
+ # Your application's name (All metrics will be marked with this tag)
4
+ #
5
+ # app_name: "My application"
6
+
7
+ #
8
+ # Whether it is active for this environment, can be true or false
9
+ # default: false
10
+ #
11
+ active: true
12
+
13
+ #
14
+ # Interval that agent will execute all registered periodic metrics
15
+ # (in seconds)
16
+ #
17
+ periodic_gauge_interval: 5 # default: 60
18
+
19
+ #
20
+ # Metrux logger configuration
21
+ #
22
+ log_file: 'log/metrux.log' # default: STDOUT
23
+ log_level: 'info' # default: info
24
+
25
+ #
26
+ # Influx configuration
27
+ # See: https://github.com/influxdata/influxdb-ruby#creating-a-client
28
+ #
29
+ influx_host: 'hostname'
30
+ influx_port: 80
31
+ influx_database: 'database'
32
+ influx_username: 'user'
33
+ influx_password: 'secret'
34
+ influx_async: true
35
+
36
+ development:
37
+ <<: *defaults
38
+ active: false
39
+
40
+ test:
41
+ <<: *defaults
42
+ log_file: '/dev/null'
43
+ active: false
44
+
45
+ production:
46
+ <<: *defaults
@@ -0,0 +1,60 @@
1
+ require 'influxdb'
2
+ require 'active_support/core_ext/hash'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/string/filters'
5
+ require 'securerandom'
6
+ require 'erb'
7
+ require 'yaml'
8
+ require 'thread'
9
+ require 'metrux/version'
10
+ require 'metrux/constants'
11
+ require 'metrux/loggable'
12
+ require 'metrux/sleeper'
13
+ require 'metrux/client'
14
+ require 'metrux/configuration'
15
+ require 'metrux/config_builders'
16
+ require 'metrux/connections'
17
+ require 'metrux/commands'
18
+ require 'metrux/plugins'
19
+ require 'metrux/plugin_register'
20
+
21
+ module Metrux
22
+ class << self
23
+ extend Forwardable
24
+
25
+ attr_reader :configured
26
+
27
+ alias configured? configured
28
+
29
+ def_delegators(:client, *Client::AVAILABLE_COMMANDS)
30
+ def_delegator :config, :logger
31
+ def_delegator :plugin_register, :register
32
+ def_delegator :plugin_register, :plugins
33
+
34
+ def setup(config = nil)
35
+ @config = config || Configuration.new
36
+ @client = Client.new(@config)
37
+ @plugin_register = PluginRegister.new(@config)
38
+ @configured = true
39
+ end
40
+
41
+ def client
42
+ lazy_setup { @client }
43
+ end
44
+
45
+ def config
46
+ lazy_setup { @config }
47
+ end
48
+
49
+ def plugin_register
50
+ lazy_setup { @plugin_register }
51
+ end
52
+
53
+ private
54
+
55
+ def lazy_setup
56
+ setup unless configured?
57
+ yield
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ module Metrux
2
+ class Client
3
+ extend Forwardable
4
+ include Loggable
5
+
6
+ AVAILABLE_COMMANDS = %i(
7
+ timer meter gauge periodic_gauge notice_error write
8
+ ).freeze
9
+
10
+ AVAILABLE_COMMANDS.each do |command|
11
+ attr_reader(:"#{command}_command")
12
+ def_delegator(:"#{command}_command", :execute, command)
13
+ end
14
+
15
+ def initialize(config)
16
+ @config = config
17
+
18
+ conn_type = config.active? ? 'influx_db' : 'null'
19
+ @connection =
20
+ "metrux/connections/#{conn_type}".camelize.constantize.new(config)
21
+
22
+ instantiate_commands
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :config, :connection
28
+
29
+ def instantiate_commands
30
+ AVAILABLE_COMMANDS.each do |command|
31
+ instance_variable_set(
32
+ "@#{command}_command",
33
+ "metrux/commands/#{command}".camelize.constantize.new(
34
+ config, connection
35
+ )
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ module Metrux
2
+ module Commands
3
+ end
4
+ end
5
+
6
+ require_relative 'commands/base'
7
+ require_relative 'commands/write'
8
+ require_relative 'commands/gauge'
9
+ require_relative 'commands/periodic_gauge'
10
+ require_relative 'commands/timer'
11
+ require_relative 'commands/meter'
12
+ require_relative 'commands/notice_error'
@@ -0,0 +1,62 @@
1
+ module Metrux
2
+ module Commands
3
+ class Base
4
+ extend Forwardable
5
+ include Loggable
6
+
7
+ DEFAULT_TAGS = {
8
+ hostname: Metrux::HOST, program_name: Metrux::PROGRAM_NAME
9
+ }.freeze
10
+
11
+ def initialize(config, connection)
12
+ @config = config
13
+ @connection = connection
14
+ @logger = config.logger
15
+ @prefix = config.prefix
16
+ end
17
+
18
+ protected
19
+
20
+ attr_reader :connection, :logger, :config, :prefix
21
+
22
+ def_delegators :config, :app_name, :env
23
+
24
+ def write(measurement, data, options = {})
25
+ precision = options[:precision].presence
26
+ retention = options[:retention].presence
27
+
28
+ log("Writing #{measurement}")
29
+
30
+ connection.write_point(
31
+ "#{prefix}/#{measurement}", default_data.deep_merge(data), precision,
32
+ retention
33
+ )
34
+
35
+ true
36
+ end
37
+
38
+ def format_data(value, params)
39
+ values = value.is_a?(Hash) ? value : { value: value }
40
+ {
41
+ values: values,
42
+ tags: params.fetch(:tags, {}),
43
+ timestamp: params.fetch(:timestamp, default_timestamp)
44
+ }
45
+ end
46
+
47
+ def format_write_options(params)
48
+ params.select { |k, _| [:precision, :retention].include?(k) }
49
+ end
50
+
51
+ private
52
+
53
+ def default_data
54
+ { tags: DEFAULT_TAGS.merge(app_name: app_name, env: env) }
55
+ end
56
+
57
+ def default_timestamp
58
+ (Time.now.utc.to_f * 1_000_000_000).to_i
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,21 @@
1
+ module Metrux
2
+ module Commands
3
+ class Gauge < Base
4
+ GAUGE_MEASUREMENT_PREFIX_KEY = 'gauges/'.freeze
5
+
6
+ def execute(key, params = {})
7
+ block_given? ? gauge(key, params) { yield } : gauge(key, params)
8
+ end
9
+
10
+ def gauge(key, params = {})
11
+ key = "#{GAUGE_MEASUREMENT_PREFIX_KEY}#{key}"
12
+
13
+ result = block_given? ? yield : params.fetch(:result)
14
+
15
+ write(key, format_data(result, params), format_write_options(params))
16
+
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Metrux
2
+ module Commands
3
+ class Meter < Base
4
+ METER_MEASUREMENT_PREFIX_KEY = 'meters/'.freeze
5
+
6
+ def execute(key, params = {})
7
+ key = "#{METER_MEASUREMENT_PREFIX_KEY}#{key}"
8
+
9
+ value = params.fetch(:value, 1).to_i
10
+
11
+ write(key, format_data(value, params), format_write_options(params))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Metrux
2
+ module Commands
3
+ class NoticeError < Base
4
+ ERROR_METER_KEY = 'meters/errors'.freeze
5
+
6
+ def execute(error, payload = {})
7
+ value = build_value(error)
8
+ options = build_options(error, payload)
9
+
10
+ write(ERROR_METER_KEY, format_data(value, options))
11
+ end
12
+
13
+ private
14
+
15
+ def build_value(error)
16
+ { message: error.message.truncate(100, separator: ' '), value: 1 }
17
+ end
18
+
19
+ def build_options(error, payload)
20
+ {}.tap do |options|
21
+ options[:tags] = fetch_tags(error, payload)
22
+
23
+ if payload[:timestamp].present?
24
+ options[:timestamp] = payload[:timestamp]
25
+ end
26
+ end
27
+ end
28
+
29
+ def fetch_tags(error, payload)
30
+ payload
31
+ .reject { |(k, _)| k.to_s == 'timestamp' }
32
+ .each_with_object({}) do |(k, v), with_string_values|
33
+ with_string_values[k] = v.is_a?(String) ? v : v.inspect
34
+ end.merge(error: error.class.to_s)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ module Metrux
2
+ module Commands
3
+ class PeriodicGauge < Gauge
4
+ require_relative 'periodic_gauge/registry'
5
+ require_relative 'periodic_gauge/agent'
6
+ require_relative 'periodic_gauge/supervisor'
7
+ require_relative 'periodic_gauge/reporter'
8
+
9
+ attr_reader :registry, :reporter
10
+
11
+ def initialize(config, connection)
12
+ super
13
+ @registry = Registry.new(config)
14
+ @reporter = Reporter.new(self, registry, config)
15
+ end
16
+
17
+ def execute(key, params = {})
18
+ registry.add(key, params) { yield }
19
+ reporter.start
20
+
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end