meter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,29 @@
1
+ # Meter
2
+
3
+ BETA!
4
+
5
+ A generic abstraction layer for fire and forgetting measurements via UDP.
6
+
7
+ # Installation
8
+
9
+ ```bash
10
+ gem install meter
11
+ ````
12
+
13
+ # Usage
14
+
15
+ #### Syntax
16
+
17
+ ```ruby
18
+ Meter.increment key, delta, options
19
+ ````
20
+
21
+ #### Examples
22
+
23
+ ```ruby
24
+ Meter.increment 'my.key'
25
+ Meter.increment 'my.key', 5
26
+ Meter.increment 'my.key', 5, sample_rate: 0.25
27
+
28
+ Meter.gauge 'my.gauge.key', 20
29
+ ```
@@ -0,0 +1,190 @@
1
+ require 'socket'
2
+
3
+ # Copy and paste from https://github.com/DataDog/dogstatsd-ruby/blob/master/lib/statsd.rb
4
+ # Changes:
5
+ # - The class is renamed to "Backend"
6
+ # - the logger class method points to meter's logger
7
+
8
+ module Meter
9
+ # = Statsd: A DogStatsd client (https://www.datadoghq.com)
10
+ #
11
+ # @example Set up a global Statsd client for a server on localhost:8125
12
+ # require 'statsd'
13
+ # $statsd = Statsd.new 'localhost', 8125
14
+ # @example Send some stats
15
+ # $statsd.increment 'page.views'
16
+ # $statsd.timing 'page.load', 320
17
+ # $statsd.gauge 'users.online', 100
18
+ # @example Use {#time} to time the execution of a block
19
+ # $statsd.time('account.activate') { @account.activate! }
20
+ # @example Create a namespaced statsd client and increment 'account.activate'
21
+ # statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
22
+ # statsd.increment 'activate'
23
+ class Backend
24
+ # A namespace to prepend to all statsd calls.
25
+ attr_reader :namespace
26
+
27
+ # StatsD host. Defaults to 127.0.0.1.
28
+ attr_accessor :host
29
+
30
+ # StatsD port. Defaults to 8125.
31
+ attr_accessor :port
32
+
33
+ def self.logger
34
+ Meter.config.logger
35
+ end
36
+
37
+ # Return the current version of the library.
38
+ def self.VERSION
39
+ "1.1.0"
40
+ end
41
+
42
+ # @param [String] host your statsd host
43
+ # @param [Integer] port your statsd port
44
+ def initialize(host = '127.0.0.1', port = 8125)
45
+ self.host, self.port = host, port
46
+ @prefix = nil
47
+ @socket = UDPSocket.new
48
+ end
49
+
50
+ def namespace=(namespace) #:nodoc:
51
+ @namespace = namespace
52
+ @prefix = "#{namespace}."
53
+ end
54
+
55
+ def host=(host) #:nodoc:
56
+ @host = host || '127.0.0.1'
57
+ end
58
+
59
+ def port=(port) #:nodoc:
60
+ @port = port || 8125
61
+ end
62
+
63
+ # Sends an increment (count = 1) for the given stat to the statsd server.
64
+ #
65
+ # @param [String] stat stat name
66
+ # @param [Hash] opts the options to create the metric with
67
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
68
+ # @option opts [Array<String>] :tags An array of tags
69
+ # @see #count
70
+ def increment(stat, opts={})
71
+ count stat, 1, opts
72
+ end
73
+
74
+ # Sends a decrement (count = -1) for the given stat to the statsd server.
75
+ #
76
+ # @param [String] stat stat name
77
+ # @param [Hash] opts the options to create the metric with
78
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
79
+ # @option opts [Array<String>] :tags An array of tags
80
+ # @see #count
81
+ def decrement(stat, opts={})
82
+ count stat, -1, opts
83
+ end
84
+
85
+ # Sends an arbitrary count for the given stat to the statsd server.
86
+ #
87
+ # @param [String] stat stat name
88
+ # @param [Integer] count count
89
+ # @param [Hash] opts the options to create the metric with
90
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
91
+ # @option opts [Array<String>] :tags An array of tags
92
+ def count(stat, count, opts={})
93
+ send_stats stat, count, :c, opts
94
+ end
95
+
96
+ # Sends an arbitary gauge value for the given stat to the statsd server.
97
+ #
98
+ # This is useful for recording things like available disk space,
99
+ # memory usage, and the like, which have different semantics than
100
+ # counters.
101
+ #
102
+ # @param [String] stat stat name.
103
+ # @param [Numeric] gauge value.
104
+ # @param [Hash] opts the options to create the metric with
105
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
106
+ # @option opts [Array<String>] :tags An array of tags
107
+ # @example Report the current user count:
108
+ # $statsd.gauge('user.count', User.count)
109
+ def gauge(stat, value, opts={})
110
+ send_stats stat, value, :g, opts
111
+ end
112
+
113
+ # Sends a value to be tracked as a histogram to the statsd server.
114
+ #
115
+ # @param [String] stat stat name.
116
+ # @param [Numeric] histogram value.
117
+ # @param [Hash] opts the options to create the metric with
118
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
119
+ # @option opts [Array<String>] :tags An array of tags
120
+ # @example Report the current user count:
121
+ # $statsd.histogram('user.count', User.count)
122
+ def histogram(stat, value, opts={})
123
+ send_stats stat, value, :h, opts
124
+ end
125
+
126
+ # Sends a timing (in ms) for the given stat to the statsd server. The
127
+ # sample_rate determines what percentage of the time this report is sent. The
128
+ # statsd server then uses the sample_rate to correctly track the average
129
+ # timing for the stat.
130
+ #
131
+ # @param [String] stat stat name
132
+ # @param [Integer] ms timing in milliseconds
133
+ # @param [Hash] opts the options to create the metric with
134
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
135
+ # @option opts [Array<String>] :tags An array of tags
136
+ def timing(stat, ms, opts={})
137
+ send_stats stat, ms, :ms, opts
138
+ end
139
+
140
+ # Reports execution time of the provided block using {#timing}.
141
+ #
142
+ # @param [String] stat stat name
143
+ # @param [Hash] opts the options to create the metric with
144
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
145
+ # @option opts [Array<String>] :tags An array of tags
146
+ # @yield The operation to be timed
147
+ # @see #timing
148
+ # @example Report the time (in ms) taken to activate an account
149
+ # $statsd.time('account.activate') { @account.activate! }
150
+ def time(stat, opts={})
151
+ start = Time.now
152
+ result = yield
153
+ timing(stat, ((Time.now - start) * 1000).round, opts)
154
+ result
155
+ end
156
+ # Sends a value to be tracked as a set to the statsd server.
157
+ #
158
+ # @param [String] stat stat name.
159
+ # @param [Numeric] set value.
160
+ # @param [Hash] opts the options to create the metric with
161
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
162
+ # @option opts [Array<String>] :tags An array of tags
163
+ # @example Record a unique visitory by id:
164
+ # $statsd.set('visitors.uniques', User.id)
165
+ def set(stat, value, opts={})
166
+ send_stats stat, value, :s, opts
167
+ end
168
+
169
+ private
170
+
171
+ def send_stats(stat, delta, type, opts={})
172
+ sample_rate = opts[:sample_rate] || 1
173
+ if sample_rate == 1 or rand < sample_rate
174
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
175
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
176
+ rate = "|@#{sample_rate}" unless sample_rate == 1
177
+ tags = "|##{opts[:tags].join(",")}" if opts[:tags]
178
+ send_to_socket "#{@prefix}#{stat}:#{delta}|#{type}#{rate}#{tags}"
179
+ end
180
+ end
181
+
182
+ def send_to_socket(message)
183
+ self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
184
+ @socket.send(message, 0, @host, @port)
185
+ rescue => boom
186
+ self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
187
+ nil
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,112 @@
1
+ require 'logger'
2
+ require 'meter/backend'
3
+
4
+ module Meter
5
+ class Configuration
6
+
7
+ attr_accessor :logger, :tags
8
+ attr_reader :primary_backend, :secondary_backend
9
+
10
+ def initialize(options={})
11
+ @logger = options[:logger] || default_logger
12
+ @primary_backend = Backend.new
13
+ @primary_backend.host = options[:primary_host] || default_host
14
+ @primary_backend.port = options[:primary_port] || default_port
15
+ @primary_backend.namespace = options[:namespace] || default_namespace
16
+ @secondary_backend = Backend.new
17
+ @secondary_backend.host = options[:secondary_host] || default_host
18
+ @secondary_backend.port = options[:secondary_port] || default_secondary_port
19
+ @secondary_backend.namespace = options[:namespace] || default_namespace
20
+ @tags = options[:tags] || {}
21
+ end
22
+
23
+ def namespace=(new_namespace)
24
+ primary_backend.namespace = new_namespace
25
+ secondary_backend.namespace = new_namespace
26
+ end
27
+
28
+ def namespace
29
+ primary_backend.namespace
30
+ end
31
+
32
+ def primary_host
33
+ primary_backend.host
34
+ end
35
+
36
+ def primary_port
37
+ primary_backend.port
38
+ end
39
+
40
+ def secondary_host
41
+ secondary_backend.host
42
+ end
43
+
44
+ def secondary_port
45
+ secondary_backend.port
46
+ end
47
+
48
+ def primary_host=(new_host)
49
+ primary_backend.host = new_host
50
+ end
51
+
52
+ def primary_port=(new_port)
53
+ primary_backend.port = new_port
54
+ end
55
+
56
+ def secondary_host=(new_host)
57
+ secondary_backend.host = new_host
58
+ end
59
+
60
+ def secondary_port=(new_port)
61
+ secondary_backend.port = new_port
62
+ end
63
+
64
+ private
65
+
66
+ def default_logger
67
+ if defined?(Rails)
68
+ Rails.logger
69
+ else
70
+ Logger.new(STDOUT)
71
+ end
72
+ end
73
+
74
+ def default_host
75
+ '127.0.0.1'
76
+ end
77
+
78
+ def default_port
79
+ 8125
80
+ end
81
+
82
+ def default_secondary_port
83
+ 3333
84
+ end
85
+
86
+ def default_namespace
87
+ 'meter'
88
+ end
89
+
90
+ end
91
+ end
92
+
93
+ module Meter
94
+
95
+ # Public: Returns the the configuration instance.
96
+ #
97
+ def self.config
98
+ @config ||= Configuration.new
99
+ end
100
+
101
+ # Public: Yields the configuration instance.
102
+ #
103
+ def self.configure(&block)
104
+ yield config
105
+ end
106
+
107
+ # Public: Reset the configuration (useful for testing).
108
+ #
109
+ def self.reset!
110
+ @config = nil
111
+ end
112
+ end
@@ -0,0 +1,9 @@
1
+ module Meter
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
+ end
9
+ end
data/lib/meter.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'meter/configuration'
2
+
3
+ # A generic wrapper for Statsd-like gauges and counters.
4
+ #
5
+ module Meter
6
+ extend self
7
+
8
+ def increment(key, options = {})
9
+ id = options.delete(:id)
10
+ primary.increment key, options
11
+ if id
12
+ secondary.increment "#{key}.#{id}", options
13
+ end
14
+ end
15
+
16
+ def count(key, delta, options = {})
17
+ id = options.delete(:id)
18
+ primary.count key, delta, options
19
+ if id
20
+ secondary.count "#{key}.#{id}", delta, options
21
+ end
22
+ end
23
+
24
+ def gauge(key, value, options = {})
25
+ primary.gauge key, value, options
26
+ end
27
+
28
+ def histogram(key, value, options = {})
29
+ primary.histogram key, value, options
30
+ end
31
+
32
+ private
33
+
34
+ def primary
35
+ config.primary_backend
36
+ end
37
+
38
+ def secondary
39
+ config.secondary_backend
40
+ end
41
+
42
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'meter'
3
+
4
+ describe Meter::Backend do
5
+
6
+ let(:backend) { Meter.config.primary_backend }
7
+
8
+ before do
9
+ Meter.reset!
10
+ end
11
+
12
+ describe '.increment' do
13
+ it 'works and I did not make any mistakes when copy and pasting the backend from StatsD' do
14
+ backend.increment 'my.counter'
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'meter/configuration'
3
+
4
+ describe Meter::Configuration do
5
+
6
+ let(:logger) { mock(:logger) }
7
+ let(:config) { Meter.config }
8
+
9
+ before do
10
+ Meter.reset!
11
+ end
12
+
13
+ describe '.config' do
14
+
15
+ describe '#logger' do
16
+ it 'is an STDOUT logger' do
17
+ Logger.should_receive(:new).with(STDOUT).and_return logger
18
+ config.logger.should be logger
19
+ end
20
+
21
+ context 'with Rails' do
22
+ before do
23
+ ensure_module :Rails
24
+ Rails.stub!(:logger).and_return(logger)
25
+ end
26
+
27
+ after do
28
+ Object.send(:remove_const, :Rails)
29
+ end
30
+
31
+ it 'is the Rails logger' do
32
+ config.logger.should be Rails.logger
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#primary_backend and #secondary_backend' do
38
+ it 'is a Backend' do
39
+ config.primary_backend.should be_instance_of Meter::Backend
40
+ config.secondary_backend.should be_instance_of Meter::Backend
41
+ end
42
+
43
+ it 'has the default host' do
44
+ config.primary_backend.host.should == '127.0.0.1'
45
+ config.secondary_backend.host.should == '127.0.0.1'
46
+ end
47
+
48
+ it 'has the default namespace' do
49
+ config.primary_backend.namespace.should == 'meter'
50
+ config.secondary_backend.namespace.should == 'meter'
51
+ end
52
+
53
+ it 'has the default port' do
54
+ config.primary_backend.port.should == 8125
55
+ config.secondary_backend.port.should == 3333
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Meter do
4
+
5
+ let(:meter) { Meter }
6
+ let(:primary) { Meter.config.primary_backend }
7
+ let(:secondary) { Meter.config.secondary_backend }
8
+
9
+ describe '.increment' do
10
+ context "when given an id as option" do
11
+ it "proxies to the secondary with the id" do
12
+ secondary.should_receive(:increment).with("my.key.123", {})
13
+ meter.increment 'my.key', :id => 123
14
+ end
15
+
16
+ it "proxies to the primary without the id" do
17
+ primary.should_receive(:increment).with("my.key", {})
18
+ meter.increment 'my.key', :id => 123
19
+ end
20
+ end
21
+
22
+ context "when id is NOT given as an option" do
23
+ it "doesn't proxy to the secondary at all" do
24
+ secondary.should_not_receive(:increment)
25
+ meter.increment 'my.key'
26
+ end
27
+
28
+ it "proxies to the primary" do
29
+ primary.should_receive(:increment).with("my.key", {})
30
+ meter.increment 'my.key'
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ require 'meter'
2
+
3
+ def ensure_class_or_module(full_name, class_or_module)
4
+ full_name.to_s.split(/::/).inject(Object) do |context, name|
5
+ begin
6
+ context.const_get(name)
7
+ rescue NameError
8
+ if class_or_module == :class
9
+ context.const_set(name, Class.new)
10
+ else
11
+ context.const_set(name, Module.new)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def ensure_module(name)
18
+ ensure_class_or_module(name, :module)
19
+ end
20
+
21
+ def ensure_class(name)
22
+ ensure_class_or_module(name, :class)
23
+ end
24
+
25
+ RSpec.configure do |config|
26
+ config.before do
27
+ Meter.config.logger = nil
28
+ end
29
+
30
+ config.after do
31
+ Meter.reset!
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meter
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - bukowskis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ none: false
21
+ name: rspec
22
+ type: :development
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ none: false
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ none: false
37
+ name: guard-rspec
38
+ type: :development
39
+ prerelease: false
40
+ requirement: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ none: false
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ none: false
53
+ name: rb-fsevent
54
+ type: :development
55
+ prerelease: false
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ none: false
62
+ description: A generic abstraction layer for fire and forgetting measurements via
63
+ UDP.
64
+ email:
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - lib/meter/backend.rb
70
+ - lib/meter/configuration.rb
71
+ - lib/meter/version.rb
72
+ - lib/meter.rb
73
+ - spec/lib/meter/backend_spec.rb
74
+ - spec/lib/meter/configuration_spec.rb
75
+ - spec/lib/meter_spec.rb
76
+ - spec/spec_helper.rb
77
+ - README.md
78
+ - LICENSE
79
+ homepage: https://github.com/bukowskis/meter
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --encoding
85
+ - UTF-8
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ none: false
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ none: false
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 1.8.23
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: A generic abstraction layer for fire and forgetting measurements via UDP.
106
+ test_files: []
107
+ has_rdoc: