sql_tracker 1.1.0 → 1.3.1

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