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.
@@ -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