profile_it 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.markdown +19 -0
- data/Gemfile +4 -0
- data/LICENSE.markdown +7 -0
- data/README.markdown +40 -0
- data/Rakefile +1 -0
- data/data/cacert.pem +3894 -0
- data/lib/profile_it/agent/logging.rb +44 -0
- data/lib/profile_it/agent/reporting.rb +84 -0
- data/lib/profile_it/agent.rb +98 -0
- data/lib/profile_it/config.rb +44 -0
- data/lib/profile_it/environment.rb +135 -0
- data/lib/profile_it/instruments/active_record_instruments.rb +85 -0
- data/lib/profile_it/instruments/mongoid_instruments.rb +10 -0
- data/lib/profile_it/instruments/moped_instruments.rb +24 -0
- data/lib/profile_it/instruments/net_http.rb +14 -0
- data/lib/profile_it/instruments/rails/action_controller_instruments.rb +41 -0
- data/lib/profile_it/instruments/rails3_or_4/action_controller_instruments.rb +45 -0
- data/lib/profile_it/metric_meta.rb +34 -0
- data/lib/profile_it/metric_stats.rb +50 -0
- data/lib/profile_it/profile.rb +39 -0
- data/lib/profile_it/stack_item.rb +18 -0
- data/lib/profile_it/store.rb +166 -0
- data/lib/profile_it/tracer.rb +105 -0
- data/lib/profile_it/version.rb +3 -0
- data/lib/profile_it.rb +34 -0
- data/profile_it.gemspec +24 -0
- metadata +74 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# Contains methods specific to logging (initializing the log file, applying the log level, applying the log format, etc.)
|
2
|
+
module ProfileIt
|
3
|
+
class Agent
|
4
|
+
module Logging
|
5
|
+
def log_path
|
6
|
+
"#{environment.root}/log"
|
7
|
+
end
|
8
|
+
|
9
|
+
def init_logger
|
10
|
+
@log_file = "#{log_path}/profile_it.log"
|
11
|
+
begin
|
12
|
+
@logger = Logger.new(@log_file)
|
13
|
+
@logger.level = log_level
|
14
|
+
apply_log_format
|
15
|
+
rescue Exception => e
|
16
|
+
@logger = Logger.new(STDOUT)
|
17
|
+
apply_log_format
|
18
|
+
@logger.error "Unable to access log file: #{e.message}"
|
19
|
+
end
|
20
|
+
@logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply_log_format
|
24
|
+
def logger.format_message(severity, timestamp, progname, msg)
|
25
|
+
# since STDOUT isn't exclusive like the profile_it.log file, apply a prefix.
|
26
|
+
prefix = @logdev.dev == STDOUT ? "profile_it " : ''
|
27
|
+
prefix + "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_level
|
32
|
+
case config.settings['log_level'].downcase
|
33
|
+
when "debug" then Logger::DEBUG
|
34
|
+
when "info" then Logger::INFO
|
35
|
+
when "warn" then Logger::WARN
|
36
|
+
when "error" then Logger::ERROR
|
37
|
+
when "fatal" then Logger::FATAL
|
38
|
+
else Logger::INFO
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end # module Logging
|
42
|
+
include Logging
|
43
|
+
end # class Agent
|
44
|
+
end # moudle ProfileIt
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Methods related to sending metrics to profile_itapp.com.
|
2
|
+
module ProfileIt
|
3
|
+
class Agent
|
4
|
+
module Reporting
|
5
|
+
|
6
|
+
CA_FILE = File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
7
|
+
VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
8
|
+
|
9
|
+
def checkin_uri
|
10
|
+
URI.parse("#{config.settings['host']}/#{config.settings['key']}/profiles/create?name=#{CGI.escape(config.settings['name'])}&rails_version=#{Rails::VERSION::STRING}&gem_version=#{ProfileIt::VERSION}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_profile(profile)
|
14
|
+
uri = checkin_uri
|
15
|
+
logger.debug "Sending profile [#{profile.uri}] to #{uri}"
|
16
|
+
form_data = profile.to_form_data.merge({
|
17
|
+
"profile[extension_version]" => Thread::current[:profile_it_extension_version],
|
18
|
+
"profile[extension_fingerprint]" => Thread::current[:profile_it_extension_fingerprint],
|
19
|
+
"profile[user_id]" => Thread::current[:profile_it_user_id]
|
20
|
+
})
|
21
|
+
Thread.new do
|
22
|
+
begin
|
23
|
+
response = post(uri, form_data)
|
24
|
+
if response and response.is_a?(Net::HTTPSuccess)
|
25
|
+
logger.debug "Profile Sent [#{profile.uri}]."
|
26
|
+
else
|
27
|
+
logger.debug "Error sending profile [#{profile.uri}]."
|
28
|
+
end
|
29
|
+
rescue Exception => e
|
30
|
+
logger.error "Exception sending profile [#{profile.uri}]: [#{e}]"
|
31
|
+
logger.error e.backtrace.join("\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def post(url, data, headers = Hash.new)
|
37
|
+
response = nil
|
38
|
+
request(url) do |connection|
|
39
|
+
post = Net::HTTP::Post.new( url.path +
|
40
|
+
(url.query ? ('?' + url.query) : ''),
|
41
|
+
HTTP_HEADERS.merge(headers) )
|
42
|
+
post.set_form_data(data)
|
43
|
+
|
44
|
+
response=connection.request(post)
|
45
|
+
end
|
46
|
+
response
|
47
|
+
end
|
48
|
+
|
49
|
+
def request(url, &connector)
|
50
|
+
response = nil
|
51
|
+
response = build_http(url).start(&connector)
|
52
|
+
logger.debug "got response: #{response.inspect}"
|
53
|
+
case response
|
54
|
+
when Net::HTTPSuccess, Net::HTTPNotModified
|
55
|
+
logger.debug "/checkin OK"
|
56
|
+
when Net::HTTPBadRequest
|
57
|
+
logger.warn "/checkin FAILED: The Account Key [#{config.settings['key']}] is invalid."
|
58
|
+
else
|
59
|
+
logger.debug "/checkin FAILED: #{response.inspect}"
|
60
|
+
end
|
61
|
+
rescue Exception
|
62
|
+
logger.debug "Exception sending request to server: #{$!.message}"
|
63
|
+
ensure
|
64
|
+
response
|
65
|
+
end
|
66
|
+
|
67
|
+
# take care of http/https proxy, if specified in command line options
|
68
|
+
# Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil
|
69
|
+
# Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil
|
70
|
+
def build_http(uri)
|
71
|
+
proxy_uri = URI.parse(config.settings['proxy'].to_s)
|
72
|
+
http = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.port).new(uri.host, uri.port)
|
73
|
+
|
74
|
+
if uri.is_a?(URI::HTTPS)
|
75
|
+
http.use_ssl = true
|
76
|
+
http.ca_file = CA_FILE
|
77
|
+
http.verify_mode = VERIFY_MODE
|
78
|
+
end
|
79
|
+
http
|
80
|
+
end
|
81
|
+
end # module Reporting
|
82
|
+
include Reporting
|
83
|
+
end # class Agent
|
84
|
+
end # module ProfileIt
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module ProfileIt
|
2
|
+
# The agent gathers performance data from a Ruby application. One Agent instance is created per-Ruby process.
|
3
|
+
#
|
4
|
+
# Each Agent object creates a worker thread (unless monitoring is disabled or we're forking).
|
5
|
+
# The worker thread wakes up every +Agent#period+, merges in-memory metrics w/those saved to disk,
|
6
|
+
# saves the merged data to disk, and sends it to the profile_it server.
|
7
|
+
class Agent
|
8
|
+
# Headers passed up with all API requests.
|
9
|
+
HTTP_HEADERS = { "Agent-Hostname" => Socket.gethostname }
|
10
|
+
# see self.instance
|
11
|
+
@@instance = nil
|
12
|
+
|
13
|
+
# Accessors below are for associated classes
|
14
|
+
attr_accessor :store
|
15
|
+
attr_accessor :config
|
16
|
+
attr_accessor :environment
|
17
|
+
|
18
|
+
attr_accessor :logger
|
19
|
+
attr_accessor :log_file # path to the log file
|
20
|
+
attr_accessor :options # options passed to the agent when +#start+ is called.
|
21
|
+
attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
|
22
|
+
|
23
|
+
# All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
|
24
|
+
def self.instance(options = {})
|
25
|
+
@@instance ||= self.new(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Note - this doesn't start instruments or the worker thread. This is handled via +#start+ as we don't
|
29
|
+
# want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn't
|
30
|
+
# be started (when forking).
|
31
|
+
def initialize(options = {})
|
32
|
+
@started = false
|
33
|
+
@options ||= options
|
34
|
+
@store = ProfileIt::Store.new
|
35
|
+
@config = ProfileIt::Config.new(options[:config_path])
|
36
|
+
@metric_lookup = Hash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def environment
|
40
|
+
@environment ||= ProfileIt::Environment.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# This is called via +ProfileIt::Agent.instance.start+ when ProfileIt is required in a Ruby application.
|
44
|
+
# It initializes the agent and starts the worker thread (if appropiate).
|
45
|
+
def start(options = {})
|
46
|
+
@options.merge!(options)
|
47
|
+
init_logger
|
48
|
+
logger.info "Attempting to start profileit.io [#{ProfileIt::VERSION}] on [#{Socket.gethostname}] reporting to #{config.settings['host']}"
|
49
|
+
if !config.settings['profile']
|
50
|
+
logger.warn "Profiling isn't enabled for the [#{environment.env}] environment."
|
51
|
+
return false
|
52
|
+
# elsif !environment.app_server
|
53
|
+
# logger.warn "Couldn't find a supported app server. Not starting agent."
|
54
|
+
# return false
|
55
|
+
elsif started?
|
56
|
+
logger.warn "Already started profileit.io."
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
@started = true
|
60
|
+
logger.info "Starting profiling. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
|
61
|
+
start_instruments
|
62
|
+
logger.info "ProfileIt [#{ProfileIt::VERSION}] Initialized"
|
63
|
+
end
|
64
|
+
|
65
|
+
def started?
|
66
|
+
@started
|
67
|
+
end
|
68
|
+
|
69
|
+
def gem_root
|
70
|
+
File.expand_path(File.join("..","..",".."), __FILE__)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Loads the instrumention logic.
|
74
|
+
def load_instruments
|
75
|
+
case environment.framework
|
76
|
+
when :rails
|
77
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails/action_controller_instruments.rb'))
|
78
|
+
when :rails3_or_4
|
79
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails3_or_4/action_controller_instruments.rb'))
|
80
|
+
end
|
81
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/active_record_instruments.rb'))
|
82
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/net_http.rb'))
|
83
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/moped_instruments.rb'))
|
84
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'instruments/mongoid_instruments.rb'))
|
85
|
+
rescue
|
86
|
+
logger.warn "Exception loading instruments:"
|
87
|
+
logger.warn $!.message
|
88
|
+
logger.warn $!.backtrace
|
89
|
+
end
|
90
|
+
|
91
|
+
# Injects instruments into the Ruby application.
|
92
|
+
def start_instruments
|
93
|
+
logger.debug "Installing instrumentation"
|
94
|
+
load_instruments
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Agent
|
98
|
+
end # module ProfileIt
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ProfileIt
|
2
|
+
class Config
|
3
|
+
DEFAULTS = {
|
4
|
+
'host' => 'https://profileit.io',
|
5
|
+
'log_level' => 'debug', # changed from info for dev
|
6
|
+
'name' => 'LOCAL APP',
|
7
|
+
'key' => 'DEV'
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(config_path = nil)
|
11
|
+
@config_path = config_path
|
12
|
+
end
|
13
|
+
|
14
|
+
def settings
|
15
|
+
return @settings if @settings
|
16
|
+
load_file
|
17
|
+
end
|
18
|
+
|
19
|
+
def config_path
|
20
|
+
@config_path || File.join(ProfileIt::Agent.instance.environment.root,"config","profile_it.yml")
|
21
|
+
end
|
22
|
+
|
23
|
+
def config_file
|
24
|
+
File.expand_path(config_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_file
|
28
|
+
begin
|
29
|
+
if !File.exist?(config_file)
|
30
|
+
ProfileIt::Agent.instance.logger.warn "No config file found at [#{config_file}]."
|
31
|
+
@settings = {}
|
32
|
+
else
|
33
|
+
@settings = YAML.load(ERB.new(File.read(config_file)).result(binding))[ProfileIt::Agent.instance.environment.env] || {}
|
34
|
+
end
|
35
|
+
rescue Exception => e
|
36
|
+
ProfileIt::Agent.instance.logger.warn "Unable to load the config file."
|
37
|
+
ProfileIt::Agent.instance.logger.warn e.message
|
38
|
+
ProfileIt::Agent.instance.logger.warn e.backtrace
|
39
|
+
@settings = {}
|
40
|
+
end
|
41
|
+
@settings = DEFAULTS.merge(@settings)
|
42
|
+
end
|
43
|
+
end # Config
|
44
|
+
end # ProfileIt
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# Used to retrieve environment information for this application.
|
2
|
+
module ProfileIt
|
3
|
+
class Environment
|
4
|
+
def env
|
5
|
+
@env ||= case framework
|
6
|
+
when :rails then RAILS_ENV.dup
|
7
|
+
when :rails3_or_4 then Rails.env
|
8
|
+
when :sinatra
|
9
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def framework
|
14
|
+
@framework ||= case
|
15
|
+
when defined?(::Rails) && defined?(ActionController)
|
16
|
+
if Rails::VERSION::MAJOR < 3
|
17
|
+
:rails
|
18
|
+
else
|
19
|
+
:rails3_or_4
|
20
|
+
end
|
21
|
+
when defined?(::Sinatra) && defined?(::Sinatra::Base) then :sinatra
|
22
|
+
else :ruby
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def processors
|
27
|
+
return @processors if @processors
|
28
|
+
unless @processors
|
29
|
+
proc_file = '/proc/cpuinfo'
|
30
|
+
if !File.exist?(proc_file)
|
31
|
+
@processors = 1
|
32
|
+
elsif `cat #{proc_file} | grep 'model name' | wc -l` =~ /(\d+)/
|
33
|
+
@processors = $1.to_i
|
34
|
+
end
|
35
|
+
if @processors < 1
|
36
|
+
@processors = 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
@processors
|
40
|
+
end
|
41
|
+
|
42
|
+
def root
|
43
|
+
if framework == :rails
|
44
|
+
RAILS_ROOT.to_s
|
45
|
+
elsif framework == :rails3_or_4
|
46
|
+
Rails.root
|
47
|
+
elsif framework == :sinatra
|
48
|
+
Sinatra::Application.root
|
49
|
+
else
|
50
|
+
'.'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# This needs to be improved. Frequently, multiple app servers gem are present and which
|
55
|
+
# ever is checked first becomes the designated app server.
|
56
|
+
#
|
57
|
+
# I've put Thin and Webrick last as they are often used in development and included in Gemfiles
|
58
|
+
# but less likely used in production.
|
59
|
+
#
|
60
|
+
# Next step: (1) list out all detected app servers (2) install hooks for those that need it (passenger, rainbows, unicorn).
|
61
|
+
#
|
62
|
+
# Believe the biggest downside is the master process for forking app servers will get a background worker. Not sure how this will
|
63
|
+
# impact metrics (it shouldn't process requests).
|
64
|
+
def app_server
|
65
|
+
@app_server ||= if passenger? then :passenger
|
66
|
+
elsif rainbows? then :rainbows
|
67
|
+
elsif unicorn? then :unicorn
|
68
|
+
elsif thin? then :thin
|
69
|
+
elsif webrick? then :webrick
|
70
|
+
else nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
### app server related-checks
|
75
|
+
|
76
|
+
def thin?
|
77
|
+
if defined?(::Thin) && defined?(::Thin::Server)
|
78
|
+
# Ensure Thin is actually initialized. It could just be required and not running.
|
79
|
+
ObjectSpace.each_object(Thin::Server) { |x| return true }
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Called via +#forking?+ since Passenger forks. Adds an event listener to start the worker thread
|
85
|
+
# inside the passenger worker process.
|
86
|
+
# Background: http://www.modrails.com/documentation/Users%20guide%20Nginx.html#spawning%5Fmethods%5Fexplained
|
87
|
+
def passenger?
|
88
|
+
(defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::PhusionPassenger)
|
89
|
+
end
|
90
|
+
|
91
|
+
def webrick?
|
92
|
+
defined?(::WEBrick) && defined?(::WEBrick::VERSION)
|
93
|
+
end
|
94
|
+
|
95
|
+
def rainbows?
|
96
|
+
if defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
|
97
|
+
ObjectSpace.each_object(::Rainbows::HttpServer) { |x| return true }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def unicorn?
|
102
|
+
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
103
|
+
# Ensure Unicorn is actually initialized. It could just be required and not running.
|
104
|
+
ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# If forking, don't start worker thread in the master process. Since it's started as a Thread, it won't survive
|
109
|
+
# the fork.
|
110
|
+
def forking?
|
111
|
+
passenger? or unicorn? or rainbows?
|
112
|
+
end
|
113
|
+
|
114
|
+
### ruby checks
|
115
|
+
|
116
|
+
def rubinius?
|
117
|
+
RUBY_VERSION =~ /rubinius/i
|
118
|
+
end
|
119
|
+
|
120
|
+
def jruby?
|
121
|
+
defined?(JRuby)
|
122
|
+
end
|
123
|
+
|
124
|
+
def ruby_19?
|
125
|
+
defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
|
126
|
+
end
|
127
|
+
|
128
|
+
### framework checks
|
129
|
+
|
130
|
+
def sinatra?
|
131
|
+
defined?(Sinatra::Application)
|
132
|
+
end
|
133
|
+
|
134
|
+
end # class Environemnt
|
135
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ProfileIt::Instruments
|
2
|
+
# Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls
|
3
|
+
# to trace calls to the database.
|
4
|
+
module ActiveRecordInstruments
|
5
|
+
def self.included(instrumented_class)
|
6
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
7
|
+
instrumented_class.class_eval do
|
8
|
+
unless instrumented_class.method_defined?(:log_without_profile_it_instruments)
|
9
|
+
alias_method :log_without_profile_it_instruments, :log
|
10
|
+
alias_method :log, :log_with_profile_it_instruments
|
11
|
+
protected :log
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end # self.included
|
15
|
+
|
16
|
+
def log_with_profile_it_instruments(*args, &block)
|
17
|
+
sql, name = args
|
18
|
+
self.class.profile_it_instrument(profile_it_ar_metric_name(sql,name), :desc => profile_it_sanitize_sql(sql)) do
|
19
|
+
log_without_profile_it_instruments(sql, name, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def profile_it_ar_metric_name(sql,name)
|
24
|
+
# sql: SELECT "places".* FROM "places" ORDER BY "places"."position" ASC
|
25
|
+
# name: Place Load
|
26
|
+
if name && (parts = name.split " ") && parts.size == 2
|
27
|
+
model = parts.first
|
28
|
+
operation = parts.last.downcase
|
29
|
+
metric_name = case operation
|
30
|
+
when 'load' then 'find'
|
31
|
+
when 'indexes', 'columns' then nil # not under developer control
|
32
|
+
when 'destroy', 'find', 'save', 'create', 'exists' then operation
|
33
|
+
when 'update' then 'save'
|
34
|
+
else
|
35
|
+
if model == 'Join'
|
36
|
+
operation
|
37
|
+
end
|
38
|
+
end
|
39
|
+
metric = "ActiveRecord/#{model}/#{metric_name}" if metric_name
|
40
|
+
metric = "ActiveRecord/SQL/other" if metric.nil?
|
41
|
+
else
|
42
|
+
metric = "ActiveRecord/SQL/Unknown"
|
43
|
+
end
|
44
|
+
metric
|
45
|
+
end
|
46
|
+
|
47
|
+
# Removes actual values from SQL. Used to both obfuscate the SQL and group
|
48
|
+
# similar queries in the UI.
|
49
|
+
def profile_it_sanitize_sql(sql)
|
50
|
+
return nil if sql.length > 1000 # safeguard - don't sanitize large SQL statements
|
51
|
+
sql = sql.dup
|
52
|
+
sql.gsub!(/\\"/, '') # removing escaping double quotes
|
53
|
+
sql.gsub!(/\\'/, '') # removing escaping single quotes
|
54
|
+
sql.gsub!(/'(?:[^']|'')*'/, '?') # removing strings (single quote)
|
55
|
+
sql.gsub!(/"(?:[^"]|"")*"/, '?') # removing strings (double quote)
|
56
|
+
sql.gsub!(/\b\d+\b/, '?') # removing integers
|
57
|
+
sql.gsub!(/\?(,\?)+/,'?') # replace multiple ? w/a single ?
|
58
|
+
sql
|
59
|
+
end
|
60
|
+
|
61
|
+
end # module ActiveRecordInstruments
|
62
|
+
end # module Instruments
|
63
|
+
|
64
|
+
def add_instruments
|
65
|
+
if defined?(ActiveRecord) && defined?(ActiveRecord::Base)
|
66
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
67
|
+
include ::ProfileIt::Instruments::ActiveRecordInstruments
|
68
|
+
include ::ProfileIt::Tracer
|
69
|
+
end
|
70
|
+
ActiveRecord::Base.class_eval do
|
71
|
+
include ::ProfileIt::Tracer
|
72
|
+
end
|
73
|
+
end
|
74
|
+
rescue
|
75
|
+
ProfileIt::Agent.instance.logger.warn "ActiveRecord instrumentation exception: #{$!.message}"
|
76
|
+
end
|
77
|
+
|
78
|
+
if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3
|
79
|
+
Rails.configuration.after_initialize do
|
80
|
+
ProfileIt::Agent.instance.logger.debug "Adding ActiveRecord instrumentation to a Rails 3 app"
|
81
|
+
add_instruments
|
82
|
+
end
|
83
|
+
else
|
84
|
+
add_instruments
|
85
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Mongoid versions that use Moped should instrument Moped.
|
2
|
+
if defined?(::Mongoid) and !defined?(::Moped)
|
3
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting Mongoid"
|
4
|
+
Mongoid::Collection.class_eval do
|
5
|
+
include ProfileIt::Tracer
|
6
|
+
(Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
|
7
|
+
profile_it_instrument_method method, :metric_name => "MongoDB/\#{@klass}/#{method}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
if defined?(::Moped)
|
2
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting Moped"
|
3
|
+
Moped::Node.class_eval do
|
4
|
+
include ProfileIt::Tracer
|
5
|
+
def process_with_profile_it_instruments(operation, &callback)
|
6
|
+
if operation.respond_to?(:collection)
|
7
|
+
collection = operation.collection
|
8
|
+
self.class.instrument("MongoDB/Process/#{collection}/#{operation.class.to_s.split('::').last}", :desc => profile_it_sanitize_log(operation.log_inspect)) do
|
9
|
+
process_without_profile_it_instruments(operation, &callback)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
alias_method :process_without_profile_it_instruments, :process
|
14
|
+
alias_method :process, :process_with_profile_it_instruments
|
15
|
+
|
16
|
+
# replaces values w/ ?
|
17
|
+
def profile_it_sanitize_log(log)
|
18
|
+
return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
|
19
|
+
log.gsub(/(=>")((?:[^"]|"")*)"/) do
|
20
|
+
$1 + '?' + '"'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
if defined?(::Net) && defined?(Net::HTTP)
|
2
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting Net::HTTP"
|
3
|
+
Net::HTTP.class_eval do
|
4
|
+
include ProfileIt::Tracer
|
5
|
+
|
6
|
+
def request_with_profile_it_instruments(*args,&block)
|
7
|
+
self.class.profile_it_instrument("HTTP/request", :desc => "#{(@address+args.first.path.split('?').first)[0..99]}") do
|
8
|
+
request_without_profile_it_instruments(*args,&block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
alias request_without_profile_it_instruments request
|
12
|
+
alias request request_with_profile_it_instruments
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ProfileIt::Instruments
|
2
|
+
module ActionControllerInstruments
|
3
|
+
def self.included(instrumented_class)
|
4
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
5
|
+
instrumented_class.class_eval do
|
6
|
+
unless instrumented_class.method_defined?(:perform_action_without_profile_it_instruments)
|
7
|
+
alias_method :perform_action_without_profile_it_instruments, :perform_action
|
8
|
+
alias_method :perform_action, :perform_action_with_profile_it_instruments
|
9
|
+
private :perform_action
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end # self.included
|
13
|
+
|
14
|
+
# In addition to instrumenting actions, this also sets the scope to the controller action name. The scope is later
|
15
|
+
# applied to metrics recorded during this profile. This lets us associate ActiveRecord calls with
|
16
|
+
# specific controller actions.
|
17
|
+
def perform_action_with_profile_it_instruments(*args, &block)
|
18
|
+
key_from_headers = request.headers['x-profileit-key']
|
19
|
+
if !key_from_headers.blank? && key_from_headers == ProfileIt::Agent.instance.config.settings['key']
|
20
|
+
profile_it_controller_action = "Controller/#{controller_path}/#{action_name}"
|
21
|
+
self.class.profile_request(profile_it_controller_action, :uri => request.request_uri) do
|
22
|
+
perform_action_without_profile_it_instruments(*args, &block)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
perform_action_without_profile_it_instruments(*args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if defined?(ActionController) && defined?(ActionController::Base)
|
32
|
+
ActionController::Base.class_eval do
|
33
|
+
include ProfileIt::Tracer
|
34
|
+
include ::ProfileIt::Instruments::ActionControllerInstruments
|
35
|
+
end
|
36
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting ActionView::Template"
|
37
|
+
ActionView::Template.class_eval do
|
38
|
+
include ::ProfileIt::Tracer
|
39
|
+
profile_it_instrument_method :render, :metric_name => 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Rendering', :scope => true
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Rails 3/4
|
2
|
+
module ProfileIt::Instruments
|
3
|
+
module ActionControllerInstruments
|
4
|
+
# Instruments the action and tracks errors.
|
5
|
+
def process_action(*args)
|
6
|
+
key_from_headers = request.headers['x-profileit-key']
|
7
|
+
# ProfileIt::Agent.instance.logger.debug "in perform_action_with_profile_it_instruments request key = #{key_from_headers}. config key = #{ProfileIt::Agent.instance.config.settings['key']}. "
|
8
|
+
if !key_from_headers.blank? && key_from_headers == ProfileIt::Agent.instance.config.settings['key']
|
9
|
+
profile_it_controller_action = "Controller/#{controller_path}/#{action_name}"
|
10
|
+
self.class.profile_request(profile_it_controller_action, :uri => request.fullpath, :request_id => request.env["action_dispatch.request_id"]) do
|
11
|
+
Thread::current[:profile_it_extension_fingerprint]=request.headers['x-profileit-extension-fingerprint']
|
12
|
+
Thread::current[:profile_it_extension_version]=request.headers['x-profileit-extension-version']
|
13
|
+
Thread::current[:profile_it_user_id]=request.headers['x-profileit-user-id']
|
14
|
+
begin
|
15
|
+
super
|
16
|
+
rescue Exception => e
|
17
|
+
raise
|
18
|
+
ensure
|
19
|
+
Thread::current[:profile_it_scope_name] = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# ActionController::Base is a subclass of ActionController::Metal, so this instruments both
|
30
|
+
# standard Rails requests + Metal.
|
31
|
+
if defined?(ActionController) && defined?(ActionController::Metal)
|
32
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting ActionController::Metal"
|
33
|
+
ActionController::Metal.class_eval do
|
34
|
+
include ProfileIt::Tracer
|
35
|
+
include ::ProfileIt::Instruments::ActionControllerInstruments
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if defined?(ActionView) && defined?(ActionView::PartialRenderer)
|
40
|
+
ProfileIt::Agent.instance.logger.debug "Instrumenting ActionView::PartialRenderer"
|
41
|
+
ActionView::PartialRenderer.class_eval do
|
42
|
+
include ProfileIt::Tracer
|
43
|
+
profile_it_instrument_method :render_partial, :metric_name => 'View/#{@template.virtual_path}/Rendering', :scope => true
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Contains the meta information associated with a metric. Used to lookup Metrics in to Store's metric_hash.
|
2
|
+
class ProfileIt::MetricMeta
|
3
|
+
def initialize(metric_name, options = {})
|
4
|
+
@metric_name = metric_name
|
5
|
+
@metric_id = nil
|
6
|
+
@scope = Thread::current[:profile_it_sub_scope] || Thread::current[:profile_it_scope_name]
|
7
|
+
@desc = options[:desc]
|
8
|
+
@extra = {}
|
9
|
+
end
|
10
|
+
attr_accessor :metric_id, :metric_name
|
11
|
+
attr_accessor :scope
|
12
|
+
attr_accessor :client_id
|
13
|
+
attr_accessor :desc, :extra
|
14
|
+
|
15
|
+
# To avoid conflicts with different JSON libaries. THIS ISN'T USED ANYMORE. STILL A CONFLICT ISSUE?
|
16
|
+
def to_json(*a)
|
17
|
+
%Q[{"metric_id":#{metric_id || 'null'},"metric_name":#{metric_name.to_json},"scope":#{scope.to_json || 'null'}}]
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(o)
|
21
|
+
self.eql?(o)
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
h = metric_name.downcase.hash
|
26
|
+
h ^= scope.downcase.hash unless scope.nil?
|
27
|
+
h ^= desc.downcase.hash unless desc.nil?
|
28
|
+
h
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(o)
|
32
|
+
self.class == o.class && metric_name.downcase.eql?(o.metric_name.downcase) && scope == o.scope && client_id == o.client_id && desc == o.desc
|
33
|
+
end
|
34
|
+
end # class MetricMeta
|