profile_it 0.2.4
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.
- 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
|