statue 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e1fa0a08733f36d16fb2077a11502e4baf8673e0
4
+ data.tar.gz: f7dc8e88e84566bce9a115f8258432cba6e27323
5
+ SHA512:
6
+ metadata.gz: 9210bfd9f5062a4e8d0d342b2f5128805646c74f73851a1f0828fdb5e30177250ba84580b749ab7e0ca5e9fec3efa64f93fb7b83a327e2cdb7cc30ae74d426bc
7
+ data.tar.gz: 6660261b0e9b82b2aa1e378d8ad09c436b811c239acc320e3a5142cc2c7524249d3256f71b0c7903facf8eb0bb6f58d5172cdadeb9063620706e6bbad306db6e
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /tmp/
4
+ *.bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in statue.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Juan Barreneche
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Statue
2
+
3
+ / ___)(_ _)/ _\(_ _)/ )( \( __)
4
+ \___ \ )( / \ )( ) \/ ( ) _)
5
+ (____/ (__)\_/\_/(__) \____/(____)
6
+ Rock solid metrics report...
7
+
8
+ Easily track application metrics into [Statsite](https://github.com/armon/statsite) (Statsd compatible).
9
+
10
+ ## Configuration
11
+
12
+ The library has different backends, one to be used for production environment (ie. actually
13
+ sending metrics using the Statsd protocol), and the others for testing or developing.
14
+
15
+ The available backends are:
16
+
17
+ `Statue::UDPBackend` -> this is the one that actually sends metrics to the Statsd.
18
+
19
+ eg.
20
+ ```ruby
21
+ Statue.backend = Statue::UDPBackend.new(statsd_host, statsd_port)
22
+ ```
23
+
24
+ `Statue::NullBackend`, this backend discards all metrics (useful for test environment, if you
25
+ aren't interested in testing which metrics are sent).
26
+
27
+ `Statue::CaptureBackend`, this backend collects all metrics (useful for test environment, if you
28
+ arent interested in testing which metrics are sent). You can check the metrics with `Statue.backend.captures`
29
+ and reset this array with `Statue.backend.captures.clear` or by setting a new instance before each test.
30
+
31
+ `Statue::LoggerBackend`, this backend logs the received metrics to a logger (useful for development purposes)
32
+
33
+ eg.
34
+ ```ruby
35
+ Statue.backend = Statue::LoggerBackend.new(Rails.logger)
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ `Statue.report_increment('metric.name')` -> send to Statsd an increment for the counter `metric.name`
41
+
42
+ `Statue.report_duration('metric.name') { some_operation } # => some_operation_result` -> send to Statsd the measure for the block duration in `metric.name`
43
+
44
+ `Statue.report_success_or_failure('metric.name') { some_operation } # => some_operation_result` -> checks the result of the block, if its a `truthy` value, then increments `metric.name.success`, else it increments `metric.name.failure`.
45
+
46
+ ### Rack Integration
47
+
48
+ We provide a middleware to track basic request metrics, see: Statue::RackStatistics
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it ( https://github.com/restorando/statue/fork )
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
@@ -0,0 +1,15 @@
1
+ module Statue
2
+ class CaptureBackend
3
+ attr_reader :captures
4
+
5
+ def initialize
6
+ @captures = []
7
+ end
8
+
9
+ def collect_metric(metric)
10
+ @captures << metric
11
+ end
12
+ alias :<< :collect_metric
13
+
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module Statue
2
+ class LoggerBackend < Struct.new(:logger)
3
+
4
+ def collect_metric(metric)
5
+ logger.info("Statue") { metric.to_s }
6
+ end
7
+ alias :<< :collect_metric
8
+
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Statue
2
+ class NullBackend
3
+
4
+ def collect_metric(metric)
5
+ end
6
+ alias :<< :collect_metric
7
+
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ require 'socket'
2
+
3
+ module Statue
4
+ class UDPBackend
5
+ attr_reader :address
6
+
7
+ def initialize(host = nil, port = nil)
8
+ @address = Addrinfo.udp(host || "127.0.0.1", port || 8125)
9
+ end
10
+
11
+ def collect_metric(metric)
12
+ if metric.sample_rate == 1 || rand < metric.sample_rate
13
+ send_to_socket metric.to_s
14
+ end
15
+ end
16
+ alias :<< :collect_metric
17
+
18
+ private
19
+
20
+ def socket
21
+ @socket ||= address.connect
22
+ end
23
+
24
+ def send_to_socket(message)
25
+ Statue.debug(message)
26
+ socket.send(message, 0)
27
+ rescue => e
28
+ Statue.error("#{e.class} #{e}")
29
+ nil
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Statue
2
+ autoload :LoggerBackend, "statue/backends/logger"
3
+ autoload :CaptureBackend, "statue/backends/capture"
4
+ autoload :NullBackend, "statue/backends/null"
5
+ autoload :UDPBackend, "statue/backends/udp"
6
+ end
@@ -0,0 +1,48 @@
1
+ module Statue
2
+ class Metric
3
+ TYPES = {
4
+ c: 'increment',
5
+ ms: 'measure',
6
+ g: 'gauge',
7
+ kv: 'key/value',
8
+ s: 'set'
9
+ }
10
+
11
+ attr_accessor :type, :name, :value, :sample_rate
12
+
13
+ def self.counter(name, value = 1, **options)
14
+ new(type: :c, value: value, name: name, **options)
15
+ end
16
+
17
+ def self.measure(name, duration: nil, **options, &block)
18
+ value = duration || Statue.duration(&block)
19
+ new(type: :ms, value: value, name: name, **options)
20
+ end
21
+
22
+ def initialize(type:, name:, value:, sample_rate: 1.0)
23
+ @type = type
24
+ @name = name
25
+ @value = value
26
+ @sample_rate = sample_rate
27
+ end
28
+
29
+ def to_s
30
+ str = "#{full_name}:#{value}|#{type}"
31
+ str << "|@#{sample_rate}" if sample_rate != 1.0
32
+ str
33
+ end
34
+
35
+ def inspect
36
+ "#<StatsD::Instrument::Metric #{full_name} #{TYPES[type]}(#{value})@#{sample_rate}>"
37
+ end
38
+
39
+ def full_name
40
+ if Statue.namespace
41
+ "#{Statue.namespace}.#{@name}"
42
+ else
43
+ @name
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,117 @@
1
+ require 'statue'
2
+
3
+ module Statue
4
+ # Middleware to send metrics about rack requests
5
+ #
6
+ # this middleware reports metrics with the following pattern:
7
+ # `request.{env['REQUEST_METHOD']}.{path_name}
8
+ #
9
+ # where `path_name` can be configured when inserting the middleware like this:
10
+ # `use RackStatistics, path_name: ->(env) { ... build the path name ... }`
11
+ # You can build the path using the environment information in the lambda or
12
+ # you can delegate that logic to your app stack and later fetching it from
13
+ # the env, Eg:
14
+ # `use RackStatistics, path_name: ->(env) { env['route.path_name'] }`
15
+ #
16
+ # This middleware will report the following metrics
17
+ #
18
+ # Counters:
19
+ #
20
+ # * <key>.status-XXX (where XXX is the status code)
21
+ # * <key>.success (on any status 2XX)
22
+ # * <key>.unmodified (on status 304)
23
+ # * <key>.redirect (on any status 3XX)
24
+ # * <key>.failure (on any status 4xx)
25
+ # * <key>.error (on any status 5xx or when an exception is raised)
26
+ #
27
+ # Timers (all measured from the middleware perspective):
28
+ #
29
+ # * <key> (request time)
30
+ # * request.queue (queue time, depends on HTTP_X_QUEUE_START header)
31
+ #
32
+ # To get accurate timers, the middleware should be as higher as
33
+ # possible in your rack stack
34
+ #
35
+ class RackStatistics
36
+ DEFAULT_PATH_NAME = lambda do |env|
37
+ # Remove duplicate and trailing '/'
38
+ path = env['REQUEST_PATH'].squeeze('/').chomp('/')
39
+ if path == ''
40
+ 'root'
41
+ else
42
+ # Skip leading '/'
43
+ env['REQUEST_PATH'][1..-1].tr('/,|', '-')
44
+ end
45
+ end
46
+
47
+ def initialize(app, path_name: DEFAULT_PATH_NAME)
48
+ @app = app
49
+ @path_name = path_name
50
+ end
51
+
52
+ def call(env)
53
+ report_header_metrics(env)
54
+
55
+ response = nil
56
+ duration = Statue.duration do
57
+ response = @app.call(env)
58
+ end
59
+
60
+ report_response_metrics(env, response, duration)
61
+
62
+ response
63
+ rescue => e
64
+ report_exception(env, e) and raise
65
+ end
66
+
67
+ private
68
+
69
+ def report_header_metrics(env)
70
+ if start_header = env['HTTP_X_QUEUE_START']
71
+ queue_start = start_header[/t=([\d\.]+)/, 1].to_f
72
+ Statue.report_duration 'request.queue', Time.now.to_f - queue_start
73
+ end
74
+ end
75
+
76
+ def report_response_metrics(env, response, duration)
77
+ metric_name = metric_name(env)
78
+ status, _headers, _body = response
79
+
80
+ Statue.report_duration metric_name, duration
81
+
82
+ Statue.report_increment "#{metric_name}.status-#{status}"
83
+ Statue.report_increment "#{metric_name}.#{status_group(status)}"
84
+ end
85
+
86
+ def report_exception(env, _exception)
87
+ Statue.report_increment "#{metric_name(env)}.error"
88
+ end
89
+
90
+ def metric_name(env)
91
+ "request.#{env['REQUEST_METHOD']}.#{@path_name.call(env)}"
92
+ end
93
+
94
+ # success: ok (2XX)
95
+ # failure: client error (4xx)
96
+ # error: server error (5xx)
97
+ # unmodified: (304)
98
+ # redirect: (3XX)
99
+ def status_group(status)
100
+ case status
101
+ when 100..299
102
+ 'success'
103
+ when 304
104
+ 'unmodified'
105
+ when 300..399
106
+ 'redirect'
107
+ when 400..499
108
+ 'failure'
109
+ when 500..599
110
+ 'error'
111
+ else
112
+ 'invalid-status'
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,3 @@
1
+ module Statue
2
+ VERSION = '0.1.1'
3
+ end
data/lib/statue.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'statue/version'
2
+
3
+ require 'statue/backends'
4
+ require 'statue/metric'
5
+
6
+ module Statue
7
+ extend self
8
+
9
+ attr_accessor :namespace, :logger
10
+
11
+ attr_accessor :backend
12
+
13
+ def report_duration(metric_name, duration = nil, **options, &block)
14
+ result = nil
15
+ backend << Metric.measure(metric_name, duration: duration, **options) do
16
+ result = block.call
17
+ end
18
+ result
19
+ end
20
+
21
+ def report_increment(metric_name, **options)
22
+ backend << Metric.counter(metric_name, **options)
23
+ end
24
+
25
+ def report_success_or_failure(metric_name, success_method: nil, **options, &block)
26
+ result = block.call
27
+ success = success_method ? result.public_send(success_method) : result
28
+
29
+ if success
30
+ report_increment("#{metric_name}.success", **options)
31
+ else
32
+ report_increment("#{metric_name}.failure", **options)
33
+ end
34
+
35
+ result
36
+ rescue
37
+ report_increment("#{metric_name}.failure", **options)
38
+ raise
39
+ end
40
+
41
+ def backend
42
+ @backend ||= UDPBackend.new
43
+ end
44
+
45
+ def duration
46
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
47
+ yield
48
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
49
+ end
50
+
51
+ def debug(text, &block)
52
+ logger.debug(text, &block) if logger
53
+ end
54
+
55
+ def error(text, &block)
56
+ logger.error(text, &block) if logger
57
+ end
58
+
59
+ end
data/statue.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'statue/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "statue"
8
+ spec.version = Statue::VERSION
9
+ spec.authors = ["Juan Barreneche"]
10
+ spec.email = ["devs@restorando.com"]
11
+ spec.summary = %q{Easily track application metrics into Statsie}
12
+ spec.description = %q{Track application metrics to Statsie}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest"
24
+ end
@@ -0,0 +1,96 @@
1
+ require 'test_helper'
2
+
3
+ describe Statue do
4
+ after do
5
+ Statue.backend.captures.clear
6
+ end
7
+
8
+ describe ".report_increment" do
9
+ it "Adds a counter metric to the backend" do
10
+ Statue.report_increment("some.counter")
11
+
12
+ assert_equal 1, Statue.backend.captures.size
13
+ assert_equal "some.counter:1|c", Statue.backend.captures.first.to_s
14
+ end
15
+ end
16
+
17
+ describe ".report_duration" do
18
+
19
+ it "adds a measure metric to the backend using the block call duration" do
20
+ Statue.stub(:duration, 1.5) do
21
+ result = Statue.report_duration("some.timer") { nil }
22
+
23
+ assert_equal 1, Statue.backend.captures.size
24
+ assert_equal "some.timer:1.5|ms", Statue.backend.captures.first.to_s
25
+ end
26
+ end
27
+
28
+ it "returns the block result" do
29
+ result = Statue.report_duration("some.timer") { 42 }
30
+
31
+ assert_equal 42, result
32
+ end
33
+
34
+ it "adds a measure metric to the backend using the fixed value" do
35
+ result = Statue.report_duration("some.timer", 2.5)
36
+
37
+ assert_equal 1, Statue.backend.captures.size
38
+ assert_equal "some.timer:2.5|ms", Statue.backend.captures.first.to_s
39
+ end
40
+
41
+ it "doesn't report duration if an exception is thrown" do
42
+ begin
43
+ Statue.report_duration("some.timer") { raise "error" }
44
+ rescue => e
45
+ assert_equal "error", e.message
46
+ assert_empty Statue.backend.captures
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ describe ".report_success_or_failure" do
53
+
54
+ it "Adds a counter metric to the backend with .success suffix with a truthy result" do
55
+ Statue.report_success_or_failure("some.event") { 42 }
56
+
57
+ assert_equal 1, Statue.backend.captures.size
58
+ assert_equal "some.event.success:1|c", Statue.backend.captures.first.to_s
59
+ end
60
+
61
+ it "Adds a counter metric to the backend with .failure suffix with a falsey result" do
62
+ Statue.report_success_or_failure("some.event") { nil }
63
+
64
+ assert_equal 1, Statue.backend.captures.size
65
+ assert_equal "some.event.failure:1|c", Statue.backend.captures.first.to_s
66
+ end
67
+
68
+ it "returns the block result" do
69
+ result = Statue.report_success_or_failure("some.event") { 42 }
70
+
71
+ assert_equal 42, result
72
+ end
73
+
74
+ it "Adds a counter metric to the backend with .failure suffix when an exception ocurrs" do
75
+ begin
76
+ Statue.report_success_or_failure("some.event") { raise "error" }
77
+ rescue => e
78
+ assert_equal "error", e.message
79
+ assert_equal 1, Statue.backend.captures.size
80
+ assert_equal "some.event.failure:1|c", Statue.backend.captures.first.to_s
81
+ end
82
+ end
83
+
84
+ it "allows to use a symbol for a custom success_method" do
85
+ Statue.report_success_or_failure("some.zero_event", success_method: :zero?) { 0 }
86
+ Statue.report_success_or_failure("some.life_event", success_method: :zero?) { 42 }
87
+ zero_event, life_event = Statue.backend.captures
88
+
89
+ assert_equal 2, Statue.backend.captures.size
90
+ assert_equal "some.zero_event.success:1|c", zero_event.to_s
91
+ assert_equal "some.life_event.failure:1|c", life_event.to_s
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,6 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/spec'
3
+
4
+ require 'statue'
5
+
6
+ Statue.backend = Statue::CaptureBackend.new
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: statue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Juan Barreneche
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-14 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.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
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: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Track application metrics to Statsie
56
+ email:
57
+ - devs@restorando.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/statue.rb
68
+ - lib/statue/backends.rb
69
+ - lib/statue/backends/capture.rb
70
+ - lib/statue/backends/logger.rb
71
+ - lib/statue/backends/null.rb
72
+ - lib/statue/backends/udp.rb
73
+ - lib/statue/metric.rb
74
+ - lib/statue/rack_statistics.rb
75
+ - lib/statue/version.rb
76
+ - statue.gemspec
77
+ - test/statue_test.rb
78
+ - test/test_helper.rb
79
+ homepage: ''
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.4.5
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Easily track application metrics into Statsie
103
+ test_files:
104
+ - test/statue_test.rb
105
+ - test/test_helper.rb