gremlin 0.0.1
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 +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +142 -0
- data/app/controllers/gremlin/metrics_controller.rb +35 -0
- data/config/routes.rb +5 -0
- data/gremlin.gemspec +39 -0
- data/lib/gremlin.rb +27 -0
- data/lib/gremlin/engine.rb +5 -0
- data/lib/gremlin/instruments.rb +82 -0
- data/lib/gremlin/instruments/counter.rb +28 -0
- data/lib/gremlin/instruments/gauge.rb +23 -0
- data/lib/gremlin/instruments/summary.rb +96 -0
- data/lib/gremlin/quantile.rb +7 -0
- data/lib/gremlin/quantile/estimator.rb +27 -0
- data/lib/gremlin/quantile/quantile.rb +16 -0
- data/lib/gremlin/railtie.rb +130 -0
- data/lib/gremlin/registry.rb +74 -0
- data/lib/gremlin/version.rb +3 -0
- metadata +182 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f7287de278b662763ddc2ca3cca5d38c41bdd190
|
|
4
|
+
data.tar.gz: 823ee29d0d1dac97672d32d3649c502699271f0a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d40b078ddda9c56352cb260a493002bc9e13fa695cb781b5065fedd08c99a0c78205c8161c02953ce0004eaaf1f72473d3971744bc2d33faf5108623dee2b741
|
|
7
|
+
data.tar.gz: 6ba936ae95928ad71d409f3213921ced78bca12e71f189b38c8da808fe158bbd9e33930a7b67d2c1a3857afbb9f845a9d83b95b4343498502e5088ab7238a6dd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
gremlin (0.0.1)
|
|
5
|
+
quantile
|
|
6
|
+
rails
|
|
7
|
+
redis (~> 3.2)
|
|
8
|
+
redis-native_hash
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
actioncable (5.0.1)
|
|
14
|
+
actionpack (= 5.0.1)
|
|
15
|
+
nio4r (~> 1.2)
|
|
16
|
+
websocket-driver (~> 0.6.1)
|
|
17
|
+
actionmailer (5.0.1)
|
|
18
|
+
actionpack (= 5.0.1)
|
|
19
|
+
actionview (= 5.0.1)
|
|
20
|
+
activejob (= 5.0.1)
|
|
21
|
+
mail (~> 2.5, >= 2.5.4)
|
|
22
|
+
rails-dom-testing (~> 2.0)
|
|
23
|
+
actionpack (5.0.1)
|
|
24
|
+
actionview (= 5.0.1)
|
|
25
|
+
activesupport (= 5.0.1)
|
|
26
|
+
rack (~> 2.0)
|
|
27
|
+
rack-test (~> 0.6.3)
|
|
28
|
+
rails-dom-testing (~> 2.0)
|
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
30
|
+
actionview (5.0.1)
|
|
31
|
+
activesupport (= 5.0.1)
|
|
32
|
+
builder (~> 3.1)
|
|
33
|
+
erubis (~> 2.7.0)
|
|
34
|
+
rails-dom-testing (~> 2.0)
|
|
35
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
36
|
+
activejob (5.0.1)
|
|
37
|
+
activesupport (= 5.0.1)
|
|
38
|
+
globalid (>= 0.3.6)
|
|
39
|
+
activemodel (5.0.1)
|
|
40
|
+
activesupport (= 5.0.1)
|
|
41
|
+
activerecord (5.0.1)
|
|
42
|
+
activemodel (= 5.0.1)
|
|
43
|
+
activesupport (= 5.0.1)
|
|
44
|
+
arel (~> 7.0)
|
|
45
|
+
activesupport (5.0.1)
|
|
46
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
47
|
+
i18n (~> 0.7)
|
|
48
|
+
minitest (~> 5.1)
|
|
49
|
+
tzinfo (~> 1.1)
|
|
50
|
+
arel (7.1.4)
|
|
51
|
+
builder (3.2.3)
|
|
52
|
+
concurrent-ruby (1.0.4)
|
|
53
|
+
diff-lcs (1.2.5)
|
|
54
|
+
erubis (2.7.0)
|
|
55
|
+
globalid (0.3.7)
|
|
56
|
+
activesupport (>= 4.1.0)
|
|
57
|
+
i18n (0.7.0)
|
|
58
|
+
loofah (2.0.3)
|
|
59
|
+
nokogiri (>= 1.5.9)
|
|
60
|
+
mail (2.6.4)
|
|
61
|
+
mime-types (>= 1.16, < 4)
|
|
62
|
+
method_source (0.8.2)
|
|
63
|
+
mime-types (3.1)
|
|
64
|
+
mime-types-data (~> 3.2015)
|
|
65
|
+
mime-types-data (3.2016.0521)
|
|
66
|
+
mini_portile2 (2.1.0)
|
|
67
|
+
minitest (5.10.1)
|
|
68
|
+
nio4r (1.2.1)
|
|
69
|
+
nokogiri (1.7.0.1)
|
|
70
|
+
mini_portile2 (~> 2.1.0)
|
|
71
|
+
quantile (0.2.0)
|
|
72
|
+
rack (2.0.1)
|
|
73
|
+
rack-test (0.6.3)
|
|
74
|
+
rack (>= 1.0)
|
|
75
|
+
rails (5.0.1)
|
|
76
|
+
actioncable (= 5.0.1)
|
|
77
|
+
actionmailer (= 5.0.1)
|
|
78
|
+
actionpack (= 5.0.1)
|
|
79
|
+
actionview (= 5.0.1)
|
|
80
|
+
activejob (= 5.0.1)
|
|
81
|
+
activemodel (= 5.0.1)
|
|
82
|
+
activerecord (= 5.0.1)
|
|
83
|
+
activesupport (= 5.0.1)
|
|
84
|
+
bundler (>= 1.3.0, < 2.0)
|
|
85
|
+
railties (= 5.0.1)
|
|
86
|
+
sprockets-rails (>= 2.0.0)
|
|
87
|
+
rails-dom-testing (2.0.2)
|
|
88
|
+
activesupport (>= 4.2.0, < 6.0)
|
|
89
|
+
nokogiri (~> 1.6)
|
|
90
|
+
rails-html-sanitizer (1.0.3)
|
|
91
|
+
loofah (~> 2.0)
|
|
92
|
+
railties (5.0.1)
|
|
93
|
+
actionpack (= 5.0.1)
|
|
94
|
+
activesupport (= 5.0.1)
|
|
95
|
+
method_source
|
|
96
|
+
rake (>= 0.8.7)
|
|
97
|
+
thor (>= 0.18.1, < 2.0)
|
|
98
|
+
rake (10.5.0)
|
|
99
|
+
rdoc (5.0.0)
|
|
100
|
+
redis (3.3.2)
|
|
101
|
+
redis-native_hash (0.2.1)
|
|
102
|
+
redis (>= 2.0.0)
|
|
103
|
+
rspec (3.5.0)
|
|
104
|
+
rspec-core (~> 3.5.0)
|
|
105
|
+
rspec-expectations (~> 3.5.0)
|
|
106
|
+
rspec-mocks (~> 3.5.0)
|
|
107
|
+
rspec-core (3.5.4)
|
|
108
|
+
rspec-support (~> 3.5.0)
|
|
109
|
+
rspec-expectations (3.5.0)
|
|
110
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
111
|
+
rspec-support (~> 3.5.0)
|
|
112
|
+
rspec-mocks (3.5.0)
|
|
113
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
114
|
+
rspec-support (~> 3.5.0)
|
|
115
|
+
rspec-support (3.5.0)
|
|
116
|
+
sprockets (3.7.1)
|
|
117
|
+
concurrent-ruby (~> 1.0)
|
|
118
|
+
rack (> 1, < 3)
|
|
119
|
+
sprockets-rails (3.2.0)
|
|
120
|
+
actionpack (>= 4.0)
|
|
121
|
+
activesupport (>= 4.0)
|
|
122
|
+
sprockets (>= 3.0.0)
|
|
123
|
+
thor (0.19.4)
|
|
124
|
+
thread_safe (0.3.5)
|
|
125
|
+
tzinfo (1.2.2)
|
|
126
|
+
thread_safe (~> 0.1)
|
|
127
|
+
websocket-driver (0.6.4)
|
|
128
|
+
websocket-extensions (>= 0.1.0)
|
|
129
|
+
websocket-extensions (0.1.2)
|
|
130
|
+
|
|
131
|
+
PLATFORMS
|
|
132
|
+
ruby
|
|
133
|
+
|
|
134
|
+
DEPENDENCIES
|
|
135
|
+
bundler (~> 1.11)
|
|
136
|
+
gremlin!
|
|
137
|
+
rake (~> 10.0)
|
|
138
|
+
rdoc
|
|
139
|
+
rspec (~> 3.3, >= 3.3.0)
|
|
140
|
+
|
|
141
|
+
BUNDLED WITH
|
|
142
|
+
1.11.2
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Gremlin
|
|
2
|
+
class MetricsController < ::ActionController::Base
|
|
3
|
+
def index
|
|
4
|
+
render plain: marshal.join("\n")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def marshal
|
|
8
|
+
registry = Gremlin.registry
|
|
9
|
+
|
|
10
|
+
metrics = registry.metrics.map do |metric|
|
|
11
|
+
metric.repr
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
plaintext(metrics)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def plaintext(metric_representations)
|
|
18
|
+
lines = []
|
|
19
|
+
metric_representations.each do |repr|
|
|
20
|
+
lines << repr[:type]
|
|
21
|
+
lines << repr[:help]
|
|
22
|
+
|
|
23
|
+
# Prometheus expects metric labels to be of the format <label>="<label_value>"
|
|
24
|
+
repr[:values].each do |labels, value|
|
|
25
|
+
l = []
|
|
26
|
+
labels.each do |k,v|
|
|
27
|
+
l << "#{k}=\"#{v}\""
|
|
28
|
+
end
|
|
29
|
+
lines << "#{repr[:name].to_s}{#{l.join(',')}} #{value}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
lines
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/config/routes.rb
ADDED
data/gremlin.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'gremlin/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "gremlin"
|
|
8
|
+
spec.version = Gremlin::VERSION
|
|
9
|
+
spec.authors = ["Kevin G. Phillips"]
|
|
10
|
+
spec.email = ["platform@omadahealth.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Gem to sanely emit metrics from Omadahealth rails applications}
|
|
13
|
+
spec.description = %q{A modular metrics harness for Omadahealth rails applications}
|
|
14
|
+
spec.homepage = "https://github.com/omadahealth/omada-metrics"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
|
19
|
+
if spec.respond_to?(:metadata)
|
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
21
|
+
else
|
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
26
|
+
spec.bindir = "exe"
|
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
|
|
33
|
+
spec.add_development_dependency 'rdoc'
|
|
34
|
+
|
|
35
|
+
spec.add_runtime_dependency 'redis-native_hash'
|
|
36
|
+
spec.add_runtime_dependency 'quantile'
|
|
37
|
+
spec.add_runtime_dependency 'rails'
|
|
38
|
+
spec.add_runtime_dependency 'redis', '~> 3.2'
|
|
39
|
+
end
|
data/lib/gremlin.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Supporting librarires
|
|
2
|
+
require "active_support/cache"
|
|
3
|
+
require "active_support/notifications" if defined?(::Rails)
|
|
4
|
+
require "quantile"
|
|
5
|
+
require "json"
|
|
6
|
+
require "redis_hash"
|
|
7
|
+
|
|
8
|
+
# Gem internals
|
|
9
|
+
require "gremlin/version"
|
|
10
|
+
require "gremlin/registry"
|
|
11
|
+
require "gremlin/instruments"
|
|
12
|
+
require "gremlin/instruments/counter"
|
|
13
|
+
require "gremlin/instruments/gauge"
|
|
14
|
+
require "gremlin/instruments/summary"
|
|
15
|
+
require "gremlin/quantile"
|
|
16
|
+
|
|
17
|
+
# Rails support
|
|
18
|
+
require "gremlin/engine" if defined?(::Rails::Engine)
|
|
19
|
+
require "gremlin/railtie" if defined?(::Rails)
|
|
20
|
+
|
|
21
|
+
module Gremlin
|
|
22
|
+
class << self
|
|
23
|
+
def registry
|
|
24
|
+
@registry ||= Gremlin::Registry.new
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Gremlin
|
|
2
|
+
module Instruments
|
|
3
|
+
class Base
|
|
4
|
+
attr_accessor :name, :base_labels, :docstring
|
|
5
|
+
|
|
6
|
+
def initialize(name, docstring="placeholder help string", base_labels={})
|
|
7
|
+
@name = name
|
|
8
|
+
@docstring = docstring
|
|
9
|
+
@base_labels = base_labels
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
|
|
12
|
+
@values = Redis::NativeHash.find(retention_key) || Redis::NativeHash.new
|
|
13
|
+
@values.key = retention_key
|
|
14
|
+
@values.save
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def node
|
|
18
|
+
`hostname`.strip
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def retention_key
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def type
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def type_string
|
|
30
|
+
"# TYPE #{@name} #{type}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def default
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def help
|
|
38
|
+
@docstring
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def help_string
|
|
42
|
+
"# HELP #{@name} #{help}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def repr
|
|
46
|
+
{
|
|
47
|
+
name: @name,
|
|
48
|
+
type: type_string,
|
|
49
|
+
help: help_string,
|
|
50
|
+
values: values.each_with_object({}) { |(l,v), m| m[l.merge(@base_labels)] = v }
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def values
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
@values.each_with_object({}) do |(labels, value), memo|
|
|
57
|
+
case type
|
|
58
|
+
when :counter
|
|
59
|
+
memo[JSON.parse(labels)] = value.to_i
|
|
60
|
+
when :gauge
|
|
61
|
+
memo[JSON.parse(labels)] = value.to_f
|
|
62
|
+
else
|
|
63
|
+
memo[JSON.parse(labels)] = value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def get(labels={})
|
|
70
|
+
v = @values[labels.to_json]
|
|
71
|
+
case type
|
|
72
|
+
when :counter
|
|
73
|
+
v.to_i
|
|
74
|
+
when :gauge
|
|
75
|
+
v.to_f
|
|
76
|
+
else
|
|
77
|
+
v
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'gremlin/instruments'
|
|
2
|
+
|
|
3
|
+
module Gremlin
|
|
4
|
+
module Instruments
|
|
5
|
+
class Counter < Base
|
|
6
|
+
def increment(labels={}, by=1)
|
|
7
|
+
@mutex.synchronize do
|
|
8
|
+
prev_val = get(labels.to_json) || default
|
|
9
|
+
@values[labels.to_json] = prev_val + by
|
|
10
|
+
@values.save
|
|
11
|
+
@values[labels.to_json]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def default
|
|
16
|
+
0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def type
|
|
20
|
+
:counter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def retention_key
|
|
24
|
+
"gremlin_prometheus_#{node}_metrics_counter_#{name}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'gremlin/instruments'
|
|
2
|
+
|
|
3
|
+
module Gremlin
|
|
4
|
+
module Instruments
|
|
5
|
+
class Gauge < Base
|
|
6
|
+
def set(labels={}, value)
|
|
7
|
+
@mutex.synchronize do
|
|
8
|
+
@values[labels.to_json] = value
|
|
9
|
+
@values.save
|
|
10
|
+
@values[labels.to_json]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
:gauge
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def retention_key
|
|
19
|
+
"gremlin_prometheus_#{node}_metrics_gauge_#{name}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'gremlin/instruments'
|
|
2
|
+
|
|
3
|
+
module Gremlin
|
|
4
|
+
module Instruments
|
|
5
|
+
class Summary < Base
|
|
6
|
+
class Value < Hash
|
|
7
|
+
def initialize(estimator)
|
|
8
|
+
@sum = estimator.sum
|
|
9
|
+
@total = estimator.observations
|
|
10
|
+
|
|
11
|
+
estimator.invariants.each do |invariant|
|
|
12
|
+
self[invariant.quantile] = estimator.query(invariant.quantile)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def observe(labels={}, value)
|
|
18
|
+
@mutex.synchronize do
|
|
19
|
+
e = @values[labels.to_json]
|
|
20
|
+
|
|
21
|
+
if e.nil?
|
|
22
|
+
estimator = default
|
|
23
|
+
else
|
|
24
|
+
deserialized = JSON.parse(e)
|
|
25
|
+
estimator = unpack(deserialized)
|
|
26
|
+
end
|
|
27
|
+
sum = estimator.observe(value)
|
|
28
|
+
|
|
29
|
+
@values[labels.to_json] = estimator.serialize
|
|
30
|
+
@values.save
|
|
31
|
+
|
|
32
|
+
sum
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def values
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
@values.each_with_object({}) do |(labels, value), memo|
|
|
39
|
+
deserialized = JSON.parse(value)
|
|
40
|
+
e = unpack(deserialized)
|
|
41
|
+
memo[JSON.parse(labels)] = Value.new(e)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def get(labels={})
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
val = @values[labels.to_json]
|
|
49
|
+
return val if val.nil?
|
|
50
|
+
|
|
51
|
+
deserialized = JSON.parse(val)
|
|
52
|
+
Value.new(unpack(deserialized))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def unpack(metric_hash)
|
|
57
|
+
Gremlin::Quantile::Estimator.new(metric_hash["buffer"],
|
|
58
|
+
metric_hash["head"],
|
|
59
|
+
metric_hash["observations"].to_i,
|
|
60
|
+
metric_hash["sum"].to_f,
|
|
61
|
+
*metric_hash["invariants"].map { |i| Gremlin::Quantile::Quantile.new i["quantile"], i["inaccuracy"] })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def repr
|
|
65
|
+
vals = values.each_with_object({}) { |(l,v), m| m[l.merge(@base_labels)] = v }
|
|
66
|
+
|
|
67
|
+
values = vals.each_with_object({}) do |(labels, value), memo|
|
|
68
|
+
if value.is_a? Hash
|
|
69
|
+
value.each do |b, i|
|
|
70
|
+
memo[labels.merge({quantile: b})] = i
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
name: @name,
|
|
77
|
+
type: type_string,
|
|
78
|
+
help: help_string,
|
|
79
|
+
values: values
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def default
|
|
84
|
+
Gremlin::Quantile::Estimator.new
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def type
|
|
88
|
+
:summary
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def retention_key
|
|
92
|
+
"gremlin_prometheus_#{node}_summary_#{name}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Gremlin
|
|
2
|
+
module Quantile
|
|
3
|
+
class Estimator < ::Quantile::Estimator
|
|
4
|
+
def initialize(buffer=[], head=nil, observations=0, sum=0, *invariants)
|
|
5
|
+
if invariants.empty?
|
|
6
|
+
invariants = [Gremlin::Quantile::Quantile.new(0.5, 0.05), Gremlin::Quantile::Quantile.new(0.9, 0.01), Gremlin::Quantile::Quantile.new(0.99, 0.001)]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@invariants = invariants
|
|
10
|
+
@buffer = buffer
|
|
11
|
+
@head = head
|
|
12
|
+
@observations = observations
|
|
13
|
+
@sum = sum
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serialize
|
|
17
|
+
JSON.dump({
|
|
18
|
+
invariants: @invariants.map { |i| i.to_h },
|
|
19
|
+
buffer: @buffer,
|
|
20
|
+
head: @head,
|
|
21
|
+
sum: @sum,
|
|
22
|
+
observations: @observations
|
|
23
|
+
})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module Gremlin
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
|
+
config.before_initialize do
|
|
4
|
+
# XXX is there anything to configure? redis?
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer "gremlin.base_instrumentation" do |app|
|
|
8
|
+
environment = ::Rails.env.to_s
|
|
9
|
+
application_name = ::Rails.application.class.parent_name.downcase
|
|
10
|
+
node = `hostname`.strip
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# ActionController
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |name, start, finish, id, payload|
|
|
17
|
+
# Payload contains:
|
|
18
|
+
# :controller --- The controller name
|
|
19
|
+
# :action --- The action
|
|
20
|
+
# :params --- Hash of request parameters without any filtered parameter
|
|
21
|
+
# :headers --- Request headers
|
|
22
|
+
# :format --- html/js/json/xml etc
|
|
23
|
+
# :method --- HTTP request verb
|
|
24
|
+
# :path --- Request path
|
|
25
|
+
|
|
26
|
+
# Counter to track event occurances
|
|
27
|
+
begin
|
|
28
|
+
occurance_counter = Gremlin::Instruments::Counter.new("action_controller_start_processing_events".to_sym,
|
|
29
|
+
"ActionController start processing occurance counter.",
|
|
30
|
+
application: application_name, environment: environment, node: node)
|
|
31
|
+
Gremlin.registry.register occurance_counter
|
|
32
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
33
|
+
occurance_counter = Gremlin.registry.get "action_controller_start_processing_events".to_sym
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
occurance_counter.increment({ controller: payload[:controller].to_s, format: payload[:format].to_s, action: payload[:action] })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, start, finish, id, payload|
|
|
40
|
+
# Payload contains:
|
|
41
|
+
# :controller --- The controller name
|
|
42
|
+
# :action --- The action
|
|
43
|
+
# :params --- Hash of request parameters *without any filtered parameter*
|
|
44
|
+
# :headers --- Request headers
|
|
45
|
+
# :format --- html/js/json/xml etc
|
|
46
|
+
# :method --- HTTP request verb
|
|
47
|
+
# :path --- Request path
|
|
48
|
+
# :status --- HTTP status code
|
|
49
|
+
# :view_runtime --- Amount spent in view in ms
|
|
50
|
+
# :db_runtime --- Amount spent executing database queries in ms
|
|
51
|
+
|
|
52
|
+
# Counter to track event occurances
|
|
53
|
+
begin
|
|
54
|
+
occurance_counter = Gremlin::Instruments::Counter.new("action_controller_process_action_events".to_sym,
|
|
55
|
+
"ActionController process action occurance counter.",
|
|
56
|
+
application: application_name, environment: environment, node: node)
|
|
57
|
+
Gremlin.registry.register occurance_counter
|
|
58
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
59
|
+
occurance_counter = Gremlin.registry.get "action_controller_process_action_events".to_sym
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
occurance_counter.increment({ controller: payload[:controller].to_s, format: payload[:format].to_s, action: payload[:action], status: payload[:status] })
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
db_summary = Gremlin::Instruments::Summary.new("action_controller_process_action_db_runtime_ms".to_sym,
|
|
66
|
+
"ActionController process action DB runtime in milliseconds.",
|
|
67
|
+
application: application_name, environment: environment, node: node)
|
|
68
|
+
Gremlin.registry.register db_summary
|
|
69
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
70
|
+
db_summary = Gremlin.registry.get "action_controller_process_action_db_runtime_ms".to_sym
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
db_summary.observe({ controller: payload[:controller], method: payload[:method], status: payload[:status], action: payload[:action] }, payload[:db_runtime])
|
|
74
|
+
|
|
75
|
+
begin
|
|
76
|
+
view_summary = Gremlin::Instruments::Summary.new("action_controller_process_action_view_runtime_ms".to_sym,
|
|
77
|
+
"ActionController process action view runtime in milliseconds.",
|
|
78
|
+
application: application_name, environment: environment, node: node)
|
|
79
|
+
Gremlin.registry.register view_summary
|
|
80
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
81
|
+
view_summary = Gremlin.registry.get "action_controller_process_action_view_runtime_ms".to_sym
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
view_summary.observe({ controller: payload[:controller], method: payload[:method], status: payload[:status], action: payload[:action] }, payload[:view_runtime])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
ActiveSupport::Notifications.subscribe "redirect_to.action_controller" do |name, start, finish, id, payload|
|
|
88
|
+
# Payload contains:
|
|
89
|
+
# :status --- HTTP response code
|
|
90
|
+
# :location --- URL to redirect to
|
|
91
|
+
|
|
92
|
+
# Counter to track event occurances
|
|
93
|
+
begin
|
|
94
|
+
occurance_counter = Gremlin::Instruments::Counter.new("action_controller_redirect_to_events".to_sym,
|
|
95
|
+
"ActionController redirect to occurance counter.",
|
|
96
|
+
application: application_name, environment: environment, node: node)
|
|
97
|
+
Gremlin.registry.register occurance_counter
|
|
98
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
99
|
+
occurance_counter = Gremlin.registry.get "action_controller_redirect_to_events".to_sym
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
occurance_counter.increment({ status: payload[:status] })
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
## ActiveRecord
|
|
107
|
+
##
|
|
108
|
+
|
|
109
|
+
ActiveSupport::Notifications.subscribe "sql.active_record" do |name, start, finish, id, payload|
|
|
110
|
+
# Payload contains:
|
|
111
|
+
# :sql --- SQL statement
|
|
112
|
+
# :name --- Name of the operation
|
|
113
|
+
# :connection_id --- self.object_id
|
|
114
|
+
# :binds --- Bind parameters
|
|
115
|
+
|
|
116
|
+
# Counter to track event occurances
|
|
117
|
+
begin
|
|
118
|
+
occurance_counter = Gremlin::Instruments::Counter.new("active_record_sql_events".to_sym,
|
|
119
|
+
"ActiveRecord SQL occurance counter.",
|
|
120
|
+
application: application_name, environment: environment, node: node)
|
|
121
|
+
Gremlin.registry.register occurance_counter
|
|
122
|
+
rescue Gremlin::Registry::AlreadyRegisteredError
|
|
123
|
+
occurance_counter = Gremlin.registry.get "active_record_sql_events".to_sym
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
occurance_counter.increment
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Gremlin
|
|
2
|
+
class Registry
|
|
3
|
+
AlreadyRegisteredError = Class.new StandardError
|
|
4
|
+
NotRegisteredError = Class.new StandardError
|
|
5
|
+
NotAMetricError = Class.new StandardError
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@metrics = Redis::NativeHash.find(retention_key) || Redis::NativeHash.new()
|
|
9
|
+
@metrics.key = retention_key
|
|
10
|
+
@metrics.save
|
|
11
|
+
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def node
|
|
16
|
+
`hostname`.strip
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retention_key
|
|
20
|
+
"gremlin_prometheus_#{node}_metrics_registry"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def metrics
|
|
24
|
+
@metrics.values.map do |m|
|
|
25
|
+
deserialize(m)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get(name)
|
|
30
|
+
m = @metrics[name.to_sym]
|
|
31
|
+
deserialize(m) unless m.nil?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def exist?(name)
|
|
35
|
+
@metrics.key? name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def register(instrument)
|
|
39
|
+
name = instrument.name
|
|
40
|
+
raise AlreadyRegisteredError.new("#{name} has already been registered") if exist? name
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
@metrics[name.to_sym] = serialize(instrument)
|
|
43
|
+
@metrics.save
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
instrument
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def deregister(instrument)
|
|
50
|
+
name = instrument.name
|
|
51
|
+
raise NotRegisteredError.new("#{name} has not been registered.") unless exist? name
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
@metrics.delete(instrument.name.to_sym)
|
|
54
|
+
@metrics.save
|
|
55
|
+
metrics
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def deserialize(metric)
|
|
60
|
+
m = JSON.parse(metric)
|
|
61
|
+
Gremlin::Instruments.const_get(m["type"].capitalize).new(m["name"].to_sym, m["docstring"], m["base_labels"])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def serialize(metric)
|
|
65
|
+
raise NotAMetricError.new("Specified object is not an instrument and cannot be serialized") unless metric.is_a? Gremlin::Instruments::Base
|
|
66
|
+
{
|
|
67
|
+
type: metric.type,
|
|
68
|
+
name: metric.name.to_s,
|
|
69
|
+
docstring: metric.docstring,
|
|
70
|
+
base_labels: metric.base_labels
|
|
71
|
+
}.to_json
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gremlin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kevin G. Phillips
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-01-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.11'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.11'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.3'
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 3.3.0
|
|
51
|
+
type: :development
|
|
52
|
+
prerelease: false
|
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - "~>"
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '3.3'
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 3.3.0
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: rdoc
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: redis-native_hash
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: quantile
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
type: :runtime
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: rails
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
type: :runtime
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: redis
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.2'
|
|
124
|
+
type: :runtime
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.2'
|
|
131
|
+
description: A modular metrics harness for Omadahealth rails applications
|
|
132
|
+
email:
|
|
133
|
+
- platform@omadahealth.com
|
|
134
|
+
executables: []
|
|
135
|
+
extensions: []
|
|
136
|
+
extra_rdoc_files: []
|
|
137
|
+
files:
|
|
138
|
+
- ".gitignore"
|
|
139
|
+
- Gemfile
|
|
140
|
+
- Gemfile.lock
|
|
141
|
+
- app/controllers/gremlin/metrics_controller.rb
|
|
142
|
+
- config/routes.rb
|
|
143
|
+
- gremlin.gemspec
|
|
144
|
+
- lib/gremlin.rb
|
|
145
|
+
- lib/gremlin/engine.rb
|
|
146
|
+
- lib/gremlin/instruments.rb
|
|
147
|
+
- lib/gremlin/instruments/counter.rb
|
|
148
|
+
- lib/gremlin/instruments/gauge.rb
|
|
149
|
+
- lib/gremlin/instruments/summary.rb
|
|
150
|
+
- lib/gremlin/quantile.rb
|
|
151
|
+
- lib/gremlin/quantile/estimator.rb
|
|
152
|
+
- lib/gremlin/quantile/quantile.rb
|
|
153
|
+
- lib/gremlin/railtie.rb
|
|
154
|
+
- lib/gremlin/registry.rb
|
|
155
|
+
- lib/gremlin/version.rb
|
|
156
|
+
homepage: https://github.com/omadahealth/omada-metrics
|
|
157
|
+
licenses:
|
|
158
|
+
- MIT
|
|
159
|
+
metadata:
|
|
160
|
+
allowed_push_host: https://rubygems.org
|
|
161
|
+
post_install_message:
|
|
162
|
+
rdoc_options: []
|
|
163
|
+
require_paths:
|
|
164
|
+
- lib
|
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - ">="
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '0'
|
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
|
+
requirements:
|
|
172
|
+
- - ">="
|
|
173
|
+
- !ruby/object:Gem::Version
|
|
174
|
+
version: '0'
|
|
175
|
+
requirements: []
|
|
176
|
+
rubyforge_project:
|
|
177
|
+
rubygems_version: 2.5.1
|
|
178
|
+
signing_key:
|
|
179
|
+
specification_version: 4
|
|
180
|
+
summary: Gem to sanely emit metrics from Omadahealth rails applications
|
|
181
|
+
test_files: []
|
|
182
|
+
has_rdoc:
|