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,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,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,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
|