nunes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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