fnordmetric 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +335 -0
- data/VERSION +1 -1
- data/fnordmetric.gemspec +7 -4
- data/haml/app.haml +6 -1
- data/lib/fnordmetric.rb +1 -1
- data/lib/fnordmetric/app.rb +5 -1
- data/lib/fnordmetric/inbound_stream.rb +1 -1
- data/lib/fnordmetric/worker.rb +1 -1
- data/pub/fnordmetric.css +10 -2
- data/pub/fnordmetric.js +138 -133
- metadata +166 -158
- data/readme.rdoc +0 -337
data/README.md
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
FnordMetric
|
2
|
+
===========
|
3
|
+
|
4
|
+
FnordMetric is a highly configurable (and pretty fast) realtime app/event tracking thing based on ruby eventmachine and redis. You define your own plotting and counting functions as ruby blocks!
|
5
|
+
|
6
|
+
[ ![Build status - Travis-ci](https://secure.travis-ci.org/paulasmuth/fnordmetric.png) ](http://travis-ci.org/paulasmuth/fnordmetric)
|
7
|
+
|
8
|
+
[SCREENCAST](http://www.screenr.com/KiJs): the FnordMetric-instance we use to track our social dating app.
|
9
|
+
|
10
|
+
----
|
11
|
+
|
12
|
+
FnordMetric keeps track of your data and draws nice timeline plots.
|
13
|
+
|
14
|
+
[ ![Nice timeline plots](https://raw.github.com/paulasmuth/fnordmetric/master/doc/preview1.png) ](https://raw.github.com/paulasmuth/fnordmetric/master/doc/preview1.png)
|
15
|
+
|
16
|
+
FnordMetric gives you a live dashboard, that shows who is using your app in realtime. You can select a single user and follow them step by step.
|
17
|
+
|
18
|
+
[ ![Live dashboard](https://raw.github.com/paulasmuth/fnordmetric/master/doc/preview2.png) ](https://raw.github.com/paulasmuth/fnordmetric/master/doc/preview2.png)
|
19
|
+
|
20
|
+
|
21
|
+
Getting Started
|
22
|
+
---------------
|
23
|
+
|
24
|
+
Copy `doc/ulm_stats.rb` (that's the configuration from the screenshots and screencast) or the simple example from below to `my_stats_app.rb`.
|
25
|
+
|
26
|
+
Simple Example: this will listen for json-events with `type=unicorn_seen` and render a timeline-plot showing the number of received events per hour.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require "fnordmetric"
|
30
|
+
|
31
|
+
FnordMetric.namespace :myapp do
|
32
|
+
|
33
|
+
# numeric (delta) gauge, 1-hour tick
|
34
|
+
gauge :unicorns_seen_per_hour,
|
35
|
+
:tick => 1.hour.to_i,
|
36
|
+
:title => "Unicorns seenper Hour"
|
37
|
+
|
38
|
+
# on every event like { _type: 'unicorn_seen' }
|
39
|
+
event(:unicorn_seen) do
|
40
|
+
# increment the unicorns_seen_per_hour gauge by 1
|
41
|
+
incr :unicorns_seen_per_hour
|
42
|
+
end
|
43
|
+
|
44
|
+
# draw a timeline showing the gauges value, auto-refresh every 2s
|
45
|
+
widget 'Overview', {
|
46
|
+
:title => "Unicorn-Sightings per Hour",
|
47
|
+
:type => :timeline,
|
48
|
+
:gauges => :unicorns_seen_per_hour,
|
49
|
+
:include_current => true,
|
50
|
+
:autoupdate => 2
|
51
|
+
}
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
FnordMetric.standalone
|
56
|
+
```
|
57
|
+
|
58
|
+
Start the app (requires ruby >= 1.9.2):
|
59
|
+
|
60
|
+
$ ruby my_stats_app.rb run
|
61
|
+
|
62
|
+
Log all incoming events:
|
63
|
+
|
64
|
+
$ ruby my_stats_app.rb log DUMP_FILE=/tmp/foo.json
|
65
|
+
|
66
|
+
This is the easiest way to submit an event:
|
67
|
+
|
68
|
+
echo "{\"_type\": \"unicorn_seen\"}" | nc localhost 1337
|
69
|
+
|
70
|
+
|
71
|
+
Installation
|
72
|
+
------------
|
73
|
+
|
74
|
+
gem install fnordmetric
|
75
|
+
|
76
|
+
or in your Gemfile:
|
77
|
+
|
78
|
+
gem 'fnordmetric', '~> 0.5'
|
79
|
+
|
80
|
+
|
81
|
+
Documentation
|
82
|
+
-------------
|
83
|
+
|
84
|
+
### Sending Events ###
|
85
|
+
|
86
|
+
The slow way: HTTP-Post the json event to the fnordmetric webinterface.
|
87
|
+
|
88
|
+
POST http://localhost:2323/events _type=unicorn_seen
|
89
|
+
|
90
|
+
curl -X POST -d "_type=unicorn_seen" http://localhost:4242/events
|
91
|
+
|
92
|
+
The easy way: Stream one ore more newline-seperated json encoded events through a tcp connection.
|
93
|
+
|
94
|
+
echo "\{\"_type\": \"unicorn_seen\"\}\n" | nc localhost 2323
|
95
|
+
|
96
|
+
The fast way: Add your event directly to the redis-based queue.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
uuid = (8**32).to_s(36)
|
100
|
+
event = { :_type => "unicorn_seen" }.to_json
|
101
|
+
|
102
|
+
redis.set("fnordmetric-event-#{my_uuid}", event)
|
103
|
+
redis.expire("fnordmetric-event-#{my_uuid}", 60)
|
104
|
+
redis.lpush("fnordmetric-queue", uuid)
|
105
|
+
```
|
106
|
+
|
107
|
+
----
|
108
|
+
|
109
|
+
### Special Events ###
|
110
|
+
|
111
|
+
```js
|
112
|
+
// track a pageview
|
113
|
+
{ "_type": "_pageview", "url": "/blob/my_super_seo_article", "_session": "mysessiontoken" }
|
114
|
+
|
115
|
+
// set the user name
|
116
|
+
{ "_type": "_set_name", "name": "Tingle Tangle Bob", "_session": "mysessiontoken" }
|
117
|
+
|
118
|
+
// set the user picture
|
119
|
+
{ "_type": "_set_picture", "url": "http://myhost/123.jpg", "_session": "mysessiontoken" }
|
120
|
+
```
|
121
|
+
|
122
|
+
----
|
123
|
+
|
124
|
+
### Event Handlers ###
|
125
|
+
|
126
|
+
Call these methods from the event-handler block
|
127
|
+
|
128
|
+
incr(gauge_name, value=1):
|
129
|
+
Increment the given (two-dimensional) gauge by value at the tick specified by event-time
|
130
|
+
|
131
|
+
incr_field(gauge_name, field_name, value=1):
|
132
|
+
Increment the given field on a three-dimensional gauge by value at the tick specified by event-time
|
133
|
+
|
134
|
+
set_value(gauge_name, value)
|
135
|
+
Set the given (two-dimensional) to value at the tick specified by event-time (overwrite existing value)
|
136
|
+
|
137
|
+
set_field(gauge_name, field_name, value)
|
138
|
+
Set the given field on a three-dimensional gauge to value at the tick specified by event-time (overwrite existing value)
|
139
|
+
|
140
|
+
----
|
141
|
+
|
142
|
+
### Options: Gauges ###
|
143
|
+
|
144
|
+
----
|
145
|
+
|
146
|
+
### Options: Widgets ###
|
147
|
+
|
148
|
+
+ `[autoupdate]` auto-refresh the timeline every n secs (0 turns autoupdate off)
|
149
|
+
|
150
|
+
TimelineWidget
|
151
|
+
|
152
|
+
+ `[plot_style]` one of: line, areaspline
|
153
|
+
+ `[include_current]` show the current tick?
|
154
|
+
+ `[ticks]` number of ticks to show (defaults to 24/30)
|
155
|
+
|
156
|
+
BarsWidget
|
157
|
+
|
158
|
+
+ `[plot_style]` one of: vertical, horizontal
|
159
|
+
+ `[order_by]`: order bars/columns by: value, field
|
160
|
+
|
161
|
+
|
162
|
+
----
|
163
|
+
|
164
|
+
### JSON API ###
|
165
|
+
|
166
|
+
|
167
|
+
Examples
|
168
|
+
--------
|
169
|
+
|
170
|
+
+ doc/ulm_stats.rb
|
171
|
+
|
172
|
+
|
173
|
+
## Full Example ##
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
require "fnordmetric"
|
177
|
+
|
178
|
+
FnordMetric.namespace :myapp do
|
179
|
+
|
180
|
+
# numeric (delta) gauge, 1-hour tick
|
181
|
+
gauge :messages_sent,
|
182
|
+
:tick => 1.hour.to_i,
|
183
|
+
:title => "Messages (sent) per Hour"
|
184
|
+
|
185
|
+
# numeric (delta) gauge, 1-hour tick
|
186
|
+
gauge :messages_read,
|
187
|
+
:tick => 1.hour.to_i,
|
188
|
+
:title => "Messages (read) per Hour"
|
189
|
+
|
190
|
+
# numeric (progressive) gauge, 1-hour tick
|
191
|
+
gauge :events_total,
|
192
|
+
:tick => 1.day.to_i,
|
193
|
+
:progressive => true,
|
194
|
+
:title => "Events (total)"
|
195
|
+
|
196
|
+
# numeric (delta) gauge, increments uniquely by session_key
|
197
|
+
gauge :pageviews_daily_unique,
|
198
|
+
:tick => 1.day.to_i,
|
199
|
+
:unique => true,
|
200
|
+
:title => "Unique Visits (Daily)"
|
201
|
+
|
202
|
+
# numeric (delta) gauge, increments uniquely by session_key, returns average
|
203
|
+
gauge :avg_age_per_session,
|
204
|
+
:tick => 1.day.to_i,
|
205
|
+
:unique => true,
|
206
|
+
:average => true,
|
207
|
+
:title => "Avg. User Age"
|
208
|
+
|
209
|
+
# three-dimensional (delta) gauge (time->key->value)
|
210
|
+
gauge :pageviews_per_url_daily,
|
211
|
+
:tick => 1.day.to_i,
|
212
|
+
:title => "Daily Pageviews per URL",
|
213
|
+
:three_dimensional => true
|
214
|
+
|
215
|
+
|
216
|
+
# on every event like { "_type": "message_sent" }
|
217
|
+
event(:message_sent) do
|
218
|
+
# increment the messages_sent gauge by 1
|
219
|
+
incr :messages_sent
|
220
|
+
end
|
221
|
+
|
222
|
+
# on every event like { "_type": "message_read" }
|
223
|
+
event(:message_read) do
|
224
|
+
# increment the messages_read gauge by 1
|
225
|
+
incr :messages_read
|
226
|
+
end
|
227
|
+
|
228
|
+
# on _every_ event
|
229
|
+
event :"*" do
|
230
|
+
# increment the events_total gauge by 1
|
231
|
+
incr :events_total
|
232
|
+
end
|
233
|
+
|
234
|
+
# on every event like { "_type": "_pageview", "_session": "sbz7jset", "url": "/page2" }
|
235
|
+
event :_pageview do
|
236
|
+
# increment the daily_uniques gauge by 1 if session_key hasn't been seen in this tick yet
|
237
|
+
incr :pageviews_daily_unique
|
238
|
+
# increment the pageviews_per_url_daily gauge by 1 where key = 'page2'
|
239
|
+
incr_field :pageviews_per_url_daily, data[:url]
|
240
|
+
end
|
241
|
+
|
242
|
+
# on every event like { "_type": "_my_set_age", "my_age_field": "23" }
|
243
|
+
event(:my_set_age) do
|
244
|
+
# add the value of my_set_age to the avg_age_per_session gauge if session_key
|
245
|
+
# hasn't been seen in this tick yet
|
246
|
+
incr :avg_age_per_session, data[:my_age_field]
|
247
|
+
end
|
248
|
+
|
249
|
+
# draw a timeline showing the pageviews_daily_unique, auto-refresh every 30s
|
250
|
+
widget 'Overview', {
|
251
|
+
:title => "Unique Visits per Day",
|
252
|
+
:type => :timeline,
|
253
|
+
:width => 70,
|
254
|
+
:gauges => :pageviews_daily_unique,
|
255
|
+
:include_current => true,
|
256
|
+
:autoupdate => 30
|
257
|
+
}
|
258
|
+
|
259
|
+
# draw the values of the messages_sent and messages_read gauge at the current tick, three ticks ago, and
|
260
|
+
# the sum of the last 10 ticks, auto-refresh every 20s
|
261
|
+
widget 'Overview', {
|
262
|
+
:title => "Messages Sent / Read",
|
263
|
+
:type => :numbers,
|
264
|
+
:width => 30,
|
265
|
+
:autoupdate => 20,
|
266
|
+
:offsets => [0,3,"10s"],
|
267
|
+
:gauges => [ :messages_sent, :messages_read ]
|
268
|
+
}
|
269
|
+
|
270
|
+
# draw a list of the most visited urls (url, visits + percentage), auto-refresh every 20s
|
271
|
+
widget 'Overview', {
|
272
|
+
:title => "Top Pages",
|
273
|
+
:type => :toplist,
|
274
|
+
:autoupdate => 20,
|
275
|
+
:gauges => [ :pageviews_per_url_daily ]
|
276
|
+
}
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
FnordMetric.standalone
|
281
|
+
```
|
282
|
+
|
283
|
+
Contributors
|
284
|
+
------------
|
285
|
+
|
286
|
+
+ Simon Menke (http://github.com/fd)
|
287
|
+
+ Bruno Michel (http://github.com/nono)
|
288
|
+
+ Marco Borromeo (http://github.com/mborromeo)
|
289
|
+
+ Leo Lou (http://github.com/l4u)
|
290
|
+
+ Andy Lindeman (http://github.com/alindeman)
|
291
|
+
|
292
|
+
To contribute, please fork this repository, make your changes and run the specs, commit them to your github repository and send me a pull request.
|
293
|
+
|
294
|
+
|
295
|
+
License
|
296
|
+
-------
|
297
|
+
|
298
|
+
Copyright (c) 2011 Paul Asmuth
|
299
|
+
|
300
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
301
|
+
a copy of this software and associated documentation files (the
|
302
|
+
"Software"), to use, copy and modify copies of the Software, subject
|
303
|
+
to the following conditions:
|
304
|
+
|
305
|
+
The above copyright notice and this permission notice shall be
|
306
|
+
included in all copies or substantial portions of the Software.
|
307
|
+
|
308
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
309
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
310
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
311
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
312
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
313
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
314
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
315
|
+
|
316
|
+
|
317
|
+
Todos
|
318
|
+
-----
|
319
|
+
|
320
|
+
* funnel-widget
|
321
|
+
* combine/calculation gauges via opts_gauge({}, &block) (+calculate ctr)
|
322
|
+
* timeline_widget: 'compare mode': compate gauge to yesterday
|
323
|
+
* numbers_widget: handle decreasing vals
|
324
|
+
* make listen-ports configurable
|
325
|
+
* referal tracking fu (parse googlequeries)
|
326
|
+
* trend detection
|
327
|
+
* opt_event options: :increment => gauge_name
|
328
|
+
* preconfigured default-dashboard (like google analytics)
|
329
|
+
* pagview+ref-tracking via js-tracking-pixel
|
330
|
+
* table/gauge-list-widget (with mini-stats!)
|
331
|
+
* prune the namespace-sessions-timline (remove event_ids older than x)
|
332
|
+
* prune the namespace-event-types-list (trim to max items)
|
333
|
+
* timelinewidget + numberswidget => should use redis hmget
|
334
|
+
* get multiple metrics in a single http get
|
335
|
+
* `{ _namespace: myns }` field
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.6
|
data/fnordmetric.gemspec
CHANGED
@@ -5,17 +5,21 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "fnordmetric"
|
8
|
-
s.version = "0.5.
|
8
|
+
s.version = "0.5.6"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Paul Asmuth"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2012-01-03"
|
13
13
|
s.description = "FnordMetric is a Ruby Event-Tracking gem on steroids"
|
14
14
|
s.email = "paul@paulasmuth.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
15
18
|
s.files = [
|
16
19
|
".travis.yml",
|
17
20
|
"Gemfile",
|
18
21
|
"Gemfile.lock",
|
22
|
+
"README.md",
|
19
23
|
"Rakefile",
|
20
24
|
"VERSION",
|
21
25
|
"doc/preview1.png",
|
@@ -60,7 +64,6 @@ Gem::Specification.new do |s|
|
|
60
64
|
"pub/sprite.png",
|
61
65
|
"pub/vendor/highcharts.js",
|
62
66
|
"pub/vendor/jquery-1.6.1.min.js",
|
63
|
-
"readme.rdoc",
|
64
67
|
"spec/app_spec.rb",
|
65
68
|
"spec/context_spec.rb",
|
66
69
|
"spec/dashboard_spec.rb",
|
@@ -76,7 +79,7 @@ Gem::Specification.new do |s|
|
|
76
79
|
s.homepage = "http://github.com/paulasmuth/fnordmetric"
|
77
80
|
s.licenses = ["MIT"]
|
78
81
|
s.require_paths = ["lib"]
|
79
|
-
s.rubygems_version = "1.8.
|
82
|
+
s.rubygems_version = "1.8.10"
|
80
83
|
s.summary = "FnordMetric is a Ruby Event-Tracking gem on steroids"
|
81
84
|
s.test_files = [
|
82
85
|
"spec/app_spec.rb",
|
data/haml/app.haml
CHANGED
@@ -14,7 +14,11 @@
|
|
14
14
|
FnordMetric.p = '#{path_prefix}';
|
15
15
|
|
16
16
|
%body
|
17
|
-
.topbar
|
17
|
+
.topbar{:class => namespaces.count > 1 ? 'shown' : 'hidden'}
|
18
|
+
%ul
|
19
|
+
-namespaces.each do |key,namespace|
|
20
|
+
%li{:class => namespace.token == current_namespace.token ? 'active' : nil}
|
21
|
+
%a{:href=>"/#{namespace.token}"}=h namespace.token
|
18
22
|
|
19
23
|
#wrap
|
20
24
|
#tabs
|
@@ -44,6 +48,7 @@
|
|
44
48
|
|
45
49
|
$('#tabs li.sessions').click(function(){
|
46
50
|
FnordMetric.renderSessionView();
|
51
|
+
window.location.hash = '';
|
47
52
|
});
|
48
53
|
|
49
54
|
$('#tabs li').click(function(){
|
data/lib/fnordmetric.rb
CHANGED
data/lib/fnordmetric/app.rb
CHANGED
@@ -16,7 +16,7 @@ class FnordMetric::App < Sinatra::Base
|
|
16
16
|
|
17
17
|
def initialize(namespaces, opts)
|
18
18
|
@namespaces = {}
|
19
|
-
@redis = Redis.
|
19
|
+
@redis = Redis.connect(:url => opts[:redis_url])
|
20
20
|
@opts = opts
|
21
21
|
namespaces.each do |key, block|
|
22
22
|
@namespaces[key] = FnordMetric::Namespace.new(key, opts.clone)
|
@@ -34,6 +34,10 @@ class FnordMetric::App < Sinatra::Base
|
|
34
34
|
request.env["SCRIPT_NAME"]
|
35
35
|
end
|
36
36
|
|
37
|
+
def namespaces
|
38
|
+
@namespaces
|
39
|
+
end
|
40
|
+
|
37
41
|
def current_namespace
|
38
42
|
@namespaces[@namespaces.keys.detect{ |k|
|
39
43
|
k.to_s == params[:namespace]
|