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 +5 -5
- data/.travis.yml +4 -1
- data/README.md +40 -0
- data/bin/sql_tracker +26 -1
- data/lib/sql_tracker.rb +10 -1
- data/lib/sql_tracker/handler.rb +22 -4
- data/lib/sql_tracker/report.rb +21 -30
- data/lib/sql_tracker/terminal.rb +28 -0
- data/lib/sql_tracker/version.rb +1 -1
- data/sql_tracker.gemspec +2 -2
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 14ea1ca1cb59b84b95fd69a46d6e3e9e48a5b518147b5728e8c63fd0e1110a65
|
4
|
+
data.tar.gz: 9eb139f7f5537f92e8f4d0b37ad15994f1dfe5f51d39b9d07b2f7e26e4915354
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2c485ee67296ca11b5f0e55ee7794faa4397a621d9b2e12df34aa9643a4f25e43d4341a89037d176b9e59f28f5780fd20812bcf1c68a1a5d50c09e9fb338504
|
7
|
+
data.tar.gz: 383fdbde395e7a0fbde65b247e38736d5a1e3de3858973c26bc3320e50cc92057275d10c9fa4ef249f5afd895d9f4d9815846aa092a4b836d4d5ea68af091ec7
|
data/.travis.yml
CHANGED
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,20 @@ module SqlTracker
|
|
8
8
|
|
9
9
|
config = SqlTracker::Config.apply_defaults
|
10
10
|
handler = SqlTracker::Handler.new(config)
|
11
|
-
|
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
|
data/lib/sql_tracker/handler.rb
CHANGED
@@ -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)('[^']+'
|
65
|
+
query.gsub!(/(\s(=|>|<|>=|<=|<>|!=)\s)('[^']+'|[\$\+\-\w\.]+)/, '\1xxx')
|
51
66
|
query.gsub!(/(\sIN\s)\([^\(\)]+\)/i, '\1(xxx)')
|
52
|
-
query.gsub!(/(\sBETWEEN\s)('[^']+'
|
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
|
data/lib/sql_tracker/report.rb
CHANGED
@@ -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(
|
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
|
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
|
data/lib/sql_tracker/version.rb
CHANGED
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'
|
23
|
-
spec.add_development_dependency 'rake'
|
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
|
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:
|
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: '
|
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: '
|
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: '
|
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: '
|
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
|
-
|
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
|