sql_tracker 1.1.1 → 1.3.2

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: d8d236c5adb10928d77ae8547ab0a88fffe16c4c
4
- data.tar.gz: af52c90be114c030abbe35ee5c4babda01b4a21f
2
+ SHA256:
3
+ metadata.gz: 7212f79d9a123731752126baff019de8dc807c78442607c594ffc9c4261281f0
4
+ data.tar.gz: bf99e463dc00c245c49135b7d18d5ed3b0b325d2534cadf9d829232c3e92beb8
5
5
  SHA512:
6
- metadata.gz: d0231bef73e7f00395eebc0afce207544cade7bd702b57abeea697ffd9dbd9da0b4cb4d42023b85b4a91d2d8c3044a936ffbbee720b59266b0e023cb5c0a08f3
7
- data.tar.gz: b41ac6f658f2dfcd9ebeab95bd694c998e8cf6a4d8ec5753564088af0110b918efa437a66fbd4687df206369d5a53927c266427f6c91ea9fbcdfed29eebd762a
6
+ metadata.gz: 689f6f10d3cb217274ce2b55e7972ba453e69b34a8fbed03837608bc9f1131afee3b3d1315d6323ddcca93a2a44e625771dc58d6fb4c39be7b68c41c16df402f
7
+ data.tar.gz: 8d9ed0706326a36d886b77a7d8591a1cfc8e117774d149a870750b983eb746630d26e01456be186eca80a83723b6b662c65b32f79c51ab9dfba84a07ce1fa8aa
@@ -0,0 +1,31 @@
1
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
2
+
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches: [ master ]
8
+ pull_request:
9
+ branches: [ master ]
10
+
11
+ jobs:
12
+ test:
13
+
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ ruby-version: ['2.3', '2.4', '2.5', '2.6', '2.7']
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - name: Set up Ruby
22
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
23
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby-version }}
27
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
28
+ - name: Install dependencies
29
+ run: bundle install
30
+ - name: Run tests
31
+ run: bundle exec rake test
@@ -0,0 +1,27 @@
1
+ name: Publish
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ build:
8
+ name: Build + Publish
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - name: Set up Ruby 2.6
14
+ uses: actions/setup-ruby@v1
15
+ with:
16
+ ruby-version: 2.6.x
17
+
18
+ - name: Publish to RubyGems
19
+ run: |
20
+ mkdir -p $HOME/.gem
21
+ touch $HOME/.gem/credentials
22
+ chmod 0600 $HOME/.gem/credentials
23
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
24
+ gem build *.gemspec
25
+ gem push *.gem
26
+ env:
27
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
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
 
data/bin/sql_tracker CHANGED
@@ -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)
data/lib/sql_tracker.rb CHANGED
@@ -8,11 +8,21 @@ 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.new
19
+ config.enabled = true
20
+ handler = SqlTracker::Handler.new(config)
21
+ handler.subscribe
22
+ yield
23
+ handler.unsubscribe
24
+ handler.data
25
+ end
16
26
  end
17
27
 
18
28
  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
 
@@ -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.1'.freeze
2
+ VERSION = '1.3.2'.freeze
3
3
  end
data/sql_tracker.gemspec CHANGED
@@ -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.1
4
+ version: 1.3.2
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: 2021-03-05 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
@@ -74,8 +74,9 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".github/workflows/ci.yml"
78
+ - ".github/workflows/publish.yml"
77
79
  - ".gitignore"
78
- - ".travis.yml"
79
80
  - Gemfile
80
81
  - LICENSE.txt
81
82
  - README.md
@@ -88,6 +89,7 @@ files:
88
89
  - lib/sql_tracker/handler.rb
89
90
  - lib/sql_tracker/railtie.rb
90
91
  - lib/sql_tracker/report.rb
92
+ - lib/sql_tracker/terminal.rb
91
93
  - lib/sql_tracker/version.rb
92
94
  - sql_tracker.gemspec
93
95
  homepage: http://www.github.com/steventen/sql_tracker
@@ -109,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
111
  - !ruby/object:Gem::Version
110
112
  version: '0'
111
113
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 2.5.1
114
+ rubygems_version: 3.0.3
114
115
  signing_key:
115
116
  specification_version: 4
116
117
  summary: Rails SQL Query Tracker
data/.travis.yml DELETED
@@ -1,8 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.3.0
6
- before_install:
7
- - gem install bundler
8
- - gem update bundler