meter 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.
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: