profile_it 0.2.4

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