scout_apm 0.1.3.1 → 0.1.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 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