fiveruns-dash-rails 0.7.1
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/README.rdoc +73 -0
- data/Rakefile +46 -0
- data/init.rb +2 -0
- data/lib/fiveruns/dash/rails.rb +166 -0
- data/lib/fiveruns/dash/rails/startup.rb +17 -0
- data/lib/fiveruns/dash/rails/version.rb +97 -0
- data/lib/fiveruns/dash/recipes/activerecord.rb +96 -0
- data/lib/fiveruns/dash/recipes/rails.rb +112 -0
- data/lib/fiveruns/dash/template_context.rb +51 -0
- data/lib/fiveruns_dash_rails.rb +9 -0
- data/lib/tasks/dash.rake +57 -0
- data/rails/init.rb +32 -0
- data/test/activerecord_test.rb +113 -0
- data/test/test_helper.rb +30 -0
- data/version.yml +4 -0
- metadata +81 -0
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= FiveRuns Dash recipe for Ruby on Rails
|
2
|
+
|
3
|
+
Provides a Ruby API to push metrics from a Rails app to the FiveRuns Dash service, http://dash.fiveruns.com, currently in beta.
|
4
|
+
|
5
|
+
You'll need a Dash account before using this library.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
This library is released as a gem from the official repository at http://github.com/fiveruns/dash-rails
|
10
|
+
|
11
|
+
sudo gem install fiveruns-dash-rails --source 'http://gems.github.com'
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
See the Ruby Language Guide http://dash.fiveruns.com/help/ruby.html for information on how to use this library.
|
16
|
+
|
17
|
+
== Authors
|
18
|
+
|
19
|
+
The FiveRuns Development Team & Dash community
|
20
|
+
|
21
|
+
== Dependencies
|
22
|
+
|
23
|
+
* The fiveruns-dash-ruby gem (see http://github.com/fiveruns/dash-ruby)
|
24
|
+
* The json gem (as a dependency for fiveruns-dash-ruby)
|
25
|
+
|
26
|
+
== Rails versions
|
27
|
+
|
28
|
+
Dash has been tested with Rails 2.2 and 2.1. It may (or may not) work with Rails 2.0 applications.
|
29
|
+
|
30
|
+
We haven't done a lot of testing with the gem being unpacked in Rails apps. YMMV; please let us know if you have issues.
|
31
|
+
|
32
|
+
== Platforms
|
33
|
+
|
34
|
+
This library has only been tested on OSX and Linux. See the notes for fiveruns-dash-ruby for more information: http://github.com/fiveruns/dash-ruby
|
35
|
+
|
36
|
+
== Contributing
|
37
|
+
|
38
|
+
As an open source project, we welcome community contributions!
|
39
|
+
|
40
|
+
The best way to contribute is by sending pull requests via GitHub. The official repository for this project is:
|
41
|
+
|
42
|
+
http://github.com/fiveruns/dash-rails
|
43
|
+
|
44
|
+
== Support
|
45
|
+
|
46
|
+
Please join the dash-users Google group, http://groups.google.com/group/dash-users
|
47
|
+
|
48
|
+
You can also contact us via Twitter, Campfire, or email; see the main help page, http://dash.fiveruns.com/help, for details.
|
49
|
+
|
50
|
+
== License
|
51
|
+
|
52
|
+
# (The FiveRuns License)
|
53
|
+
#
|
54
|
+
# Copyright (c) 2006-2008 FiveRuns Corporation
|
55
|
+
#
|
56
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
57
|
+
# a copy of this software and associated documentation files (the
|
58
|
+
# 'Software'), to deal in the Software without restriction, including
|
59
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
60
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
61
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
62
|
+
# the following conditions:
|
63
|
+
#
|
64
|
+
# The above copyright notice and this permission notice shall be
|
65
|
+
# included in all copies or substantial portions of the Software.
|
66
|
+
#
|
67
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
68
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
69
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
70
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
71
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
72
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
73
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.verbose = true
|
6
|
+
t.test_files = FileList['test/*_test.rb']
|
7
|
+
t.libs << 'lib'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
|
15
|
+
Jeweler::Tasks.new do |s|
|
16
|
+
s.name = "dash-rails"
|
17
|
+
s.rubyforge_project = 'fiveruns'
|
18
|
+
s.summary = "FiveRuns Dash recipe for Ruby on Rails"
|
19
|
+
s.email = "dev@fiveruns.com"
|
20
|
+
s.homepage = "http://github.com/fiveruns/dash-rails"
|
21
|
+
s.description = "Provides an API to send metrics from Rails applications to the FiveRuns Dash service"
|
22
|
+
s.authors = ["FiveRuns Development Team"]
|
23
|
+
s.files = FileList['README.rdoc', 'Rakefile', 'version.yml', 'init.rb', "{lib,rails,test}/**/*", ]
|
24
|
+
s.add_dependency 'fiveruns-dash-ruby', '>= 0.7.1'
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
29
|
+
|
30
|
+
NAME = "dash-rails"
|
31
|
+
AUTHOR = "FiveRuns Development Team"
|
32
|
+
EMAIL = "dev@fiveruns.com"
|
33
|
+
HOMEPAGE = "http://dash.fiveruns.com/"
|
34
|
+
SUMMARY = "FiveRuns Dash library for Ruby on Rails"
|
35
|
+
|
36
|
+
task :coverage do
|
37
|
+
rm_f "coverage"
|
38
|
+
rm_f "coverage.data"
|
39
|
+
rcov = "rcov --exclude gems --exclude version.rb --sort coverage --text-summary --html -o coverage"
|
40
|
+
system("#{rcov} test/*_test.rb")
|
41
|
+
if ccout = ENV['CC_BUILD_ARTIFACTS']
|
42
|
+
FileUtils.rm_rf '#{ccout}/coverage'
|
43
|
+
FileUtils.cp_r 'coverage', ccout
|
44
|
+
end
|
45
|
+
system "open coverage/index.html" if PLATFORM['darwin']
|
46
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require File.dirname(__FILE__) << "/rails/version"
|
2
|
+
require File.dirname(__FILE__) << "/rails/startup"
|
3
|
+
|
4
|
+
if START_FIVERUNS_DASH_RAILS
|
5
|
+
|
6
|
+
module Fiveruns
|
7
|
+
module Dash
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :server
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.queue_size
|
16
|
+
return 0 unless server_type
|
17
|
+
case server_type
|
18
|
+
when :mongrel
|
19
|
+
server.workers.list.length
|
20
|
+
else
|
21
|
+
0 # Skip
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.server_type
|
26
|
+
return @server_type if defined?(@server_type)
|
27
|
+
@server_type = if server
|
28
|
+
case server.class.to_s
|
29
|
+
when /Mongrel/
|
30
|
+
:mongrel
|
31
|
+
else
|
32
|
+
::Fiveruns::Dash.logger.warn "Unrecognized app server type: #{server.class}, not collecting queue size"
|
33
|
+
false
|
34
|
+
end
|
35
|
+
else
|
36
|
+
::Fiveruns::Dash.logger.warn "Could not find app server, not collecting queue size"
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load_recipes
|
42
|
+
Dir[File.dirname(__FILE__) << "/recipes/**/*.rb"].each do |file|
|
43
|
+
require file
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.start(tokens = {}, &block)
|
48
|
+
return if Fiveruns::Dash.session.reporter.started?
|
49
|
+
::Rails::Initializer.send(:include, Fiveruns::Dash::Rails::Initializer)
|
50
|
+
store_dash_start_block do
|
51
|
+
configure(tokens, &block) unless tokens.empty?
|
52
|
+
if Fiveruns::Dash.configuration.ready?
|
53
|
+
RAILS_DEFAULT_LOGGER.info "Starting Dash"
|
54
|
+
Fiveruns::Dash.session.start
|
55
|
+
else
|
56
|
+
log_error unless env == 'development'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.store_dash_start_block(&block)
|
62
|
+
@dash_start_block = block
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.dash_start_block
|
66
|
+
@dash_start_block ||= lambda {}
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def self.configure(tokens = {}, &block)
|
71
|
+
tokens.each do |environment, token|
|
72
|
+
if environment.to_s == self.env
|
73
|
+
Fiveruns::Dash.configure({:app => token}, &block)
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.log_error
|
80
|
+
# TODO: Add URL for help
|
81
|
+
message =<<-EOM
|
82
|
+
FiveRuns Dash [Rails] (v#{Version::STRING}) Application token missing
|
83
|
+
===
|
84
|
+
In config/initializers/dash.rb or at the bottom of config/environment.rb, please add:
|
85
|
+
|
86
|
+
Fiveruns::Dash::Rails.configure :#{env} => 'YOUR-#{env.upcase}-ENV-APP-TOKEN-HERE'
|
87
|
+
|
88
|
+
You can also set app tokens for other environments (eg, staging), at the same time.
|
89
|
+
See http://todo/path/to/help for more information
|
90
|
+
===
|
91
|
+
EOM
|
92
|
+
RAILS_DEFAULT_LOGGER.warn(message.strip)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.env
|
96
|
+
::Rails.env # >= Rails 2.1
|
97
|
+
rescue
|
98
|
+
ENV['RAILS_ENV'] # <= Rails 2.0
|
99
|
+
end
|
100
|
+
|
101
|
+
module ActionContext
|
102
|
+
|
103
|
+
def self.included(base)
|
104
|
+
base.send(:include, InstanceMethods)
|
105
|
+
base.alias_method_chain :perform_action, :fiveruns_dash_context
|
106
|
+
base.extend(ClassMethods)
|
107
|
+
(class << base; self; end).alias_method_chain :process, :fiveruns_dash_tracing
|
108
|
+
end
|
109
|
+
|
110
|
+
module ClassMethods
|
111
|
+
|
112
|
+
def process_with_fiveruns_dash_tracing(*args, &block)
|
113
|
+
operation = lambda { process_without_fiveruns_dash_tracing(*args, &block) }
|
114
|
+
params = args.first.parameters
|
115
|
+
# TODO/FIXME: For now, we simply look for a 'trace' parameter to select requests to trace; in the
|
116
|
+
# future, we need a more advanced sampling mechanism (some operation in a recipe a
|
117
|
+
# request must pass, or selection criteria returned in a response from the service)
|
118
|
+
trace_context = ['action', "#{params['controller'].camelize}Controller##{params['action']}"]
|
119
|
+
if ::Fiveruns::Dash.trace_contexts.include?(trace_context)
|
120
|
+
::Fiveruns::Dash.session.trace(trace_context) do
|
121
|
+
operation.call
|
122
|
+
end
|
123
|
+
else
|
124
|
+
operation.call
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
module InstanceMethods
|
131
|
+
|
132
|
+
def perform_action_with_fiveruns_dash_context(*args, &block)
|
133
|
+
action_name = (request.parameters['action'] || 'index').to_s
|
134
|
+
Fiveruns::Dash::Context.set ['action', %(#{self.class.name}##{action_name})]
|
135
|
+
perform_action_without_fiveruns_dash_context(*args, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
module Initializer
|
143
|
+
|
144
|
+
def self.included(base)
|
145
|
+
base.send(:include, InstanceMethods)
|
146
|
+
base.alias_method_chain :load_application_classes, :dash
|
147
|
+
end
|
148
|
+
|
149
|
+
module InstanceMethods
|
150
|
+
|
151
|
+
def load_application_classes_with_dash
|
152
|
+
load_application_classes_without_dash
|
153
|
+
Fiveruns::Dash::Rails.dash_start_block.call
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# AKK: remove me in favor of inlining into init
|
2
|
+
START_FIVERUNS_DASH_RAILS = if ENV['START_FIVERUNS_DASH'] || File.basename($0) != 'irb'
|
3
|
+
true
|
4
|
+
else
|
5
|
+
module Fiveruns
|
6
|
+
module Dash
|
7
|
+
module Rails
|
8
|
+
|
9
|
+
def self.configure(*args, &block)
|
10
|
+
RAILS_DEFAULT_LOGGER.info "[FiveRuns Dash] Skipping configuration (`#{$0}' not supported for collection)"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
false
|
17
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# (The MIT License)
|
2
|
+
#
|
3
|
+
# Copyright (c) 2008 Jamis Buck <jamis@37signals.com>,
|
4
|
+
# with modifications by Bruce Williams <bruce@fiveruns.com>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# 'Software'), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
module Fiveruns
|
25
|
+
|
26
|
+
module Dash
|
27
|
+
|
28
|
+
module Rails
|
29
|
+
|
30
|
+
# A class for describing the current version of a library. The version
|
31
|
+
# consists of three parts: the +major+ number, the +minor+ number, and the
|
32
|
+
# +tiny+ (or +patch+) number.
|
33
|
+
class Version
|
34
|
+
|
35
|
+
include Comparable
|
36
|
+
|
37
|
+
# A convenience method for instantiating a new Version instance with the
|
38
|
+
# given +major+, +minor+, and +tiny+ components.
|
39
|
+
def self.[](major, minor, tiny)
|
40
|
+
new(major, minor, tiny)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Borrowed from TuneUp
|
44
|
+
def self.rails
|
45
|
+
@rails ||= begin
|
46
|
+
# handle ::Rails::VERSION not being set
|
47
|
+
Version.new(::Rails::VERSION::MAJOR, ::Rails::VERSION::MINOR, ::Rails::VERSION::TINY) rescue Version.new(0,0,0)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
attr_reader :major, :minor, :tiny
|
53
|
+
|
54
|
+
# Create a new Version object with the given components.
|
55
|
+
def initialize(major, minor, tiny)
|
56
|
+
@major, @minor, @tiny = major, minor, tiny
|
57
|
+
end
|
58
|
+
|
59
|
+
# Compare this version to the given +version+ object.
|
60
|
+
def <=>(version)
|
61
|
+
to_i <=> version.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
# Converts this version object to a string, where each of the three
|
65
|
+
# version components are joined by the '.' character. E.g., 2.0.0.
|
66
|
+
def to_s
|
67
|
+
@to_s ||= [@major, @minor, @tiny].join(".")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Converts this version to a canonical integer that may be compared
|
71
|
+
# against other version objects.
|
72
|
+
def to_i
|
73
|
+
@to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_a
|
77
|
+
[@major, @minor, @tiny]
|
78
|
+
end
|
79
|
+
|
80
|
+
PARSED = YAML.load(File.read(File.dirname(__FILE__) << "/../../../../version.yml"))
|
81
|
+
|
82
|
+
MAJOR = PARSED['major']
|
83
|
+
MINOR = PARSED['minor']
|
84
|
+
TINY = PARSED['patch']
|
85
|
+
|
86
|
+
# The current version as a Version instance
|
87
|
+
CURRENT = new(MAJOR, MINOR, TINY)
|
88
|
+
# The current version as a String
|
89
|
+
STRING = CURRENT.to_s
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Fiveruns::Dash::ActiveRecordContext
|
2
|
+
CLASS_METHODS = %w(find find_by_sql calculate create create! update_all destroy destroy_all delete delete_all)
|
3
|
+
INSTANCE_METHODS = %w(update save save! destroy)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
class << base
|
7
|
+
CLASS_METHODS.each do |meth|
|
8
|
+
head = meth
|
9
|
+
tail = ''
|
10
|
+
head, tail = meth[0..(meth.length-2)], meth[-1..-1] if %w(? !).include? meth[-1..-1]
|
11
|
+
self.class_eval <<-EOM
|
12
|
+
def #{head}_with_dash_context#{tail}(*args, &block)
|
13
|
+
Fiveruns::Dash::ActiveRecordContext.with_model_context(self.name) do
|
14
|
+
#{head}_without_dash_context#{tail}(*args, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
EOM
|
18
|
+
alias_method_chain(meth.to_sym, :dash_context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
INSTANCE_METHODS.each do |meth|
|
23
|
+
head = meth
|
24
|
+
tail = ''
|
25
|
+
head, tail = meth[0..meth.length-2], meth[-1..-1] if %w(? !).include? meth[-1..-1]
|
26
|
+
base.class_eval <<-EOM
|
27
|
+
def #{head}_with_dash_context#{tail}(*args, &block)
|
28
|
+
Fiveruns::Dash::ActiveRecordContext.with_model_context(self.class.name) do
|
29
|
+
#{head}_without_dash_context#{tail}(*args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
EOM
|
33
|
+
base.alias_method_chain(meth.to_sym, :dash_context)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.with_model_context(model_name)
|
38
|
+
ctx = Fiveruns::Dash::Context.context
|
39
|
+
# don't change context if model context has already been set.
|
40
|
+
return yield if ctx.size > 0 && ctx[-2] == 'model' && ctx[-1] == model_name
|
41
|
+
|
42
|
+
original_context = Fiveruns::Dash::Context.context.dup
|
43
|
+
begin
|
44
|
+
if ctx[-2] == 'model'
|
45
|
+
# Some models will internally load other models.
|
46
|
+
Fiveruns::Dash::Context.context.pop
|
47
|
+
Fiveruns::Dash::Context.context << model_name
|
48
|
+
else
|
49
|
+
Fiveruns::Dash::Context.context << 'model'
|
50
|
+
Fiveruns::Dash::Context.context << model_name
|
51
|
+
end
|
52
|
+
return yield
|
53
|
+
ensure
|
54
|
+
Fiveruns::Dash::Context.set original_context
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.all_methods
|
59
|
+
CLASS_METHODS.map { |m| "ActiveRecord::Base.#{m}" } + INSTANCE_METHODS.map { |m| "ActiveRecord::Base##{m}"}
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# ActiveRecord ################################################################
|
65
|
+
|
66
|
+
Fiveruns::Dash.register_recipe :activerecord, :url => 'http://dash.fiveruns.com' do |recipe|
|
67
|
+
recipe.time :ar_time, 'ActiveRecord Time', :methods => Fiveruns::Dash::ActiveRecordContext.all_methods, :reentrant => true
|
68
|
+
recipe.time :db_time, 'Database Time', :methods => %w(ActiveRecord::ConnectionAdapters::AbstractAdapter#log)
|
69
|
+
|
70
|
+
# We need a way to get the total time for a request/operation so that we can
|
71
|
+
# calculate the relative percentage used by AR/DB. Default to "response_time" for the Rails
|
72
|
+
# recipe but daemons can set this constant to provide their own total time metric.
|
73
|
+
total_time = recipe.options[:ar_total_time] ? recipe.options[:ar_total_time] : "response_time"
|
74
|
+
|
75
|
+
recipe.percentage :ar_util, 'ActiveRecord Utilization', :sources => ["ar_time", total_time] do |ar_time, all_time|
|
76
|
+
(ar_time / all_time) * 100.0
|
77
|
+
end
|
78
|
+
recipe.percentage :db_util, 'Database Utilization', :sources => ["db_time", total_time] do |db_time, all_time|
|
79
|
+
(db_time / all_time) * 100.0
|
80
|
+
end
|
81
|
+
|
82
|
+
recipe.modify :recipe_name => :activerecord, :recipe_url => 'http://dash.fiveruns.com' do |metric|
|
83
|
+
metric.find_context_with do |obj, *args|
|
84
|
+
if Fiveruns::Dash::Context.context == []
|
85
|
+
[]
|
86
|
+
else
|
87
|
+
[[], Fiveruns::Dash::Context.context]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
recipe.added do
|
93
|
+
ActiveRecord::Base.send(:include, Fiveruns::Dash::ActiveRecordContext)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Fiveruns::Dash::Rails::Hash
|
2
|
+
|
3
|
+
def self.clean(extended_hash = {})
|
4
|
+
(extended_hash || {}).keys.inject({}) do |all, key|
|
5
|
+
val = extended_hash[key]
|
6
|
+
if val.kind_of? Hash
|
7
|
+
val = clean(val)
|
8
|
+
end
|
9
|
+
all[key.to_s] = val
|
10
|
+
all
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# ActionPack ##################################################################
|
17
|
+
Fiveruns::Dash.register_recipe :actionpack, :url => 'http://dash.fiveruns.com' do |recipe|
|
18
|
+
recipe.time :response_time, :method => 'ActionController::Base#perform_action'
|
19
|
+
recipe.counter :requests, 'Requests', :incremented_by => 'ActionController::Base#perform_action'
|
20
|
+
|
21
|
+
targets = []
|
22
|
+
targets << 'ActionView::Template#render' if defined?(ActionView::Template)
|
23
|
+
targets << 'ActionView::PartialTemplate#render' if defined?(ActionView::PartialTemplate)
|
24
|
+
if !targets.empty?
|
25
|
+
recipe.time :render_time, :method => targets
|
26
|
+
else
|
27
|
+
Fiveruns::Dash.logger.warn 'Collection of "render_time" unsupported for this version of Rails'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Rails #######################################################################
|
32
|
+
Fiveruns::Dash.register_recipe :rails, :url => 'http://dash.fiveruns.com' do |recipe|
|
33
|
+
recipe.add_recipe :activerecord, :url => 'http://dash.fiveruns.com'
|
34
|
+
|
35
|
+
recipe.add_recipe :actionpack, :url => 'http://dash.fiveruns.com'
|
36
|
+
recipe.modify :recipe_name => :actionpack, :recipe_url => 'http://dash.fiveruns.com' do |metric|
|
37
|
+
if metric.name.to_s == 'render_time'
|
38
|
+
metric.find_context_with do |obj, *args|
|
39
|
+
Fiveruns::Dash::Context.context
|
40
|
+
end
|
41
|
+
else
|
42
|
+
metric.find_context_with do |obj, *args|
|
43
|
+
[[], Fiveruns::Dash::Context.context]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
recipe.add_exceptions_from 'ActionController::Base#perform_action_without_rescue' do |ex, controller|
|
49
|
+
session_data = nil
|
50
|
+
begin
|
51
|
+
session_data = controller.request.session.instance_variable_get("@data")
|
52
|
+
rescue Exception => e
|
53
|
+
Fiveruns::Dash.logger.warn "Could not retrieve session data for exception: #{e.message}"
|
54
|
+
end
|
55
|
+
{
|
56
|
+
:name => "#{ex.class.name} in #{controller.class.name}##{controller.params[:action]}", # Override the standard name
|
57
|
+
:session => Fiveruns::Dash::Rails::Hash.clean(session_data).to_json,
|
58
|
+
:headers => Fiveruns::Dash::Rails::Hash.clean(controller.request.headers).to_json,
|
59
|
+
:request => { :url => controller.request.url, :params => controller.params.inspect }.to_json,
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Same classes as the exception_notification plugin
|
64
|
+
IGNORE = [ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction]
|
65
|
+
|
66
|
+
recipe.ignore_exceptions do |exc|
|
67
|
+
IGNORE.include? exc.class
|
68
|
+
end
|
69
|
+
|
70
|
+
recipe.added do
|
71
|
+
ActionController::Base.send(:include, Fiveruns::Dash::Rails::ActionContext)
|
72
|
+
ActionView::Template.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::Template)
|
73
|
+
ActionView::InlineTemplate.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::InlineTemplate)
|
74
|
+
ActionView::PartialTemplate.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::PartialTemplate)
|
75
|
+
|
76
|
+
begin
|
77
|
+
if defined?(Mongrel)
|
78
|
+
ActiveSupport::Deprecation.silence do
|
79
|
+
# Unfortunately there is no known way to get direct access
|
80
|
+
# to the Mongrel singleton. Wade through the Ruby heap to
|
81
|
+
# find it.
|
82
|
+
ObjectSpace.each_object do |obj|
|
83
|
+
if obj.class == Mongrel::HttpServer
|
84
|
+
Fiveruns::Dash::Rails.server = obj
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue Exception => e
|
90
|
+
if RUBY_PLATFORM =~ /java/ && e.message =~ /ObjectSpace/
|
91
|
+
Fiveruns::Dash.logger.info "Cannot find Mongrel: #{e.message}"
|
92
|
+
else
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Passenger forks the Rails processes, which has the side effect of
|
98
|
+
# killing our reporter thread. We need to revive the thread.
|
99
|
+
class ActionController::Base
|
100
|
+
def perform_action_with_dash_startup(*args, &block)
|
101
|
+
Fiveruns::Dash.session.reporter.revive!
|
102
|
+
perform_action_without_dash_startup(*args, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
alias_method_chain :perform_action, :dash_startup
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
recipe.absolute :queue_size do
|
110
|
+
Fiveruns::Dash::Rails.queue_size || 0
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module Fiveruns::Dash::Rails::TemplateContext
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:include, InstanceMethods)
|
6
|
+
base.alias_method_chain(:render, :fiveruns_dash_context)
|
7
|
+
end
|
8
|
+
|
9
|
+
RAILS_ROOT_RE = /\A#{Regexp.quote RAILS_ROOT}/
|
10
|
+
|
11
|
+
GEM_REs = Gem.path.map do |path|
|
12
|
+
/\A#{Regexp.quote path}\/gems/
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.sanitize_view_path(path)
|
16
|
+
path = if path[0..0] == '/'
|
17
|
+
if path =~ RAILS_ROOT_RE
|
18
|
+
trimmed = path.sub(RAILS_ROOT_RE, 'RAILS_ROOT')
|
19
|
+
trimmed
|
20
|
+
elsif (re = GEM_REs.find { |re| path =~ re })
|
21
|
+
trimmed = path.sub(re, 'GEMS')
|
22
|
+
else
|
23
|
+
path
|
24
|
+
end
|
25
|
+
else
|
26
|
+
path
|
27
|
+
end
|
28
|
+
# Remove extensions, if any
|
29
|
+
path.sub(/\.[^\/\\]*$/, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
|
34
|
+
def render_with_fiveruns_dash_context(*args, &block)
|
35
|
+
original_context = Fiveruns::Dash::Context.context.dup
|
36
|
+
|
37
|
+
begin
|
38
|
+
template = Fiveruns::Dash::Rails::TemplateContext.sanitize_view_path(path)
|
39
|
+
Fiveruns::Dash::Context.context << 'view'
|
40
|
+
Fiveruns::Dash::Context.context << template
|
41
|
+
result = render_without_fiveruns_dash_context(*args, &block)
|
42
|
+
ensure
|
43
|
+
Fiveruns::Dash::Context.set original_context
|
44
|
+
end
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.dirname(__FILE__) << "/fiveruns/dash/rails/startup"
|
2
|
+
|
3
|
+
if START_FIVERUNS_DASH_RAILS
|
4
|
+
gem 'fiveruns-dash-ruby' # Put its path first
|
5
|
+
require 'fiveruns/dash'
|
6
|
+
require 'fiveruns/dash/rails'
|
7
|
+
require 'fiveruns/dash/template_context'
|
8
|
+
require File.dirname(__FILE__) << "/../rails/init"
|
9
|
+
end
|
data/lib/tasks/dash.rake
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
namespace :dash do
|
2
|
+
desc "Verify FiveRuns Dash connectivity and configuration"
|
3
|
+
task :test => :environment do
|
4
|
+
unless $tested
|
5
|
+
$tested = true
|
6
|
+
begin
|
7
|
+
puts ""
|
8
|
+
puts "FiveRuns Dash installation verification"
|
9
|
+
puts "======================================="
|
10
|
+
puts ""
|
11
|
+
RAILS_DEFAULT_LOGGER = Fiveruns::Dash.logger = Logger.new(STDOUT)
|
12
|
+
Fiveruns::Dash.logger.level = Logger::WARN
|
13
|
+
verify('FiveRuns Dash loaded',
|
14
|
+
'The FiveRuns Dash plugin has not been loaded. Verify you are initializing Dash in config/initializers/dash.rb.') do
|
15
|
+
defined? ::Fiveruns::Dash::Rails
|
16
|
+
end
|
17
|
+
verify('FiveRuns Dash configuration',
|
18
|
+
"No application token was found for the #{Rails.env} environment.") do
|
19
|
+
Fiveruns::Dash.configuration.options[:app]
|
20
|
+
end
|
21
|
+
verify('FiveRuns Dash session running', "FiveRuns Dash session is not active") do
|
22
|
+
Fiveruns::Dash.session.reporter.alive?
|
23
|
+
end
|
24
|
+
verify('FiveRuns Dash network connectivity') do
|
25
|
+
Fiveruns::Dash.session.reporter.ping
|
26
|
+
end
|
27
|
+
puts ""
|
28
|
+
puts "All appears normal. If you are experiencing a problem, please email support@fiveruns.com with details about the problem and your environment."
|
29
|
+
puts ""
|
30
|
+
rescue ArgumentError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def verify(test, fail=nil)
|
37
|
+
$test_count = ($test_count || 0) + 1
|
38
|
+
print " #{$test_count}. #{test}..."
|
39
|
+
begin
|
40
|
+
result = yield
|
41
|
+
if result
|
42
|
+
puts "OK." if fail
|
43
|
+
else
|
44
|
+
if fail
|
45
|
+
puts "FAIL!"
|
46
|
+
puts fail
|
47
|
+
end
|
48
|
+
raise ArgumentError
|
49
|
+
end
|
50
|
+
rescue ArgumentError => ex
|
51
|
+
raise ex
|
52
|
+
rescue => e
|
53
|
+
puts "FAIL!"
|
54
|
+
puts fail
|
55
|
+
raise e
|
56
|
+
end
|
57
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
unless defined?(START_FIVERUNS_DASH_RAILS)
|
3
|
+
START_FIVERUNS_DASH_RAILS = if ENV['START_FIVERUNS_DASH'] || File.basename($0) != 'irb'
|
4
|
+
true
|
5
|
+
else
|
6
|
+
module Fiveruns
|
7
|
+
module Dash
|
8
|
+
module Rails
|
9
|
+
|
10
|
+
def self.configure(*args, &block)
|
11
|
+
RAILS_DEFAULT_LOGGER.info "[FiveRuns Dash] Skipping configuration (`#{$0}' not supported for collection)"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if START_FIVERUNS_DASH_RAILS
|
22
|
+
|
23
|
+
require 'fiveruns_dash_rails'
|
24
|
+
Fiveruns::Dash.logger = RAILS_DEFAULT_LOGGER
|
25
|
+
Fiveruns::Dash::Rails.load_recipes
|
26
|
+
Fiveruns::Dash.configure do |config|
|
27
|
+
config.add_recipe :ruby, :url => 'http://dash.fiveruns.com'
|
28
|
+
config.add_recipe :jruby, :url => 'http://dash.fiveruns.com' if RUBY_PLATFORM[/java/]
|
29
|
+
config.add_recipe :rails, :url => 'http://dash.fiveruns.com'
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.dirname(__FILE__) << "/test_helper"
|
2
|
+
|
3
|
+
class ActiverecordTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
class TestModel < ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
class TestEngine
|
9
|
+
def doit
|
10
|
+
sleep 1
|
11
|
+
2.times do
|
12
|
+
t = TestModel.create!(:name => 'foo')
|
13
|
+
t.destroy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def conn
|
18
|
+
TestModel.connection.execute("select sleep(1)")
|
19
|
+
end
|
20
|
+
|
21
|
+
def entry(meth)
|
22
|
+
send(meth)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
context "Metric" do
|
28
|
+
|
29
|
+
setup do
|
30
|
+
ActiveRecord::Base.configurations = { 'test' => { 'database' => 'test', 'adapter' => 'mysql', 'user' => 'root', 'hostname' => 'localhost' }}
|
31
|
+
ActiveRecord::Base.establish_connection
|
32
|
+
ActiveRecord::Base.connection.execute("create table if not exists test_models (id integer PRIMARY KEY, name varchar(32) not null)")
|
33
|
+
ActiveRecord::Base.connection.execute("delete from test_models")
|
34
|
+
end
|
35
|
+
|
36
|
+
should "collect basic AR metrics" do
|
37
|
+
ar_scenario do
|
38
|
+
TestEngine.new.entry(:doit)
|
39
|
+
|
40
|
+
data = Fiveruns::Dash.session.data
|
41
|
+
# data.each do |hsh|
|
42
|
+
# puts "#{hsh[:name]}: #{hsh[:values].inspect}"
|
43
|
+
# end
|
44
|
+
|
45
|
+
assert metric('test_time', data) > 1.0
|
46
|
+
assert metric('ar_util', data) > metric('db_util', data)
|
47
|
+
assert metric('db_util', data) < 5
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
should "collect DB metrics" do
|
52
|
+
ar_scenario do
|
53
|
+
TestEngine.new.entry(:conn)
|
54
|
+
|
55
|
+
data = Fiveruns::Dash.session.data
|
56
|
+
# data.each do |hsh|
|
57
|
+
# puts "#{hsh[:name]}: #{hsh[:values].inspect}"
|
58
|
+
# end
|
59
|
+
|
60
|
+
assert metric('test_time', data) > 1.0
|
61
|
+
assert metric('test_time', data) < 1.1
|
62
|
+
assert metric('db_time', data) > 1.0
|
63
|
+
assert metric('db_time', data) < 1.1
|
64
|
+
assert metric('db_util', data) > 90.0
|
65
|
+
assert metric('db_util', data) < 100.0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def ar_scenario(&block)
|
71
|
+
child = fork do
|
72
|
+
mock_activerecord!
|
73
|
+
yield
|
74
|
+
end
|
75
|
+
Process.wait
|
76
|
+
assert_equal 0, $?.exitstatus
|
77
|
+
end
|
78
|
+
|
79
|
+
def metric(metric, data, in_ctx=[])
|
80
|
+
hsh = data.detect { |hsh| hsh[:name] == metric }
|
81
|
+
assert hsh, "No metric named #{metric} was found in metrics payload"
|
82
|
+
vals = hsh[:values]
|
83
|
+
assert vals, "No values found for #{metric} in metrics payload"
|
84
|
+
val = vals.detect { |val| val[:context] == in_ctx }
|
85
|
+
assert val, "No value for #{metric} found for context #{in_ctx.inspect}"
|
86
|
+
val[:value]
|
87
|
+
end
|
88
|
+
|
89
|
+
def mock_activerecord!
|
90
|
+
require 'fiveruns/dash'
|
91
|
+
require 'fiveruns/dash/recipes/activerecord'
|
92
|
+
|
93
|
+
eval <<-MOCK
|
94
|
+
module Fiveruns::Dash
|
95
|
+
class Reporter
|
96
|
+
private
|
97
|
+
def run
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
MOCK
|
102
|
+
|
103
|
+
Fiveruns::Dash.register_recipe :tester, :url => 'http://dash.fiveruns.com' do |recipe|
|
104
|
+
recipe.time :test_time, 'Test Time', :method => 'ActiverecordTest::TestEngine#entry'
|
105
|
+
end
|
106
|
+
Fiveruns::Dash.configure :app => '666', :ar_total_time => 'test_time' do |config|
|
107
|
+
config.add_recipe :ruby
|
108
|
+
config.add_recipe :activerecord
|
109
|
+
config.add_recipe :tester
|
110
|
+
end
|
111
|
+
Fiveruns::Dash.session.start(true)
|
112
|
+
end
|
113
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
RAILS_ENV='test'
|
2
|
+
RAILS_ROOT=File.dirname(__FILE__)
|
3
|
+
require 'test/unit'
|
4
|
+
require 'logger'
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'shoulda'
|
9
|
+
require 'flexmock/test_unit'
|
10
|
+
require 'fake_web'
|
11
|
+
rescue
|
12
|
+
puts "Please install the Shoulda, FakeWeb and flexmock gems to run the Dash plugin tests."
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'shoulda'
|
16
|
+
require 'flexmock/test_unit'
|
17
|
+
|
18
|
+
RAILS_DEFAULT_LOGGER=Logger.new(STDOUT)
|
19
|
+
require 'active_record'
|
20
|
+
require 'action_controller'
|
21
|
+
require 'action_controller/test_process'
|
22
|
+
require 'action_controller/test_case'
|
23
|
+
require 'action_view'
|
24
|
+
|
25
|
+
$:.unshift(File.dirname(__FILE__) << '/../lib')
|
26
|
+
$:.unshift(File.dirname(__FILE__) << '/../../fiveruns_dash/lib')
|
27
|
+
|
28
|
+
class Test::Unit::TestCase
|
29
|
+
|
30
|
+
end
|
data/version.yml
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fiveruns-dash-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- FiveRuns Development Team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-28 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: fiveruns-dash-ruby
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.7.1
|
23
|
+
version:
|
24
|
+
description: Provides an API to send metrics from Rails applications to the FiveRuns Dash service
|
25
|
+
email: dev@fiveruns.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- README.rdoc
|
34
|
+
- Rakefile
|
35
|
+
- version.yml
|
36
|
+
- init.rb
|
37
|
+
- lib/fiveruns
|
38
|
+
- lib/fiveruns/dash
|
39
|
+
- lib/fiveruns/dash/rails
|
40
|
+
- lib/fiveruns/dash/rails/startup.rb
|
41
|
+
- lib/fiveruns/dash/rails/version.rb
|
42
|
+
- lib/fiveruns/dash/rails.rb
|
43
|
+
- lib/fiveruns/dash/recipes
|
44
|
+
- lib/fiveruns/dash/recipes/activerecord.rb
|
45
|
+
- lib/fiveruns/dash/recipes/rails.rb
|
46
|
+
- lib/fiveruns/dash/template_context.rb
|
47
|
+
- lib/fiveruns_dash_rails.rb
|
48
|
+
- lib/tasks
|
49
|
+
- lib/tasks/dash.rake
|
50
|
+
- rails/init.rb
|
51
|
+
- test/activerecord_test.rb
|
52
|
+
- test/test_helper.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/fiveruns/dash-rails
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --inline-source
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: fiveruns
|
76
|
+
rubygems_version: 1.2.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: FiveRuns Dash recipe for Ruby on Rails
|
80
|
+
test_files: []
|
81
|
+
|