nosey 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in nosey.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard 'rspec', :version => 2, :bundler => true, :cli => '--colour --format nested --debugger' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
6
+
7
+ guard 'bundler' do
8
+ watch('Gemfile')
9
+ watch(/^.+\.gemspec/)
10
+ end
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+
2
+ | |
3
+ | |
4
+ / \
5
+ ( __ __ ) Nosey - Realtime Ruby app instrumentation.
6
+ '--'-; ;
7
+ | |
8
+ _ | |
9
+ __ /` ``""-;_ |
10
+ \ '.;------. `\
11
+ | | __.. |
12
+ | \.-'' _ |
13
+ | | ,-'-, |
14
+ | \__.-' |
15
+ \ '. /
16
+ \ \ /
17
+ '. |
18
+ jgs ) |
19
+
20
+ Nosey is a way to instrument your Evented Ruby applications to track counts, aggregates, etc. It was built a Poll Everywhere because we need a way to peer into our Evented apps and grab some basic statistics so that we could graph on Munin. Since we needed this instrumentation available in several EM projects, we gathered the basics up into this gem.
21
+
22
+ ## Getting Started
23
+
24
+ Install the gem.
25
+
26
+ gem install nosey
27
+
28
+ Instrument your Ruby app with nosey.
29
+
30
+ require 'nosey'
31
+
32
+ class PandaBear
33
+ include Nosey::Instrumentation
34
+
35
+ def initialize
36
+ nosey.touch 'started_at'
37
+ end
38
+
39
+ def growl
40
+ nosey.increment 'growls'
41
+ nosey.touch 'last_growled_at'
42
+ "Grrrrr!"
43
+ end
44
+ end
45
+
46
+ princess = PandaBear.new
47
+ princess.growl
48
+ princess.nosey.report # Soon to be a fantastico report, prolly in YML
49
+
50
+ When you fire Ruby this up, Nosey will open up a socket and report the stats.
51
+
52
+ $ cat /tmp/panda_bear.rb.socket
53
+
54
+ panda_bear.rb:
55
+ started_at: 2011-09-13 23:21:52.530452000 -07:00
56
+ last_growled_at: 2011-09-13 23:21:52.530452000 -07:00
57
+ growls: 1
58
+
59
+ Thats the plan anyway. For now this is only going to work in EM, so its super handy if you're building EM servers and want to dump out the stats.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module Nosey
2
+ VERSION = "0.0.1"
3
+ end
data/lib/nosey.rb ADDED
@@ -0,0 +1,237 @@
1
+ require "nosey/version"
2
+ require 'eventmachine'
3
+
4
+ module EventMachine
5
+ module Nosey
6
+ class SocketServer < EventMachine::Connection
7
+ Host = '/tmp/nosey.socket'
8
+ Port = nil
9
+
10
+ attr_accessor :report
11
+
12
+ # Accept a collection of aggregators that we'll use to report our stats.
13
+ def initialize(report)
14
+ @report = report
15
+ end
16
+
17
+ # Dump out the stats and close down the connection
18
+ def post_init
19
+ begin
20
+ send_data report.to_s
21
+ rescue => e
22
+ send_data "Exception! #{e}\n#{e.backtrace}"
23
+ ensure
24
+ close_connection
25
+ end
26
+ end
27
+
28
+ # A nice short-cut for peeps who aren't familar with EM to fire up
29
+ # an Reporting server with an array of aggregators, host, and a port.
30
+ def self.start(report, host=SocketServer::Host, port=SocketServer::Port)
31
+ EventMachine::start_server(host, port, self, report)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ module Nosey
38
+ class Report
39
+ attr_reader :probe_sets
40
+
41
+ def initialize
42
+ yield self if block_given?
43
+ self
44
+ end
45
+
46
+ def probe_sets
47
+ @probe_sets ||= Array.new
48
+ end
49
+
50
+ # Hash representation of all the probe_sets. This gives is an intermediate
51
+ # format that we can parse from other systems or code that needs reporting
52
+ # data for formatting, or whatever.
53
+ def to_hash
54
+ probe_sets.inject({}) do |report, set|
55
+ report[set.name.to_s] = set.probes.inject({}) do |memo, (_, probe)|
56
+ memo[probe.name] = probe.value
57
+ memo
58
+ end
59
+ report
60
+ end
61
+ end
62
+
63
+ # String representation of all the probe_sets that's suitable for
64
+ # flushing out over a socket.
65
+ def to_s
66
+ to_hash.to_yaml
67
+ end
68
+ end
69
+
70
+ module Instrumentation
71
+ # Inject nosey instrumentation into the owning class.
72
+ def self.included(base)
73
+ base.send :include, DSL::InstanceMethods
74
+ end
75
+
76
+ module DSL
77
+ module InstanceMethods
78
+ # Setup instrumentation that we'll use to report stats for this thing
79
+ def nosey
80
+ @_nosey ||= Nosey::Probe::Set.new("#{self.class.name}##{object_id}")
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ module Probe
87
+ # Base class for containing key/value pairs in nosey. This class is responsible
88
+ # for reseting probes.
89
+ class Base
90
+ attr_reader :name
91
+ attr_accessor :value
92
+
93
+ def initialize(name=nil, value=nil)
94
+ @name, @value = name, value
95
+ end
96
+
97
+ # Set the value (don't increment or whateves)
98
+ def set(value)
99
+ @value = value
100
+ end
101
+
102
+ # Reset the value to nil
103
+ def reset
104
+ @value = nil
105
+ end
106
+
107
+ # Representation of this probe
108
+ def to_hash
109
+ { name => value }
110
+ end
111
+ end
112
+
113
+ # Calulcates a min, max, and avg for a given number of samples
114
+ class Sampler < Base
115
+ attr_reader :min, :max, :sum, :count
116
+
117
+ def initialize(*args)
118
+ reset
119
+ super(*args)
120
+ end
121
+
122
+ def sample(value)
123
+ @min = @max = value unless @min and @max
124
+
125
+ @min = value if value < @min
126
+ @max = value if value > @max
127
+ @sum += value
128
+ @count += 1
129
+
130
+ to_hash
131
+ end
132
+
133
+ def avg
134
+ sum / count if count > 0 and sum
135
+ end
136
+
137
+ def to_hash
138
+ {
139
+ 'max' => max,
140
+ 'min' => min,
141
+ 'sum' => sum,
142
+ 'avg' => avg,
143
+ 'count' => count
144
+ }
145
+ end
146
+
147
+ def value
148
+ to_hash
149
+ end
150
+
151
+ def reset
152
+ @min = @max = nil
153
+ @sum = @count = 0
154
+ end
155
+ end
156
+
157
+ # Count up/down values.
158
+ class Counter < Base
159
+ def increment(by=1)
160
+ change by
161
+ end
162
+
163
+ def decrement(by=1)
164
+ change -by
165
+ end
166
+
167
+ private
168
+ def change(by)
169
+ self.value ||= 0 # Init at 0 if the stat is nil
170
+ self.value += by
171
+ end
172
+ end
173
+
174
+ class Touch < Base
175
+ def touch
176
+ self.value = Time.now
177
+ end
178
+ end
179
+ end
180
+
181
+ # Contains a collection of probes that calculate velocities, counts, etc.
182
+ class Probe::Set
183
+ attr_reader :name
184
+
185
+ def initialize(name)
186
+ @name = name
187
+ yield self if block_given?
188
+ self
189
+ end
190
+
191
+ # Increment a counter probe
192
+ def increment(key,by=1)
193
+ ensure_probe(Probe::Counter, key).increment(by)
194
+ end
195
+
196
+ # Decrement a counter probe
197
+ def decrement(key,by=1)
198
+ ensure_probe(Probe::Counter, key).decrement(by)
199
+ end
200
+
201
+ # Sample a number and get a sum/avg/count/min/max
202
+ def sample(key,val)
203
+ ensure_probe(Probe::Sampler, key).sample(val)
204
+ end
205
+
206
+ # Touch a timestamp probe
207
+ def touch(key)
208
+ ensure_probe(Probe::Touch, key).touch
209
+ end
210
+
211
+ # List of all the probes that are active
212
+ def probes
213
+ @probes ||= Hash.new
214
+ end
215
+
216
+ # Get a probe and do all sorts of crazy stuff to it.
217
+ def probe(key)
218
+ probes[key]
219
+ end
220
+
221
+ # Generate a report with this ProbeSet
222
+ def report
223
+ Report.new do |r|
224
+ r.probe_sets << self
225
+ end
226
+ end
227
+
228
+ private
229
+ # This factory creates probes based on the methods called from
230
+ # the instrumentation. If a probe doesn't exist, we create an instance
231
+ # from the klass and args passed in, then set the thing up in the hash key.
232
+ def ensure_probe(klass, key, *args)
233
+ probes[key] ||= klass.new(key, *args)
234
+ end
235
+ end
236
+
237
+ end
data/nosey.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "nosey/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "nosey"
7
+ s.version = Nosey::VERSION
8
+ s.authors = ["Brad Gessler"]
9
+ s.email = ["brad@bradgessler.com"]
10
+ s.homepage = "https://github.com/polleverywhere/nosey"
11
+ s.summary = %q{Instrument Ruby EventMachine applications}
12
+ s.description = %q{Nosey is a way to instrument your Evented Ruby applications to track counts, aggregates, etc. It was built a Poll Everywhere because we need a way to peer into our Evented apps and grab some basic statistics so that we could graph on Munin. Since we needed this instrumentation available in several EM projects, we gathered the basics up into this gem.}
13
+
14
+ s.rubyforge_project = "nosey"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "eventmachine"
22
+
23
+ s.add_development_dependency 'rspec'
24
+ s.add_development_dependency 'guard-rspec'
25
+ s.add_development_dependency 'guard-bundler'
26
+ s.add_development_dependency 'growl'
27
+ s.add_development_dependency 'rb-fsevent'
28
+ s.add_development_dependency 'em-ventually'
29
+ s.add_development_dependency 'ruby-debug19'
30
+ end
@@ -0,0 +1,168 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventMachine::Nosey::SocketServer do
4
+ include EM::Ventually
5
+
6
+ before(:each) do
7
+ @report = Nosey::Report.new do |r|
8
+ 3.times do |n|
9
+ r.probe_sets << Nosey::Probe::Set.new("Group #{n}") do |set|
10
+ set.increment 'hit'
11
+ set.touch 'generated-at'
12
+ set.sample 'zie-number', 1
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ it "should write report to socket" do
19
+ EventMachine::Nosey::SocketServer.start @report
20
+ Nosey::Test::ReadSocket.start('/tmp/nosey.socket').callback{|data|
21
+ @response = data
22
+ }
23
+ ly{ @response }.test{|response| YAML.load(response).is_a?(Hash) }
24
+ end
25
+ end
26
+
27
+ describe Nosey::Report do
28
+ before(:each) do
29
+ @report = Nosey::Report.new do |r|
30
+ 3.times do |n|
31
+ r.probe_sets << Nosey::Probe::Set.new("Group #{n}") do |set|
32
+ set.touch 'generated-at'
33
+ set.increment 'hit'
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ context "report hash" do
40
+ it "should have groups" do
41
+ @report.to_hash.keys.should include('Group 0', 'Group 1', 'Group 2')
42
+ end
43
+
44
+ it "should have probes" do
45
+ @report.to_hash['Group 1'].keys.should include('generated-at', 'hit')
46
+ end
47
+ end
48
+
49
+ it "should generate report YML for string" do
50
+ # Spot check for a probe key
51
+ YAML.load(@report.to_s)['Group 2'].keys.should include('generated-at')
52
+ end
53
+ end
54
+
55
+ describe Nosey::Probe::Set do
56
+ before(:each) do
57
+ @probes = Nosey::Probe::Set.new("My Probe Set")
58
+ end
59
+
60
+ it "should have name" do
61
+ @probes.name.should eql("My Probe Set")
62
+ end
63
+
64
+ it "should increment" do
65
+ @probes.increment('count').should eql(1)
66
+ end
67
+
68
+ it "should decrement" do
69
+ @probes.decrement('count').should eql(-1)
70
+ end
71
+
72
+ it "should touch" do
73
+ @probes.touch('touched-at').should be_instance_of(Time)
74
+ end
75
+
76
+ it "should sample" do
77
+ @probes.sample('foobers', 1).should be_instance_of(Hash)
78
+ end
79
+
80
+ it "should get probe" do
81
+ @probes.touch('barf')
82
+ @probes.probe('barf').should be_instance_of(Nosey::Probe::Touch)
83
+ end
84
+
85
+ it "should return report" do
86
+ @probes.report.probe_sets.first.should eql(@probes)
87
+ end
88
+ end
89
+
90
+ describe Nosey::Probe::Counter do
91
+ before(:all) do
92
+ @counter = Nosey::Probe::Counter.new
93
+ end
94
+
95
+ it "should init null" do
96
+ @counter.value.should be_nil
97
+ end
98
+
99
+ it "should increment" do
100
+ @counter.increment.should eql(1)
101
+ end
102
+
103
+ it "should decrement" do
104
+ @counter.decrement(2).should eql(-1)
105
+ end
106
+
107
+ it "should set" do
108
+ @counter.set(0).should eql(0)
109
+ end
110
+ end
111
+
112
+ describe Nosey::Probe::Touch do
113
+ before(:all) do
114
+ @touch = Nosey::Probe::Touch.new
115
+ end
116
+ it "should init null" do
117
+ @touch.value.should be_nil
118
+ end
119
+
120
+ it "should touch" do
121
+ @touch.touch
122
+ @touch.value.should_not be_nil
123
+ end
124
+ end
125
+
126
+ describe Nosey::Probe::Sampler do
127
+ before(:each) do
128
+ @counter = Nosey::Probe::Sampler.new
129
+ @counter.sample 1
130
+ @counter.sample 2
131
+ @counter.sample 3
132
+ end
133
+
134
+ it "should init hash" do
135
+ @counter.value.should be_instance_of(Hash)
136
+ end
137
+
138
+ it "should have avg" do
139
+ @counter.value['avg'].should eql(2)
140
+ end
141
+
142
+ it "should have sum" do
143
+ @counter.value['sum'].should eql(6)
144
+ end
145
+
146
+ it "should have min" do
147
+ @counter.value['min'].should eql(1)
148
+ end
149
+
150
+ it "should have max" do
151
+ @counter.value['max'].should eql(3)
152
+ end
153
+
154
+ it "should have count" do
155
+ @counter.value['count'].should eql(3)
156
+ end
157
+ end
158
+
159
+
160
+ describe Nosey::Instrumentation do
161
+ before(:each) do
162
+ @instance = Nosey::Test::VanillaClass.new
163
+ end
164
+
165
+ it "should have nosey Probe::Set instance" do
166
+ @instance.nosey.should be_instance_of Nosey::Probe::Set
167
+ end
168
+ end
@@ -0,0 +1,41 @@
1
+ require 'nosey'
2
+ require 'em-ventually'
3
+ require 'socket'
4
+
5
+ module Nosey
6
+ module Test
7
+ class VanillaClass
8
+ include Nosey::Instrumentation
9
+
10
+ def count
11
+ nosey.count('count')
12
+ end
13
+
14
+ def touch
15
+ nosey.touch('touched-at')
16
+ end
17
+ end
18
+
19
+ # Read data from a socket and then kill it right away.
20
+ class ReadSocket < EventMachine::Connection
21
+ include EventMachine::Deferrable
22
+
23
+ def receive_data(data)
24
+ buffer << data
25
+ end
26
+
27
+ def unbind
28
+ succeed buffer
29
+ end
30
+
31
+ def self.start(host,port=nil)
32
+ EventMachine::connect host, port, self
33
+ end
34
+
35
+ private
36
+ def buffer
37
+ @buffer ||= ""
38
+ end
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nosey
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Brad Gessler
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-09-14 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rspec
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: guard-rspec
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ - !ruby/object:Gem::Dependency
57
+ name: guard-bundler
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :development
67
+ version_requirements: *id004
68
+ - !ruby/object:Gem::Dependency
69
+ name: growl
70
+ prerelease: false
71
+ requirement: &id005 !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id005
80
+ - !ruby/object:Gem::Dependency
81
+ name: rb-fsevent
82
+ prerelease: false
83
+ requirement: &id006 !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ type: :development
91
+ version_requirements: *id006
92
+ - !ruby/object:Gem::Dependency
93
+ name: em-ventually
94
+ prerelease: false
95
+ requirement: &id007 !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :development
103
+ version_requirements: *id007
104
+ - !ruby/object:Gem::Dependency
105
+ name: ruby-debug19
106
+ prerelease: false
107
+ requirement: &id008 !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ type: :development
115
+ version_requirements: *id008
116
+ description: Nosey is a way to instrument your Evented Ruby applications to track counts, aggregates, etc. It was built a Poll Everywhere because we need a way to peer into our Evented apps and grab some basic statistics so that we could graph on Munin. Since we needed this instrumentation available in several EM projects, we gathered the basics up into this gem.
117
+ email:
118
+ - brad@bradgessler.com
119
+ executables: []
120
+
121
+ extensions: []
122
+
123
+ extra_rdoc_files: []
124
+
125
+ files:
126
+ - .gitignore
127
+ - Gemfile
128
+ - Guardfile
129
+ - README.md
130
+ - Rakefile
131
+ - lib/nosey.rb
132
+ - lib/nosey/version.rb
133
+ - nosey.gemspec
134
+ - spec/lib/nosey_spec.rb
135
+ - spec/spec_helper.rb
136
+ has_rdoc: true
137
+ homepage: https://github.com/polleverywhere/nosey
138
+ licenses: []
139
+
140
+ post_install_message:
141
+ rdoc_options: []
142
+
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ segments:
157
+ - 0
158
+ version: "0"
159
+ requirements: []
160
+
161
+ rubyforge_project: nosey
162
+ rubygems_version: 1.3.6
163
+ signing_key:
164
+ specification_version: 3
165
+ summary: Instrument Ruby EventMachine applications
166
+ test_files:
167
+ - spec/lib/nosey_spec.rb
168
+ - spec/spec_helper.rb