meter 0.1.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4fd53f330e039e19bf426396f41ef53246f47aff
4
- data.tar.gz: 077c2be0e237a1f06af9aafd846d536027c6eaf8
3
+ metadata.gz: c7251d39b8598a821768f8b5194d091a4cf567da
4
+ data.tar.gz: 2a901ecbdbbdd2ba0f1e663b09b526dd2d485e8c
5
5
  SHA512:
6
- metadata.gz: 8d31866e279376f65636be45c9cfbc5056907e559bd5eaaa3cc7106d11e37a8e44b630881286456d7edd04dced7aa4e13fa1642cf5131f012f27761050e21712
7
- data.tar.gz: 01fdf3b7af762fece7e79eacf40bdc66914daaa4363d0936f7eea85259e0c6bc0ca2f146479719bb18a32fecd89e7fede96afb745a7ebf13c33bbf5a8bcf55ae
6
+ metadata.gz: 6f681d9e84b87ed9ae215a54149a4a7ffe8e376b9452f67641cb8c46ddbcde141761e886dde90821fd3bbd7fd23c51caded9b12455dec197230e5c6f74a5ed55
7
+ data.tar.gz: 193dddf526ebbe275b2278bc1f69f8af1b4805e05845b11b2c21d519713f29f0e14d792fa9355f3fd32025221ff97e62ac01f4d740f35eaac401b660d938ccc8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2013 Bukowskis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Meter
2
+
3
+ A generic abstraction layer for tracking metrics and other data via configurable backends.
4
+
5
+ # Installation
6
+
7
+ ```bash
8
+ gem install meter
9
+ ````
10
+
11
+ # Usage
12
+
13
+ #### Configuration
14
+
15
+ ```ruby
16
+ Meter.configure do |config|
17
+ config.namespace = 'my_app'
18
+ config.backends << Meter::Backends::Datadog.new
19
+ config.backends << Meter::Backends::JsonLog.new
20
+ end
21
+
22
+ #### Syntax
23
+
24
+ ```ruby
25
+ Meter.increment key, sample_rate: 0.5, tags: {}, data: {}
26
+ ````
27
+
28
+ #### Examples
29
+
30
+ ```ruby
31
+ Meter.increment 'my.key'
32
+ Meter.increment 'my.key', 5
33
+ Meter.increment 'my.key', 5, sample_rate: 0.25
34
+
35
+ Meter.gauge 'my.gauge.key', 20
36
+ ```
data/lib/meter.rb CHANGED
@@ -1,36 +1,58 @@
1
- require 'meter/backends'
1
+ require 'meter/backends/base'
2
+ require 'meter/backends/udp'
3
+ require 'meter/backends/statsd'
4
+ require 'meter/backends/datadog'
5
+ require 'meter/backends/json_log'
2
6
  require 'meter/configure'
7
+ require 'meter/mdc'
8
+ require 'meter/metric/base'
9
+ require 'meter/metric/counter'
10
+ require 'meter/metric/gauge'
11
+ require 'meter/metric/histogram'
12
+ require 'meter/metric/timing'
13
+ require 'meter/rails/middleware'
14
+ require "meter/rails/railtie" if defined?(Rails::Railtie)
3
15
 
4
16
  module Meter
5
17
 
6
- def self.increment(key, options = {})
7
- id = options.delete(:id)
8
- backends.datadog.increment key, options
9
- backends.counter.increment("#{::Meter.config.namespace}.#{key}.#{id}", options) if id
10
-
11
- rescue => exception
12
- ::Meter.config.logger.error exception.inspect
13
- end
14
-
15
- def self.gauge(key, value, options = {})
16
- backends.datadog.gauge key, value, options
17
-
18
- rescue => exception
19
- ::Meter.config.logger.error exception.inspect
18
+ class << self
19
+ def increment(key, value: 1, sample_rate: 1, tags: {}, data: {})
20
+ metric = Metric::Counter.new(name: key, value: value, sample_rate: sample_rate, tags: tags, data: data)
21
+ send_metric_to_backends(metric)
22
+ rescue => exception
23
+ ::Meter.config.logger.error exception.inspect
24
+ end
25
+ alias :track :increment
26
+
27
+ def gauge(key, value, sample_rate: 1, tags: {}, data: {})
28
+ metric = Metric::Gauge.new(name: key, value: value, sample_rate: sample_rate, tags: tags, data: data)
29
+ send_metric_to_backends(metric)
30
+ rescue => exception
31
+ ::Meter.config.logger.error exception.inspect
32
+ end
33
+
34
+ def histogram(key, value, sample_rate: 1, tags: {}, data: {})
35
+ metric = Metric::Histogram.new(name: key, value: value, sample_rate: sample_rate, tags: tags, data: data)
36
+ send_metric_to_backends(metric)
37
+ rescue => exception
38
+ ::Meter.config.logger.error exception.inspect
39
+ end
40
+
41
+ def timing(key, value, sample_rate: 1, tags: {}, data: {})
42
+ metric = Metric::Timing.new(name: key, value: value, sample_rate: sample_rate, tags: tags, data: data)
43
+ send_metric_to_backends(metric)
44
+ rescue => exception
45
+ ::Meter.config.logger.error exception.inspect
46
+ end
47
+
48
+ def send_metric_to_backends(metric)
49
+ return unless should_sample? metric.sample_rate
50
+ config.backends.each {|backend| backend.emit_metric(metric)}
51
+ metric
52
+ end
53
+
54
+ def should_sample?(sample_rate = 1)
55
+ rand < sample_rate
56
+ end
20
57
  end
21
-
22
- def self.histogram(key, value, options = {})
23
- backends.datadog.histogram key, value, options
24
-
25
- rescue => exception
26
- ::Meter.config.logger.error exception.inspect
27
- end
28
-
29
- def self.log(key, data)
30
- backends.heka.log key, data
31
-
32
- rescue => exception
33
- ::Meter.config.logger.error exception.inspect
34
- end
35
-
36
58
  end
@@ -0,0 +1,27 @@
1
+ module Meter
2
+ module Backends
3
+ class Base
4
+
5
+ def self.supported_metrics
6
+ []
7
+ end
8
+
9
+ def supported_metric?(metric)
10
+ self.class.supported_metrics.include? metric.type
11
+ end
12
+
13
+ def emit_metric(metric)
14
+ return unless supported_metric? metric
15
+ metric_data = convert_to_backend_format(metric)
16
+ output_data(metric_data)
17
+ end
18
+
19
+ def convert_to_backend_format(metric)
20
+ end
21
+
22
+ def output_data(data)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'socket'
2
+
3
+ module Meter
4
+ module Backends
5
+ class Datadog < ::Meter::Backends::Statsd
6
+
7
+ def self.supported_metrics
8
+ [:counter, :gauge, :timing, :histogram]
9
+ end
10
+
11
+ def convert_to_backend_format(metric)
12
+ rate = "|@#{metric.sample_rate}" unless metric.sample_rate == 1
13
+ tags = "|##{convert_tags_to_datadog_format(metric.tags)}" unless metric.tags.empty?
14
+ "#{metric.name}:#{metric.value}|#{statsd_type(metric)}#{rate}#{tags}"
15
+ end
16
+
17
+ def convert_tags_to_datadog_format(tags = {})
18
+ tags.map{|key,val| "#{key}:#{val}"}.join(',')
19
+ end
20
+
21
+ def statsd_type(metric)
22
+ types = {counter: ':c', gauge: ':g', timing: ':ms', histogram: ':h'}
23
+ types[metric.type]
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+
3
+ module Meter
4
+ module Backends
5
+ class JsonLog < ::Meter::Backends::Base
6
+
7
+ def self.supported_metrics
8
+ [:counter, :gauge, :timing, :histogram]
9
+ end
10
+
11
+ def convert_to_backend_format(metric)
12
+ {statname: metric.name, metric_type: metric.type, metric_value: metric.value}.merge(metric.data).merge(metric.tags)
13
+ end
14
+
15
+ def output_data(data)
16
+ log_file.open('a') { |f| f.puts(JSON.dump data) }
17
+ end
18
+
19
+ private
20
+
21
+ def log_file
22
+ ::Meter.config.log_dir.join('application.json.log')
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'socket'
2
+
3
+ module Meter
4
+ module Backends
5
+ class Statsd < ::Meter::Backends::Udp
6
+
7
+ def self.supported_metrics
8
+ [:counter, :gauge, :timing]
9
+ end
10
+
11
+ def convert_to_backend_format(metric)
12
+ rate = "|@#{metric.sample_rate}" unless metric.sample_rate == 1
13
+ "#{metric.name}:#{metric.value}|#{statsd_type(metric)}#{rate}"
14
+ end
15
+
16
+ def statsd_type(metric)
17
+ types = {counter: ':c', gauge: ':g', timing: ':ms'}
18
+ types[metric.type]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'json'
2
+ require 'socket'
3
+
4
+ module Meter
5
+ module Backends
6
+ class Udp < ::Meter::Backends::Base
7
+
8
+ attr_reader :host, :port
9
+
10
+ def initialize(host: '127.0.0.1', port: 8125)
11
+ @host, @port = host, port
12
+ end
13
+
14
+ def output_data(data)
15
+ socket.send data, 0, self.host, self.port
16
+ end
17
+
18
+ def socket
19
+ Thread.current["meter_socket_#{self.class.name}"] ||= UDPSocket.new
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,5 @@
1
1
  require 'logger'
2
2
  require 'pathname'
3
- require 'meter/backend'
4
3
 
5
4
  module Meter
6
5
  class Configuration
@@ -39,6 +38,11 @@ module Meter
39
38
  end
40
39
  attr_writer :hostname
41
40
 
41
+ def backends
42
+ @backends ||= []
43
+ end
44
+ attr_writer :backends
45
+
42
46
  private
43
47
 
44
48
  def default_logger
data/lib/meter/mdc.rb ADDED
@@ -0,0 +1,18 @@
1
+ # stolen from https://github.com/steveklabnik/request_store
2
+ module Meter
3
+ module MDC
4
+ def self.tags
5
+ Thread.current[:meter_mdc_tags] ||= {}
6
+ end
7
+
8
+ def self.data
9
+ Thread.current[:meter_mdc_data] ||= {}
10
+ end
11
+
12
+ def self.clear!
13
+ Thread.current[:meter_mdc_tags] = {}
14
+ Thread.current[:meter_mdc_data] = {}
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ module Meter
2
+ module Metric
3
+ class Base
4
+
5
+ attr_reader :name
6
+ attr_reader :tags
7
+ attr_reader :data
8
+ attr_accessor :value
9
+ attr_accessor :sample_rate
10
+
11
+ def type
12
+ end
13
+
14
+ def initialize(name:, value: default_value, sample_rate: 1, tags: {}, data: {})
15
+ self.name = name
16
+ self.value = value
17
+ self.sample_rate = sample_rate
18
+ self.tags = tags
19
+ self.data = data
20
+ end
21
+
22
+ def name=(new_name)
23
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
24
+ @name = new_name.to_s.gsub('::', '.').tr(':|@', '_')
25
+ end
26
+
27
+ def tags=(new_tags)
28
+ @tags = default_tags.merge(new_tags)
29
+ end
30
+
31
+ def data=(new_data)
32
+ @data = default_data.merge(new_data)
33
+ end
34
+
35
+ private
36
+
37
+ def default_value
38
+ end
39
+
40
+ def default_tags
41
+ {
42
+ app: ::Meter.config.namespace,
43
+ environment: ::Meter.config.environment
44
+ }.merge(::Meter::MDC.tags)
45
+ end
46
+
47
+ def default_data
48
+ {
49
+ timestamp: Time.now.strftime("%FT%H:%M:%S%:z"),
50
+ host: ::Meter.config.hostname,
51
+ }.merge(::Meter::MDC.data)
52
+ end
53
+
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ module Meter
2
+ module Metric
3
+ class Counter < Meter::Metric::Base
4
+
5
+ def type
6
+ :counter
7
+ end
8
+
9
+ private
10
+
11
+ def default_value
12
+ 1
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Meter
2
+ module Metric
3
+ class Gauge < Meter::Metric::Base
4
+
5
+ def type
6
+ :gauge
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Meter
2
+ module Metric
3
+ class Histogram < Meter::Metric::Base
4
+
5
+ def type
6
+ :histogram
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Meter
2
+ module Metric
3
+ class Timing < Meter::Metric::Base
4
+
5
+ def type
6
+ :timing
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Meter
2
+ module Rails
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env)
10
+ ensure
11
+ Meter::MDC.clear!
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Meter
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "meter.insert_middleware" do |app|
5
+ if ActionDispatch.const_defined? :RequestId
6
+ app.config.middleware.insert_after ActionDispatch::RequestId, Meter::Rails::Middleware
7
+ else
8
+ app.config.middleware.insert_after Rack::MethodOverride, Meter::Rails::Middleware
9
+ end
10
+
11
+ if ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup)
12
+ ActionDispatch::Reloader.to_cleanup do
13
+ Meter::MDC.clear!
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/meter/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Meter
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 1
5
- TINY = 7
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bukowskis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-04 00:00:00.000000000 Z
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -73,11 +73,24 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - LICENSE
77
+ - README.md
76
78
  - lib/meter.rb
77
- - lib/meter/backend.rb
78
- - lib/meter/backends.rb
79
+ - lib/meter/backends/base.rb
80
+ - lib/meter/backends/datadog.rb
81
+ - lib/meter/backends/json_log.rb
82
+ - lib/meter/backends/statsd.rb
83
+ - lib/meter/backends/udp.rb
79
84
  - lib/meter/configuration.rb
80
85
  - lib/meter/configure.rb
86
+ - lib/meter/mdc.rb
87
+ - lib/meter/metric/base.rb
88
+ - lib/meter/metric/counter.rb
89
+ - lib/meter/metric/gauge.rb
90
+ - lib/meter/metric/histogram.rb
91
+ - lib/meter/metric/timing.rb
92
+ - lib/meter/rails/middleware.rb
93
+ - lib/meter/rails/railtie.rb
81
94
  - lib/meter/version.rb
82
95
  homepage: https://github.com/bukowskis/meter
83
96
  licenses:
data/lib/meter/backend.rb DELETED
@@ -1,57 +0,0 @@
1
- require 'json'
2
- require 'socket'
3
-
4
- module Meter
5
- class Backend
6
-
7
- attr_reader :host, :port
8
-
9
- def initialize(host = '127.0.0.1', port = 8125)
10
- @host, @port = host, port
11
-
12
- @socket = UDPSocket.new
13
- end
14
-
15
- def increment(stat, options = {})
16
- send_stats stat, 1, :c, options
17
- end
18
-
19
- def gauge(stat, value, options = {})
20
- send_stats stat, value, :g, options
21
- end
22
-
23
- def histogram(stat, value, options = {})
24
- send_stats stat, value, :h, options
25
- end
26
-
27
- def log(stat, data = {})
28
- data = { environment: ::Meter.config.environment, Timestamp: Time.now.strftime("%FT%H:%M:%S%:z") }.merge data
29
- data.merge! app: ::Meter.config.namespace, host: ::Meter.config.hostname, statname: stat
30
- ::Meter.config.logger.debug { "Logging #{log_file} - #{data}"}
31
- log_file.open('a') { |f| f.puts(JSON.dump data) }
32
- end
33
-
34
- private
35
-
36
- def log_file
37
- ::Meter.config.log_dir.join('application.json.log')
38
- end
39
-
40
- # See https://github.com/DataDog/dogstatsd-ruby/blob/master/lib/statsd.rb
41
- def send_stats(stat, delta, type, options = {})
42
- sample_rate = options[:sample_rate] || 1
43
- return unless sample_rate == 1 or rand < sample_rate
44
- stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
45
- rate = "|@#{sample_rate}" unless sample_rate == 1
46
- app_tag = ["app:#{::Meter.config.namespace}"]
47
- tags = "|##{(Array(options[:tags]) + app_tag).join(",")}"
48
- send_to_socket "#{stat}:#{delta}|#{type}#{rate}#{tags}"
49
- end
50
-
51
- def send_to_socket(message)
52
- ::Meter.config.logger.debug { "UDP #{self.host}:#{self.port} - #{message}" }
53
- @socket.send message, 0, self.host, self.port
54
- end
55
-
56
- end
57
- end
@@ -1,16 +0,0 @@
1
- require 'meter/backend'
2
- require 'ostruct'
3
-
4
- module Meter
5
-
6
- # Holds the currently known backends.
7
- #
8
- def self.backends
9
- @backends ||= ::OpenStruct.new(
10
- datadog: ::Meter::Backend.new('127.0.0.1', 8125),
11
- counter: ::Meter::Backend.new('127.0.0.1', 3333),
12
- heka: ::Meter::Backend.new('127.0.0.1', 8128),
13
- )
14
- end
15
-
16
- end