ramaze-fnordmetric 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/examples/fn.rb ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require 'fnordmetric'
4
+
5
+ FnordMetric.namespace :example do
6
+
7
+ #
8
+ # numeric (delta) gauge, 1-day tick
9
+ gauge :icecreams_eaten_per_day, :tick => 1.day.to_i, :title => "Daily icecreams"
10
+ gauge :icecreams_eaten_per_hour, :tick => 1.hour.to_i, :title => "Hourly icecreams"
11
+ gauge :icecreams_eaten_per_minute, :tick => 1.minute.to_i, :title => "Hourly icecreams"
12
+
13
+ gauge :strawberry_icecreams_eaten_per_day, :tick => 1.day.to_i, :title => "Daily strawberry icecreams"
14
+
15
+ event(:icecream) do
16
+ incr :icecreams_eaten_per_day
17
+ incr :icecreams_eaten_per_hour
18
+ incr :icecreams_eaten_per_minute
19
+ incr :strawberry_icecreams_eaten_per_day if data[:flavor] = 'strawberry'
20
+ end
21
+
22
+ # Performance
23
+ gauge :delivery_performance_per_minute, :tick => 1.minute.to_i, :average => true, :progressive => true, :title => "Delivery performance per minute"
24
+ gauge :delivery_performance_per_hour, :tick => 1.hour.to_i, :average => true, :progressive => true, :title => "Delivery performance per hour"
25
+
26
+ event(:delivery_performance) do
27
+ puts "got #{data[:time]} for method #{data[:method]}"
28
+ incr :delivery_performance_per_hour, data[:time] if data[:method] == 'MainController#deliver'
29
+ incr :delivery_performance_per_minute, data[:time] if data[:method] == 'MainController#deliver'
30
+ end
31
+
32
+ # All events
33
+ # numeric (progressive) gauge, 1-day tick
34
+ gauge :events_total, :tick => 1.day.to_i, :progressive => true, :title => "Daily Events (total)"
35
+
36
+ # on _every_ event
37
+ event :"*" do
38
+ incr :events_total
39
+ end
40
+
41
+ # Unique pageviews
42
+ # numeric (delta) gauge, increments uniquely by session_key
43
+ gauge :pageviews_daily_unique, :tick => 1.day.to_i, :unique => true, :title => "Unique Visits (Daily)"
44
+
45
+ # three-dimensional (delta) gauge (time->key->value)
46
+ gauge :pageviews_per_url_daily, :tick => 1.day.to_i, :title => "Daily Pageviews per URL", :three_dimensional => true
47
+
48
+ event :_pageview do
49
+ # increment the daily_uniques gauge by 1 if session_key hasn't been seen
50
+ # in this tick yet
51
+ incr :pageviews_daily_unique
52
+ # increment the pageviews_per_url_daily gauge by 1 where key = 'page2'
53
+ incr_field :pageviews_per_url_daily, data[:url]
54
+ end
55
+
56
+ #
57
+ # WIDGETS
58
+ #
59
+
60
+ widget 'Overview', {
61
+ :title => "Icecream per day",
62
+ :type => :timeline,
63
+ :plot_style => :areaspline,
64
+ :gauges => :icecreams_eaten_per_day,
65
+ :include_current => true,
66
+ :autoupdate => 10
67
+ }
68
+
69
+ widget 'Overview', {
70
+ :title => "Icecream per hour",
71
+ :type => :timeline,
72
+ :plot_style => :areaspline,
73
+ :gauges => :icecreams_eaten_per_hour,
74
+ :include_current => true,
75
+ :autoupdate => 10
76
+ }
77
+
78
+ widget 'Overview', {
79
+ :title => "Icecream per minute",
80
+ :type => :timeline,
81
+ :plot_style => :areaspline,
82
+ :gauges => :icecreams_eaten_per_minute,
83
+ :include_current => true,
84
+ :autoupdate => 10
85
+ }
86
+
87
+ widget 'Pages', {
88
+ :title => "Top Pages",
89
+ :type => :toplist,
90
+ :autoupdate => 20,
91
+ :include_current => true,
92
+ :gauges => [ :pageviews_per_url_daily ]
93
+ }
94
+
95
+ widget 'Performance', {
96
+ :title => "Delivery performance metrics per hour",
97
+ :type => :timeline,
98
+ :plot_style => :areaspline,
99
+ :gauges => :delivery_performance_per_hour,
100
+ :include_current => true,
101
+ :autoupdate => 2
102
+ }
103
+
104
+ widget 'Performance', {
105
+ :title => "Delivery performance metrics per minute",
106
+ :type => :timeline,
107
+ :plot_style => :areaspline,
108
+ :gauges => :delivery_performance_per_minute,
109
+ :include_current => true,
110
+ :autoupdate => 2
111
+ }
112
+
113
+
114
+
115
+ end
116
+
117
+ FnordMetric.standalone
118
+
@@ -0,0 +1,306 @@
1
+ # encode: utf-8
2
+ #
3
+ # FNordmetric helper
4
+ #
5
+ #
6
+ require 'ramaze'
7
+ require 'fnordmetric'
8
+ require 'redis'
9
+
10
+ module Ramaze
11
+ module Helper
12
+ ##
13
+ # This helper provides a convenience wrapper for sending events to
14
+ # Fnordmetric.
15
+ #
16
+ # events can be anything, its just an indication that something happened.
17
+ # Fnordmetric can then make some agregates on events received per period.
18
+ #
19
+ # Since events can carry arbitrary data, this helper adds methods that send
20
+ # performance data to Fnordmetric, so one can easily measure code execution
21
+ # times.
22
+ #
23
+ # events are associated to the Innate session id, and thus are linked to
24
+ # visitors of your site. this is really usefull since you can, for instance,
25
+ # see how long a controller action took for a particular user.
26
+ #
27
+ # If you want so use a redis server other than the usual localhost:6379, you
28
+ # need to define :fnord_redis_url trait, e.g. :
29
+ #
30
+ # trait :fnord_redis_url => "redis://redis.example.com:6332"
31
+ #
32
+ # TODO: @example Basic usage here...
33
+ # TODO: Implement optional with_id that uses specific id instead of innate.sid in conjunction with...
34
+ # TODO: simple keys instead of list, for the above
35
+ #
36
+ module Fnordmetric
37
+ # @@fnord will hold Fnordmetric API instance
38
+ # @@redis holds a Redis connection
39
+ # A timer is an Array holding the event name, a Hash of arguments and a timestamp
40
+ @@fnord = nil
41
+ @@redis = nil
42
+ @@sstack_key_root = "fnordmetric.%s.%s.%s" % [ ENV['HOSTNAME'] || "localhost", ENV['USER'], Ramaze.options.app.name.to_s ]
43
+
44
+ ##
45
+ # We need clock as a class method
46
+ # Let's extend the includer when it includes us
47
+ def self.included(base)
48
+ Ramaze::Log.debug("Fnordmetric helper is being included in %s" % base.name)
49
+ base.extend(ClassMethods)
50
+ end
51
+
52
+ ##
53
+ # Sends an event to Fnordmetric
54
+ #
55
+ # This helper method sends an event to Fnordmetric, populating the
56
+ # :_session field with the current innate sid.
57
+ #
58
+ # @param [Symbol] evt the name of the event to send to Fnordmetric.
59
+ # @param [Hash] args a hash of supplemental data to send
60
+ #
61
+ def event(evt, args = {})
62
+ # Let's connect first, it will have to be done anyway
63
+ return unless evt
64
+ _connect unless @@fnord
65
+
66
+ evt = { :_type => evt.to_s, :_session => session.sid }
67
+
68
+ evt.merge!(args)
69
+
70
+ Ramaze::Log.debug("Logging Fnordmetric event %s" % evt.inspect)
71
+ @@fnord.event(evt)
72
+ end
73
+
74
+ ##
75
+ # All in one timing function for a block
76
+ #
77
+ # This method will send an event containing the execution time of
78
+ # the passed block.
79
+ #
80
+ # @example Block style usage
81
+ #
82
+ # times(:performance, :method => :whatever) do
83
+ # # do stuff to be measured
84
+ # end
85
+ #
86
+ # @param [Symbol] event_name the name of the event to send to Fnordmetric.
87
+ # @param [Hash] args a hash of supplemental data to send
88
+ # @param [Block] block code to be executed and timed
89
+ #
90
+ def times(event_name, args = {}, &block)
91
+ push_timer(event_name, args)
92
+ # THINK: may be raise since there is no point in using times without a
93
+ # block
94
+ yield if block_given?
95
+ ensure
96
+ pop_timer
97
+ end
98
+
99
+ ##
100
+ # Starts a timer and pushes it to the timers stack
101
+ #
102
+ # @param [Symbol] event_name the name of the event to send to Fnordmetric.
103
+ # @param [Hash] args a hash of supplemental data to send
104
+ #
105
+ # @example Push/Pop style usage
106
+ #
107
+ # push_timer(:performance, :field => :whatever)
108
+ # # some code
109
+ # pop_timer
110
+ #
111
+ def push_timer(event_name, args = {})
112
+ _connect unless @@redis
113
+ @@redis.lpush(_key, [event_name, args, Time.now.to_f].to_json)
114
+ @@redis.expire(_key, _ttl)
115
+ Ramaze::Log.debug("Timer pushed and TTL set to %s for %s to %s (stack level is now %s)" %
116
+ [ _ttl,
117
+ event_name,
118
+ _key,
119
+ @@redis.llen(_key) ])
120
+ end
121
+
122
+ ##
123
+ # Pops a timer and sends an event
124
+ #
125
+ # This method pops the last pushed timer and sends an event
126
+ # No arguments are needed, since they were stored by push_timer
127
+ #
128
+ def pop_timer
129
+ len = @@redis.llen(_key)
130
+ if len > 0
131
+ json = @@redis.lpop(_key)
132
+
133
+ wat, args, wen = JSON.parse(json)
134
+ Ramaze::Log.debug("Timer popped for %s (stack level is now %s)" % [ wat, len - 1])
135
+ # We log millisecs
136
+ time = Time.now-Time.at(wen)
137
+ time *= 1000
138
+ event(wat, args.merge(:time => time.to_i))
139
+ else
140
+ Ramaze::Log.error("Unable to pop timer in %s (no event in stack)" % _key)
141
+ #raise RuntimeError, "Unable to pop timer in %s (no event in stack)" % _key
142
+ end
143
+ end
144
+
145
+ ##
146
+ # Removes all timers in the stack
147
+ #
148
+ def clear_timers
149
+ Ramaze::Log.debug("Cleared %s timers for %s" % [ @@redis.llen(_key), _key ])
150
+ @@redis.del _key
151
+ end
152
+
153
+ ##
154
+ # Sends a _pageview Fnordmetric event
155
+ #
156
+ # This method sends a specific _pageview event Fnordmetric event
157
+ # This event is treated in a special way by Fnordmetric (see doc).
158
+ #
159
+ # @param [String] url the URl that is accessed. Defaults to request.env['REQUEST_PATH']
160
+ #
161
+ # @example Logging all page views
162
+ #
163
+ # If all your controllers inherit 'Controller', you can log all page view
164
+ # very easily :
165
+ #
166
+ # class Controller < Ramaze::Controller
167
+ # helper :fnordmetric
168
+ #
169
+ # before_all do
170
+ # pageview
171
+ # end
172
+ #
173
+ def pageview(url=request.env['REQUEST_PATH'])
174
+ event(:_pageview, :url => url)
175
+ end
176
+
177
+ ##
178
+ # Sets username for the current session
179
+ #
180
+ # This manually sets a user name for the current session. It calls the
181
+ # specific :_set_name Fnordmetric event
182
+ # This comes handy for user tracking
183
+ #
184
+ # @params [String] name the user name
185
+ #
186
+ def set_name(name)
187
+ event(:_set_name, :name => name)
188
+ end
189
+
190
+ ##
191
+ # Sets the picture URL for the user
192
+ #
193
+ # This manually sets a user picture for the current session. It calls the
194
+ # specific :_set_picture Fnordmetric event.
195
+ # Using this method, you'll be able to have a picture associated to the user
196
+ # in Fnordmetric's user tracking panel
197
+ #
198
+ # @param [String] url Picture url
199
+ #
200
+ # @example Using Gravatar to set user picture
201
+ #
202
+ #
203
+ # class Users < Controller
204
+ # helper :user, :gravatar, :fnordmetric
205
+ # ...
206
+ # def login
207
+ # ...
208
+ # redirect_referrer if logged_in?
209
+ # user_login(request.subset(:email, :password))
210
+ # if logged_in?
211
+ # set_name("#{user.name} #{user.surname}")
212
+ # set_picture(gravatar(user.email.to_s)) if user.email
213
+ # end
214
+ # ...
215
+ # end
216
+ #
217
+ def set_picture(url="http://placekitten.com/80/80")
218
+ url = url.to_s if url.class.to_s == 'URI::HTTP'
219
+ event(:_set_picture, :url => url)
220
+ end
221
+
222
+ private
223
+
224
+ ##
225
+ # Creates a fnordmetric instance, holding the Redis connection
226
+ #
227
+ #:nocov:
228
+ def _connect
229
+ Ramaze::Log.debug("In connect")
230
+ begin
231
+ url = ancestral_trait[:fnord_redis_url]
232
+ rescue
233
+ url = "redis://localhost:6379"
234
+ ensure
235
+ @@fnord = FnordMetric::API.new(:redis_url => url)
236
+ @@redis = Redis.new(:url => url)
237
+ Ramaze::Log.debug("Connected to FnordMetric")
238
+ end
239
+ end
240
+ #:nocov:
241
+
242
+ ##
243
+ # Returns the Redis key
244
+ #
245
+ def _key
246
+ "%s.%s" % [ @@sstack_key_root, session.sid ]
247
+ end
248
+
249
+ ##
250
+ # Returns the ttl to use for internal keys
251
+ #
252
+ def _ttl
253
+ ancestral_trait[:fnord_helper_key_ttl] || Innate::Session.options.ttl
254
+ end
255
+
256
+ ##
257
+ # Holds class methods
258
+ #
259
+ # This is used to extend the calling controller so these methods are
260
+ # available at the class level
261
+ # Since helpers are only included, extending the calling controller is
262
+ # done via the 'included' hook.
263
+ #
264
+ module ClassMethods
265
+ ##
266
+ # This method replaces the original controller method with a times
267
+ # call that yields the original method.
268
+ # This allows to measure execution time for the method without manually
269
+ # modifying the method itself
270
+ #
271
+ # @param [Symbol] method the method measure
272
+ # @param [Symbol] event_name the name of the event to send to Fnordmetric.
273
+ # @param [Hash] args a hash of supplemental data to send
274
+ #
275
+ # @example Measuring execution time for a controller action
276
+ #
277
+ # class Users < Controller
278
+ # helper :user, :gravatar, :fnordmetric
279
+ # ...
280
+ # def login
281
+ # ...
282
+ # # whatever login does
283
+ # ...
284
+ # end
285
+ # clock :login, :performance, :some_field => "some value"
286
+ #
287
+ def clock(method, event_name, args = {})
288
+ # Let's alias the original controller method to original_name
289
+ original = "__fnordmetric_%s" % method
290
+
291
+ # We merge the method name in the args that will be sent in the event
292
+ args.merge!(:method => "#{self.name}##{method}")
293
+
294
+ self.class_eval do
295
+ # Let's create a shiny new method replacing the old one
296
+ alias_method original, method
297
+ private original
298
+ define_method(method) { |*a| times(event_name, args) do send(original, *a) end }
299
+ end
300
+ Ramaze::Log.debug("Clo(a)cking enabled for %s (renamed as %s)" % [ method, original ])
301
+ end
302
+ end
303
+
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,39 @@
1
+ #:nodoc:
2
+ module Bacon
3
+ #:nodoc:
4
+ module ColorOutput
5
+ #:nodoc:
6
+ def handle_specification(name)
7
+ puts spaces + name
8
+ yield
9
+ puts if Counter[:context_depth] == 1
10
+ end
11
+
12
+ #:nodoc:
13
+ def handle_requirement(description)
14
+ error = yield
15
+
16
+ if !error.empty?
17
+ puts "#{spaces} \e[31m- #{description} [FAILED]\e[0m"
18
+ else
19
+ puts "#{spaces} \e[32m- #{description}\e[0m"
20
+ end
21
+ end
22
+
23
+ #:nodoc:
24
+ def handle_summary
25
+ print ErrorLog if Backtraces
26
+ puts "%d specifications (%d requirements), %d failures, %d errors" %
27
+ Counter.values_at(:specifications, :requirements, :failed, :errors)
28
+ end
29
+
30
+ #:nodoc:
31
+ def spaces
32
+ if Counter[:context_depth] === 0
33
+ Counter[:context_depth] = 1
34
+ end
35
+
36
+ return ' ' * (Counter[:context_depth] - 1)
37
+ end
38
+ end # ColorOutput
39
+ end # Bacon