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.
- 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
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/playground
ADDED
@@ -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)
|
data/bin/rake
ADDED
@@ -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")
|
data/bin/rspec
ADDED
@@ -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")
|
data/bin/rubocop
ADDED
@@ -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")
|
data/bin/setup
ADDED
@@ -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
|
data/lib/metrux.rb
ADDED
@@ -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
|