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,64 @@
1
+ module Metrux
2
+ module ConfigBuilders
3
+ class Yaml
4
+ FileLoadError = Class.new(ConfigurationError)
5
+ EnvironmentNotFoundError = Class.new(FileLoadError)
6
+
7
+ def initialize(config_path, env)
8
+ @config_path = config_path
9
+ @env = env
10
+ end
11
+
12
+ def build
13
+ file_exists? ? yaml_content : null_content
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :config_path, :env
19
+
20
+ def file_exists?
21
+ File.exist?(config_path)
22
+ end
23
+
24
+ def null_content
25
+ {}
26
+ end
27
+
28
+ def yaml_content
29
+ from_environment(load_file(config_path)).with_indifferent_access
30
+ end
31
+
32
+ def load_file(config_path)
33
+ content = File.read(config_path)
34
+ template = ERB.new(content)
35
+ YAML.load(template.result)
36
+ rescue => e
37
+ raise(FileLoadError, "#{e.class}: #{e.message}")
38
+ end
39
+
40
+ def from_environment(config_content)
41
+ config_content.fetch(env)
42
+ rescue KeyError => e
43
+ if env == default_environment
44
+ raise(EnvironmentNotFoundError, "#{e.class}: #{e.message}")
45
+ end
46
+
47
+ warn_environment_change
48
+ @env = default_environment
49
+ retry
50
+ end
51
+
52
+ def default_environment
53
+ Configuration::DEFAULT_ENVIRONMENT
54
+ end
55
+
56
+ def warn_environment_change
57
+ Kernel.warn(
58
+ "[WARNING] Metrux's configuration wasn't found for environment "\
59
+ "\"#{env}\". Switching to default: \"#{default_environment}\"."
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,52 @@
1
+ module Metrux
2
+ class Configuration
3
+ DEFAULT_ENVIRONMENT = 'development'.freeze
4
+
5
+ def initialize(
6
+ config_path = File.join(File.expand_path('.'), 'config', 'metrux.yml')
7
+ )
8
+ @config_path = config_path
9
+ end
10
+
11
+ def env
12
+ @env ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || DEFAULT_ENVIRONMENT
13
+ end
14
+
15
+ def app_name
16
+ @app_name ||= commons[:app_name]
17
+ end
18
+
19
+ def prefix
20
+ @prefix ||= commons[:prefix]
21
+ end
22
+
23
+ def active?
24
+ @active ||= commons[:active]
25
+ end
26
+
27
+ def influx
28
+ @influx ||= ConfigBuilders::Influx.new(yaml).build
29
+ end
30
+
31
+ def periodic_gauge_interval
32
+ @periodic_gauge_interval ||=
33
+ ConfigBuilders::PeriodicGauge.new(yaml).build
34
+ end
35
+
36
+ def logger
37
+ @logger ||= ConfigBuilders::Logger.new(yaml).build
38
+ end
39
+
40
+ def yaml
41
+ @yaml ||= ConfigBuilders::Yaml.new(config_path, env).build
42
+ end
43
+
44
+ def commons
45
+ @commons ||= ConfigBuilders::Common.new(yaml).build
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :config_path
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ module Metrux
2
+ module Connections
3
+ end
4
+ end
5
+
6
+ require_relative 'connections/influx_db'
7
+ require_relative 'connections/null'
@@ -0,0 +1,17 @@
1
+ module Metrux
2
+ module Connections
3
+ class InfluxDb
4
+ extend Forwardable
5
+
6
+ def_delegator :client, :write_point
7
+
8
+ def initialize(config)
9
+ @client = ::InfluxDB::Client.new(config.influx)
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :client
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Metrux
2
+ module Connections
3
+ class Null
4
+ include Metrux::Loggable
5
+
6
+ def initialize(config)
7
+ @logger = config.logger
8
+ end
9
+
10
+ (InfluxDb.public_instance_methods - public_instance_methods)
11
+ .each do |method|
12
+ define_method(method) do |*args, &block|
13
+ log("Calling #{method} with #{args}. Block given? #{block.present?}")
14
+ self
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Metrux
2
+ HOST = Socket.gethostname.freeze
3
+ MAIN_PROGRAM_NAME = $PROGRAM_NAME
4
+ .split('/').last
5
+ .split(' ').first.gsub(/\W/, '')
6
+ .freeze
7
+ PUMA_WORKER =
8
+ $PROGRAM_NAME
9
+ .split('/').last
10
+ .scan(/^puma: cluster worker (\d+): */)
11
+ .flatten.last
12
+ .freeze
13
+
14
+ PROGRAM_NAME = if PUMA_WORKER.present?
15
+ "#{MAIN_PROGRAM_NAME}-#{PUMA_WORKER}"
16
+ else
17
+ MAIN_PROGRAM_NAME
18
+ end.freeze
19
+ end
@@ -0,0 +1,24 @@
1
+ module Metrux
2
+ module Loggable
3
+ PREFIX_PROGRAM_NAME = 'metrux'.freeze
4
+ private_constant :PREFIX_PROGRAM_NAME
5
+
6
+ LOG_PROGRAM_NAME = "#{PREFIX_PROGRAM_NAME}/#{Metrux::PROGRAM_NAME}".freeze
7
+ private_constant :LOG_PROGRAM_NAME
8
+
9
+ private
10
+
11
+ def log(message, severity = :debug)
12
+ return if __logger__.blank?
13
+
14
+ __logger__.public_send(severity, LOG_PROGRAM_NAME) do
15
+ "[#{self.class}][thread=#{Thread.current.object_id.to_s(16)}] " \
16
+ "#{message}"
17
+ end
18
+ end
19
+
20
+ def __logger__
21
+ @logger || Metrux.logger
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ module Metrux
2
+ class PluginRegister
3
+ include Loggable
4
+
5
+ # Registered plugins
6
+ attr_reader :plugins
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @logger = config.logger
11
+ @plugins = []
12
+ end
13
+
14
+ # Register a plugin
15
+ #
16
+ # == Arguments
17
+ #
18
+ # * +klass+ - The plugin class
19
+ # * +options+ - Any option that you might use on plugin
20
+ # * +block+ - If you don't have a class, you can pass a block that receives
21
+ # the `config` and `options` as arguments.
22
+ #
23
+ # === Examples
24
+ #
25
+ # * Passing a class as plugin
26
+ #
27
+ # plugin_register.register(
28
+ # Metrux::Plugins::MyPlugin, tags: { a: 'tag' }
29
+ # ) # => true
30
+ #
31
+ # * Passing a block as plugin
32
+ #
33
+ # plugin_register.register(tags: { a: 'tag' }) do |config, options|
34
+ # # do something
35
+ # end # => true
36
+ #
37
+ def register(klass = nil, **options)
38
+ plugin = if block_given?
39
+ -> () { yield(config, options) }
40
+ else
41
+ klass.new(config, options)
42
+ end
43
+
44
+ log("Registering plugin #{plugin.class}")
45
+
46
+ plugin.call
47
+
48
+ @plugins << plugin
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :config, :logger
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ module Metrux
2
+ module Plugins
3
+ end
4
+ end
5
+
6
+ require_relative 'plugins/periodic_gauge'
7
+ require_relative 'plugins/gc'
8
+ require_relative 'plugins/process'
9
+ require_relative 'plugins/thread'
10
+ require_relative 'plugins/yarv'
@@ -0,0 +1,52 @@
1
+ module Metrux
2
+ module Plugins
3
+ class Gc < PeriodicGauge
4
+ def data
5
+ {
6
+ count: count, major_count: major_count, minor_count: minor_count,
7
+ total_allocated_objects: total_allocated_objects,
8
+ heap_live: heap_live, heap_free: heap_free
9
+ }
10
+ end
11
+
12
+ def key
13
+ 'gc'.freeze
14
+ end
15
+
16
+ private
17
+
18
+ def count
19
+ ::GC.count
20
+ end
21
+
22
+ def major_count
23
+ gc_stats[:major_gc_count]
24
+ end
25
+
26
+ def minor_count
27
+ gc_stats[:minor_gc_count]
28
+ end
29
+
30
+ def total_allocated_objects
31
+ gc_stats[:total_allocated_objects] ||
32
+ gc_stats[:total_allocated_object]
33
+ end
34
+
35
+ def heap_live
36
+ gc_stats[:heap_live_slots] ||
37
+ gc_stats[:heap_live_slot] ||
38
+ gc_stats[:heap_live_num]
39
+ end
40
+
41
+ def heap_free
42
+ gc_stats[:heap_free_slots] ||
43
+ gc_stats[:heap_free_slot] ||
44
+ gc_stats[:heap_free_num]
45
+ end
46
+
47
+ def gc_stats
48
+ ::GC.stat
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module Metrux
2
+ module Plugins
3
+ class PeriodicGauge
4
+ def initialize(config, options = {})
5
+ @config = config
6
+ @options = options
7
+ end
8
+
9
+ def call
10
+ Metrux.periodic_gauge(key, options) { data }
11
+ end
12
+
13
+ def key
14
+ not_implemented
15
+ end
16
+
17
+ def data
18
+ not_implemented
19
+ end
20
+
21
+ protected
22
+
23
+ attr_reader :config, :options
24
+
25
+ private
26
+
27
+ def not_implemented
28
+ raise NotImplementedError, 'This is a base plugin'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ module Metrux
2
+ module Plugins
3
+ class Process < PeriodicGauge
4
+ def initialize(*)
5
+ super
6
+ @pid = ::Process.pid
7
+ end
8
+
9
+ def data
10
+ { rss: rss }
11
+ end
12
+
13
+ def key
14
+ 'process'.freeze
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :pid
20
+
21
+ def rss
22
+ case ::RbConfig::CONFIG['host_os']
23
+ when /darwin|mac os/
24
+ default_rss
25
+ when /linux/
26
+ linux_rss
27
+ else
28
+ 0
29
+ end
30
+ end
31
+
32
+ def linux_rss
33
+ statm? ? (fetch_statm_rss * kernel_page_size) / 1_024 : default_rss
34
+ rescue
35
+ 0
36
+ end
37
+
38
+ def default_rss
39
+ exec("ps -o rss= -p #{pid}").chomp.to_i
40
+ rescue
41
+ 0
42
+ end
43
+
44
+ def kernel_page_size
45
+ @kernel_page_size ||= fetch_pagesize
46
+ end
47
+
48
+ def statm_path
49
+ @statm_path ||= "/proc/#{pid}/statm".freeze
50
+ end
51
+
52
+ def statm?
53
+ @statm_found ||= ::File.exist?(statm_path)
54
+ end
55
+
56
+ def fetch_statm_rss
57
+ ::File.read(statm_path).split(' ')[1].to_i
58
+ end
59
+
60
+ def fetch_pagesize
61
+ exec('getconf PAGESIZE').chomp.to_i
62
+ rescue
63
+ 4_096
64
+ end
65
+
66
+ def exec(cmd)
67
+ ::Kernel.public_send(:`, cmd)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ module Metrux
2
+ module Plugins
3
+ class Thread < PeriodicGauge
4
+ def data
5
+ { count: ::Thread.list.count }
6
+ end
7
+
8
+ def key
9
+ 'thread'.freeze
10
+ end
11
+ end
12
+ end
13
+ end