Module: DulyNoted
- Extended by:
- DulyNoted
- Included in:
- DulyNoted
- Defined in:
- lib/duly_noted.rb,
lib/duly_noted/helpers.rb,
lib/duly_noted/updater.rb,
lib/duly_noted/version.rb
Overview
-
‘track`
-
‘update`
-
‘query`
-
‘count`
-
‘chart`
Defined Under Namespace
Modules: Helpers, Updater Classes: InvalidId, InvalidOptions, InvalidStep, NotValidMetric, UpdateError
Constant Summary collapse
- VERSION =
"1.0.1"
Instance Method Summary collapse
-
#chart(metric_name, options = {}) ⇒ Object
##Chart.
-
#count(metric_name, options = {}) ⇒ Object
##Count.
-
#count_x_by_y(metric_name, meta_field, options) ⇒ Object
##Magic.
-
#method_missing(method, *args, &block) ⇒ Object
##Behind the curtain (metaprogramming).
-
#query(metric_name, options = {}) ⇒ Object
##Query.
- #redis ⇒ Object
-
#redis=(url) ⇒ Object
##Redis.
-
#track(metric_name, options = {}) ⇒ Object
DulyNoted.track(“page_views”, for: “home”, meta: “chrome”).
-
#update(id, options = {}) ⇒ Object
##Update.
Methods included from Updater
Methods included from Helpers
#assemble_for, #build_key, #fields_for, #find_keys, #metrics, #normalize, #parse_time_range, #valid_metric?
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, &block) ⇒ Object
##Behind the curtain (metaprogramming)
###method_missing
As I’m sure you’re aware, method_missing is the magic tool for ruby developers to define dynamic methods like the above ‘count_x_by_y`, which is exactly what we use it for.
340 341 342 343 344 345 346 |
# File 'lib/duly_noted.rb', line 340 def method_missing(method, *args, &block) if method.to_s =~ /^count_(.+)_by_(.+)$/ count_x_by_y($1, $2, args[0]) else super end end |
Instance Method Details
#chart(metric_name, options = {}) ⇒ Object
##Chart
_parameters: ‘metric_name`, `data_points`(required),`for`(optional), `time_start`(optional), `time_end`(optional), `time_range`(optional)_
Chart is a little complex, but I’ll try to explain all of the possibilities. It’s main purpose is to pull out your data and prepare it in a way that makes it easy to chart. The smallest amount of input it will take is just a ‘metric_name` and an amount of `data_points` to capture. This will check the time of the earliest known data point, and the time of the last known data point, and run chart with those values as the `time_start` and `time_end` respectively. It will take the amount of time that that spans, and divide it by the number of data points you asked for, and will split the time up evenly, and return a hash of times, and counts. If you specify both a `time_start` and a `time_end`, and a number of `data_points`, then it will divide the amount of time that that spans and will return a hash of times and counts. The other option is that you can specify either a `time_start` OR a `time_end` and a `step` and a number of `data_points`. This will start at whatever time you specified, and (if it’s ‘time_end`) count down by the step (if you specified `time_start`, it would count up), as many times as the number of data points you requested.
###Usage
DulyNoted.chart("page_views",
:time_range => 1.month.ago..Time.now,
:step => 1.day)
DulyNoted.chart("page_views",
:time_range => 1.day.ago..Time.now,
:data_points => 12)
DulyNoted.chart("page_views",
:time_start => 1.day.ago,
:step => 1.hour,
:data_points => 12)
DulyNoted.chart("downloads",
:time_end => Time.now,
:step => 1.month,
:data_points => 12)
DulyNoted.chart("page_views",
:data_points => 100)
Chart can be a little confusing but it’s pretty powerful, so play around with it.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/duly_noted.rb', line 269 def chart(metric_name, ={}) parse_time_range() chart = Hash.new(0) if [:time_start] && [:time_end] time = [:time_start] if [:data_points] total_time = [:time_end] - [:time_start] [:step] = total_time.to_i / [:data_points] end while time < [:time_end] chart[time.to_i] = DulyNoted.count(metric_name, :time_start => time, :time_end => time+[:step], :for => [:for]) time += [:step] end elsif [:step] && [:data_points] && ([:time_end] || [:time_start]) raise InvalidStep if [:step] == 0 [:step] *= -1 if ([:step] > 0 && [:time_end]) || ([:step] < 0 && [:time_start]) time = [:time_start] || [:time_end] step = [:step] [:data_points].times do [:time_start] = time [:time_start] += step if step < 0 [:time_end] = time [:time_end] += step if step > 0 chart[time.to_i] = DulyNoted.count(metric_name, ) time += step end elsif [:data_points] key = build_key(metric_name) key << assemble_for() [:time_start] = Time.at(DulyNoted.redis.zrange(key, 0, 0, :withscores => true)[1].to_f) [:time_end] = Time.at(DulyNoted.redis.zrevrange(key, 0, 0, :withscores => true)[1].to_f) chart = DulyNoted.chart(metric_name, ) else raise InvalidOptions end return chart end |
#count(metric_name, options = {}) ⇒ Object
##Count
_parameters: ‘metric_name`, `for`(optional), `time_start`(optional), `time_end`(optional), `time_range`(optional)_
Count will return the number of events logged in a given time range, or if no time range is given, the total count. As with ‘#query`, you can specify `for` to return a subset of counts, or you can leave it off to get the count across the whole `metric_name`.
###Usage
DulyNoted.count("page_views",
for: "home_page",
time_start: 1.day.ago,
time_end: Time.now)
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/duly_noted.rb', line 218 def count(metric_name, ={}) parse_time_range() key = build_key(metric_name) key << assemble_for() keys = find_keys(key) sum = 0 if [:time_start] && [:time_end] keys.each do |key| sum += DulyNoted.redis.zcount(key, [:time_start].to_f, [:time_end].to_f) end return sum else keys.each do |key| sum += DulyNoted.redis.zcard(key) end return sum end end |
#count_x_by_y(metric_name, meta_field, options) ⇒ Object
##Magic
###count_x_by_y
If you want to count a number of events by a meta field, you can use this magic command. So imagine this scenario:
DulyNoted.track("page_views", meta: {browser: "chrome"})
And you wanted to see a break down of page views by various browsers, you can call ‘DulyNoted.count_page_views_by_browser` and you’d get a hash that looked something like this:
{"chrome" => 2913, "firefox" => 5281, "IE" => 7182, "safari" => 3213}
So that method will work as soon as you’ve tracked something with that metric name. If you try to call the method on a metric that you haven’t yet tracked you will get a ‘DulyNoted::NotValidMetric`. But if you reference a meta field that didn’t exist, you’d just get a hash that looks like
{nil => 1}
323 324 325 326 327 328 329 330 331 332 |
# File 'lib/duly_noted.rb', line 323 def count_x_by_y(metric_name, , ) ||= {} = {:meta_fields => []}.merge() = query(metric_name, ) result = Hash.new(0) .each do || result[[]] += 1 end result end |
#query(metric_name, options = {}) ⇒ Object
##Query
_parameters: ‘metric_name`, `for`(optional), `meta_fields`(optional), `time_start`(optional), `time_end`(optional), `time_range`(optional)_
Query will return an array of all the metadata in chronological order from a time range, or for the whole data set. If for is specified, it will limit it by that context. For instance, if you have ‘track`ed several page views with `for` set to the name of the page that was viewed, you could query with `for` set to `home_page` to get all of the metadata from the page views from the home page, or you could leave off the `for`, and return all of the metadata for all of the page views, across all pages.
###Usage
DulyNoted.query("page_views",
for: "home_page",
time_start: 1.day.ago,
time_end: Time.now)
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/duly_noted.rb', line 160 def query(metric_name, ={}) key = build_key(metric_name) parse_time_range() key << assemble_for() if [:id] key = "dnid:#{[:id]}" real_key = DulyNoted.redis.get key if [:meta_fields] [:meta_fields].collect! { |x| x.to_s } result = {} [:meta_fields].each do |field| result[field] = DulyNoted.redis.hget real_key, field end results = [result] else results = [DulyNoted.redis.hgetall(real_key)] end else keys = find_keys(key) grab_results = Proc.new do |metric| if [:meta_fields] [:meta_fields].collect! { |x| x.to_s } result = {} [:meta_fields].each do |field| result[field] = DulyNoted.redis.hget metric, field end result else DulyNoted.redis.hgetall metric end end results = [] if [:time_start] && [:time_end] keys.each do |key| results += DulyNoted.redis.zrangebyscore(key, [:time_start].to_f, [:time_end].to_f).collect(&grab_results) end else keys.each do |key| results += DulyNoted.redis.zrange(key, 0, -1).collect(&grab_results) end end end return results end |
#redis ⇒ Object
360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/duly_noted.rb', line 360 def redis @redis ||= ( url = URI(@redis_url || "redis://127.0.0.1:6379/0") check_schema(Redis.new({ :host => url.host, :port => url.port, :db => url.path[1..-1], :password => url.password })) ) end |
#redis=(url) ⇒ Object
354 355 356 357 358 |
# File 'lib/duly_noted.rb', line 354 def redis=(url) @redis = nil @redis_url = url redis end |
#track(metric_name, options = {}) ⇒ Object
DulyNoted.track(“page_views”,
for: "home",
meta: {browser: "chrome"})
DulyNoted.track("video_plays",
for: ["user_7261", "video_917216"],
meta: {amount_watched: 0})
DulyNoted.track("purchases",
for: "user_281",
generated_at: 1.day.ago)
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/duly_noted.rb', line 106 def track(metric_name, ={}) = {:generated_at => Time.now}.merge() key = build_key(metric_name) key_without_for = key.dup id = DulyNoted.redis.incr "dnid" key << assemble_for() DulyNoted.redis.pipelined do DulyNoted.redis.sadd build_key("metrics", false), key_without_for DulyNoted.redis.zadd key, [:generated_at].to_f, "#{key}:#{id}:meta" DulyNoted.redis.set "dnid:#{id}", "#{key}:#{id}:meta" # set alias key if [:meta] # set meta data DulyNoted.redis.mapped_hmset "#{key}:#{id}:meta", [:meta] [:meta].keys.each do |field| DulyNoted.redis.sadd "#{key}:fields", field end end end id end |