scout_apm 0.1.3.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6dd9fa6823ce48d2b2b36ae4238cfaa3152bfd08
4
- data.tar.gz: c0916b57ea440cedaf275792089bedf20249ffe9
3
+ metadata.gz: eadc6cf693f387c94fdbbaa5bc96a759c80be140
4
+ data.tar.gz: e6c9465cde1d1589f72e6c46f95a9e45ba54f627
5
5
  SHA512:
6
- metadata.gz: d62355ce9a305aa40ecd58722b1c518a1dc2606e06aa3fd1101b73100bded03a48e51c2a2bcc54907f0eb5da5e3444bdb819cc420766e90823cca19f4d7344bc
7
- data.tar.gz: 27c9bc33de0af9d4aff82503ad7e3bb1a8fde34a6448fba5ebe61b84f80d2354a517aecf7ddb7fcf48a059193c3027064c7a159223335156dc74a22cee798056
6
+ metadata.gz: 77a3ee7b7e4fd8b1c3ee62231d4566d1bf9937ea4d48498194b991d7ffb55782c13baaae1021aa127325dccc9ccd2a200722b5b5ae7a341825a2acf45f5c854b
7
+ data.tar.gz: 2aa25dca40e455d4463676c77f6bd105d07402c6358d9e479ba6033b143464e8d546ff39a4ea50e82f0b9e9c92959bb393f69ba9cec3d50ba3d35c67268591a9
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.1.4
2
+
3
+ * Tweaks to Postgres query parsing
4
+ * Fix for missing scout_apm.yml file causing rake commands to fail because of a missing log file.
5
+
1
6
  # 0.1.3.1
2
7
 
3
8
  * Adds Puma support
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+ task :test do
5
+ $: << File.expand_path(File.dirname(__FILE__) + "/test")
6
+ Dir.glob('./test/**/*_test.rb').each { |file| require file }
7
+ end
8
+
@@ -6,8 +6,8 @@ module ScoutApm
6
6
  # saves tshe merged data to disk, and sends it to the Scout server.
7
7
  class Agent
8
8
  # see self.instance
9
- @@instance = nil
10
-
9
+ @@instance = nil
10
+
11
11
  # Accessors below are for associated classes
12
12
  attr_accessor :store
13
13
  attr_accessor :layaway
@@ -18,31 +18,32 @@ module ScoutApm
18
18
  attr_accessor :log_file # path to the log file
19
19
  attr_accessor :options # options passed to the agent when +#start+ is called.
20
20
  attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
21
-
21
+
22
22
  # All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
23
23
  def self.instance(options = {})
24
24
  @@instance ||= self.new(options)
25
25
  end
26
-
26
+
27
27
  # Note - this doesn't start instruments or the worker thread. This is handled via +#start+ as we don't
28
28
  # want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn't
29
29
  # be started (when forking).
30
30
  def initialize(options = {})
31
31
  @started = false
32
32
  @options ||= options
33
+ @config = ScoutApm::Config.new(options[:config_path])
34
+
33
35
  @store = ScoutApm::Store.new
34
36
  @layaway = ScoutApm::Layaway.new
35
- @config = ScoutApm::Config.new(options[:config_path])
36
37
  @metric_lookup = Hash.new
37
- @process_cpu=ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors)
38
- @process_memory=ScoutApm::Instruments::Process::ProcessMemory.new
38
+ @process_cpu = ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors)
39
+ @process_memory = ScoutApm::Instruments::Process::ProcessMemory.new
39
40
  @capacity = ScoutApm::Capacity.new
40
41
  end
41
-
42
+
42
43
  def environment
43
44
  @environment ||= ScoutApm::Environment.new
44
45
  end
45
-
46
+
46
47
  # This is called via +ScoutApm::Agent.instance.start+ when ScoutApm is required in a Ruby application.
47
48
  # It initializes the agent and starts the worker thread (if appropiate).
48
49
  def start(options = {})
@@ -64,7 +65,7 @@ module ScoutApm
64
65
  end
65
66
  @started = true
66
67
  logger.info "Starting monitoring for [#{config.value('name')}]. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
67
- start_instruments
68
+ load_instruments
68
69
  if !start_background_worker?
69
70
  logger.debug "Not starting worker thread. Will start worker loops after forking."
70
71
  install_passenger_events if environment.app_server == :passenger
@@ -168,8 +169,8 @@ module ScoutApm
168
169
  end
169
170
  rescue
170
171
  logger.warn "Unable to install Puma worker loop: #{$!.message}"
171
- end
172
-
172
+ end
173
+
173
174
  # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
174
175
  # processes data, either saving it to disk or reporting to Scout.
175
176
  def start_background_worker
@@ -180,7 +181,7 @@ module ScoutApm
180
181
  end # thread new
181
182
  logger.debug "Done creating worker thread."
182
183
  end
183
-
184
+
184
185
  # Called from #process_metrics, which is run via the background worker.
185
186
  def run_samplers
186
187
  begin
@@ -203,9 +204,10 @@ module ScoutApm
203
204
  logger.debug e.backtrace.join("\n")
204
205
  end
205
206
  end
206
-
207
+
207
208
  # Loads the instrumention logic.
208
209
  def load_instruments
210
+ logger.debug "Installing instrumentation"
209
211
  case environment.framework
210
212
  when :rails
211
213
  require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails/action_controller_instruments.rb'))
@@ -221,12 +223,5 @@ module ScoutApm
221
223
  logger.warn $!.message
222
224
  logger.warn $!.backtrace
223
225
  end
224
-
225
- # Injects instruments into the Ruby application.
226
- def start_instruments
227
- logger.debug "Installing instrumentation"
228
- load_instruments
229
- end
230
-
231
226
  end # class Agent
232
- end # module ScoutApm
227
+ end # module ScoutApm
@@ -6,11 +6,18 @@ module ScoutApm
6
6
  def default_log_path
7
7
  "#{environment.root}/log"
8
8
  end
9
-
9
+
10
10
  def init_logger
11
- @log_file = wants_stdout? ? STDOUT : "#{log_file_path}/scout_apm.log"
12
- begin
13
- @logger = Logger.new(@log_file)
11
+ begin
12
+ @log_file = wants_stdout? ? STDOUT : "#{log_file_path}/scout_apm.log"
13
+ rescue => e
14
+ puts "OHH NO"
15
+ puts e.message
16
+ puts e.backtrace.join("\n\t")
17
+ end
18
+
19
+ begin
20
+ @logger = Logger.new(@log_file)
14
21
  @logger.level = log_level
15
22
  apply_log_format
16
23
  rescue Exception => e
@@ -50,4 +57,4 @@ module ScoutApm
50
57
  end # module Logging
51
58
  include Logging
52
59
  end # class Agent
53
- end # moudle ScoutApm
60
+ end # moudle ScoutApm
@@ -23,7 +23,7 @@ module ScoutApm
23
23
  if meta.metric_name =~ /\AController/
24
24
  controller_count += stats.call_count
25
25
  end
26
- end
26
+ end
27
27
  payload = Marshal.dump(:metrics => metrics, :slow_transactions => slow_transactions)
28
28
  slow_transactions_kb = Marshal.dump(slow_transactions).size/1024 # just for performance debugging
29
29
  logger.debug "#{config.value('name')} Delivering total payload [#{payload.size/1024} KB] for #{controller_count} requests and slow transactions [#{slow_transactions_kb} KB] for #{slow_transactions.size} transactions of durations: #{slow_transactions.map(&:total_call_time).join(',')}."
@@ -47,7 +47,7 @@ module ScoutApm
47
47
  logger.info $!.message
48
48
  logger.debug $!.backtrace
49
49
  end
50
-
50
+
51
51
  # Before reporting, lookup metric_id for each MetricMeta. This speeds up
52
52
  # reporting on the server-side.
53
53
  def add_metric_ids(metrics)
@@ -57,7 +57,7 @@ module ScoutApm
57
57
  end
58
58
  end
59
59
  end
60
-
60
+
61
61
  def checkin_uri
62
62
  URI.parse("#{config.value('host')}/apps/checkin.scout?key=#{config.value('key')}&name=#{CGI.escape(config.value('name'))}")
63
63
  end
@@ -113,4 +113,4 @@ module ScoutApm
113
113
  end # module Reporting
114
114
  include Reporting
115
115
  end # class Agent
116
- end # module ScoutApm
116
+ end # module ScoutApm
@@ -1,15 +1,22 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require 'scout_apm/environment'
5
+
1
6
  module ScoutApm
2
- class Config
7
+ class Config
3
8
  DEFAULTS = {
4
9
  'host' => 'https://apm.scoutapp.com',
5
10
  'log_level' => 'info'
6
- }
11
+ }.freeze
7
12
 
8
13
  def initialize(config_path = nil)
9
14
  @config_path = config_path
10
15
  end
11
-
12
- # Fetch a config value. It first attempts to fetch an ENV var prefixed with 'SCOUT_', then from the settings file.
16
+
17
+ # Fetch a config value.
18
+ # It first attempts to fetch an ENV var prefixed with 'SCOUT_',
19
+ # then from the settings file.
13
20
  def value(key)
14
21
  value = ENV['SCOUT_'+key.upcase] || settings[key]
15
22
  value.to_s.strip.length.zero? ? nil : value
@@ -18,33 +25,46 @@ module ScoutApm
18
25
  private
19
26
 
20
27
  def config_path
21
- @config_path || File.join(ScoutApm::Agent.instance.environment.root,"config","scout_apm.yml")
28
+ @config_path || File.join(ScoutApm::Environment.new.root, "config", "scout_apm.yml")
22
29
  end
23
-
30
+
24
31
  def config_file
25
32
  File.expand_path(config_path)
26
33
  end
27
34
 
28
35
  def settings
29
- return @settings if @settings
30
- load_file
36
+ @settings ||= load_file
37
+ end
38
+
39
+ def config_environment
40
+ @config_environment ||= ScoutApm::Environment.new.env
31
41
  end
32
-
42
+
33
43
  def load_file
44
+ settings_hash = {}
34
45
  begin
35
- if !File.exist?(config_file)
36
- ScoutApm::Agent.instance.logger.warn "No config file found at [#{config_file}]."
37
- @settings = {}
46
+ if File.exist?(config_file)
47
+ settings_hash = YAML.load(ERB.new(File.read(config_file)).result(binding))[config_environment] || {}
38
48
  else
39
- @settings = YAML.load(ERB.new(File.read(config_file)).result(binding))[ScoutApm::Agent.instance.environment.env] || {}
40
- end
49
+ logger.warn "No config file found at [#{config_file}]."
50
+ end
41
51
  rescue Exception => e
42
- ScoutApm::Agent.instance.logger.warn "Unable to load the config file."
43
- ScoutApm::Agent.instance.logger.warn e.message
44
- ScoutApm::Agent.instance.logger.warn e.backtrace
45
- @settings = {}
52
+ logger.warn "Unable to load the config file."
53
+ logger.warn e.message
54
+ logger.warn e.backtrace
55
+ end
56
+ DEFAULTS.merge(settings_hash)
57
+ end
58
+
59
+ # if we error out early enough, we don't have access to ScoutApm's logger
60
+ # in that case, be silent unless ENV['SCOUT_DEBUG'] is set, then STDOUT it
61
+ def logger
62
+ if defined?(ScoutApm::Agent) && (apm_log = ScoutApm::Agent.instance.logger)
63
+ apm_log
64
+ else
65
+ require 'scout_apm/utils/null_logger'
66
+ ENV['SCOUT_DEBUG'] ? Logger.new(STDOUT) : ScoutApm::Utils::NullLogger.new
46
67
  end
47
- @settings = DEFAULTS.merge(@settings)
48
68
  end
49
- end # Config
50
- end # ScoutApm
69
+ end
70
+ end
@@ -3,13 +3,17 @@ module ScoutApm
3
3
  class Environment
4
4
  def env
5
5
  @env ||= case framework
6
- when :rails then RAILS_ENV.dup
7
- when :rails3_or_4 then Rails.env
6
+ when :rails
7
+ RAILS_ENV.dup
8
+ when :rails3_or_4
9
+ Rails.env
8
10
  when :sinatra
9
11
  ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
12
+ when :ruby
13
+ ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
10
14
  end
11
15
  end
12
-
16
+
13
17
  def framework
14
18
  @framework ||= case
15
19
  when defined?(::Rails) && defined?(ActionController)
@@ -22,7 +26,29 @@ module ScoutApm
22
26
  else :ruby
23
27
  end
24
28
  end
25
-
29
+
30
+ def database_engine
31
+ default = :mysql
32
+
33
+ if defined?(ActiveRecord::Base)
34
+ return default unless ActiveRecord::Base.connected?
35
+
36
+ case ActiveRecord::Base.connection.class.to_s
37
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
38
+ :mysql
39
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
40
+ :postgres
41
+ when "ActiveRecord::ConnectionAdapters::SQLite3Adapter"
42
+ :sqlite
43
+ else
44
+ default
45
+ end
46
+ else
47
+ # TODO: detection outside of Rails
48
+ default
49
+ end
50
+ end
51
+
26
52
  def processors
27
53
  return @processors if @processors
28
54
  unless @processors
@@ -38,7 +64,7 @@ module ScoutApm
38
64
  end
39
65
  @processors
40
66
  end
41
-
67
+
42
68
  def root
43
69
  if framework == :rails
44
70
  RAILS_ROOT.to_s
@@ -52,23 +78,23 @@ module ScoutApm
52
78
  end
53
79
 
54
80
  def heroku?
55
- ENV['DYNO']
81
+ ENV['DYNO']
56
82
  end
57
83
 
58
84
  def hostname
59
85
  heroku? ? ENV['DYNO'] : Socket.gethostname
60
86
  end
61
-
87
+
62
88
  # This needs to be improved. Frequently, multiple app servers gem are present and which
63
- # ever is checked first becomes the designated app server.
64
- #
89
+ # ever is checked first becomes the designated app server.
90
+ #
65
91
  # I've put Thin and Webrick last as they are often used in development and included in Gemfiles
66
92
  # but less likely used in production.
67
93
  #
68
94
  # Next step: (1) list out all detected app servers (2) install hooks for those that need it (passenger, rainbows, unicorn).
69
95
  #
70
96
  # Believe the biggest downside is the master process for forking app servers will get a background worker. Not sure how this will
71
- # impact metrics (it shouldn't process requests).
97
+ # impact metrics (it shouldn't process requests).
72
98
  def app_server
73
99
  @app_server ||= if passenger? then :passenger
74
100
  elsif rainbows? then :rainbows
@@ -79,9 +105,8 @@ module ScoutApm
79
105
  else nil
80
106
  end
81
107
  end
82
-
108
+
83
109
  ### app server related-checks
84
-
85
110
  def thin?
86
111
  if defined?(::Thin) && defined?(::Thin::Server)
87
112
  # Ensure Thin is actually initialized. It could just be required and not running.
@@ -89,14 +114,14 @@ module ScoutApm
89
114
  false
90
115
  end
91
116
  end
92
-
117
+
93
118
  # Called via +#forking?+ since Passenger forks. Adds an event listener to start the worker thread
94
119
  # inside the passenger worker process.
95
120
  # Background: http://www.modrails.com/documentation/Users%20guide%20Nginx.html#spawning%5Fmethods%5Fexplained
96
121
  def passenger?
97
122
  (defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::PhusionPassenger)
98
123
  end
99
-
124
+
100
125
  def webrick?
101
126
  defined?(::WEBrick) && defined?(::WEBrick::VERSION)
102
127
  end
@@ -107,7 +132,7 @@ module ScoutApm
107
132
  false
108
133
  end
109
134
  end
110
-
135
+
111
136
  def unicorn?
112
137
  if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
113
138
  # Ensure Unicorn is actually initialized. It could just be required and not running.
@@ -119,15 +144,15 @@ module ScoutApm
119
144
  def puma?
120
145
  defined?(::Puma) && File.basename($0) == 'puma'
121
146
  end
122
-
147
+
123
148
  # If forking, don't start worker thread in the master process. Since it's started as a Thread, it won't survive
124
- # the fork.
149
+ # the fork.
125
150
  def forking?
126
151
  passenger? or unicorn? or rainbows? or puma?
127
152
  end
128
-
153
+
129
154
  ### ruby checks
130
-
155
+
131
156
  def rubinius?
132
157
  RUBY_VERSION =~ /rubinius/i
133
158
  end
@@ -135,11 +160,11 @@ module ScoutApm
135
160
  def jruby?
136
161
  defined?(JRuby)
137
162
  end
138
-
163
+
139
164
  def ruby_19?
140
165
  defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
141
166
  end
142
-
167
+
143
168
  ### framework checks
144
169
 
145
170
  def sinatra?
@@ -147,4 +172,4 @@ module ScoutApm
147
172
  end
148
173
 
149
174
  end # class Environemnt
150
- end
175
+ end
@@ -1,65 +1,54 @@
1
- module ScoutApm::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
- ScoutApm::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
7
- instrumented_class.class_eval do
8
- unless instrumented_class.method_defined?(:log_without_scout_instruments)
9
- alias_method :log_without_scout_instruments, :log
10
- alias_method :log, :log_with_scout_instruments
11
- protected :log
1
+ require 'scout_apm/utils/sql_sanitizer'
2
+
3
+ module ScoutApm
4
+ module Instruments
5
+ # Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls
6
+ # to trace calls to the database.
7
+ module ActiveRecordInstruments
8
+ def self.included(instrumented_class)
9
+ ScoutApm::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
10
+ instrumented_class.class_eval do
11
+ unless instrumented_class.method_defined?(:log_without_scout_instruments)
12
+ alias_method :log_without_scout_instruments, :log
13
+ alias_method :log, :log_with_scout_instruments
14
+ protected :log
15
+ end
16
+ end
17
+ end # self.included
18
+
19
+ def log_with_scout_instruments(*args, &block)
20
+ sql, name = args
21
+ self.class.instrument(scout_ar_metric_name(sql,name), :desc => Utils::SqlSanitizer.new(sql).to_s) do
22
+ log_without_scout_instruments(sql, name, &block)
12
23
  end
13
24
  end
14
- end # self.included
15
-
16
- def log_with_scout_instruments(*args, &block)
17
- sql, name = args
18
- self.class.instrument(scout_ar_metric_name(sql,name), :desc => scout_sanitize_sql(sql)) do
19
- log_without_scout_instruments(sql, name, &block)
20
- end
21
- end
22
-
23
- def scout_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
25
+
26
+ def scout_ar_metric_name(sql,name)
27
+ # sql: SELECT "places".* FROM "places" ORDER BY "places"."position" ASC
28
+ # name: Place Load
29
+ if name && (parts = name.split " ") && parts.size == 2
30
+ model = parts.first
31
+ operation = parts.last.downcase
32
+ metric_name = case operation
33
+ when 'load' then 'find'
34
+ when 'indexes', 'columns' then nil # not under developer control
35
+ when 'destroy', 'find', 'save', 'create', 'exists' then operation
36
+ when 'update' then 'save'
37
+ else
38
+ if model == 'Join'
39
+ operation
40
+ end
37
41
  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"
42
+ metric = "ActiveRecord/#{model}/#{metric_name}" if metric_name
43
+ metric = "ActiveRecord/SQL/other" if metric.nil?
44
+ else
45
+ metric = "ActiveRecord/SQL/Unknown"
46
+ end
47
+ metric
43
48
  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 scout_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
49
+ end # module ActiveRecordInstruments
50
+ end # module Instruments
51
+ end
63
52
 
64
53
  def add_instruments
65
54
  if defined?(ActiveRecord) && defined?(ActiveRecord::Base)
@@ -82,4 +71,4 @@ if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3 && ::Rails.respond_to?
82
71
  end
83
72
  else
84
73
  add_instruments
85
- end
74
+ end
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ module ScoutApm
4
+ module Utils
5
+ class NullLogger < Logger
6
+ def initialize(*args)
7
+ end
8
+
9
+ def add(*args, &block)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,74 @@
1
+ require 'scout_apm/environment'
2
+
3
+ # Removes actual values from SQL. Used to both obfuscate the SQL and group
4
+ # similar queries in the UI.
5
+ module ScoutApm
6
+ module Utils
7
+ class SqlSanitizer
8
+ attr_reader :sql
9
+ attr_accessor :database_engine
10
+
11
+ def initialize(sql)
12
+ @sql = sql.dup
13
+ @database_engine = ScoutApm::Environment.new.database_engine
14
+ end
15
+
16
+ def to_s
17
+ return nil if sql.length > 1000 # safeguard - don't sanitize large SQL statements
18
+ case database_engine
19
+ when :postgres then to_s_postgres
20
+ when :mysql then to_s_mysql
21
+ when :sqlite then to_s_sqlite
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ MULTIPLE_SPACES = %r|\s+|.freeze
28
+ MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
29
+ TRAILING_SPACES = /\s+$/.freeze
30
+
31
+ PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
32
+ PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
33
+ PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
34
+ PSQL_PLACEHOLDER = /\$\d+/.freeze
35
+
36
+ def to_s_postgres
37
+ sql.gsub!(PSQL_PLACEHOLDER, '?')
38
+ sql.gsub!(PSQL_VAR_INTERPOLATION, '')
39
+ sql.gsub!(PSQL_REMOVE_STRINGS, '?')
40
+ sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
41
+ sql.gsub!(MULTIPLE_SPACES, ' ')
42
+ sql.gsub!(TRAILING_SPACES, '')
43
+ sql
44
+ end
45
+
46
+ MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
47
+ MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
48
+ MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = /'(?:[^']|'')*'/.freeze
49
+ MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = /"(?:[^"]|"")*"/.freeze
50
+
51
+ def to_s_mysql
52
+ sql.gsub!(MYSQL_VAR_INTERPOLATION, '')
53
+ sql.gsub!(MYSQL_REMOVE_SINGLE_QUOTE_STRINGS, '?')
54
+ sql.gsub!(MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS, '?')
55
+ sql.gsub!(MYSQL_REMOVE_INTEGERS, '?')
56
+ sql.gsub!(MULTIPLE_QUESTIONS, '?')
57
+ sql.gsub!(TRAILING_SPACES, '')
58
+ sql
59
+ end
60
+
61
+ SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
62
+ SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
63
+ SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
64
+
65
+ def to_s_sqlite
66
+ sql.gsub!(SQLITE_VAR_INTERPOLATION, '')
67
+ sql.gsub!(SQLITE_REMOVE_STRINGS, '?')
68
+ sql.gsub!(SQLITE_REMOVE_INTEGERS, '?')
69
+ sql.gsub!(MULTIPLE_SPACES, ' ')
70
+ sql.gsub!(TRAILING_SPACES, '')
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "0.1.3.1"
2
+ VERSION = "0.1.4"
3
3
  end
data/scout_apm.gemspec CHANGED
@@ -5,7 +5,7 @@ require "scout_apm/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "scout_apm"
7
7
  s.version = ScoutApm::VERSION
8
- s.authors = ["Derek Haynes",'Andre Lewis']
8
+ s.authors = ["Derek Haynes", 'Andre Lewis']
9
9
  s.email = ["support@scoutapp.com"]
10
10
  s.homepage = "https://github.com/scoutapp/scout_apm_ruby"
11
11
  s.summary = "Ruby application performance monitoring"
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib","data"]
20
20
 
21
- # specify any dependencies here; for example:
22
- # s.add_development_dependency "rspec"
23
- # s.add_runtime_dependency "rest-client"
21
+ s.add_development_dependency "minitest"
22
+ s.add_development_dependency "pry"
23
+ s.add_development_dependency "m"
24
24
  end
@@ -0,0 +1,21 @@
1
+ common: &defaults
2
+ name: Scout APM
3
+ key: 000011110000
4
+ log_level: debug
5
+ monitor: true
6
+
7
+ production:
8
+ <<: *defaults
9
+ name: APM Test Conf (Production)
10
+
11
+ development:
12
+ <<: *defaults
13
+ name: APM Test Conf (Development)
14
+ host: http://localhost:3000
15
+ monitor: true
16
+
17
+ test:
18
+ <<: *defaults
19
+ name: APM Test Conf (Test)
20
+ monitor: false
21
+
@@ -0,0 +1,16 @@
1
+
2
+ require 'minitest/autorun'
3
+ require 'minitest/unit'
4
+ require 'minitest/pride'
5
+
6
+ require 'pry'
7
+
8
+ Kernel.module_eval do
9
+ # Unset a constant without private access.
10
+ def self.const_unset(const)
11
+ self.instance_eval { remove_const(const) }
12
+ end
13
+ end
14
+
15
+ # require 'scout_apm'
16
+
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/config'
4
+
5
+ class ConfigTest < Minitest::Test
6
+ def test_initalize_without_a_config
7
+ conf = ScoutApm::Config.new(nil)
8
+
9
+ # nil for random keys
10
+ assert_nil conf.value("log_file_path")
11
+
12
+ # but has values for defaulted keys
13
+ assert conf.value("host")
14
+
15
+ # and still reads from ENV
16
+ ENV['SCOUT_CONFIG_TEST_KEY'] = 'testval'
17
+ assert_equal 'testval', conf.value("config_test_key")
18
+ end
19
+
20
+ def test_loading_a_file
21
+ ENV['RACK_ENV'] = "production"
22
+ conf_file = File.expand_path("../../data/config_test_1.yml", __FILE__)
23
+ conf = ScoutApm::Config.new(conf_file)
24
+
25
+ assert_equal "debug", conf.value('log_level')
26
+ assert_equal "APM Test Conf (Production)", conf.value('name')
27
+ end
28
+ end
29
+
@@ -0,0 +1,60 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/environment'
4
+
5
+ class EnvironmentTest < Minitest::Test
6
+ def teardown
7
+ clean_fake_rails
8
+ clean_fake_sinatra
9
+ end
10
+
11
+ def test_framework_rails
12
+ fake_rails(2)
13
+ assert_equal :rails, ScoutApm::Environment.new.framework
14
+
15
+ clean_fake_rails
16
+ fake_rails(3)
17
+ assert_equal :rails3_or_4, ScoutApm::Environment.new.framework
18
+
19
+ clean_fake_rails
20
+ fake_rails(4)
21
+ assert_equal :rails3_or_4, ScoutApm::Environment.new.framework
22
+ end
23
+
24
+ def test_framework_sinatra
25
+ fake_sinatra
26
+ assert_equal :sinatra, ScoutApm::Environment.new.framework
27
+ end
28
+
29
+ def test_framework_ruby
30
+ assert_equal :ruby, ScoutApm::Environment.new.framework
31
+ end
32
+
33
+ ############################################################
34
+
35
+ def fake_rails(version)
36
+ Kernel.const_set("Rails", Module.new)
37
+ Kernel.const_set("ActionController", Module.new)
38
+ r = Kernel.const_get("Rails")
39
+ r.const_set("VERSION", Module.new)
40
+ v = r.const_get("VERSION")
41
+ v.const_set("MAJOR", version)
42
+
43
+ assert_equal version, Rails::VERSION::MAJOR
44
+ end
45
+
46
+ def clean_fake_rails
47
+ Kernel.const_unset("Rails") if defined?(Kernel::Rails)
48
+ Kernel.const_unset("ActionController") if defined?(Kernel::ActionController)
49
+ end
50
+
51
+ def fake_sinatra
52
+ Kernel.const_set("Sinatra", Module.new)
53
+ s = Kernel.const_get("Sinatra")
54
+ s.const_set("Base", Module.new)
55
+ end
56
+
57
+ def clean_fake_sinatra
58
+ Kernel.const_unset("Sinatra") if defined?(Kernel::Sinatra)
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/instruments/active_record_instruments'
4
+
5
+ class ActiveRecordInstrumentsTest < Minitest::Test
6
+ end
7
+
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/utils/sql_sanitizer'
4
+
5
+ module ScoutApm
6
+ module Utils
7
+ class SqlSanitizerTest < Minitest::Test
8
+ # Too long, and we just bail out to prevent long running instrumentation
9
+ def test_long_sql
10
+ sql = " " * 1001
11
+ assert_nil SqlSanitizer.new(sql).to_s
12
+ end
13
+
14
+ def test_postgres_simple_select_of_first
15
+ sql = %q|SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1|
16
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
17
+ assert_equal %q|SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1|, ss.to_s
18
+ end
19
+
20
+ def test_postgres_where
21
+ sql = %q|SELECT "users".* FROM "users" WHERE "users"."name" = $1 [["name", "chris"]]|
22
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
23
+ assert_equal %q|SELECT "users".* FROM "users" WHERE "users"."name" = ?|, ss.to_s
24
+ end
25
+
26
+ def test_postgres_strips_literals
27
+ # Strip strings
28
+ sql = %q|SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" WHERE (blogs.title = 'hello world')|
29
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
30
+ assert_equal %q|SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" WHERE (blogs.title = ?)|, ss.to_s
31
+
32
+ # Strip integers
33
+ sql = %q|SELECT "blogs".* FROM "blogs" WHERE (view_count > 10)|
34
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
35
+ assert_equal %q|SELECT "blogs".* FROM "blogs" WHERE (view_count > ?)|, ss.to_s
36
+ end
37
+
38
+ def test_mysql_where
39
+ sql = %q|SELECT `users`.* FROM `users` WHERE `users`.`name` = ? [["name", "chris"]]|
40
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
41
+ assert_equal %q|SELECT `users`.* FROM `users` WHERE `users`.`name` = ?|, ss.to_s
42
+ end
43
+
44
+ def test_mysql_limit
45
+ sql = %q|SELECT `blogs`.* FROM `blogs` ORDER BY `blogs`.`id` ASC LIMIT 1|
46
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
47
+ assert_equal %q|SELECT `blogs`.* FROM `blogs` ORDER BY `blogs`.`id` ASC LIMIT 1|, ss.to_s
48
+ end
49
+
50
+ def test_mysql_literals
51
+ sql = %q|SELECT `blogs`.* FROM `blogs` WHERE (title = 'abc')|
52
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
53
+ assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = ?)|, ss.to_s
54
+
55
+ sql = %q|SELECT `blogs`.* FROM `blogs` WHERE (title = "abc")|
56
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
57
+ assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = ?)|, ss.to_s
58
+ end
59
+ end
60
+ end
61
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3.1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,8 +9,50 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-08-10 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2015-08-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: pry
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: m
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
14
56
  description: Monitors Ruby apps and reports detailed metrics on performance to Scout.
15
57
  email:
16
58
  - support@scoutapp.com
@@ -49,8 +91,16 @@ files:
49
91
  - lib/scout_apm/stack_item.rb
50
92
  - lib/scout_apm/store.rb
51
93
  - lib/scout_apm/tracer.rb
94
+ - lib/scout_apm/utils/null_logger.rb
95
+ - lib/scout_apm/utils/sql_sanitizer.rb
52
96
  - lib/scout_apm/version.rb
53
97
  - scout_apm.gemspec
98
+ - test/data/config_test_1.yml
99
+ - test/test_helper.rb
100
+ - test/unit/config_test.rb
101
+ - test/unit/environment_test.rb
102
+ - test/unit/instruments/active_record_instruments_test.rb
103
+ - test/unit/sql_sanitizer_test.rb
54
104
  homepage: https://github.com/scoutapp/scout_apm_ruby
55
105
  licenses: []
56
106
  metadata: {}
@@ -71,9 +121,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
121
  version: '0'
72
122
  requirements: []
73
123
  rubyforge_project: scout_apm
74
- rubygems_version: 2.4.6
124
+ rubygems_version: 2.2.2
75
125
  signing_key:
76
126
  specification_version: 4
77
127
  summary: Ruby application performance monitoring
78
- test_files: []
79
- has_rdoc:
128
+ test_files:
129
+ - test/data/config_test_1.yml
130
+ - test/test_helper.rb
131
+ - test/unit/config_test.rb
132
+ - test/unit/environment_test.rb
133
+ - test/unit/instruments/active_record_instruments_test.rb
134
+ - test/unit/sql_sanitizer_test.rb