abstract_analyzer 0.0.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.
- data/.gitignore +6 -0
- data/README.rdoc +63 -0
- data/Rakefile +27 -0
- data/VERSION.yml +4 -0
- data/lib/abstract_analyzer.rb +16 -0
- data/lib/dash_analyzer.rb +24 -0
- data/lib/dash_analyzer/base.rb +55 -0
- data/lib/dash_analyzer/dash_extensions.rb +88 -0
- data/lib/dash_analyzer/view.rb +89 -0
- data/lib/middleware.rb +7 -0
- data/lib/middleware/rails.rb +41 -0
- data/lib/middleware/rails/dash.rb +35 -0
- data/lib/view.rb +75 -0
- data/test/lib/dash_analyzer/test_base.rb +133 -0
- data/test/lib/dash_analyzer/test_dash_extensions.rb +7 -0
- data/test/lib/dash_analyzer/test_view.rb +27 -0
- data/test/lib/middleware/rails.rb +7 -0
- data/test/lib/middleware/rails/dash.rb +7 -0
- data/test/lib/test_dash_analyzer.rb +7 -0
- data/test/lib/test_middleware.rb +7 -0
- data/test/test_helper.rb +23 -0
- metadata +82 -0
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
=Abstract Analyzer
|
2
|
+
|
3
|
+
This is a Rack based analyzer. The idea is to use different methods and libraries to track requests on Rack app and report those back in some simple views.
|
4
|
+
|
5
|
+
I'm starting with a Fiveruns Dash analyzer that reports to a MongoDB instance. Reporting actually be it's own small Rack app and for the time being, will mostly text based.
|
6
|
+
|
7
|
+
==Requirements
|
8
|
+
|
9
|
+
I hope to trim this down, but for now...
|
10
|
+
|
11
|
+
gems:
|
12
|
+
* mongodb-mongo
|
13
|
+
* fiveruns-dash-ruby
|
14
|
+
* usher
|
15
|
+
* ruport
|
16
|
+
|
17
|
+
In addition for the tests to pass, you need mongod running on localhost:27017
|
18
|
+
|
19
|
+
==Implementation
|
20
|
+
|
21
|
+
You can use the DashAnalyzer with any rack app you'd like. Check the tests for an example.
|
22
|
+
|
23
|
+
===Rails
|
24
|
+
|
25
|
+
If Rails is your bag, I've included a middleware implementation of the DashAnalyzer. Implementing is pretty easy.
|
26
|
+
|
27
|
+
First, this thing isn't a gem yet, so just load the whole thing into lib or somewhere else Rails will see it.
|
28
|
+
|
29
|
+
Secound, in RAILS_ROOT/config/initializers/middlewares.rb
|
30
|
+
|
31
|
+
require 'abstract_analyzer/abstract_analyzer'
|
32
|
+
|
33
|
+
# Setup the Analyzer DB
|
34
|
+
abstract_analyzer_db = Mongo::Connection.new('localhost', 27017).db('aa-rails-dash-analyzer-db')
|
35
|
+
AbstractAnalyzer.const_set("DB", abstract_analyzer_db)
|
36
|
+
|
37
|
+
# Setup the Analyzer LOGGER
|
38
|
+
abstract_logger = Logger.new("#{RAILS_ROOT}/log/abstract_analyzer_logger.log")
|
39
|
+
AbstractAnalyzer.const_set("LOGGER", abstract_logger)
|
40
|
+
|
41
|
+
# Use the Analyzer and View middlewares
|
42
|
+
ActionController::Dispatcher.middleware.use AbstractAnalyzer::Middleware::Rails::Dash::Analyzer
|
43
|
+
ActionController::Dispatcher.middleware.use AbstractAnalyzer::Middleware::Rails::Dash::View
|
44
|
+
|
45
|
+
Third, make sure you actually have a mongo db running on localhost:27017 or change the connection accordingly ;)
|
46
|
+
|
47
|
+
==Todo
|
48
|
+
|
49
|
+
* Basic html views
|
50
|
+
* Implementing reports for Non-time based metrics
|
51
|
+
* Non-Rails middlewares?
|
52
|
+
* Non-Dash implementations?
|
53
|
+
|
54
|
+
==Props
|
55
|
+
|
56
|
+
* Carl Lerche and Yehuda Katz for their Extending Rails training at the Lone Star Ruby Conference that planted this seed
|
57
|
+
* The company formally known as Fiveruns for open sourcing their reporter gems
|
58
|
+
* Adam Keys for explaining how the Dash gems work
|
59
|
+
* The guys a SqueeJee for saying MongoDB enough times to make me try it in something
|
60
|
+
|
61
|
+
==License
|
62
|
+
|
63
|
+
See LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "abstract_analyzer"
|
8
|
+
gem.summary = %Q{A rack based app analyzer}
|
9
|
+
gem.email = "markmcspadden@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/garlandgroup/abstract-analyzer"
|
11
|
+
gem.authors = ["Mark McSpadden"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/rdoctask'
|
21
|
+
Rake::RDocTask.new do |rdoc|
|
22
|
+
rdoc.rdoc_dir = 'rdoc'
|
23
|
+
rdoc.title = 'abstract_analyzer'
|
24
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
25
|
+
rdoc.rdoc_files.include('README*')
|
26
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
27
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) << '/view'
|
2
|
+
require File.dirname(__FILE__) << '/dash_analyzer'
|
3
|
+
require File.dirname(__FILE__) << '/middleware'
|
4
|
+
|
5
|
+
module AbstractAnalyzer
|
6
|
+
|
7
|
+
# CONSTANTS DB, LOGGER, STORE are currently being set and expected
|
8
|
+
|
9
|
+
# STORE = "mongoDB"
|
10
|
+
# DB = Mongo::Connection.new('localhost', 27017).db('abstract-analyzer-db')
|
11
|
+
# LOGGER = Logger.new(File.dirname(__FILE__) << "/abstract_analyzer_logger.log")
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'rubygems' # I know I know...
|
6
|
+
|
7
|
+
|
8
|
+
gem 'fiveruns-dash-ruby' # Put its path first
|
9
|
+
require 'fiveruns/dash'
|
10
|
+
|
11
|
+
require 'mongo'
|
12
|
+
|
13
|
+
require File.dirname(__FILE__) << '/dash_analyzer/dash_extensions'
|
14
|
+
require File.dirname(__FILE__) << '/dash_analyzer/base'
|
15
|
+
require File.dirname(__FILE__) << '/dash_analyzer/view'
|
16
|
+
|
17
|
+
module DashAnalyzer
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module DashAnalyzer
|
2
|
+
# Hooks up dash to your db
|
3
|
+
# Implement in as a middleware like so:
|
4
|
+
#
|
5
|
+
# class MyApp < DashAnalyzer::Base
|
6
|
+
# Fiveruns::Dash.register_recipe :testpack, :url => 'http://example.org' do |recipe|
|
7
|
+
# Fiveruns::Dash.logger.info 'REGISTERING ACTIONPACK RECIPE'
|
8
|
+
# recipe.time :response_time, :method => 'DashAnalyzer::Base#call', :mark => true
|
9
|
+
# recipe.time :another_response_time, :method => 'DashAnalyzer::Base#call', :mark => true
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# def initialize(*)
|
13
|
+
# @recipes = [{:name => :testpack, :url => 'http://example.org'}]
|
14
|
+
# super
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class Base
|
19
|
+
attr_accessor :db, :logger
|
20
|
+
|
21
|
+
def initialize(app, dash_interval=60)
|
22
|
+
@db = AbstractAnalyzer.const_get("DB")
|
23
|
+
@logger = AbstractAnalyzer.const_get("LOGGER")
|
24
|
+
|
25
|
+
startup_dash(dash_interval)
|
26
|
+
|
27
|
+
@app = app
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
@app.call(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
def startup_dash(interval = 60)
|
35
|
+
return if Fiveruns::Dash.session.reporter.started?
|
36
|
+
|
37
|
+
Fiveruns::Dash.session.reset
|
38
|
+
|
39
|
+
Fiveruns::Dash.logger = @logger
|
40
|
+
|
41
|
+
ENV['DASH_UPDATE'] = "mongo://db"
|
42
|
+
|
43
|
+
Fiveruns::Dash.configure do |config|
|
44
|
+
config.db = db
|
45
|
+
|
46
|
+
@recipes.each do |r|
|
47
|
+
config.add_recipe r[:name], r[:url]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Fiveruns::Dash.session.interval = interval
|
52
|
+
Fiveruns::Dash.session.start
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Setup db to receive rollups
|
2
|
+
class Fiveruns::Dash::Configuration
|
3
|
+
attr_accessor :db
|
4
|
+
end
|
5
|
+
|
6
|
+
# Allow the Fiveruns::Dash::Session interval to be set
|
7
|
+
module FiverunsDashSessionExtensions
|
8
|
+
def interval=(value)
|
9
|
+
reporter.interval = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
Fiveruns::Dash::Session.__send__ :include, FiverunsDashSessionExtensions
|
13
|
+
|
14
|
+
|
15
|
+
# Allow direct access to the Fiveruns::Dash::Payload data hash
|
16
|
+
module FiverunsDashPayloadExtensions
|
17
|
+
attr_reader :data
|
18
|
+
end
|
19
|
+
Fiveruns::Dash::Payload.__send__ :include, FiverunsDashPayloadExtensions
|
20
|
+
|
21
|
+
# Setup a store_mongo method on Fiveruns::Dash::Store
|
22
|
+
# NOTE: I think there is a better way to do this
|
23
|
+
module Fiveruns::Dash::Store::Mongo
|
24
|
+
def store_mongo(*uris)
|
25
|
+
Fiveruns::Dash.logger.info "Attempting to send #{payload.class}"
|
26
|
+
|
27
|
+
if payload.is_a? Fiveruns::Dash::DataPayload
|
28
|
+
data = payload.data
|
29
|
+
|
30
|
+
data.each do |d|
|
31
|
+
recipe_name = d[:recipe_name]
|
32
|
+
name = d[:name]
|
33
|
+
storage_name = "#{recipe_name}-#{name}"
|
34
|
+
d[:created_at] = Time.now
|
35
|
+
|
36
|
+
# TODO: Use upsert to handle cluser wide implementations
|
37
|
+
Fiveruns::Dash.session.configuration.db.collection(storage_name).insert(d)
|
38
|
+
Fiveruns::Dash.logger.info "Sent #{payload.class} to #{Fiveruns::Dash.session.configuration.db}"
|
39
|
+
end
|
40
|
+
else
|
41
|
+
raise "Payload of type #{payload.class} Not Currently Supported"
|
42
|
+
end
|
43
|
+
rescue
|
44
|
+
Fiveruns::Dash.logger.warn "Could not send #{payload.class}: #{$!}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Allow Fiveruns::Dash::Update to recognize mongo style urls
|
49
|
+
# Yes I think I totally just made up mongo style urls
|
50
|
+
# They look like 'mongo://ANYTHING_GOES_HERE_FOR_NOW'
|
51
|
+
|
52
|
+
# Also, I think I like the duck punching better than this send/include/send/alias_method mess
|
53
|
+
# Open to rewrites
|
54
|
+
module FiverunsDashUpdateExtensions
|
55
|
+
include Fiveruns::Dash::Store::Mongo
|
56
|
+
|
57
|
+
private
|
58
|
+
def storage_method_for_with_mongo(scheme)
|
59
|
+
if scheme =~ /^mongo/
|
60
|
+
:mongo
|
61
|
+
else
|
62
|
+
storage_method_for_without_mongo(scheme)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
Fiveruns::Dash::Update.__send__ :include, FiverunsDashUpdateExtensions
|
67
|
+
Fiveruns::Dash::Update.__send__ :alias_method, :storage_method_for_without_mongo, :storage_method_for
|
68
|
+
Fiveruns::Dash::Update.__send__ :alias_method, :storage_method_for, :storage_method_for_with_mongo
|
69
|
+
|
70
|
+
# Duck punched version
|
71
|
+
# module Fiveruns::Dash
|
72
|
+
# class Update
|
73
|
+
# include Store::Mongo
|
74
|
+
#
|
75
|
+
# private
|
76
|
+
#
|
77
|
+
# def storage_method_for_with_mongo(scheme)
|
78
|
+
# if scheme =~ /^mongo/
|
79
|
+
# :mongo
|
80
|
+
# else
|
81
|
+
# storage_method_for_without_mongo(scheme)
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# # alias_method_chain :storage_method_for, :mongo
|
85
|
+
# alias_method :storage_method_for_without_mongo, :storage_method_for
|
86
|
+
# alias_method :storage_method_for, :storage_method_for_with_mongo
|
87
|
+
# end
|
88
|
+
# end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'ruport'
|
2
|
+
|
3
|
+
module DashAnalyzer
|
4
|
+
class TimeView < AbstractAnalyzer::View
|
5
|
+
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
setup_show
|
9
|
+
end
|
10
|
+
|
11
|
+
# Create some kind of index view
|
12
|
+
get "/analytics" do
|
13
|
+
# Only look at time metrics for now
|
14
|
+
collection_names = db.collection_names.select{ |n| n.match(/time/)}
|
15
|
+
|
16
|
+
table = Table(:column_names => ["Metric", "Total Number of Calls", "Total Time (s)", "Avg Time per Call (s)"])
|
17
|
+
|
18
|
+
collection_names.each do |name|
|
19
|
+
coll = db.collection(name)
|
20
|
+
|
21
|
+
total_values = 0
|
22
|
+
total_invocations = 0
|
23
|
+
|
24
|
+
coll.find().each do |row|
|
25
|
+
values = row["values"]
|
26
|
+
|
27
|
+
# Why is this an array
|
28
|
+
if values && !values.empty?
|
29
|
+
v = values.first
|
30
|
+
value = v["value"]
|
31
|
+
invocations = v["invocations"]
|
32
|
+
|
33
|
+
total_values += value.to_f
|
34
|
+
total_invocations += invocations.to_i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
table << [name.to_s.titlecase, total_invocations.to_i, total_values.to_f, total_values.to_f/total_invocations.to_f]
|
39
|
+
end
|
40
|
+
|
41
|
+
trail = "There are more details at:\n#{collection_names.collect{ |n| " * /analytics/show/#{n}"}.join("\n")}"
|
42
|
+
|
43
|
+
[table.to_s, trail].join("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Use the collection names to create views
|
47
|
+
def setup_show
|
48
|
+
db.collection_names.each do |name|
|
49
|
+
self.class.class_eval do
|
50
|
+
get "/analytics/show/#{name}" do
|
51
|
+
coll = db.collection(name)
|
52
|
+
|
53
|
+
lead = "Listing #{coll.count} #{name.to_s.titlecase} Rollups in the Last Hour"
|
54
|
+
|
55
|
+
table = Table(:column_names => ["Time", "Metric Name", "Number of Calls", "Total Time"])
|
56
|
+
|
57
|
+
total_invocations = 0
|
58
|
+
total_values = 0.0
|
59
|
+
|
60
|
+
# TODO: Reverse this collection
|
61
|
+
coll.find({:created_at => {:$gte => Time.now.advance(:hours => -1)}}, {:sort => {:created_at => Mongo::DESCENDING}}).each do |row|
|
62
|
+
values = row["values"]
|
63
|
+
|
64
|
+
# Why is this an array
|
65
|
+
if values && !values.empty?
|
66
|
+
value = values.first["value"]
|
67
|
+
invocations = values.first["invocations"]
|
68
|
+
|
69
|
+
total_values = value.to_f
|
70
|
+
total_invocations += invocations.to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
table << [row["created_at"], row["description"], invocations.to_i, value.to_f]
|
74
|
+
end
|
75
|
+
|
76
|
+
results = []
|
77
|
+
results << "Total Calls: #{total_invocations}"
|
78
|
+
results << "Total Time: #{total_values} seconds"
|
79
|
+
results << "Avg Time per Call: #{total_values/total_invocations.to_f} seconds"
|
80
|
+
results = results.join("\n")
|
81
|
+
|
82
|
+
[lead, results, table.to_s].join("\n")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/lib/middleware.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# TODO: Figure out what to do with these.
|
2
|
+
# They already be loaded if it's actually in a Rails project
|
3
|
+
# But if it's not, we'd hate to load all this mess.
|
4
|
+
|
5
|
+
RAILS_ROOT = File.dirname(__FILE__) unless defined?(RAILS_ROOT)
|
6
|
+
RAILS_DEFAULT_LOGGER = Logger.new(File.dirname(__FILE__) << "/middleware_rails_dash.log") unless defined?(RAILS_DEFAULT_LOGGER)
|
7
|
+
gem 'rails'
|
8
|
+
gem 'activesupport'
|
9
|
+
gem 'activerecord'
|
10
|
+
gem 'actionpack'
|
11
|
+
require 'action_controller'
|
12
|
+
require 'action_controller/base'
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) << '/rails/dash'
|
17
|
+
|
18
|
+
module AbstractAnalyzer
|
19
|
+
module Middleware
|
20
|
+
# Implementation
|
21
|
+
# In RAILS_ROOT/config/initializers/middlewares.rb
|
22
|
+
#
|
23
|
+
# require 'abstract_analyzer/abstract_analyzer'
|
24
|
+
#
|
25
|
+
# # Setup the Analyzer DB
|
26
|
+
# abstract_analyzer_db = Mongo::Connection.new('localhost', 27017).db('aa-rails-dash-analyzer-db')
|
27
|
+
# AbstractAnalyzer.const_set("DB", abstract_analyzer_db)
|
28
|
+
#
|
29
|
+
# # Setup the Analyzer LOGGER
|
30
|
+
# abstract_logger = Logger.new("#{RAILS_ROOT}/log/abstract_analyzer_logger.log")
|
31
|
+
# AbstractAnalyzer.const_set("LOGGER", abstract_logger)
|
32
|
+
#
|
33
|
+
# # Use the Analyzer and View middlewares
|
34
|
+
# ActionController::Dispatcher.middleware.use AbstractAnalyzer::Middleware::Rails::Dash::Analyzer
|
35
|
+
# ActionController::Dispatcher.middleware.use AbstractAnalyzer::Middleware::Rails::Dash::View
|
36
|
+
#
|
37
|
+
# NOTE: The require piece will be replace by an environment.rb config.gem call at some point
|
38
|
+
module Rails
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fiveruns-dash-rails'
|
2
|
+
|
3
|
+
module AbstractAnalyzer
|
4
|
+
module Middleware
|
5
|
+
module Rails
|
6
|
+
module Dash
|
7
|
+
# This is where we grab the analytics
|
8
|
+
class Analyzer < DashAnalyzer::Base
|
9
|
+
# Fiveruns::Dash.register_recipe :actionpack, :url => 'http://example.org' do |recipe|
|
10
|
+
# Fiveruns::Dash.logger.info 'REGISTERING ACTIONPACK RECIPE'
|
11
|
+
#
|
12
|
+
# recipe.time :response_time, :method => 'AbstractController::Base#process_action', :mark => true
|
13
|
+
# end
|
14
|
+
|
15
|
+
def initialize(*)
|
16
|
+
# Fiveruns::Dash::Rails.load_recipes
|
17
|
+
|
18
|
+
#@recipes = [{:name => :actionpack, :url => 'http://example.org'}]
|
19
|
+
@recipes = [{:name => :ruby, :url => 'http://dash.fiveruns.com'},
|
20
|
+
{:name => :rails, :url => 'http://dash.fiveruns.com'}]
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This is where we view them
|
26
|
+
class View < DashAnalyzer::TimeView
|
27
|
+
def initialize(*)
|
28
|
+
super
|
29
|
+
#self.collection = 'actionpack-response_time'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/view.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'usher'
|
2
|
+
|
3
|
+
module AbstractAnalyzer
|
4
|
+
class View
|
5
|
+
def db
|
6
|
+
AbstractAnalyzer.const_get("DB")
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :collection
|
10
|
+
def initialize(app, collection = nil)
|
11
|
+
@collection = collection.to_s
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
method_name = "[#{env["REQUEST_METHOD"].to_s.downcase}] #{env["PATH_INFO"]}"
|
17
|
+
|
18
|
+
# This has got to be not good for performance
|
19
|
+
# Consider using the PATH_INFO var instead
|
20
|
+
if self.respond_to?(method_name)
|
21
|
+
content = self.__send__(method_name)
|
22
|
+
[200, {"Content-Type" => "text/plain"}, content]
|
23
|
+
else
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# From Railsnatra
|
30
|
+
# Set @_routes on child classes
|
31
|
+
def inherited(klass)
|
32
|
+
klass.class_eval { @_routes = [] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_app
|
36
|
+
routes, controller = @_routes, self
|
37
|
+
|
38
|
+
# From Railsnatra ;)
|
39
|
+
# We're using Usher as a router for this project. To
|
40
|
+
# simplify things, we just used the rack interface to
|
41
|
+
# router and it's DSL.
|
42
|
+
app = Usher::Interface.for(:rack) do
|
43
|
+
routes.each do |route|
|
44
|
+
conditions = {:request_method => route[:method]}
|
45
|
+
add(route[:uri], :conditions => conditions.merge(route[:options])).
|
46
|
+
to(controller.action(route[:action]))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
app
|
51
|
+
end
|
52
|
+
|
53
|
+
def get(uri, options = {}, &block)
|
54
|
+
route(:get, uri, options, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
# From Railsnatra
|
59
|
+
def route(http_method, uri, options, &block)
|
60
|
+
# Since we need unique actions for each possible GET,
|
61
|
+
# POST, etc... URLs, we add the method in the action
|
62
|
+
# name. ActionController::Metal has the ability to
|
63
|
+
# route an action with any name format.
|
64
|
+
action_name = "[#{http_method}] #{uri}"
|
65
|
+
# We save the route options in the global @_routes
|
66
|
+
# variable so that we can build the routes when the
|
67
|
+
# app is generated.
|
68
|
+
@_routes << {:method => http_method.to_s.upcase, :uri => uri,
|
69
|
+
:action => action_name, :options => options}
|
70
|
+
# Now, we finally create the action method.
|
71
|
+
define_method(action_name, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
|
2
|
+
|
3
|
+
class BaseTest < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class MyApp < DashAnalyzer::Base
|
7
|
+
|
8
|
+
Fiveruns::Dash.register_recipe :testpack, :url => 'http://example.org' do |recipe|
|
9
|
+
Fiveruns::Dash.logger.info 'REGISTERING ACTIONPACK RECIPE'
|
10
|
+
recipe.time :response_time, :method => 'DashAnalyzer::Base#call', :mark => true
|
11
|
+
recipe.time :another_response_time, :method => 'DashAnalyzer::Base#call', :mark => true
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
@recipes = [{:name => :testpack, :url => 'http://example.org'}]
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def app
|
21
|
+
MyApp.new(FooApp.new, 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
# MAKE SURE MONGO IS RUNNING ON localhost:27017
|
25
|
+
def test_mongodb
|
26
|
+
get "/foo"
|
27
|
+
|
28
|
+
assert_not_nil app.db
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_success
|
32
|
+
get "/foo"
|
33
|
+
|
34
|
+
assert last_response.ok?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Should I really be testing Dash internals? I think not.
|
38
|
+
def x_test_session_data_store
|
39
|
+
5.times do
|
40
|
+
get "/foo"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Not really sure what this is all about, but I got it to work
|
44
|
+
data = Fiveruns::Dash.session.data
|
45
|
+
|
46
|
+
assert_equal 5, data.first[:values].first[:invocations]
|
47
|
+
|
48
|
+
7.times do
|
49
|
+
get "/foo"
|
50
|
+
end
|
51
|
+
|
52
|
+
# OK. Accessing the session data clears it out.
|
53
|
+
data2 = Fiveruns::Dash.session.data
|
54
|
+
assert_equal 7, data2.first[:values].first[:invocations]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_mongo_data_store
|
58
|
+
coll = app.db.collection('testpack-response_time')
|
59
|
+
coll.clear
|
60
|
+
|
61
|
+
5.times do
|
62
|
+
get "/foo"
|
63
|
+
end
|
64
|
+
|
65
|
+
sleep Fiveruns::Dash.session.reporter.interval
|
66
|
+
|
67
|
+
total_invocations = 0
|
68
|
+
|
69
|
+
coll.find().each do |row|
|
70
|
+
values = row[:values]
|
71
|
+
if values
|
72
|
+
invocations = values[:invocations]
|
73
|
+
total_invocations += invocations
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
assert 5, total_invocations
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_mongo_data_store_with_multiple_metrics
|
81
|
+
coll1 = app.db.collection('testpack-response_time')
|
82
|
+
coll1.clear
|
83
|
+
|
84
|
+
coll2 = app.db.collection('testpack-another_response_time')
|
85
|
+
coll2.clear
|
86
|
+
|
87
|
+
10.times do
|
88
|
+
get "/foo"
|
89
|
+
end
|
90
|
+
|
91
|
+
sleep Fiveruns::Dash.session.reporter.interval
|
92
|
+
|
93
|
+
#coll.find().each { |row| puts row.class; puts row.inspect; puts "-------" }
|
94
|
+
|
95
|
+
total_coll1_invocations = 0
|
96
|
+
|
97
|
+
coll1.find().each do |row|
|
98
|
+
values = row[:values]
|
99
|
+
if values
|
100
|
+
invocations = values[:invocations]
|
101
|
+
total_coll1_invocations += invocations
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
total_coll2_invocations = 0
|
106
|
+
|
107
|
+
coll2.find().each do |row|
|
108
|
+
values = row[:values]
|
109
|
+
if values
|
110
|
+
invocations = values[:invocations]
|
111
|
+
total_coll2_invocations += invocations
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
assert 5, total_coll1_invocations
|
116
|
+
assert 5, total_coll2_invocations
|
117
|
+
end
|
118
|
+
|
119
|
+
# Just trying to ensure Dash is sending info when requests aren't being made
|
120
|
+
def test_frequency_of_mongo_data_inserts
|
121
|
+
coll = app.db.collection('testpack-response_time')
|
122
|
+
coll.clear
|
123
|
+
|
124
|
+
5.times do
|
125
|
+
get "/foo"
|
126
|
+
end
|
127
|
+
|
128
|
+
sleep Fiveruns::Dash.session.reporter.interval*20
|
129
|
+
|
130
|
+
assert 20 <= coll.count
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
|
2
|
+
|
3
|
+
class ViewTest < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class MyApp < DashAnalyzer::TimeView
|
7
|
+
end
|
8
|
+
|
9
|
+
def app
|
10
|
+
MyApp.new(FooApp.new, 'testpack-response_time')
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_index
|
14
|
+
get "/analytics"
|
15
|
+
|
16
|
+
assert last_response.ok?
|
17
|
+
|
18
|
+
puts last_response.body
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_show
|
22
|
+
get "/analytics/show/testpack-response_time"
|
23
|
+
|
24
|
+
assert last_response.ok?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rubygems" # I know...I know...don't do this
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
require "autotest"
|
5
|
+
|
6
|
+
require "rack/test"
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) << "/../lib/abstract_analyzer"
|
9
|
+
|
10
|
+
# Create a test mongo db
|
11
|
+
test_mongo_db = Mongo::Connection.new('localhost', 27017).db('test-dash-analyzer-db')
|
12
|
+
AbstractAnalyzer.const_set("DB", test_mongo_db)
|
13
|
+
|
14
|
+
# Create a test log
|
15
|
+
test_logger = Logger.new(File.dirname(__FILE__) << "/abstract_analyzer_logger.log")
|
16
|
+
AbstractAnalyzer.const_set("LOGGER", test_logger)
|
17
|
+
|
18
|
+
# Create a test rack app
|
19
|
+
class FooApp
|
20
|
+
def call(env)
|
21
|
+
[200, {"Content-Type" => "text/plain"}, ["Hello world!"]]
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abstract_analyzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark McSpadden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-28 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: markmcspadden@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README.rdoc
|
27
|
+
- Rakefile
|
28
|
+
- VERSION.yml
|
29
|
+
- lib/abstract_analyzer.rb
|
30
|
+
- lib/dash_analyzer.rb
|
31
|
+
- lib/dash_analyzer/base.rb
|
32
|
+
- lib/dash_analyzer/dash_extensions.rb
|
33
|
+
- lib/dash_analyzer/view.rb
|
34
|
+
- lib/middleware.rb
|
35
|
+
- lib/middleware/rails.rb
|
36
|
+
- lib/middleware/rails/dash.rb
|
37
|
+
- lib/view.rb
|
38
|
+
- test/lib/dash_analyzer/test_base.rb
|
39
|
+
- test/lib/dash_analyzer/test_dash_extensions.rb
|
40
|
+
- test/lib/dash_analyzer/test_view.rb
|
41
|
+
- test/lib/middleware/rails.rb
|
42
|
+
- test/lib/middleware/rails/dash.rb
|
43
|
+
- test/lib/test_dash_analyzer.rb
|
44
|
+
- test/lib/test_middleware.rb
|
45
|
+
- test/test_helper.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/garlandgroup/abstract-analyzer
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.3.5
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: A rack based app analyzer
|
74
|
+
test_files:
|
75
|
+
- test/lib/dash_analyzer/test_base.rb
|
76
|
+
- test/lib/dash_analyzer/test_dash_extensions.rb
|
77
|
+
- test/lib/dash_analyzer/test_view.rb
|
78
|
+
- test/lib/middleware/rails/dash.rb
|
79
|
+
- test/lib/middleware/rails.rb
|
80
|
+
- test/lib/test_dash_analyzer.rb
|
81
|
+
- test/lib/test_middleware.rb
|
82
|
+
- test/test_helper.rb
|