nunes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +150 -0
  5. data/lib/nunes.rb +39 -0
  6. data/lib/nunes/adapter.rb +56 -0
  7. data/lib/nunes/adapters/default.rb +10 -0
  8. data/lib/nunes/adapters/memory.rb +62 -0
  9. data/lib/nunes/adapters/timing_aliased.rb +17 -0
  10. data/lib/nunes/instrumentable.rb +102 -0
  11. data/lib/nunes/subscriber.rb +69 -0
  12. data/lib/nunes/subscribers/action_controller.rb +80 -0
  13. data/lib/nunes/subscribers/action_mailer.rb +33 -0
  14. data/lib/nunes/subscribers/action_view.rb +44 -0
  15. data/lib/nunes/subscribers/active_record.rb +33 -0
  16. data/lib/nunes/subscribers/active_support.rb +58 -0
  17. data/lib/nunes/subscribers/nunes.rb +24 -0
  18. data/lib/nunes/version.rb +3 -0
  19. data/nunes.gemspec +22 -0
  20. data/script/bootstrap +21 -0
  21. data/script/test +25 -0
  22. data/script/watch +30 -0
  23. data/test/adapter_test.rb +40 -0
  24. data/test/adapters/default_test.rb +26 -0
  25. data/test/adapters/timing_aliased_test.rb +26 -0
  26. data/test/cache_instrumentation_test.rb +79 -0
  27. data/test/controller_instrumentation_test.rb +88 -0
  28. data/test/fake_udp_socket_test.rb +26 -0
  29. data/test/helper.rb +25 -0
  30. data/test/instrumentable_test.rb +100 -0
  31. data/test/mailer_instrumentation_test.rb +26 -0
  32. data/test/model_instrumentation_test.rb +55 -0
  33. data/test/nunes_test.rb +20 -0
  34. data/test/rails_app/.gitignore +15 -0
  35. data/test/rails_app/Rakefile +7 -0
  36. data/test/rails_app/app/assets/images/rails.png +0 -0
  37. data/test/rails_app/app/assets/javascripts/application.js +15 -0
  38. data/test/rails_app/app/assets/stylesheets/application.css +13 -0
  39. data/test/rails_app/app/controllers/application_controller.rb +3 -0
  40. data/test/rails_app/app/controllers/posts_controller.rb +28 -0
  41. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  42. data/test/rails_app/app/mailers/.gitkeep +0 -0
  43. data/test/rails_app/app/mailers/post_mailer.rb +11 -0
  44. data/test/rails_app/app/models/.gitkeep +0 -0
  45. data/test/rails_app/app/models/post.rb +2 -0
  46. data/test/rails_app/app/views/layouts/application.html.erb +14 -0
  47. data/test/rails_app/app/views/post_mailer/created.text.erb +1 -0
  48. data/test/rails_app/app/views/posts/_post.html.erb +1 -0
  49. data/test/rails_app/app/views/posts/index.html.erb +5 -0
  50. data/test/rails_app/config.ru +4 -0
  51. data/test/rails_app/config/application.rb +67 -0
  52. data/test/rails_app/config/boot.rb +6 -0
  53. data/test/rails_app/config/database.yml +6 -0
  54. data/test/rails_app/config/environment.rb +5 -0
  55. data/test/rails_app/config/environments/development.rb +31 -0
  56. data/test/rails_app/config/environments/production.rb +64 -0
  57. data/test/rails_app/config/environments/test.rb +35 -0
  58. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  59. data/test/rails_app/config/initializers/force_test_schema_load.rb +3 -0
  60. data/test/rails_app/config/initializers/inflections.rb +15 -0
  61. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  62. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  63. data/test/rails_app/config/initializers/session_store.rb +8 -0
  64. data/test/rails_app/config/initializers/wrap_parameters.rb +10 -0
  65. data/test/rails_app/config/locales/en.yml +5 -0
  66. data/test/rails_app/config/routes.rb +8 -0
  67. data/test/rails_app/db/migrate/20130417154459_create_posts.rb +8 -0
  68. data/test/rails_app/db/schema.rb +22 -0
  69. data/test/rails_app/db/seeds.rb +7 -0
  70. data/test/rails_app/lib/assets/.gitkeep +0 -0
  71. data/test/rails_app/lib/tasks/.gitkeep +0 -0
  72. data/test/rails_app/public/404.html +26 -0
  73. data/test/rails_app/public/422.html +26 -0
  74. data/test/rails_app/public/500.html +25 -0
  75. data/test/rails_app/public/favicon.ico +0 -0
  76. data/test/rails_app/public/index.html +241 -0
  77. data/test/rails_app/public/robots.txt +5 -0
  78. data/test/rails_app/script/rails +6 -0
  79. data/test/subscriber_test.rb +50 -0
  80. data/test/support/adapter_test_helpers.rb +33 -0
  81. data/test/support/fake_udp_socket.rb +50 -0
  82. data/test/view_instrumentation_test.rb +30 -0
  83. metadata +205 -0
@@ -0,0 +1,69 @@
1
+ require "active_support/notifications"
2
+
3
+ module Nunes
4
+ class Subscriber
5
+ # Private: The bang character that is the first char of some events.
6
+ BANG = '!'
7
+
8
+ # Public: Setup a subscription for the subscriber using the
9
+ # provided adapter.
10
+ #
11
+ # adapter - The adapter instance to send instrumentation to.
12
+ def self.subscribe(adapter, options = {})
13
+ subscriber = options.fetch(:subscriber) { ActiveSupport::Notifications }
14
+ subscriber.subscribe pattern, new(adapter)
15
+ end
16
+
17
+ def self.pattern
18
+ raise "Not Implemented, override in subclass and provide a regex or string."
19
+ end
20
+
21
+ # Private: The adapter to send instrumentation to.
22
+ attr_reader :adapter
23
+
24
+ # Internal: Initializes a new instance.
25
+ #
26
+ # adapter - The adapter instance to send instrumentation to.
27
+ def initialize(adapter)
28
+ @adapter = Nunes::Adapter.wrap(adapter)
29
+ end
30
+
31
+ # Private: Dispatcher that converts incoming events to method calls.
32
+ def call(name, start, ending, transaction_id, payload)
33
+ # rails doesn't recommend instrumenting methods that start with bang
34
+ # when in production
35
+ return if name.starts_with?(BANG)
36
+
37
+ method_name = name.split('.').first
38
+
39
+ if respond_to?(method_name)
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
+ end
44
+ end
45
+
46
+ # Internal: Increment a metric for the client.
47
+ #
48
+ # metric - The String name of the metric to increment.
49
+ #
50
+ # Returns nothing.
51
+ def increment(metric)
52
+ if @adapter
53
+ @adapter.increment metric
54
+ end
55
+ end
56
+
57
+ # Internal: Track the timing of a metric for the client.
58
+ #
59
+ # metric - The String name of the metric.
60
+ # duration_in_ms - The Integer duration of the event in milliseconds.
61
+ #
62
+ # Returns nothing.
63
+ def timing(metric, duration_in_ms)
64
+ if @adapter
65
+ @adapter.timing metric, duration_in_ms
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,80 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class ActionController < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.action_controller\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def process_action(start, ending, transaction_id, payload)
15
+ controller = payload[:controller].to_s.gsub('Controller', '').underscore
16
+ action = payload[:action]
17
+ status = payload[:status]
18
+ exception_info = payload[:exception]
19
+
20
+ format = payload[:format] || "all"
21
+ format = "all" if format == "*/*"
22
+
23
+ db_runtime = payload[:db_runtime]
24
+ db_runtime = db_runtime.round if db_runtime
25
+
26
+ view_runtime = payload[:view_runtime]
27
+ view_runtime = view_runtime.round if view_runtime
28
+
29
+ runtime = ((ending - start) * 1_000).round
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
34
+
35
+ increment "action_controller.format.#{format}" if format
36
+ increment "action_controller.status.#{status}" if status
37
+
38
+ if controller && action
39
+ namespace = "action_controller.#{controller}.#{action}"
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
44
+ end
45
+
46
+ if exception_info
47
+ exception_class, exception_message = exception_info
48
+
49
+ increment "action_controller.exception.#{exception_class}"
50
+ end
51
+ end
52
+
53
+ ##########################################################################
54
+ # All of the events below don't really matter. Most of them also go #
55
+ # through process_action. The only value that could be pulled from them #
56
+ # would be topk related which graphite doesn't do. #
57
+ ##########################################################################
58
+
59
+ def start_processing(*)
60
+ # noop
61
+ end
62
+
63
+ def halted_callback(*)
64
+ # noop
65
+ end
66
+
67
+ def redirect_to(*)
68
+ # noop
69
+ end
70
+
71
+ def send_file(*)
72
+ # noop
73
+ end
74
+
75
+ def send_data(*)
76
+ # noop
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,33 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class ActionMailer < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.action_mailer\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def deliver(start, ending, transaction_id, payload)
15
+ runtime = ((ending - start) * 1_000).round
16
+ mailer = payload[:mailer]
17
+
18
+ if mailer
19
+ timing "action_mailer.deliver.#{mailer.to_s.underscore}", runtime
20
+ end
21
+ end
22
+
23
+ def receive(start, ending, transaction_id, payload)
24
+ runtime = ((ending - start) * 1_000).round
25
+ mailer = payload[:mailer]
26
+
27
+ if mailer
28
+ timing "action_mailer.receive.#{mailer.to_s.underscore}", runtime
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class ActionView < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.action_view\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def render_template(start, ending, transaction_id, payload)
15
+ instrument_identifier payload[:identifier], start, ending
16
+ end
17
+
18
+ def render_partial(start, ending, transaction_id, payload)
19
+ instrument_identifier payload[:identifier], start, ending
20
+ end
21
+
22
+ private
23
+
24
+ # Private: Sends timing information about identifier event.
25
+ def instrument_identifier(identifier, start, ending)
26
+ if identifier.present?
27
+ runtime = ((ending - start) * 1_000).round
28
+ timing identifier_to_metric(identifier), runtime
29
+ end
30
+ end
31
+
32
+ # Private: Converts an identifier to a metric name. Strips out the rails
33
+ # root from the full path.
34
+ #
35
+ # identifier - The String full path to the template or partial.
36
+ def identifier_to_metric(identifier)
37
+ rails_root = ::Rails.root.to_s + '/'
38
+ view_path = identifier.gsub(rails_root, '')
39
+ metric = view_path.gsub('/', '.')
40
+ "action_view.#{metric}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class ActiveRecord < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.active_record\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def sql(start, ending, transaction_id, payload)
15
+ runtime = ((ending - start) * 1_000).round
16
+ name = payload[:name]
17
+ sql = payload[:sql].to_s.strip
18
+ operation = sql.split(' ', 2).first.to_s.downcase
19
+
20
+ timing "active_record.sql", runtime
21
+
22
+ case operation
23
+ when "begin"
24
+ timing "active_record.sql.transaction_begin", runtime
25
+ when "commit"
26
+ timing "active_record.sql.transaction_commit", runtime
27
+ else
28
+ timing "active_record.sql.#{operation}", runtime
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class ActiveSupport < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.active_support\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def cache_read(start, ending, transaction_id, payload)
15
+ super_operation = payload[:super_operation]
16
+ runtime = ((ending - start) * 1_000).round
17
+
18
+ case super_operation
19
+ when Symbol
20
+ timing "active_support.cache_#{super_operation}", runtime
21
+ else
22
+ timing "active_support.cache_read", runtime
23
+ end
24
+
25
+ hit = payload[:hit]
26
+ unless hit.nil?
27
+ hit_type = hit ? :hit : :miss
28
+ increment "active_support.cache_#{hit_type}"
29
+ end
30
+ end
31
+
32
+ def cache_generate(start, ending, transaction_id, payload)
33
+ runtime = ((ending - start) * 1_000).round
34
+ timing "active_support.cache_generate", runtime
35
+ end
36
+
37
+ def cache_fetch_hit(start, ending, transaction_id, payload)
38
+ runtime = ((ending - start) * 1_000).round
39
+ timing "active_support.cache_fetch_hit", runtime
40
+ end
41
+
42
+ def cache_write(start, ending, transaction_id, payload)
43
+ runtime = ((ending - start) * 1_000).round
44
+ timing "active_support.cache_write", runtime
45
+ end
46
+
47
+ def cache_delete(start, ending, transaction_id, payload)
48
+ runtime = ((ending - start) * 1_000).round
49
+ timing "active_support.cache_delete", runtime
50
+ end
51
+
52
+ def cache_exist?(start, ending, transaction_id, payload)
53
+ runtime = ((ending - start) * 1_000).round
54
+ timing "active_support.cache_exist", runtime
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ require "nunes/subscriber"
2
+
3
+ module Nunes
4
+ module Subscribers
5
+ class Nunes < ::Nunes::Subscriber
6
+ # Private
7
+ Pattern = /\.nunes\Z/
8
+
9
+ # Private: The namespace for events to subscribe to.
10
+ def self.pattern
11
+ Pattern
12
+ end
13
+
14
+ def instrument_method_time(start, ending, transaction_id, payload)
15
+ runtime = ((ending - start) * 1_000).round
16
+ metric = payload[:metric]
17
+
18
+ if metric
19
+ timing "#{metric}", runtime
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Nunes
2
+ VERSION = "0.1.0"
3
+ end
data/nunes.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nunes/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nunes"
8
+ spec.version = Nunes::VERSION
9
+ spec.authors = ["John Nunemaker"]
10
+ spec.email = ["nunemaker@gmail.com"]
11
+ spec.description = %q{The friendly gem that instruments everything for you, like I would if I could.}
12
+ spec.summary = %q{The friendly gem that instruments everything for you, like I would if I could.}
13
+ spec.homepage = "https://github.com/jnunemaker/nunes"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ #/ Usage: bootstrap [bundle options]
3
+ #/
4
+ #/ Bundle install the dependencies.
5
+ #/
6
+ #/ Examples:
7
+ #/
8
+ #/ bootstrap
9
+ #/ bootstrap --local
10
+ #/
11
+
12
+ set -e
13
+ cd $(dirname "$0")/..
14
+
15
+ [ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
16
+ grep '^#/' <"$0"| cut -c4-
17
+ exit 0
18
+ }
19
+
20
+ rm -rf .bundle/{binstubs,config}
21
+ bundle install --binstubs .bundle/binstubs --path .bundle --quiet "$@"
data/script/test ADDED
@@ -0,0 +1,25 @@
1
+ #!/bin/sh
2
+ #/ Usage: test [individual test file]
3
+ #/
4
+ #/ Bootstrap and run all tests or an individual test.
5
+ #/
6
+ #/ Examples:
7
+ #/
8
+ #/ # run all tests
9
+ #/ test
10
+ #/
11
+ #/ # run individual test
12
+ #/ test test/controller_instrumentation_test.rb
13
+ #/
14
+
15
+ set -e
16
+ cd $(dirname "$0")/..
17
+
18
+ [ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
19
+ grep '^#/' <"$0"| cut -c4-
20
+ exit 0
21
+ }
22
+
23
+ script/bootstrap && ruby -I lib -I test -r rubygems \
24
+ -e 'require "bundler/setup"' \
25
+ -e '(ARGV.empty? ? Dir["test/**/*_test.rb"] : ARGV).each { |f| load f }' -- "$@"
data/script/watch ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: watch
3
+ #/
4
+ #/ Run the tests whenever any relevant files change.
5
+ #/
6
+
7
+ require "pathname"
8
+ require "rubygems"
9
+ require "bundler"
10
+ Bundler.setup :watch
11
+
12
+ # Put us where we belong, in the root dir of the project.
13
+ Dir.chdir Pathname.new(__FILE__).realpath + "../.."
14
+
15
+ # Run the tests to start.
16
+ system "clear; script/test"
17
+
18
+ require "rb-fsevent"
19
+
20
+ IgnoreRegex = /\/log|db/
21
+
22
+ fs = FSEvent.new
23
+ fs.watch ["lib", "test"], latency: 1 do |args|
24
+ unless args.first =~ IgnoreRegex
25
+ system "clear"
26
+ puts "#{args.first} changed..."
27
+ system "script/test"
28
+ end
29
+ end
30
+ fs.run