nunes 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # 0.2.0
2
+
3
+ ## Backwards Compatibility Break
4
+
5
+ * No longer using the inflector to pretty up metric names. This means that when you upgrade from 0.1 and 0.2 some metrics will change names.
6
+ * Namespaced several of the stats to make them easier to graph.
7
+
8
+ ## Fixed
9
+
10
+ * Instrumenting namespaced controllers
11
+
12
+ # 0.1.0
13
+
14
+ * Initial release.
data/Gemfile CHANGED
@@ -9,3 +9,7 @@ gem "rake"
9
9
  group :watch do
10
10
  gem "rb-fsevent", require: false
11
11
  end
12
+
13
+ group :bench do
14
+ gem "rblineprof"
15
+ end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Nunes
1
+ # nunes
2
2
 
3
3
  The friendly gem that instruments everything for you, like I would if I could.
4
4
 
@@ -10,7 +10,7 @@ Because I don't work for you, but even that could not stop me from trying to mak
10
10
 
11
11
  Add this line to your application's Gemfile:
12
12
 
13
- gem 'nunes'
13
+ gem "nunes"
14
14
 
15
15
  Or install it yourself as:
16
16
 
@@ -18,28 +18,31 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- nunes works out of the box with statsd and instrument_agent. All you need to do is subscribe using an instance of statsd or instrumental's agent and you are good to go.
21
+ nunes works out of the box with [instrumental app](http://instrumentalapp.com) (my person favorite) and [statsd](https://github.com/reinh/statsd). All you need to do is subscribe using an instance of statsd or instrumental's agent and you are good to go.
22
22
 
23
- ### With Statsd
23
+ ### With Instrumental
24
24
 
25
25
  ```ruby
26
- require 'nunes'
26
+ require "nunes"
27
+ I = Instrument::Agent.new(...)
28
+ Nunes.subscribe(I)
29
+ ```
30
+
31
+ ### With Statsd
27
32
 
33
+ ```ruby
34
+ require "nunes"
28
35
  statsd = Statsd.new(...)
29
36
  Nunes.subscribe(statsd)
30
37
  ```
31
38
 
32
- ### With Instrumental
39
+ ### With Some Other Service
33
40
 
34
- ```ruby
35
- require 'nunes'
36
- I = Instrument::Agent.new(...)
37
- Nunes.subscribe(I)
38
- ```
41
+ If you would like for nunes to work with some other service, you can easily make an adapter. Check out the [existing adapters](https://github.com/jnunemaker/nunes/tree/master/lib/nunes/adapters) for examples. The key is to inherit from `Nunes::Adapter` and then convert the `increment` and `timing` methods to whatever the service requires.
39
42
 
40
43
  ## What Can I Do For You?
41
44
 
42
- If you are using nunes with rails, out of the box, I'll subscribe to Actve Support's notifications for:
45
+ If you are using nunes with Rails, I will subscribe to the following events:
43
46
 
44
47
  * `process_action.action_controller`
45
48
  * `render_template.action_view`
@@ -56,39 +59,44 @@ If you are using nunes with rails, out of the box, I'll subscribe to Actve Suppo
56
59
 
57
60
  Whoa! You would do all that for me? Yep, I would. Because I care. Deeply.
58
61
 
59
- Based on those events, you'll get metrics like this in statsd and instrumental:
62
+ Based on those events, you'll get metrics like this in instrumental and statsd:
60
63
 
61
64
  #### Counters
62
65
 
63
66
  * `action_controller.status.200`
64
67
  * `action_controller.format.html`
65
68
  * `action_controller.exception.RuntimeError` - where RuntimeError is the class of any exceptions that occur while processing a controller's action.
66
- * `active_support.cache_hit`
67
- * `active_support.cache_miss`
69
+ * `active_support.cache.hit`
70
+ * `active_support.cache.miss`
68
71
 
69
72
  #### Timers
70
73
 
71
- * `action_controller.runtime`
72
- * `action_controller.view_runtime`
73
- * `action_controller.db_runtime`
74
- * `action_controller.posts.index.runtime` - where `posts` is the controller and `index` is the action
75
- * `action_view.app.views.posts.index.html.erb` - where `app.views.posts.index.html.erb` is the path of the view file
76
- * `action_view.app.views.posts._post.html.erb` - I can even do partials! woot woot!
77
- * `action_mailer.deliver.post_mailer` - where `post_mailer` is the name of the mailer
78
- * `action_mailer.receive.post_mailer` - where `post_mailer` is the name of the mailer
74
+ * `action_controller.runtime.total`
75
+ * `action_controller.runtime.view`
76
+ * `action_controller.runtime.db`
77
+ * `action_controller.controller.PostsController.index.runtime.total`
78
+ * `action_controller.controller.PostsController.index.runtime.view`
79
+ * `action_controller.controller.PostsController.index.runtime.db`
80
+ * `action_view.template.app.views.posts.index.html.erb` - where `app.views.posts.index.html.erb` is the path of the view file
81
+ * `action_view.partial.app.views.posts._post.html.erb` - I can even do partials! woot woot!
82
+ * `action_mailer.deliver.PostMailer`
83
+ * `action_mailer.receive.PostMailer`
79
84
  * `active_record.sql`
80
- * `active_record.sql.select` - also supported are insert, update, delete, transaction_begin and transaction_commit
81
- * `active_support.cache_read`
82
- * `active_support.cache_generate`
83
- * `active_support.cache_fetch`
84
- * `active_support.cache_fetch_hit`
85
- * `active_support.cache_write`
86
- * `active_support.cache_delete`
87
- * `active_support.cache_exist`
85
+ * `active_record.sql.select`
86
+ * `active_record.sql.insert`
87
+ * `active_record.sql.update`
88
+ * `active_record.sql.delete`
89
+ * `active_support.cache.read`
90
+ * `active_support.cache.fetch`
91
+ * `active_support.cache.fetch_hit`
92
+ * `active_support.cache.fetch_generate`
93
+ * `active_support.cache.write`
94
+ * `active_support.cache.delete`
95
+ * `active_support.cache.exist`
88
96
 
89
97
  ### But wait, there's more!!!
90
98
 
91
- In addition to doing all that work for you out of the box, I also allow you to wrap your own code with instrumentation. I know, I know, sounds too good to be true.
99
+ In addition to doing all that automagical work for you, I also allow you to wrap your own code with instrumentation. I know, I know, sounds too good to be true.
92
100
 
93
101
  ```ruby
94
102
  class User < ActiveRecord::Base
@@ -106,7 +114,7 @@ user = User.new(name: 'NUNES!')
106
114
  user.save
107
115
  ```
108
116
 
109
- An event named `instrument_method_time.nunes` will be generated, which in turn is subscribed to and sent to whatever you used to send instrumentation to (statsd, instrumental, etc.). The metric name will default to class.method. For the example above, the metric name would be `user.save`. No fear, you can customize this.
117
+ An event named `instrument_method_time.nunes` will be generated, which in turn is subscribed to and sent to whatever you used to send instrumentation to (statsd, instrumental, etc.). The metric name will default to class.method. For the example above, the metric name would be `User.save`. No fear, you can customize this.
110
118
 
111
119
  ```ruby
112
120
  class User < ActiveRecord::Base
@@ -141,6 +149,28 @@ end
141
149
 
142
150
  If you subscribe to the event on your own, say to log some things, you'll get a key named `:pay` with a value of `"loading"` in the event's payload. Pretty neat, eh?
143
151
 
152
+ ## `script/bootstrap`
153
+
154
+ This script will get all the dependencies ready so you can start hacking on nunes.
155
+
156
+ ```
157
+ # to learn more about script/bootstrap
158
+ script/bootstrap help
159
+ ```
160
+
161
+ ## `script/test`
162
+
163
+ For your convenience, there is a script to run the tests. It will also perform `script/bootstrap`, which bundles and all that jazz.
164
+
165
+ ```
166
+ # to learn more about script test
167
+ script/test help
168
+ ```
169
+
170
+ ## `script/watch`
171
+
172
+ If you are like me, you are too lazy to continually run `script/test`. For this scenario, I have included `script/watch`, which will run `script/test` automatically anytime a relevant file changes.
173
+
144
174
  ## Contributing
145
175
 
146
176
  1. Fork it
@@ -1,7 +1,6 @@
1
1
  require "nunes/instrumentable"
2
2
 
3
3
  require "nunes/adapters/memory"
4
- require "nunes/adapters/default"
5
4
  require "nunes/adapters/timing_aliased"
6
5
 
7
6
  require "nunes/subscriber"
@@ -6,29 +6,27 @@ module Nunes
6
6
  #
7
7
  # Returns Nunes::Adapter instance.
8
8
  def self.wrap(client)
9
- if client.nil?
10
- raise ArgumentError.new("client cannot be nil")
11
- end
9
+ raise ArgumentError, "client cannot be nil" if client.nil?
10
+ return client if client.is_a?(self)
12
11
 
13
- if client.is_a?(self)
14
- return client
15
- end
12
+ adapter = adapters.detect { |adapter| adapter.wraps?(client) }
16
13
 
17
- if client.is_a?(Hash)
18
- return Adapters::Memory.new(client)
14
+ if adapter.nil?
15
+ raise ArgumentError,
16
+ "I have no clue how to wrap what you've given me (#{client.inspect})"
19
17
  end
20
18
 
21
- has_increment = client.respond_to?(:increment)
22
- has_timing = client.respond_to?(:timing)
23
- has_gauge = client.respond_to?(:gauge)
19
+ adapter.new(client)
20
+ end
24
21
 
25
- if has_increment && has_timing
26
- Adapters::Default.new(client)
27
- elsif has_increment && has_gauge && !has_timing
28
- Adapters::TimingAliased.new(client)
29
- else
30
- raise "I have no clue how to wrap what you've given me (#{client.inspect})"
31
- end
22
+ # Private
23
+ def self.wraps?(client)
24
+ client.respond_to?(:increment) && client.respond_to?(:timing)
25
+ end
26
+
27
+ # Private
28
+ def self.adapters
29
+ [Nunes::Adapter, *subclasses]
32
30
  end
33
31
 
34
32
  # Private
@@ -44,13 +42,35 @@ module Nunes
44
42
  # Internal: Increment a metric by a value. Override in subclass if client
45
43
  # interface does not match.
46
44
  def increment(metric, value = 1)
47
- @client.increment metric, value
45
+ @client.increment prepare(metric), value
48
46
  end
49
47
 
50
48
  # Internal: Record a metric's duration. Override in subclass if client
51
49
  # interface does not match.
52
50
  def timing(metric, duration)
53
- @client.timing metric, duration
51
+ @client.timing prepare(metric), duration
52
+ end
53
+
54
+ # Private: What Ruby uses to separate namespaces.
55
+ ReplaceRegex = /[^a-z0-9\-_]+/i
56
+
57
+ # Private: The default metric namespace separator.
58
+ Separator = "."
59
+
60
+ RegexSeparator = Regexp.escape(Separator)
61
+
62
+ # Private: Regex to match metric ending with separator.
63
+ StartsOrEndsWithSeparator = /\A#{RegexSeparator}|#{RegexSeparator}\Z/
64
+
65
+ # Private
66
+ Nothing = ""
67
+
68
+ # Private: Prepare a metric name before it is sent to the adapter's client.
69
+ def prepare(metric)
70
+ metric = metric.to_s.gsub(ReplaceRegex, Separator)
71
+ metric.squeeze!(Separator)
72
+ metric.gsub!(StartsOrEndsWithSeparator, Nothing)
73
+ metric
54
74
  end
55
75
  end
56
76
  end
@@ -5,17 +5,21 @@ module Nunes
5
5
  # Internal: Memory backend for recording instrumentation calls. This should
6
6
  # never need to be used directly by a user of the gem.
7
7
  class Memory < ::Nunes::Adapter
8
+ def self.wraps?(client)
9
+ client.is_a?(Hash)
10
+ end
11
+
8
12
  def initialize(client = nil)
9
13
  @client = client || {}
10
14
  clear
11
15
  end
12
16
 
13
17
  def increment(metric, value = 1)
14
- counters << [metric, value]
18
+ counters << [prepare(metric), value]
15
19
  end
16
20
 
17
21
  def timing(metric, value)
18
- timers << [metric, value]
22
+ timers << [prepare(metric), value]
19
23
  end
20
24
 
21
25
  # Internal: Returns Array of any recorded timers with durations.
@@ -8,9 +8,15 @@ module Nunes
8
8
  # adapter their gauge interface to the timing one used internally in the
9
9
  # gem. This should never need to be used directly by a user of the gem.
10
10
  class TimingAliased < ::Nunes::Adapter
11
+ def self.wraps?(client)
12
+ client.respond_to?(:increment) &&
13
+ client.respond_to?(:gauge) &&
14
+ !client.respond_to?(:timing)
15
+ end
16
+
11
17
  # Internal: Adapter timing to gauge.
12
18
  def timing(metric, duration)
13
- @client.gauge metric, duration
19
+ @client.gauge prepare(metric), duration
14
20
  end
15
21
  end
16
22
  end
@@ -1,49 +1,6 @@
1
1
  require "active_support/notifications"
2
2
 
3
3
  module Nunes
4
- # Extend and instrument. Simple class that makes it easy to instrument method
5
- # timing using ActiveSupport::Notifications.
6
- #
7
- # The event name is the name of the method being instrumented and the event
8
- # namespace is the name of the class the method is in.
9
- #
10
- # Examples
11
- #
12
- # # To instrument an instance method, extend the module and instrument it.
13
- # class User
14
- # # Only need to do this once.
15
- # extend Nunes::Instrumentable
16
- #
17
- # def something
18
- # # ...
19
- # end
20
- #
21
- # instrument_method_time :something
22
- #
23
- # # you can customize the event and namespace by providing the name option
24
- # instrument_method_time :something, {
25
- # name: "something.else.User",
26
- # }
27
- #
28
- # # you can also add additional payload items
29
- # instrument_method_time :something, {
30
- # payload: {some: 'thing'},
31
- # }
32
- # end
33
- #
34
- # # To instrument a class method, you need to extend the module on the
35
- # # singleton class and use the same to call the method.
36
- # class User
37
- # # Only need to do this once.
38
- # singleton_class.extend Nunes::Instrumentable
39
- #
40
- # def self.something_class_level
41
- # # ...
42
- # end
43
- #
44
- # singleton_class.instrument_method_time :someting_class_level
45
- # end
46
- #
47
4
  module Instrumentable
48
5
  # Private
49
6
  MethodTimeEventName = "instrument_method_time.nunes".freeze
@@ -64,18 +21,17 @@ module Nunes
64
21
  instrumenter = options.fetch(:instrumenter) { ActiveSupport::Notifications }
65
22
 
66
23
  payload[:metric] = options.fetch(:name) {
67
- "#{self.name}/#{method_name}"
68
- }.to_s.underscore.gsub('/', '.')
24
+ if name.nil?
25
+ raise ArgumentError, "For class methods you must provide the full name of the metric."
26
+ else
27
+ "#{name}.#{method_name}"
28
+ end
29
+ }
69
30
 
70
31
  nunes_wrap_method(method_name, action) do |old_method_name, new_method_name|
71
32
  define_method(new_method_name) do |*args, &block|
72
33
  instrumenter.instrument(MethodTimeEventName, payload) {
73
- result = send(old_method_name, *args, &block)
74
-
75
- payload[:arguments] = args
76
- payload[:result] = result
77
-
78
- result
34
+ send(old_method_name, *args, &block)
79
35
  }
80
36
  end
81
37
  end
@@ -32,38 +32,33 @@ module Nunes
32
32
  def call(name, start, ending, transaction_id, payload)
33
33
  # rails doesn't recommend instrumenting methods that start with bang
34
34
  # when in production
35
- return if name.starts_with?(BANG)
35
+ return if name.start_with?(BANG)
36
36
 
37
37
  method_name = name.split('.').first
38
38
 
39
39
  if respond_to?(method_name)
40
40
  send(method_name, start, ending, transaction_id, payload)
41
- else
42
- $stderr.puts "#{self.class.name} did not respond to #{method_name} therefore it cannot instrument the event named #{name}."
43
41
  end
44
42
  end
45
43
 
46
44
  # Internal: Increment a metric for the client.
47
45
  #
48
46
  # metric - The String name of the metric to increment.
47
+ # value - The Integer value to increment by.
49
48
  #
50
49
  # Returns nothing.
51
- def increment(metric)
52
- if @adapter
53
- @adapter.increment metric
54
- end
50
+ def increment(metric, value = 1)
51
+ @adapter.increment metric, value
55
52
  end
56
53
 
57
54
  # Internal: Track the timing of a metric for the client.
58
55
  #
59
56
  # metric - The String name of the metric.
60
- # duration_in_ms - The Integer duration of the event in milliseconds.
57
+ # value - The Integer duration of the event in milliseconds.
61
58
  #
62
59
  # Returns nothing.
63
- def timing(metric, duration_in_ms)
64
- if @adapter
65
- @adapter.timing metric, duration_in_ms
66
- end
60
+ def timing(metric, value)
61
+ @adapter.timing metric, value
67
62
  end
68
63
  end
69
64
  end
@@ -12,7 +12,7 @@ module Nunes
12
12
  end
13
13
 
14
14
  def process_action(start, ending, transaction_id, payload)
15
- controller = payload[:controller].to_s.gsub('Controller', '').underscore
15
+ controller = payload[:controller]
16
16
  action = payload[:action]
17
17
  status = payload[:status]
18
18
  exception_info = payload[:exception]
@@ -28,19 +28,19 @@ module Nunes
28
28
 
29
29
  runtime = ((ending - start) * 1_000).round
30
30
 
31
- timing "action_controller.runtime", runtime if runtime
32
- timing "action_controller.view_runtime", view_runtime if view_runtime
33
- timing "action_controller.db_runtime", db_runtime if db_runtime
31
+ timing "action_controller.runtime.total", runtime
32
+ timing "action_controller.runtime.view", view_runtime if view_runtime
33
+ timing "action_controller.runtime.db", db_runtime if db_runtime
34
34
 
35
35
  increment "action_controller.format.#{format}" if format
36
36
  increment "action_controller.status.#{status}" if status
37
37
 
38
38
  if controller && action
39
- namespace = "action_controller.#{controller}.#{action}"
39
+ namespace = "action_controller.controller.#{controller}.#{action}"
40
40
 
41
- timing "#{namespace}.runtime", runtime if runtime
42
- timing "#{namespace}.view_runtime", view_runtime if view_runtime
43
- timing "#{namespace}.db_runtime", db_runtime if db_runtime
41
+ timing "#{namespace}.runtime.total", runtime
42
+ timing "#{namespace}.runtime.view", view_runtime if view_runtime
43
+ timing "#{namespace}.runtime.db", db_runtime if db_runtime
44
44
  end
45
45
 
46
46
  if exception_info
@@ -16,7 +16,7 @@ module Nunes
16
16
  mailer = payload[:mailer]
17
17
 
18
18
  if mailer
19
- timing "action_mailer.deliver.#{mailer.to_s.underscore}", runtime
19
+ timing "action_mailer.deliver.#{mailer}", runtime
20
20
  end
21
21
  end
22
22
 
@@ -25,7 +25,7 @@ module Nunes
25
25
  mailer = payload[:mailer]
26
26
 
27
27
  if mailer
28
- timing "action_mailer.receive.#{mailer.to_s.underscore}", runtime
28
+ timing "action_mailer.receive.#{mailer}", runtime
29
29
  end
30
30
  end
31
31
  end
@@ -12,20 +12,20 @@ module Nunes
12
12
  end
13
13
 
14
14
  def render_template(start, ending, transaction_id, payload)
15
- instrument_identifier payload[:identifier], start, ending
15
+ instrument_identifier :template, payload[:identifier], start, ending
16
16
  end
17
17
 
18
18
  def render_partial(start, ending, transaction_id, payload)
19
- instrument_identifier payload[:identifier], start, ending
19
+ instrument_identifier :partial, payload[:identifier], start, ending
20
20
  end
21
21
 
22
22
  private
23
23
 
24
24
  # Private: Sends timing information about identifier event.
25
- def instrument_identifier(identifier, start, ending)
26
- if identifier.present?
25
+ def instrument_identifier(kind, identifier, start, ending)
26
+ if identifier
27
27
  runtime = ((ending - start) * 1_000).round
28
- timing identifier_to_metric(identifier), runtime
28
+ timing identifier_to_metric(kind, identifier), runtime
29
29
  end
30
30
  end
31
31
 
@@ -33,11 +33,10 @@ module Nunes
33
33
  # root from the full path.
34
34
  #
35
35
  # identifier - The String full path to the template or partial.
36
- def identifier_to_metric(identifier)
37
- rails_root = ::Rails.root.to_s + '/'
36
+ def identifier_to_metric(kind, identifier)
37
+ rails_root = ::Rails.root.to_s + File::SEPARATOR
38
38
  view_path = identifier.gsub(rails_root, '')
39
- metric = view_path.gsub('/', '.')
40
- "action_view.#{metric}"
39
+ "action_view.#{kind}.#{view_path}" if view_path
41
40
  end
42
41
  end
43
42
  end
@@ -17,41 +17,41 @@ module Nunes
17
17
 
18
18
  case super_operation
19
19
  when Symbol
20
- timing "active_support.cache_#{super_operation}", runtime
20
+ timing "active_support.cache.#{super_operation}", runtime
21
21
  else
22
- timing "active_support.cache_read", runtime
22
+ timing "active_support.cache.read", runtime
23
23
  end
24
24
 
25
25
  hit = payload[:hit]
26
26
  unless hit.nil?
27
27
  hit_type = hit ? :hit : :miss
28
- increment "active_support.cache_#{hit_type}"
28
+ increment "active_support.cache.#{hit_type}"
29
29
  end
30
30
  end
31
31
 
32
32
  def cache_generate(start, ending, transaction_id, payload)
33
33
  runtime = ((ending - start) * 1_000).round
34
- timing "active_support.cache_generate", runtime
34
+ timing "active_support.cache.fetch_generate", runtime
35
35
  end
36
36
 
37
37
  def cache_fetch_hit(start, ending, transaction_id, payload)
38
38
  runtime = ((ending - start) * 1_000).round
39
- timing "active_support.cache_fetch_hit", runtime
39
+ timing "active_support.cache.fetch_hit", runtime
40
40
  end
41
41
 
42
42
  def cache_write(start, ending, transaction_id, payload)
43
43
  runtime = ((ending - start) * 1_000).round
44
- timing "active_support.cache_write", runtime
44
+ timing "active_support.cache.write", runtime
45
45
  end
46
46
 
47
47
  def cache_delete(start, ending, transaction_id, payload)
48
48
  runtime = ((ending - start) * 1_000).round
49
- timing "active_support.cache_delete", runtime
49
+ timing "active_support.cache.delete", runtime
50
50
  end
51
51
 
52
52
  def cache_exist?(start, ending, transaction_id, payload)
53
53
  runtime = ((ending - start) * 1_000).round
54
- timing "active_support.cache_exist", runtime
54
+ timing "active_support.cache.exist", runtime
55
55
  end
56
56
  end
57
57
  end
@@ -15,9 +15,7 @@ module Nunes
15
15
  runtime = ((ending - start) * 1_000).round
16
16
  metric = payload[:metric]
17
17
 
18
- if metric
19
- timing "#{metric}", runtime
20
- end
18
+ timing "#{metric}", runtime if metric
21
19
  end
22
20
  end
23
21
  end
@@ -1,3 +1,3 @@
1
1
  module Nunes
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,48 @@
1
+ require "benchmark"
2
+ require "securerandom"
3
+ require "active_support/notifications"
4
+ require "bundler"
5
+ Bundler.setup :default, :bench
6
+
7
+ require_relative "../lib/nunes"
8
+
9
+ adapter = Nunes::Adapter.wrap({})
10
+ Nunes::Subscribers::ActionController.subscribe(adapter)
11
+
12
+ require "rblineprof"
13
+ $profile = lineprof(/./) do
14
+ puts Benchmark.realtime {
15
+ 1_000.times do
16
+ ActiveSupport::Notifications.instrument("process_action.action_controller")
17
+ end
18
+ }
19
+ end
20
+
21
+ def show_file(file)
22
+ file = File.expand_path(file)
23
+ File.readlines(file).each_with_index do |line, num|
24
+ wall, cpu, calls = $profile[file][num+1]
25
+ if calls && calls > 0
26
+ printf "% 8.1fms + % 8.1fms (% 5d) | %s", cpu/1000.0, (wall-cpu)/1000.0, calls, line
27
+ # printf "% 8.1fms (% 5d) | %s", wall/1000.0, calls, line
28
+ else
29
+ printf " | %s", line
30
+ # printf " | %s", line
31
+ end
32
+ end
33
+ end
34
+
35
+ puts
36
+ $profile.each do |file, data|
37
+ total, child, exclusive = data[0]
38
+ puts file
39
+ printf " % 10.1fms in this file\n", exclusive/1000.0
40
+ printf " % 10.1fms in this file + children\n", total/1000.0
41
+ printf " % 10.1fms in children\n", child/1000.0
42
+
43
+ if file =~ /nunes\/lib/
44
+ show_file(file)
45
+ end
46
+
47
+ puts
48
+ end
@@ -0,0 +1,42 @@
1
+ #!/bin/sh
2
+ #/ Usage: release
3
+ #/
4
+ #/ Tag the version in the repo and push the gem.
5
+ #/
6
+
7
+ set -e
8
+ cd $(dirname "$0")/..
9
+
10
+ [ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
11
+ grep '^#/' <"$0"| cut -c4-
12
+ exit 0
13
+ }
14
+
15
+ gem_name=nunes
16
+
17
+ # Build a new gem archive.
18
+ rm -rf $gem_name-*.gem
19
+ gem build -q $gem_name.gemspec
20
+
21
+ # Make sure we're on the master branch.
22
+ (git branch | grep -q '* master') || {
23
+ echo "Only release from the master branch."
24
+ exit 1
25
+ }
26
+
27
+ # Figure out what version we're releasing.
28
+ tag=v`ls $gem_name-*.gem | sed "s/^$gem_name-\(.*\)\.gem$/\1/"`
29
+
30
+ echo "Releasing $tag"
31
+
32
+ # Make sure we haven't released this version before.
33
+ git fetch -t origin
34
+
35
+ (git tag -l | grep -q "$tag") && {
36
+ echo "Whoops, there's already a '${tag}' tag."
37
+ exit 1
38
+ }
39
+
40
+ # Tag it and bag it.
41
+ gem push $gem_name-*.gem && git tag "$tag" &&
42
+ git push origin master && git push origin "$tag"
@@ -1,6 +1,11 @@
1
1
  require "helper"
2
+ require "minitest/mock"
2
3
 
3
4
  class AdapterTest < ActiveSupport::TestCase
5
+ def separator
6
+ Nunes::Adapter::Separator
7
+ end
8
+
4
9
  test "wrap for statsd" do
5
10
  client_with_gauge_and_timing = Class.new do
6
11
  def increment(*args); end
@@ -9,7 +14,7 @@ class AdapterTest < ActiveSupport::TestCase
9
14
  end.new
10
15
 
11
16
  adapter = Nunes::Adapter.wrap(client_with_gauge_and_timing)
12
- assert_instance_of Nunes::Adapters::Default, adapter
17
+ assert_instance_of Nunes::Adapter, adapter
13
18
  end
14
19
 
15
20
  test "wrap for instrumental" do
@@ -37,4 +42,75 @@ class AdapterTest < ActiveSupport::TestCase
37
42
  test "wrap with nil" do
38
43
  assert_raises(ArgumentError) { Nunes::Adapter.wrap(nil) }
39
44
  end
45
+
46
+ test "wrap with straight up gibberish yo" do
47
+ assert_raises(ArgumentError) { Nunes::Adapter.wrap(Object.new) }
48
+ end
49
+
50
+ test "passes increment along" do
51
+ mock = MiniTest::Mock.new
52
+ mock.expect :increment, nil, ["single", 1]
53
+ mock.expect :increment, nil, ["double", 2]
54
+
55
+ client = Nunes::Adapter.new(mock)
56
+ client.increment("single")
57
+ client.increment("double", 2)
58
+
59
+ mock.verify
60
+ end
61
+
62
+ test "passes timing along" do
63
+ mock = MiniTest::Mock.new
64
+ mock.expect :timing, nil, ["foo", 23]
65
+
66
+ client = Nunes::Adapter.new(mock)
67
+ client.timing("foo", 23)
68
+
69
+ mock.verify
70
+ end
71
+
72
+ test "prepare leaves good metrics alone" do
73
+ adapter = Nunes::Adapter.new(nil)
74
+
75
+ [
76
+ "foo",
77
+ "foo1234",
78
+ "foo-bar",
79
+ "foo_bar",
80
+ "Foo",
81
+ "FooBar",
82
+ "FOOBAR",
83
+ "foo#{separator}bar",
84
+ "foo#{separator}bar_baz",
85
+ "foo#{separator}bar_baz-wick",
86
+ "Foo#{separator}1234",
87
+ "Foo#{separator}Bar1234",
88
+ ].each do |expected|
89
+ assert_equal expected, adapter.prepare(expected)
90
+ end
91
+ end
92
+
93
+ test "prepare with bad metric names" do
94
+ adapter = Nunes::Adapter.new(nil)
95
+
96
+ {
97
+ "#{separator}foo" => "foo",
98
+ "foo#{separator}" => "foo",
99
+ "foo@bar" => "foo#{separator}bar",
100
+ "foo@$%^*^&bar" => "foo#{separator}bar",
101
+ "foo#{separator}#{separator}bar" => "foo#{separator}bar",
102
+ "app/views/posts" => "app#{separator}views#{separator}posts",
103
+ "Admin::PostsController#{separator}index" => "Admin#{separator}PostsController#{separator}index",
104
+ }.each do |metric, expected|
105
+ assert_equal expected, adapter.prepare(metric)
106
+ end
107
+ end
108
+
109
+ test "prepare does not modify original metric" do
110
+ adapter = Nunes::Adapter.new(nil)
111
+ original = "app/views/posts"
112
+ result = adapter.prepare("original")
113
+
114
+ assert_equal "app/views/posts", original
115
+ end
40
116
  end
@@ -27,8 +27,8 @@ class CacheInstrumentationTest < ActiveSupport::TestCase
27
27
  test "cache_read miss" do
28
28
  cache.read('foo')
29
29
 
30
- assert_timer "active_support.cache_read"
31
- assert_counter "active_support.cache_miss"
30
+ assert_timer "active_support.cache.read"
31
+ assert_counter "active_support.cache.miss"
32
32
  end
33
33
 
34
34
  test "cache_read hit" do
@@ -36,13 +36,13 @@ class CacheInstrumentationTest < ActiveSupport::TestCase
36
36
  adapter.clear
37
37
  cache.read('foo')
38
38
 
39
- assert_timer "active_support.cache_read"
40
- assert_counter "active_support.cache_hit"
39
+ assert_timer "active_support.cache.read"
40
+ assert_counter "active_support.cache.hit"
41
41
  end
42
42
 
43
43
  test "cache_generate" do
44
44
  cache.fetch('foo') { |key| :generate_me_please }
45
- assert_timer "active_support.cache_generate"
45
+ assert_timer "active_support.cache.fetch_generate"
46
46
  end
47
47
 
48
48
  test "cache_fetch with hit" do
@@ -50,30 +50,30 @@ class CacheInstrumentationTest < ActiveSupport::TestCase
50
50
  adapter.clear
51
51
  cache.fetch('foo') { |key| :never_gets_here }
52
52
 
53
- assert_timer "active_support.cache_fetch"
54
- assert_timer "active_support.cache_fetch_hit"
53
+ assert_timer "active_support.cache.fetch"
54
+ assert_timer "active_support.cache.fetch_hit"
55
55
  end
56
56
 
57
57
  test "cache_fetch with miss" do
58
58
  cache.fetch('foo') { 'foo value set here' }
59
59
 
60
- assert_timer "active_support.cache_fetch"
61
- assert_timer "active_support.cache_generate"
62
- assert_timer "active_support.cache_write"
60
+ assert_timer "active_support.cache.fetch"
61
+ assert_timer "active_support.cache.fetch_generate"
62
+ assert_timer "active_support.cache.write"
63
63
  end
64
64
 
65
65
  test "cache_write" do
66
66
  cache.write('foo', 'bar')
67
- assert_timer "active_support.cache_write"
67
+ assert_timer "active_support.cache.write"
68
68
  end
69
69
 
70
70
  test "cache_delete" do
71
71
  cache.delete('foo')
72
- assert_timer "active_support.cache_delete"
72
+ assert_timer "active_support.cache.delete"
73
73
  end
74
74
 
75
75
  test "cache_exist?" do
76
76
  cache.exist?('foo')
77
- assert_timer "active_support.cache_exist"
77
+ assert_timer "active_support.cache.exist"
78
78
  end
79
79
  end
@@ -22,11 +22,11 @@ class ControllerInstrumentationTest < ActionController::TestCase
22
22
  assert_counter "action_controller.status.200"
23
23
  assert_counter "action_controller.format.html"
24
24
 
25
- assert_timer "action_controller.runtime"
26
- assert_timer "action_controller.view_runtime"
25
+ assert_timer "action_controller.runtime.total"
26
+ assert_timer "action_controller.runtime.view"
27
27
 
28
- assert_timer "action_controller.posts.index.runtime"
29
- assert_timer "action_controller.posts.index.view_runtime"
28
+ assert_timer "action_controller.controller.PostsController.index.runtime.total"
29
+ assert_timer "action_controller.controller.PostsController.index.runtime.view"
30
30
  end
31
31
 
32
32
  test "send_data" do
@@ -36,11 +36,11 @@ class ControllerInstrumentationTest < ActionController::TestCase
36
36
 
37
37
  assert_counter "action_controller.status.200"
38
38
 
39
- assert_timer "action_controller.runtime"
40
- assert_timer "action_controller.view_runtime"
39
+ assert_timer "action_controller.runtime.total"
40
+ assert_timer "action_controller.runtime.view"
41
41
 
42
- assert_timer "action_controller.posts.some_data.runtime"
43
- assert_timer "action_controller.posts.some_data.view_runtime"
42
+ assert_timer "action_controller.controller.PostsController.some_data.runtime.total"
43
+ assert_timer "action_controller.controller.PostsController.some_data.runtime.view"
44
44
  end
45
45
 
46
46
  test "send_file" do
@@ -50,11 +50,11 @@ class ControllerInstrumentationTest < ActionController::TestCase
50
50
 
51
51
  assert_counter"action_controller.status.200"
52
52
 
53
- assert_timer "action_controller.runtime"
54
- assert_timer "action_controller.posts.some_file.runtime"
53
+ assert_timer "action_controller.runtime.total"
54
+ assert_timer "action_controller.controller.PostsController.some_file.runtime.total"
55
55
 
56
- assert ! adapter.timer?("action_controller.view_runtime")
57
- assert ! adapter.timer?("action_controller.posts.some_file.view_runtime")
56
+ assert ! adapter.timer?("action_controller.runtime.view")
57
+ assert ! adapter.timer?("action_controller.controller.PostsController.some_file.runtime.view")
58
58
  end
59
59
 
60
60
  test "redirect_to" do
@@ -64,11 +64,11 @@ class ControllerInstrumentationTest < ActionController::TestCase
64
64
 
65
65
  assert_counter "action_controller.status.302"
66
66
 
67
- assert_timer "action_controller.runtime"
68
- assert_timer "action_controller.posts.some_redirect.runtime"
67
+ assert_timer "action_controller.runtime.total"
68
+ assert_timer "action_controller.controller.PostsController.some_redirect.runtime.total"
69
69
 
70
- assert_no_timer "action_controller.view_runtime"
71
- assert_no_timer "action_controller.posts.some_redirect.view_runtime"
70
+ assert_no_timer "action_controller.runtime.view"
71
+ assert_no_timer "action_controller.controller.PostsController.some_redirect.runtime.view"
72
72
  end
73
73
 
74
74
  test "action with exception" do
@@ -79,10 +79,10 @@ class ControllerInstrumentationTest < ActionController::TestCase
79
79
  assert_counter "action_controller.exception.RuntimeError"
80
80
  assert_counter "action_controller.format.html"
81
81
 
82
- assert_timer "action_controller.runtime"
83
- assert_timer "action_controller.posts.some_boom.runtime"
82
+ assert_timer "action_controller.runtime.total"
83
+ assert_timer "action_controller.controller.PostsController.some_boom.runtime.total"
84
84
 
85
- assert_no_timer "action_controller.view_runtime"
86
- assert_no_timer "action_controller.posts.some_boom.view_runtime"
85
+ assert_no_timer "action_controller.runtime.view"
86
+ assert_no_timer "action_controller.controller.PostsController.some_boom.runtime.view"
87
87
  end
88
88
  end
@@ -18,6 +18,14 @@ class InstrumentationTest < ActiveSupport::TestCase
18
18
  @thing_class = Class.new {
19
19
  extend Nunes::Instrumentable
20
20
 
21
+ class << self
22
+ extend Nunes::Instrumentable
23
+
24
+ def find(*args)
25
+ :nope
26
+ end
27
+ end
28
+
21
29
  def self.name
22
30
  'Thing'
23
31
  end
@@ -50,12 +58,31 @@ class InstrumentationTest < ActiveSupport::TestCase
50
58
  event = slurp_events { thing_class.new.yo(some: 'thing') }.last
51
59
 
52
60
  assert_not_nil event, "No events were found."
53
- assert_equal "thing.yo", event.payload[:metric]
54
- assert_equal [{some: "thing"}], event.payload[:arguments]
55
- assert_equal :dude, event.payload[:result]
61
+ assert_equal "Thing.yo", event.payload[:metric]
62
+ assert_in_delta 0, event.duration, 0.1
63
+
64
+ assert_timer "Thing.yo"
65
+ end
66
+
67
+ test "instrument_method_time for class method without full metric name" do
68
+ # I'd really like to not do this, but I don't have a name for the class
69
+ # which makes it hard to automatically set the name. If anyone has a fix,
70
+ # let me know.
71
+ assert_raises ArgumentError do
72
+ thing_class.singleton_class.instrument_method_time :find
73
+ end
74
+ end
75
+
76
+ test "instrument_method_time for class method" do
77
+ thing_class.singleton_class.instrument_method_time :find, "Thing.find"
78
+
79
+ event = slurp_events { thing_class.find(1) }.last
80
+
81
+ assert_not_nil event, "No events were found."
82
+ assert_equal "Thing.find", event.payload[:metric]
56
83
  assert_in_delta 0, event.duration, 0.1
57
84
 
58
- assert_timer "thing.yo"
85
+ assert_timer "Thing.find"
59
86
  end
60
87
 
61
88
  test "instrument_method_time with custom name in hash" do
@@ -64,9 +91,9 @@ class InstrumentationTest < ActiveSupport::TestCase
64
91
  event = slurp_events { thing_class.new.yo(some: 'thing') }.last
65
92
 
66
93
  assert_not_nil event, "No events were found."
67
- assert_equal "thingy.yohoho", event.payload[:metric]
94
+ assert_equal "Thingy.yohoho", event.payload[:metric]
68
95
 
69
- assert_timer "thingy.yohoho"
96
+ assert_timer "Thingy.yohoho"
70
97
  end
71
98
 
72
99
  test "instrument_method_time with custom name as string" do
@@ -75,9 +102,9 @@ class InstrumentationTest < ActiveSupport::TestCase
75
102
  event = slurp_events { thing_class.new.yo(some: 'thing') }.last
76
103
 
77
104
  assert_not_nil event, "No events were found."
78
- assert_equal "thingy.yohoho", event.payload[:metric]
105
+ assert_equal "Thingy.yohoho", event.payload[:metric]
79
106
 
80
- assert_timer "thingy.yohoho"
107
+ assert_timer "Thingy.yohoho"
81
108
  end
82
109
 
83
110
  test "instrument_method_time with custom payload" do
@@ -88,7 +115,7 @@ class InstrumentationTest < ActiveSupport::TestCase
88
115
  assert_not_nil event, "No events were found."
89
116
  assert_equal "loadin", event.payload[:pay]
90
117
 
91
- assert_timer "thing.yo"
118
+ assert_timer "Thing.yo"
92
119
  end
93
120
 
94
121
  def slurp_events(&block)
@@ -16,11 +16,11 @@ class MailerInstrumentationTest < ActionMailer::TestCase
16
16
 
17
17
  test "deliver" do
18
18
  PostMailer.created.deliver
19
- assert_timer "action_mailer.deliver.post_mailer"
19
+ assert_timer "action_mailer.deliver.PostMailer"
20
20
  end
21
21
 
22
22
  test "receive" do
23
23
  PostMailer.receive PostMailer.created
24
- assert_timer "action_mailer.receive.post_mailer"
24
+ assert_timer "action_mailer.receive.PostMailer"
25
25
  end
26
26
  end
@@ -0,0 +1,25 @@
1
+ require "helper"
2
+
3
+ class NamespacedControllerInstrumentationTest < ActionController::TestCase
4
+ tests Admin::PostsController
5
+
6
+ setup :setup_subscriber
7
+ teardown :teardown_subscriber
8
+
9
+ def setup_subscriber
10
+ @subscriber = Nunes::Subscribers::ActionController.subscribe(adapter)
11
+ end
12
+
13
+ def teardown_subscriber
14
+ ActiveSupport::Notifications.unsubscribe @subscriber if @subscriber
15
+ end
16
+
17
+ test "process_action" do
18
+ get :index
19
+
20
+ assert_response :success
21
+
22
+ assert_timer "action_controller.controller.Admin.PostsController.index.runtime.total"
23
+ assert_timer "action_controller.controller.Admin.PostsController.index.runtime.view"
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ class Admin::PostsController < ApplicationController
2
+ # Use fake post for controller as I don't want active record to mingle here.
3
+ Post = Struct.new(:title)
4
+
5
+ def index
6
+ @posts = [
7
+ Post.new('First'),
8
+ Post.new('Second'),
9
+ ]
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ <h1>Admin Posts</h1>
2
+
3
+ <% @posts.each do |post| %>
4
+ <%= render 'posts/post', post: post %>
5
+ <% end %>
@@ -1,4 +1,8 @@
1
1
  RailsApp::Application.routes.draw do
2
+ namespace :admin do
3
+ resources :posts, only: :index
4
+ end
5
+
2
6
  resources :posts, only: :index
3
7
 
4
8
  get "/some-data", to: "posts#some_data"
@@ -18,13 +18,13 @@ class ViewInstrumentationTest < ActionController::TestCase
18
18
  get :index
19
19
 
20
20
  assert_response :success
21
- assert_timer "action_view.app.views.posts.index.html.erb"
21
+ assert_timer "action_view.template.app.views.posts.index.html.erb"
22
22
  end
23
23
 
24
24
  test "render_partial" do
25
25
  get :index
26
26
 
27
27
  assert_response :success
28
- assert_timer "action_view.app.views.posts._post.html.erb"
28
+ assert_timer "action_view.partial.app.views.posts._post.html.erb"
29
29
  end
30
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nunes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-18 00:00:00.000000000 Z
12
+ date: 2013-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -36,12 +36,12 @@ extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
38
  - .gitignore
39
+ - Changelog.md
39
40
  - Gemfile
40
41
  - LICENSE.txt
41
42
  - README.md
42
43
  - lib/nunes.rb
43
44
  - lib/nunes/adapter.rb
44
- - lib/nunes/adapters/default.rb
45
45
  - lib/nunes/adapters/memory.rb
46
46
  - lib/nunes/adapters/timing_aliased.rb
47
47
  - lib/nunes/instrumentable.rb
@@ -54,11 +54,12 @@ files:
54
54
  - lib/nunes/subscribers/nunes.rb
55
55
  - lib/nunes/version.rb
56
56
  - nunes.gemspec
57
+ - script/bench.rb
57
58
  - script/bootstrap
59
+ - script/release
58
60
  - script/test
59
61
  - script/watch
60
62
  - test/adapter_test.rb
61
- - test/adapters/default_test.rb
62
63
  - test/adapters/timing_aliased_test.rb
63
64
  - test/cache_instrumentation_test.rb
64
65
  - test/controller_instrumentation_test.rb
@@ -67,12 +68,14 @@ files:
67
68
  - test/instrumentable_test.rb
68
69
  - test/mailer_instrumentation_test.rb
69
70
  - test/model_instrumentation_test.rb
71
+ - test/namespaced_controller_instrumentation_test.rb
70
72
  - test/nunes_test.rb
71
73
  - test/rails_app/.gitignore
72
74
  - test/rails_app/Rakefile
73
75
  - test/rails_app/app/assets/images/rails.png
74
76
  - test/rails_app/app/assets/javascripts/application.js
75
77
  - test/rails_app/app/assets/stylesheets/application.css
78
+ - test/rails_app/app/controllers/admin/posts_controller.rb
76
79
  - test/rails_app/app/controllers/application_controller.rb
77
80
  - test/rails_app/app/controllers/posts_controller.rb
78
81
  - test/rails_app/app/helpers/application_helper.rb
@@ -80,6 +83,7 @@ files:
80
83
  - test/rails_app/app/mailers/post_mailer.rb
81
84
  - test/rails_app/app/models/.gitkeep
82
85
  - test/rails_app/app/models/post.rb
86
+ - test/rails_app/app/views/admin/posts/index.html.erb
83
87
  - test/rails_app/app/views/layouts/application.html.erb
84
88
  - test/rails_app/app/views/post_mailer/created.text.erb
85
89
  - test/rails_app/app/views/posts/_post.html.erb
@@ -144,7 +148,6 @@ specification_version: 3
144
148
  summary: The friendly gem that instruments everything for you, like I would if I could.
145
149
  test_files:
146
150
  - test/adapter_test.rb
147
- - test/adapters/default_test.rb
148
151
  - test/adapters/timing_aliased_test.rb
149
152
  - test/cache_instrumentation_test.rb
150
153
  - test/controller_instrumentation_test.rb
@@ -153,12 +156,14 @@ test_files:
153
156
  - test/instrumentable_test.rb
154
157
  - test/mailer_instrumentation_test.rb
155
158
  - test/model_instrumentation_test.rb
159
+ - test/namespaced_controller_instrumentation_test.rb
156
160
  - test/nunes_test.rb
157
161
  - test/rails_app/.gitignore
158
162
  - test/rails_app/Rakefile
159
163
  - test/rails_app/app/assets/images/rails.png
160
164
  - test/rails_app/app/assets/javascripts/application.js
161
165
  - test/rails_app/app/assets/stylesheets/application.css
166
+ - test/rails_app/app/controllers/admin/posts_controller.rb
162
167
  - test/rails_app/app/controllers/application_controller.rb
163
168
  - test/rails_app/app/controllers/posts_controller.rb
164
169
  - test/rails_app/app/helpers/application_helper.rb
@@ -166,6 +171,7 @@ test_files:
166
171
  - test/rails_app/app/mailers/post_mailer.rb
167
172
  - test/rails_app/app/models/.gitkeep
168
173
  - test/rails_app/app/models/post.rb
174
+ - test/rails_app/app/views/admin/posts/index.html.erb
169
175
  - test/rails_app/app/views/layouts/application.html.erb
170
176
  - test/rails_app/app/views/post_mailer/created.text.erb
171
177
  - test/rails_app/app/views/posts/_post.html.erb
@@ -1,10 +0,0 @@
1
- require "nunes/adapter"
2
-
3
- module Nunes
4
- module Adapters
5
- # Internal: Default is the assumed interface, so we don't need to override
6
- # anything. This should never need to be used directly by a user of the gem.
7
- class Default < ::Nunes::Adapter
8
- end
9
- end
10
- end
@@ -1,26 +0,0 @@
1
- require "helper"
2
- require "minitest/mock"
3
-
4
- class DefaultAdapterTest < ActiveSupport::TestCase
5
- test "passes increment along" do
6
- mock = MiniTest::Mock.new
7
- mock.expect :increment, nil, ["single", 1]
8
- mock.expect :increment, nil, ["double", 2]
9
-
10
- client = Nunes::Adapters::Default.new(mock)
11
- client.increment("single")
12
- client.increment("double", 2)
13
-
14
- mock.verify
15
- end
16
-
17
- test "passes timing along" do
18
- mock = MiniTest::Mock.new
19
- mock.expect :timing, nil, ["foo", 23]
20
-
21
- client = Nunes::Adapters::Default.new(mock)
22
- client.timing("foo", 23)
23
-
24
- mock.verify
25
- end
26
- end