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