sql_tracker 1.1.0 → 1.3.1

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
- SHA1:
3
- metadata.gz: 29226162337ee889c358d175c76c9aea25a6e7ec
4
- data.tar.gz: 51c36a794abd3c99398247a8884cfc5bd472a982
2
+ SHA256:
3
+ metadata.gz: 14ea1ca1cb59b84b95fd69a46d6e3e9e48a5b518147b5728e8c63fd0e1110a65
4
+ data.tar.gz: 9eb139f7f5537f92e8f4d0b37ad15994f1dfe5f51d39b9d07b2f7e26e4915354
5
5
  SHA512:
6
- metadata.gz: 8d1d5d91215717b8ab3a928a0a2568683fa26e2fb4d782b166b4ac5218bab710963fa1778717dfe7fa1679e9859ea216214636233db36b132d12cc3af666cca2
7
- data.tar.gz: e87a892f671de2faedce163085e4ec89326581ab572550a856ba6628517ccb445c7bc200392701e462106f556e1a3142a40451131333668909403438a1a04ecb
6
+ metadata.gz: a2c485ee67296ca11b5f0e55ee7794faa4397a621d9b2e12df34aa9643a4f25e43d4341a89037d176b9e59f28f5780fd20812bcf1c68a1a5d50c09e9fb338504
7
+ data.tar.gz: 383fdbde395e7a0fbde65b247e38736d5a1e3de3858973c26bc3320e50cc92057275d10c9fa4ef249f5afd895d9f4d9815846aa092a4b836d4d5ea68af091ec7
@@ -2,7 +2,10 @@ sudo: false
2
2
  language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.3.0
5
+ - 2.3
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
6
9
  before_install:
7
10
  - gem install bundler
8
11
  - gem update bundler
data/README.md CHANGED
@@ -29,6 +29,24 @@ To start tracking, simply start your rails application server. When your server
29
29
 
30
30
  `sql_tracker` can also track sql queries when running rails tests (e.g. your controller or integration tests), it will dump the data after all the tests are finished.
31
31
 
32
+ ### Tracking Using a Block
33
+
34
+ It is also possible to track queries executed within a block. This method uses a new subscriber to `sql.active_record` event notifications for each invocation. Results using this method are not saved to a file.
35
+
36
+ ```ruby
37
+ query_data = SqlTracker.track do
38
+ # Run some active record queries
39
+ end
40
+
41
+ query_data.values
42
+ # =>
43
+ # [{
44
+ # :sql=>"SELECT * FROM users",
45
+ # :count=>1,
46
+ # :duration=>1.0,
47
+ # :source=>["app/models/user.rb:12"]
48
+ # }]
49
+ ```
32
50
 
33
51
  ## Reporting
34
52
 
@@ -36,6 +54,28 @@ To generate report, run
36
54
  ```bash
37
55
  sql_tracker tmp/sql_tracker-*.json
38
56
  ```
57
+ The output report looks like this:
58
+ ```
59
+ ==================================
60
+ Total Unique SQL Queries: 24
61
+ ==================================
62
+ Count | Avg Time (ms) | SQL Query | Source
63
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
64
+ 8 | 0.33 | SELECT `users`.* FROM `users` WHERE `users`.`id` = xxx LIMIT 1 | app/controllers/users_controller.rb:125:in `create'
65
+ | | | app/controllers/projects_controller.rb:9:in `block in update'
66
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
67
+ 4 | 0.27 | SELECT `projects`.* FROM `projects` WHERE `projects`.`user_id` = xxx AND `projects`.`id` = xxx LIMIT 1 | app/controllers/projects_controller.rb:4:in `update'
68
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
69
+ 2 | 0.27 | UPDATE `projects` SET `updated_at` = xxx WHERE `projects`.`id` = xxx | app/controllers/projects_controller.rb:9:in `block in update'
70
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
71
+ 2 | 1.76 | SELECT projects.* FROM projects WHERE projects.priority BETWEEN xxx AND xxx ORDER BY created_at DESC | app/controllers/projects_controller.rb:35:in `index'
72
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
73
+ ... ...
74
+ ```
75
+ By default, the report will be sorted by the total count of each query, you can also choose to sort it by average duration:
76
+ ```bash
77
+ sql_tracker tmp/sql_tracker-*.json --sort-by=duration
78
+ ```
39
79
 
40
80
  ## Configurations
41
81
 
@@ -1,6 +1,31 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'sql_tracker/terminal'
2
3
  require 'sql_tracker/report'
3
4
  require 'json'
5
+ require 'optparse'
6
+
7
+ options = {}
8
+ default_options = { sort_by: 'count', terminal_width: SqlTracker::Terminal.width }
9
+
10
+ parser = OptionParser.new(ARGV) do |o|
11
+ o.banner = 'Usage: sql_tracker [file.json]+ [--sort-by=COLUMN]'
12
+
13
+ o.on('-s', '--sort-by COLUMN', 'The name of column that is used for sorting the table. Options: count, duration') do |arg|
14
+ options[:sort_by] = arg if %(count duration).include?(arg)
15
+ end
16
+
17
+ o.on('-w', '--terminal-width WIDTH', Integer,
18
+ "The width of the printed report. " \
19
+ "Default: #{default_options[:terminal_width]}. " \
20
+ "Minimum #{SqlTracker::Terminal::MIN_WIDTH}") do |arg|
21
+ options[:terminal_width] = arg if arg >= SqlTracker::Terminal::MIN_WIDTH
22
+ end
23
+ end
24
+
25
+ parser.parse!
26
+ parser.abort(parser.help) if ARGV.empty?
27
+
28
+ options = default_options.merge(options)
4
29
 
5
30
  reports = []
6
31
  until ARGV.empty?
@@ -23,4 +48,4 @@ if reports.empty?
23
48
  end
24
49
 
25
50
  report = reports.inject(:+)
26
- report.print_text
51
+ report.print_text(options)
@@ -8,11 +8,20 @@ module SqlTracker
8
8
 
9
9
  config = SqlTracker::Config.apply_defaults
10
10
  handler = SqlTracker::Handler.new(config)
11
- ActiveSupport::Notifications.subscribe('sql.active_record', handler)
11
+ handler.subscribe
12
12
  @already_initialized = true
13
13
 
14
14
  at_exit { handler.save }
15
15
  end
16
+
17
+ def self.track
18
+ config = SqlTracker::Config.apply_defaults
19
+ handler = SqlTracker::Handler.new(config)
20
+ handler.subscribe
21
+ yield
22
+ handler.unsubscribe
23
+ handler.data
24
+ end
16
25
  end
17
26
 
18
27
  if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i >= 3
@@ -12,10 +12,25 @@ module SqlTracker
12
12
  @data = {} # {key: {sql:, count:, duration, source: []}, ...}
13
13
  end
14
14
 
15
+ def subscribe
16
+ @subscription ||= ActiveSupport::Notifications.subscribe(
17
+ 'sql.active_record',
18
+ self
19
+ )
20
+ end
21
+
22
+ def unsubscribe
23
+ return unless @subscription
24
+
25
+ ActiveSupport::Notifications.unsubscribe(@subscription)
26
+
27
+ @subscription = nil
28
+ end
29
+
15
30
  def call(_name, started, finished, _id, payload)
16
31
  return unless @config.enabled
17
32
 
18
- sql = payload[:sql]
33
+ sql = payload[:sql].dup
19
34
  return unless track?(sql)
20
35
 
21
36
  cleaned_trace = clean_trace(caller)
@@ -47,9 +62,12 @@ module SqlTracker
47
62
 
48
63
  def clean_sql_query(query)
49
64
  query.squish!
50
- query.gsub!(/(\s(=|>|<|>=|<=|<>|!=)\s)('[^']+'|\w+)/, '\1xxx')
65
+ query.gsub!(/(\s(=|>|<|>=|<=|<>|!=)\s)('[^']+'|[\$\+\-\w\.]+)/, '\1xxx')
51
66
  query.gsub!(/(\sIN\s)\([^\(\)]+\)/i, '\1(xxx)')
52
- query.gsub!(/(\sBETWEEN\s)('[^']+'|\w+)(\sAND\s)('[^']+'|\w+)/i, '\1xxx\3xxx')
67
+ query.gsub!(/(\sBETWEEN\s)('[^']+'|[\+\-\w\.]+)(\sAND\s)('[^']+'|[\+\-\w\.]+)/i, '\1xxx\3xxx')
68
+ query.gsub!(/(\sVALUES\s)\(.+\)/i, '\1(xxx)')
69
+ query.gsub!(/(\s(LIKE|ILIKE|SIMILAR TO|NOT SIMILAR TO)\s)('[^']+')/i, '\1xxx')
70
+ query.gsub!(/(\s(LIMIT|OFFSET)\s)(\d+)/i, '\1xxx')
53
71
  query
54
72
  end
55
73
 
@@ -62,7 +80,7 @@ module SqlTracker
62
80
 
63
81
  Rails.backtrace_cleaner.remove_silencers!
64
82
 
65
- if config.tracked_paths.respond_to?(:join)
83
+ if @config.tracked_paths.respond_to?(:join)
66
84
  Rails.backtrace_cleaner.add_silencer do |line|
67
85
  line !~ trace_path_matcher
68
86
  end
@@ -1,6 +1,7 @@
1
1
  module SqlTracker
2
2
  class Report
3
3
  attr_accessor :raw_data
4
+ attr_accessor :terminal_width
4
5
 
5
6
  def initialize(data)
6
7
  self.raw_data = data
@@ -25,7 +26,9 @@ module SqlTracker
25
26
  raw_data['data']
26
27
  end
27
28
 
28
- def print_text(f = STDOUT)
29
+ def print_text(options)
30
+ self.terminal_width = options.fetch(:terminal_width)
31
+ f = STDOUT
29
32
  f.puts '=================================='
30
33
  f.puts "Total Unique SQL Queries: #{data.keys.size}"
31
34
  f.puts '=================================='
@@ -35,7 +38,8 @@ module SqlTracker
35
38
  )
36
39
  f.puts '-' * terminal_width
37
40
 
38
- data.values.sort_by { |d| -d['count'] }.each do |row|
41
+ sorted_data = sort_data(data.values, options[:sort_by])
42
+ sorted_data.each do |row|
39
43
  chopped_sql = wrap_text(row['sql'], sql_width)
40
44
  source_list = wrap_list(row['source'].uniq, sql_width - 10)
41
45
  avg_duration = row['duration'].to_f / row['count']
@@ -50,15 +54,26 @@ module SqlTracker
50
54
  duration = line == 0 ? avg_duration.round(2) : ''
51
55
  source = source_list.length > line ? source_list[line] : ''
52
56
  query = row['sql'].length > line ? chopped_sql[line] : ''
53
- f.printf(
54
- "%-#{count_width}s | %-#{duration_width}s | %-#{sql_width}s | %-#{sql_width}s\n",
55
- count, duration, query, source
56
- )
57
+ f.printf(row_format, count, duration, query, source)
57
58
  end
58
59
  f.puts '-' * terminal_width
59
60
  end
60
61
  end
61
62
 
63
+ def row_format
64
+ "%-#{count_width}s | %-#{duration_width}s | %-#{sql_width}s | %-#{sql_width}s\n"
65
+ end
66
+
67
+ def sort_data(data, sort_by)
68
+ data.sort_by do |d|
69
+ if sort_by == 'duration'
70
+ -d['duration'].to_f / d['count']
71
+ else
72
+ -d['count']
73
+ end
74
+ end
75
+ end
76
+
62
77
  def +(other)
63
78
  unless self.class == other.class
64
79
  raise ArgumentError, "cannot combine #{other.class}"
@@ -112,29 +127,5 @@ module SqlTracker
112
127
  def duration_width
113
128
  15
114
129
  end
115
-
116
- def terminal_width
117
- @terminal_width ||= begin
118
- result = unix? ? dynamic_width : 80
119
- result < 10 ? 80 : result
120
- end
121
- end
122
-
123
- def dynamic_width
124
- @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
125
- end
126
-
127
- def dynamic_width_stty
128
- `stty size 2>/dev/null`.split[1].to_i
129
- end
130
-
131
- def dynamic_width_tput
132
- `tput cols 2>/dev/null`.to_i
133
- end
134
-
135
- def unix?
136
- RUBY_PLATFORM =~
137
- /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
138
- end
139
130
  end
140
131
  end
@@ -0,0 +1,28 @@
1
+ module SqlTracker
2
+ class Terminal
3
+ DEFAULT_WIDTH = 80
4
+ MIN_WIDTH = 10
5
+
6
+ def self.width
7
+ if unix?
8
+ result = (dynamic_width_stty.nonzero? || dynamic_width_tput)
9
+ result < MIN_WIDTH ? DEFAULT_WIDTH : result
10
+ else
11
+ DEFAULT_WIDTH
12
+ end
13
+ end
14
+
15
+ def self.dynamic_width_stty
16
+ `stty size 2>/dev/null`.split[1].to_i
17
+ end
18
+
19
+ def self.dynamic_width_tput
20
+ `tput cols 2>/dev/null`.to_i
21
+ end
22
+
23
+ def self.unix?
24
+ RUBY_PLATFORM =~
25
+ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module SqlTracker
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.3.1'.freeze
3
3
  end
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = ['sql_tracker']
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.12'
23
- spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
24
  spec.add_development_dependency 'minitest', '~> 5.0'
25
25
  spec.add_development_dependency 'activesupport', '>= 3.0.0'
26
26
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sql_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Yue
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-03 00:00:00.000000000 Z
11
+ date: 2020-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.12'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -88,6 +88,7 @@ files:
88
88
  - lib/sql_tracker/handler.rb
89
89
  - lib/sql_tracker/railtie.rb
90
90
  - lib/sql_tracker/report.rb
91
+ - lib/sql_tracker/terminal.rb
91
92
  - lib/sql_tracker/version.rb
92
93
  - sql_tracker.gemspec
93
94
  homepage: http://www.github.com/steventen/sql_tracker
@@ -109,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
112
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 2.5.1
113
+ rubygems_version: 3.0.6
114
114
  signing_key:
115
115
  specification_version: 4
116
116
  summary: Rails SQL Query Tracker