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