caliper 0.0.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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +34 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/caliper.gemspec +27 -0
- data/lib/caliper/app_error.rb +33 -0
- data/lib/caliper/rack.rb +33 -0
- data/lib/caliper/rails/append_info.rb +25 -0
- data/lib/caliper/rails/instrumentation.rb +27 -0
- data/lib/caliper/railtie.rb +42 -0
- data/lib/caliper/route_inspector.rb +108 -0
- data/lib/caliper/tasks/caliper.rake +10 -0
- data/lib/caliper/tracer.rb +77 -0
- data/lib/caliper/version.rb +3 -0
- data/lib/caliper.rb +60 -0
- data/lib/caliper_api/http.rb +62 -0
- data/spec/caliper/tracer_spec.rb +64 -0
- data/spec/data/rails_get_request_samples.txt +9 -0
- data/spec/data/trace.json +1 -0
- data/spec/dummy/.gitignore +15 -0
- data/spec/dummy/Gemfile +43 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/images/rails.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/posts_controller.rb +18 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/posts/index.html.erb +15 -0
- data/spec/dummy/config/application.rb +62 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/caliper.yml +17 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121203233624_create_posts.rb +9 -0
- data/spec/dummy/db/schema.rb +22 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/index.html +241 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/test/integration/.gitkeep +0 -0
- data/spec/dummy/test/integration/caliper_test.rb +64 -0
- data/spec/dummy/test/lib/caliper/route_inspector_test.rb +14 -0
- data/spec/dummy/test/test_helper.rb +30 -0
- data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +32 -0
- metadata +270 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
All other components of this product are
|
2
|
+
Copyright (c) 2012-2013 Coherence Solutions Inc. All rights reserved.
|
3
|
+
|
4
|
+
Subject to the terms of this notice, Coherence Solutions Inc grants you a
|
5
|
+
nonexclusive, nontransferable license, without the right to
|
6
|
+
sublicense, to (a) install and execute one copy of these files on any
|
7
|
+
number of workstations owned or controlled by you and (b) distribute
|
8
|
+
verbatim copies of these files to third parties. As a condition to the
|
9
|
+
foregoing grant, you must provide this notice along with each copy you
|
10
|
+
distribute and you must not remove, alter, or obscure this notice. All
|
11
|
+
other use, reproduction, modification, distribution, or other
|
12
|
+
exploitation of these files is strictly prohibited, except as may be set
|
13
|
+
forth in a separate written license agreement between you and Coherence
|
14
|
+
Solutions Inc. The terms of any such license agreement will control over this
|
15
|
+
notice. The license stated above will be automatically terminated and
|
16
|
+
revoked if you exceed its scope or violate any of the terms of this
|
17
|
+
notice.
|
18
|
+
|
19
|
+
This License does not grant permission to use the trade names,
|
20
|
+
trademarks, service marks, or product names of Coherence Solutions Inc
|
21
|
+
except as required for reasonable and customary use in describing the origin
|
22
|
+
of this file and reproducing the content of this notice. You may
|
23
|
+
not mark or brand this file with any trade name, trademarks,
|
24
|
+
servicemarks, or product names other than the original brand
|
25
|
+
(if any)provided by Coherence Solutions Inc.
|
26
|
+
|
27
|
+
Unless otherwise expressly agreed by Coherence Solutions Inc, in a
|
28
|
+
separate written license agreement, these files are provided AS IS,
|
29
|
+
WITHOUT WARRANTY OF ANY KIND, including without any implied warranties
|
30
|
+
of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, or NON-INFRINGEMENT.
|
31
|
+
As a condition to your use of these files, you are solely responsible for
|
32
|
+
such use. Coherence Solutions Inc will have no liability to you for direct,
|
33
|
+
indirect, consequential, incidental, special, or punitive damages or
|
34
|
+
for lost profits or data.
|
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Caliper
|
2
|
+
|
3
|
+
To use this gem please register for a beta invite at
|
4
|
+
[http://caliper.io](http://caliper.io)
|
5
|
+
|
6
|
+
## Tests
|
7
|
+
|
8
|
+
Caliper gem:
|
9
|
+
|
10
|
+
bundle exec rake
|
11
|
+
|
12
|
+
Caliper rails dummy app (could only get rack middleware working with
|
13
|
+
IntegrationTest for now):
|
14
|
+
|
15
|
+
cd spec/dummy; bundle exec rake
|
16
|
+
|
17
|
+
## Contributing
|
18
|
+
|
19
|
+
We welcome pull requests for fixing bugs or new features. We follow the
|
20
|
+
[rails coding
|
21
|
+
conventions](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#follow-the-coding-conventions)
|
22
|
+
as best we can.
|
data/Rakefile
ADDED
data/caliper.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'caliper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "caliper"
|
8
|
+
gem.version = Caliper::VERSION
|
9
|
+
gem.authors = ["Kalvir Sandhu"]
|
10
|
+
gem.email = ["kalv@coherence.io"]
|
11
|
+
gem.description = %q{Caliper your rails applications}
|
12
|
+
gem.summary = %q{Caliper profiles your rails application giving you insight on customers performance}
|
13
|
+
gem.homepage = "http://caliper.io"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency(%q<rack>, [">=0"])
|
21
|
+
gem.add_dependency(%q<yajl-ruby>, [">= 0"])
|
22
|
+
gem.add_dependency(%q<uuid>, [">= 0"])
|
23
|
+
|
24
|
+
gem.add_development_dependency(%q<rspec>, [">= 0"])
|
25
|
+
gem.add_development_dependency(%q<activesupport>, [">= 3.2.9"])
|
26
|
+
gem.add_development_dependency(%q<rake>,[">=10.0.2"])
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Caliper
|
2
|
+
module AppError
|
3
|
+
def self.create(exception, request_env)
|
4
|
+
req = ::Rack::Request.new(request_env)
|
5
|
+
|
6
|
+
remote_ip = request_env["action_dispatch.remote_ip"]
|
7
|
+
remote_ip = remote_ip.first if remote_ip.is_a?(Array)
|
8
|
+
|
9
|
+
error_attributes = {
|
10
|
+
"message" => exception.message,
|
11
|
+
"class_name" => exception.class.name,
|
12
|
+
"host" => Socket.gethostname,
|
13
|
+
"request_data" => {
|
14
|
+
"http_method" => request_env["REQUEST_METHOD"],
|
15
|
+
"uri" => request_env["REQUEST_URI"],
|
16
|
+
"path" => request_env["PATH_INFO"],
|
17
|
+
"remote_ip" => "#{remote_ip}",
|
18
|
+
"parameters" => req.params,
|
19
|
+
"session_data" => req.session,
|
20
|
+
"HTTP_USER_AGENT" => request_env["HTTP_USER_AGENT"],
|
21
|
+
"HTTP_HOST" => request_env["HTTP_HOST"],
|
22
|
+
"HTTP_X_REAL_IP" => request_env["HTTP_X_REAL_IP"],
|
23
|
+
"HTTP_X_FORWARDED_FOR" => request_env["HTTP_X_FORWARDED_FOR"]
|
24
|
+
}
|
25
|
+
}
|
26
|
+
error_attributes["backtrace"] = exception.backtrace.join("\n") if exception.backtrace
|
27
|
+
|
28
|
+
CaliperApi.create_error(
|
29
|
+
Yajl::Encoder.encode({"error" => error_attributes})
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/caliper/rack.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Caliper
|
2
|
+
class Rack
|
3
|
+
def initialize(app, options = {}, &blk)
|
4
|
+
@app = app
|
5
|
+
|
6
|
+
yield self if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if Caliper.config[:enabled] && !env['PATH_INFO'][/^\/assets/]
|
11
|
+
env['caliper.tracer'] = Caliper::Tracer.new(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
@status, @headers, @response = @app.call(env)
|
15
|
+
|
16
|
+
# check for routing error in rails way
|
17
|
+
if @status == 404 && @headers['X-Cascade'] == 'pass'
|
18
|
+
Caliper::AppError.create(
|
19
|
+
ActionController::RoutingError.new("No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"),
|
20
|
+
env
|
21
|
+
)
|
22
|
+
elsif env['caliper.tracer']
|
23
|
+
env['caliper.tracer'].finish
|
24
|
+
end
|
25
|
+
|
26
|
+
[@status, @headers, @response]
|
27
|
+
rescue Exception => exception
|
28
|
+
Caliper::AppError.create(exception, env)
|
29
|
+
raise exception
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# add the resolved controller and action to the trace
|
2
|
+
module Caliper
|
3
|
+
module Rails
|
4
|
+
module AppendInfo
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_filter :add_request_info_to_tracer
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_request_info_to_tracer
|
12
|
+
return true unless env['caliper.tracer'] # elegantly fail if something went wrong with middleware and also when errors are handled
|
13
|
+
request.env["caliper.tracer"].reqs = "#{params[:controller]}##{params[:action]}"
|
14
|
+
|
15
|
+
# check if developer has added customer data
|
16
|
+
if defined? self.add_to_caliper_trace
|
17
|
+
request.env["caliper.tracer"].add_to_trace(self.add_to_caliper_trace)
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: add other parameters for the trace?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# monkey patch the action controller to get reference to middleware caliper tracer
|
2
|
+
module ActionController
|
3
|
+
module Instrumentation
|
4
|
+
def process_action(action, *args)
|
5
|
+
raw_payload = {
|
6
|
+
:controller => self.class.name,
|
7
|
+
:action => self.action_name,
|
8
|
+
:params => request.filtered_parameters,
|
9
|
+
:formats => request.formats.map(&:to_sym),
|
10
|
+
:method => request.method,
|
11
|
+
:path => (request.fullpath rescue "unknown"),
|
12
|
+
|
13
|
+
# Need payload to reference the request env tracer
|
14
|
+
:caliper_tracer => request.env['caliper.tracer']
|
15
|
+
}
|
16
|
+
|
17
|
+
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
20
|
+
result = super
|
21
|
+
payload[:status] = response.status
|
22
|
+
append_info_to_payload(payload)
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'caliper/rails/instrumentation'
|
2
|
+
require 'caliper/rails/append_info'
|
3
|
+
|
4
|
+
module Caliper
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
rake_tasks do
|
7
|
+
load "caliper/tasks/caliper.rake"
|
8
|
+
end
|
9
|
+
|
10
|
+
config.caliper = ActiveSupport::OrderedOptions.new
|
11
|
+
|
12
|
+
initializer "caliper.initialize" do |app|
|
13
|
+
app.middleware.use Caliper::Rack
|
14
|
+
|
15
|
+
ActiveSupport.on_load(:action_controller) do
|
16
|
+
ActionController::Base.send(:include, Caliper::Rails::AppendInfo)
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.subscribe(/(^render|action_controller|active_record)/) do |*args|
|
20
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
21
|
+
|
22
|
+
# set this so that sql and view notifications has access to caliper tracer
|
23
|
+
if event.name == 'start_processing.action_controller'
|
24
|
+
@caliper_tracer = event.payload[:caliper_tracer]
|
25
|
+
end
|
26
|
+
|
27
|
+
@caliper_tracer.record(event) if @caliper_tracer
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# update routes to Caliper
|
33
|
+
config.after_initialize do
|
34
|
+
::Rails.application.reload_routes!
|
35
|
+
inspector = Caliper::RouteInspector.new
|
36
|
+
all_routes = ::Rails.application.routes.routes
|
37
|
+
CaliperApi.update_routes(
|
38
|
+
Yajl::Encoder.encode({"routes" => inspector.routes(all_routes)})
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Influenced by ActionDispatch::Routing::RoutesInspector
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Caliper
|
5
|
+
|
6
|
+
class RouteWrapper < SimpleDelegator
|
7
|
+
def endpoint
|
8
|
+
unless rack_app
|
9
|
+
"#{controller}##{action}"
|
10
|
+
end
|
11
|
+
# ignore rack apps for now (don't want to know about redirects)
|
12
|
+
#rack_app ? rack_app.inspect : "#{controller}##{action}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def constraints
|
16
|
+
requirements.except(:controller, :action)
|
17
|
+
end
|
18
|
+
|
19
|
+
def rack_app(app = self.app)
|
20
|
+
@rack_app ||= begin
|
21
|
+
class_name = app.class.name.to_s
|
22
|
+
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
|
23
|
+
rack_app(app.app)
|
24
|
+
elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
|
25
|
+
app
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def verb
|
31
|
+
super.source.gsub(/[$^]/, '')
|
32
|
+
end
|
33
|
+
|
34
|
+
def path
|
35
|
+
super.spec.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def name
|
39
|
+
super.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def reqs
|
43
|
+
@reqs ||= begin
|
44
|
+
reqs = endpoint
|
45
|
+
# commenting out for now until tested and really required
|
46
|
+
#reqs += " #{constraints.to_s}" unless constraints.empty?
|
47
|
+
reqs
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def controller
|
52
|
+
requirements[:controller] || ':controller'
|
53
|
+
end
|
54
|
+
|
55
|
+
def action
|
56
|
+
requirements[:action] || ':action'
|
57
|
+
end
|
58
|
+
|
59
|
+
def internal?
|
60
|
+
path =~ %r{/rails/info.*|^#{::Rails.application.config.assets.prefix}}
|
61
|
+
end
|
62
|
+
|
63
|
+
def engine?
|
64
|
+
rack_app && rack_app.respond_to?(:routes)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class RouteInspector
|
69
|
+
def initialize
|
70
|
+
@engines = Hash.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def routes(all_routes)
|
74
|
+
routes = collect_routes(all_routes)
|
75
|
+
|
76
|
+
routes + routes_for_engines
|
77
|
+
end
|
78
|
+
|
79
|
+
def collect_routes(routes)
|
80
|
+
routes = routes.collect do |route|
|
81
|
+
RouteWrapper.new(route)
|
82
|
+
end.reject do |route|
|
83
|
+
route.internal?
|
84
|
+
end.collect do |route|
|
85
|
+
collect_engine_routes(route)
|
86
|
+
|
87
|
+
{:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs } unless route.reqs.nil?
|
88
|
+
end.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def collect_engine_routes(route)
|
92
|
+
name = route.endpoint
|
93
|
+
return unless route.engine?
|
94
|
+
return if @engines[name]
|
95
|
+
|
96
|
+
routes = route.rack_app.routes
|
97
|
+
if routes.is_a?(ActionDispatch::Routing::RouteSet)
|
98
|
+
@engines[name] = collect_routes(routes.routes)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def routes_for_engines
|
103
|
+
@engines.map do |name, routes|
|
104
|
+
formatted_routes(routes)
|
105
|
+
end.flatten
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :caliper do
|
2
|
+
|
3
|
+
desc "Send a test call to Caliper to ensure everything is working correctly"
|
4
|
+
task :test => :environment do
|
5
|
+
Rails.logger = Logger.new(STDOUT)
|
6
|
+
puts "Posting a test message to Caliper with API KEY: #{Caliper.config[:api_key]} to #{Caliper.config[:api_host]}"
|
7
|
+
CaliperApi.test_post
|
8
|
+
puts "Test sent. Go ahead and deploy and view data on #{Caliper.config[:api_host]}"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'uuid'
|
2
|
+
require 'yajl'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Caliper
|
6
|
+
class Tracer
|
7
|
+
attr_accessor :uuid, :http_method, :uri, :path, :remote_ip, :samples, :host, :reqs, :extra_info
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@uuid = UUID.generate
|
11
|
+
|
12
|
+
@http_method = env['REQUEST_METHOD']
|
13
|
+
@uri = env['REQUEST_URI'] # full URI
|
14
|
+
@path = env['PATH_INFO'] # user path_info because it's in the rack spec rather than REQUEST_PATH
|
15
|
+
|
16
|
+
@remote_ip = env["action_dispatch.remote_ip"] #requesting ip
|
17
|
+
@remote_ip = @remote_ip.first if @remote_ip.is_a?(Array)
|
18
|
+
|
19
|
+
@host = Socket.gethostname
|
20
|
+
|
21
|
+
@samples = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def record(event)
|
25
|
+
event.payload.delete(:caliper_tracer) if event.payload[:caliper_tracer]
|
26
|
+
return if event.name[/active_record/] && (event.payload[:name] == nil || event.payload[:name][/SCHEMA/])
|
27
|
+
|
28
|
+
# strip un-required payload data for activerecord notifications
|
29
|
+
if event.name[/active_record/]
|
30
|
+
["binds", "connection_id"].each do |key|
|
31
|
+
event.payload.delete(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#Caliper.logger.debug [event.name, event.time, event.end, event.transaction_id, event.payload].join("||")
|
36
|
+
@samples << event
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_to_trace(data = {})
|
40
|
+
@extra_info = data
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish
|
44
|
+
return true if samples.size == 0
|
45
|
+
|
46
|
+
trace_data = {
|
47
|
+
"uuid" => uuid,
|
48
|
+
"http_method" => http_method,
|
49
|
+
"uri" => uri,
|
50
|
+
"path" => path,
|
51
|
+
"remote_ip" => remote_ip.to_s,
|
52
|
+
"host" => host,
|
53
|
+
"reqs" => reqs,
|
54
|
+
"extra_info" => extra_info,
|
55
|
+
"samples" => []
|
56
|
+
}
|
57
|
+
|
58
|
+
samples.each do |sample|
|
59
|
+
trace_data["samples"] << {
|
60
|
+
"name" => sample.name,
|
61
|
+
"duration" => sample.duration,
|
62
|
+
#"start" => sample.time,
|
63
|
+
#"end" => sample.end,
|
64
|
+
"payload" => sample.payload
|
65
|
+
}
|
66
|
+
if sample.name[/process_action.action_controller/]
|
67
|
+
trace_data["view_runtime"] = sample.payload[:view_runtime]
|
68
|
+
trace_data["db_runtime"] = sample.payload[:db_runtime]
|
69
|
+
trace_data["duration"] = sample.duration
|
70
|
+
end
|
71
|
+
end
|
72
|
+
CaliperApi.create_trace(
|
73
|
+
Yajl::Encoder.encode({ "trace" => trace_data })
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/caliper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'uuid'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'caliper/version'
|
5
|
+
require 'caliper/tracer'
|
6
|
+
require 'caliper/rack'
|
7
|
+
require 'caliper/route_inspector'
|
8
|
+
require 'caliper/app_error'
|
9
|
+
|
10
|
+
require 'caliper_api/http'
|
11
|
+
|
12
|
+
|
13
|
+
if defined? ::Rails
|
14
|
+
if ::Rails::VERSION::MAJOR >= 3
|
15
|
+
require 'caliper/railtie'
|
16
|
+
else
|
17
|
+
raise "Your Rails application is currently not supported with Caliper, get in touch we can make it so soon"
|
18
|
+
end
|
19
|
+
elsif !ENV["CALIPER_API_HOST"] #(might be in testing mode)
|
20
|
+
raise "Your Ruby application is currently not supported with Caliper, get in touch, we can make it so soon"
|
21
|
+
end
|
22
|
+
|
23
|
+
module Caliper
|
24
|
+
def self.logger
|
25
|
+
unless @logger
|
26
|
+
# if in Rails use Rails logger
|
27
|
+
if defined? ::Rails
|
28
|
+
@logger = ::Rails.logger
|
29
|
+
else
|
30
|
+
@logger = Logger.new(STDOUT)
|
31
|
+
@logger.level = Logger::INFO
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@logger
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.config
|
39
|
+
if defined? ::Rails
|
40
|
+
config_path = ::Rails.root
|
41
|
+
env = ::Rails.env
|
42
|
+
else
|
43
|
+
config_path = File.new("./")
|
44
|
+
env = "test"
|
45
|
+
end
|
46
|
+
begin
|
47
|
+
@config ||= YAML.load_file("#{config_path.join("config", "caliper.yml")}")[::Rails.env].symbolize_keys
|
48
|
+
|
49
|
+
Caliper.logger.error "No API Key set in caliper.yml" unless @config[:api_key]
|
50
|
+
|
51
|
+
# set host
|
52
|
+
@config[:api_host] = ENV["CALIPER_API_HOST"] || 'http://alpha.caliper.io/'
|
53
|
+
|
54
|
+
@config
|
55
|
+
rescue StandardError => e
|
56
|
+
Caliper.logger.error "No caliper.yml config file could be found!"
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module CaliperApi
|
6
|
+
|
7
|
+
class Poster
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@base_uri = Caliper.config[:api_host]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.send(url, data)
|
15
|
+
instance.post(url, data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def post(url, data = "")
|
19
|
+
begin
|
20
|
+
uri = URI.parse(@base_uri + url)
|
21
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
22
|
+
|
23
|
+
http.open_timeout = 5 # in seconds
|
24
|
+
http.read_timeout = 5 # in seconds
|
25
|
+
|
26
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
27
|
+
request["X_API_KEY"] = Caliper.config[:api_key]
|
28
|
+
request["Content-Type"] = "application/json"
|
29
|
+
request["Accept"] = "application/json"
|
30
|
+
|
31
|
+
request.body = data
|
32
|
+
|
33
|
+
response = http.request(request)
|
34
|
+
|
35
|
+
status = response.code.to_i
|
36
|
+
if status == 401
|
37
|
+
Caliper.logger.error "CaliperApi: Error: Not authorized check your API key on #{Caliper.config[:api_host]}"
|
38
|
+
elsif status > 400
|
39
|
+
Caliper.logger.error "CaliperApi: Error: got #{response.code} from server"
|
40
|
+
end
|
41
|
+
rescue Exception => e
|
42
|
+
Caliper.logger.error "CaliperApi: Http error: #{e.message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.test_post
|
48
|
+
Poster.send("/api/test", "")
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create_trace(tracer_json)
|
52
|
+
Poster.send("/api/traces", tracer_json)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.create_error(error_json)
|
56
|
+
Poster.send("/api/errors", error_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.update_routes(routes_json)
|
60
|
+
Poster.send("/api/routes", routes_json)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caliper::Tracer do
|
4
|
+
|
5
|
+
it 'should generate a uid on creation' do
|
6
|
+
tracer = Caliper::Tracer.new({"REQEUEST_METHOD" => "POST", "REQUEST_PATH" => "/users"})
|
7
|
+
tracer.uuid.should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
context "When storing 2 events" do
|
11
|
+
before do
|
12
|
+
@tracer = Caliper::Tracer.new({"REQEUEST_METHOD" => "GET", "REQUEST_PATH" => "/users"})
|
13
|
+
@events = []
|
14
|
+
2.times {
|
15
|
+
event = mock_event
|
16
|
+
@tracer.record(event)
|
17
|
+
@events << event
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should store the two events to referencing later' do
|
22
|
+
@tracer.samples.should_not be_nil
|
23
|
+
@tracer.samples.size.should eq 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should post data to CaliperAPI when finished' do
|
27
|
+
CaliperApi.should_receive(:create_trace)
|
28
|
+
@tracer.finish
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "Given a tracer with no samples" do
|
33
|
+
before do
|
34
|
+
@tracer = Caliper::Tracer.new({"REQUEST_METHOD" => "GET", "REQUEST_PATH" => "/users"})
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should not post data to CaliperAPI when finished' do
|
38
|
+
CaliperApi.should_not_receive(:create_trace)
|
39
|
+
@tracer.finish
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "A typical rails controller request trace" do
|
44
|
+
before do
|
45
|
+
@tracer = Caliper::Tracer.new({"REQEUEST_METHOD" => "GET", "REQUEST_PATH" => "/users"})
|
46
|
+
|
47
|
+
replay_into_tracer(@tracer, "rails_get_request_samples")
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should have stored correct amount of samples ignoring SCHEMA named events' do
|
51
|
+
@tracer.samples.each do |sample|
|
52
|
+
if sample.name[/active_record/]
|
53
|
+
sample.payload[:name].should_not eq "SCHEMA"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@tracer.samples.size.should eq 6
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should dump the correct json data' do
|
60
|
+
pending
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
start_processing.action_controller||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:controller=>"PostsController", :action=>"index", :params=>{"action"=>"index", "controller"=>"posts"}, :formats=>[:html], :method=>"GET", :path=>"/posts"}
|
2
|
+
sql.active_record||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:sql=>"PRAGMA table_info(\"posts\")", :name=>"SCHEMA", :connection_id=>70199742565160, :binds=>[]}
|
3
|
+
sql.active_record||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:sql=>" SELECT name\n FROM sqlite_master\n WHERE type = 'table' AND NOT name = 'sqlite_sequence'\n AND name = \"posts\"", :name=>"SCHEMA", :connection_id=>70199742565160, :binds=>[]}
|
4
|
+
sql.active_record||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:sql=>"PRAGMA table_info(\"posts\")", :name=>"SCHEMA", :connection_id=>70199742565160, :binds=>[]}
|
5
|
+
sql.active_record||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:sql=>"SELECT \"posts\".* FROM \"posts\" ", :name=>"Post Load", :connection_id=>70199742565160, :binds=>[]}
|
6
|
+
!render_template.action_view||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:virtual_path=>"posts/index"}
|
7
|
+
render_template.action_view||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:identifier=>"/Users/kalv/Development/ruby/test-caliper/app/views/posts/index.html.erb", :layout=>"layouts/application"}
|
8
|
+
!render_template.action_view||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:virtual_path=>"layouts/application"}
|
9
|
+
process_action.action_controller||2012-11-30 15:07:13 -0800||2012-11-30 15:07:13 -0800||36dc476cf70478c5aecb||{:controller=>"PostsController", :action=>"index", :params=>{"action"=>"index", "controller"=>"posts"}, :formats=>[:html], :method=>"GET", :path=>"/posts", :status=>200, :view_runtime=>22.308999999999997, :db_runtime=>1.735}
|