shiba 0.1.1 → 0.1.2

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
2
  SHA1:
3
- metadata.gz: 0eff935dfd9607d514fc16e2615f5af7d415602d
4
- data.tar.gz: c7aa551bccf103087df9848638462d8b3b2d0c30
3
+ metadata.gz: f6436b087e61bcce7488d2fbb04003c3fd32fb36
4
+ data.tar.gz: 348c1fa32744f1879e489fa5530294a4ea70fe4e
5
5
  SHA512:
6
- metadata.gz: 6f0d6576e905ced52788b0ea247f7f0d51c5ea997f1064114028ab4cc3f1811b6adf8e75f9f7b97344b58eb2d579953bfab77a32fc6fcae33c755cb8a8832a2a
7
- data.tar.gz: a16096e13a86dc7a8b977c21334590f46701bc1ef9e77c06e8a23f334fa36bcad78a52f2716892881c852569232c9fbb79341c93ea3b311867a4da6f7eb8bf6a
6
+ metadata.gz: e84dcdbd7cb06d3a2dfa7fa87e9c0bf40e8a3059a8396284c8503599d3d94cbb4c45681b081e8074abc700a7285f64239777cd30c5d1f5eed17f6302756ac98a
7
+ data.tar.gz: be47f54c87cad50e7260d64468b2c0489f55163a57db59173c3302da9e82a9f62380b239a65eecab6d6db849b044b38d1c0821fdb66457d8497d73f29a6b34f0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiba (0.1.1)
4
+ shiba (0.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -11,17 +11,20 @@ You can run shiba either as a gem in your test suite, or as a standalone utility
11
11
 
12
12
  ### Gem
13
13
 
14
- Add this line to your application's Gemfile:
14
+ Install
15
15
 
16
16
  ```ruby
17
- group :test do
18
- gem 'shiba'
19
- end
17
+ bundle add shiba --group "development, test"
18
+ ```
20
19
 
21
- bundle
20
+ Run shiba
22
21
 
22
+ ```ruby
23
23
  # Run some some code using shiba to generate a SQL report
24
24
  bundle exec shiba analyze rake test:functional
25
+
26
+ # Or run a single test
27
+ bundle exec shiba analyze rails test test/controllers/users_controller_test.rb
25
28
  ```
26
29
 
27
30
  ### Standalone:
@@ -50,4 +53,4 @@ local:$ bin/analyze.rb -h 127.0.0.1 -d TESTDB -u MYSQLUSER -p MYSQLPASS -s shiba
50
53
 
51
54
  local:$ jq -C -s 'sort_by(.cost) | reverse' results.json | less -R
52
55
 
53
- ```
56
+ ```
data/bin/analyze CHANGED
@@ -4,55 +4,14 @@
4
4
 
5
5
  require 'bundler/setup'
6
6
  require 'optionparser'
7
+ require 'shiba'
7
8
  require 'shiba/configure'
8
9
  require 'shiba/output'
9
10
 
10
11
  options = {}
11
- parser = OptionParser.new do |opts|
12
- opts.banner = "analyze <command>. Creates report of SQL from the running process."
13
-
14
- opts.on("-h","--host HOST", "sql host") do |h|
15
- options["host"] = h
16
- end
17
-
18
- opts.on("-d","--database DATABASE", "sql database") do |d|
19
- options["database"] = d
20
- end
21
-
22
- opts.on("-u","--user USER", "sql user") do |u|
23
- options["user"] = u
24
- end
25
-
26
- opts.on("-p","--password PASSWORD", "sql password") do |p|
27
- options["password"] = p
28
- end
29
-
30
- opts.on("-i","--index INDEX", "index of query to inspect") do |i|
31
- options["index"] = i.to_i
32
- end
33
-
34
- opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
35
- options["limit"] = l.to_i
36
- end
37
-
38
- opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
39
- options["stats"] = f
40
- end
41
-
42
- opts.on("-f", "--file FILE", "location of file containing queries") do |f|
43
- options["file"] = f
44
- end
45
-
46
- opts.on("-o", "--output FILE", "write to file instead of stdout") do |f|
47
- options["output"] = f
48
- end
49
-
50
- opts.on("-t", "--test", "analyze queries at --file instead of analyzing a process") do |f|
51
- options["test"] = true
52
- end
53
-
54
- end
55
12
 
13
+ parser = Shiba::Configure.make_options_parser(options)
14
+ parser.banner = "analyze <command>. Creates report of SQL from the running process."
56
15
  parser.parse!
57
16
 
58
17
  if options["test"] && !options["file"]
@@ -63,24 +22,26 @@ end
63
22
 
64
23
  # Automagic configuration goes here
65
24
  if !options["database"]
66
- config = Shiba::Configure.activerecord_configuration
67
-
68
- if tc = config && config['test']
69
- $stderr.puts "Reading configuration from '#{`pwd`.chomp}/config/database.yml'[:test]."
70
- options['host'] ||= tc['host']
71
- options['database'] ||= tc['database']
72
- options['user'] ||= tc['username']
73
- options['password'] ||= tc['password']
74
- end
25
+ config = Shiba::Configure.activerecord_configuration
26
+
27
+ if tc = config && config['test']
28
+ $stderr.puts "Reading configuration from '#{`pwd`.chomp}/config/database.yml'[:test]."
29
+ options['host'] ||= tc['host']
30
+ options['database'] ||= tc['database']
31
+ options['username'] ||= tc['username']
32
+ options['password'] ||= tc['password']
75
33
  end
34
+ end
76
35
 
77
- if !options["file"]
78
- options["file"] = `mktemp /tmp/shiba-query.log-#{Time.now.to_i}`.chomp
79
- end
36
+ Shiba.configure(options)
80
37
 
81
- if !options["output"]
82
- options["output"] = `mktemp /tmp/shiba-explain.log-#{Time.now.to_i}`.chomp
83
- end
38
+ if !options["file"]
39
+ options["file"] = `mktemp /tmp/shiba-query.log-#{Time.now.to_i}`.chomp
40
+ end
41
+
42
+ if !options["explain"]
43
+ options["explain"] = `mktemp /tmp/shiba-explain.log-#{Time.now.to_i}`.chomp
44
+ end
84
45
 
85
46
  # Log process queries
86
47
  if !options.delete("test")
@@ -105,7 +66,7 @@ if !options.delete("test")
105
66
  end
106
67
 
107
68
  # Explain
108
- $stderr.puts "Analyzing SQL to '#{options["output"]}'..."
69
+ $stderr.puts "Analyzing SQL to '#{options["explain"]}'..."
109
70
  path = "#{File.dirname(__FILE__)}/explain"
110
71
  args = options.select { |_,v| !v.nil? }.map { |k,v| [ "--#{k}", v ] }.flatten
111
72
 
data/bin/explain CHANGED
@@ -10,50 +10,9 @@ require 'shiba/output'
10
10
  require 'optionparser'
11
11
 
12
12
  options = {}
13
- parser = OptionParser.new do |opts|
14
- opts.banner = "Usage: explain -h HOST -d DB -u USER -p PASS [-f QUERY_FILE] [-s STATS_FILE] "
15
-
16
- opts.on("-h","--host HOST", "sql host") do |h|
17
- options["host"] = h
18
- end
19
-
20
- opts.on("-d","--database DATABASE", "sql database") do |d|
21
- options["database"] = d
22
- end
23
-
24
- opts.on("-u","--user USER", "sql user") do |u|
25
- options["username"] = u
26
- end
27
-
28
- opts.on("-p","--password PASSWORD", "sql password") do |p|
29
- options["password"] = p
30
- end
31
-
32
- opts.on("-i","--index INDEX", "index of query to inspect") do |i|
33
- options["index"] = i.to_i
34
- end
35
-
36
- opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
37
- options["limit"] = l.to_i
38
- end
39
-
40
- opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
41
- options["stats_file"] = f
42
- end
43
-
44
- opts.on("-f", "--file FILE", "location of file containing queries") do |f|
45
- options["file"] = f
46
- end
47
-
48
- opts.on("-o", "--output FILE", "write to file instead of stdout") do |f|
49
- options["output"] = f
50
- end
51
-
52
- opts.on("--debug") do
53
- options["debug"] = true
54
- end
55
- end
56
13
 
14
+ parser = Shiba::Configure.make_options_parser(options)
15
+ parser.banner = "Run a list of queries through shiba's analyzer."
57
16
  parser.parse!
58
17
 
59
18
  ["database", "username"].each do |opt|
@@ -67,12 +26,12 @@ end
67
26
  file = options.delete("file")
68
27
  file = File.open(file, "r") if file
69
28
 
70
- output = options.delete("output")
29
+ output = options.delete("explain")
71
30
  output = File.open(output, 'w') if output
72
31
 
73
32
  Shiba.configure(options)
74
33
 
75
- schema_stats_fname = options["stats_file"]
34
+ schema_stats_fname = options["stats"]
76
35
 
77
36
  if schema_stats_fname && !File.exist?(schema_stats_fname)
78
37
  $stderr.puts "No such file: #{schema_stats_fname}"
@@ -102,4 +61,4 @@ file = $stdin if file.nil?
102
61
  output = $stdout if output.nil?
103
62
 
104
63
  queries = Shiba::Analyzer.analyze(file, output, schema_stats, options)
105
- Shiba::Output.new(queries).make_web!
64
+ Shiba::Output.new(queries, { 'output' => options['output'] }).make_web!
@@ -45,7 +45,15 @@ end
45
45
 
46
46
  class Watcher < Redmine; end
47
47
  class Relation < Redmine; end
48
- class User < Redmine; end
48
+ class User < Redmine
49
+ def enhance(hash, owner)
50
+ if rand < 0.01
51
+ hash['type'] = 'admin'
52
+ else
53
+ hash['type'] = 'user'
54
+ end
55
+ end
56
+ end
49
57
 
50
58
  class Sampler
51
59
  def initialize(interested_in)
@@ -105,9 +113,17 @@ class Sampler
105
113
  def sample_model(model, params = {})
106
114
  # go exponentially up from 1.
107
115
  last = find_max(model, 0, 1, params)
116
+
108
117
  # go up from the last hit value.
109
- if last
110
- find_max(model, last.id, 1, params)
118
+ last = find_max(model, last.id, 1, params)
119
+
120
+ max_id = last.id
121
+ num_to_sample = (max_id * 0.05).to_i
122
+
123
+ num_to_sample.times do
124
+ instance = get_instance(model, rand(max_id), params)
125
+ extract_hash(instance) if instance
126
+ sleep(1)
111
127
  end
112
128
  end
113
129
 
data/lib/shiba.rb CHANGED
@@ -1,11 +1,22 @@
1
1
  require "shiba/version"
2
+ require "shiba/configure"
2
3
  require "mysql2"
3
4
 
4
5
  module Shiba
5
6
  class Error < StandardError; end
6
7
 
7
- def self.configure(connection_hash)
8
- @connection_hash = connection_hash
8
+ def self.configure(options)
9
+ @connection_hash = options.select { |k, v| ['username', 'database', 'host', 'password'].include?(k) }
10
+ @main_config = Configure.read_config_file(options['config'], "config/shiba.yml")
11
+ @index_config = Configure.read_config_file(options['index'], "config/shiba_index.yml")
12
+ end
13
+
14
+ def self.config
15
+ @main_config
16
+ end
17
+
18
+ def self.index_config
19
+ @index_config
9
20
  end
10
21
 
11
22
  def self.connection
@@ -18,4 +29,4 @@ module Shiba
18
29
  end
19
30
 
20
31
  # This goes at the end so that Shiba.root is defined.
21
- require "shiba/railtie" if defined?(Rails)
32
+ require "shiba/railtie" if defined?(Rails)
@@ -1,31 +1,103 @@
1
1
  require 'pathname'
2
-
2
+ require 'pp'
3
3
  module Shiba
4
- module Configure
5
-
6
- # avoiding Rails dependency on the cli tools for now.
7
- # yanked from https://github.com/rails/rails/blob/v5.0.5/railties/lib/rails/application/configuration.rb
8
- def self.activerecord_configuration
9
- yaml = Pathname.new("config/database.yml")
10
-
11
- config = if yaml && yaml.exist?
12
- require "yaml"
13
- require "erb"
14
- YAML.load(ERB.new(yaml.read).result) || {}
15
- elsif ENV['DATABASE_URL']
16
- # Value from ENV['DATABASE_URL'] is set to default database connection
17
- # by Active Record.
18
- {}
19
- end
20
-
21
- config
22
- rescue Psych::SyntaxError => e
23
- raise "YAML syntax error occurred while parsing #{yaml.to_s}. " \
24
- "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
25
- "Error: #{e.message}"
26
- rescue => e
27
- raise e, "Cannot load `#{path}`:\n#{e.message}", e.backtrace
4
+ module Configure
5
+
6
+ # avoiding Rails dependency on the cli tools for now.
7
+ # yanked from https://github.com/rails/rails/blob/v5.0.5/railties/lib/rails/application/configuration.rb
8
+ def self.activerecord_configuration
9
+ yaml = Pathname.new("config/database.yml")
10
+
11
+ config = if yaml && yaml.exist?
12
+ require "yaml"
13
+ require "erb"
14
+ YAML.load(ERB.new(yaml.read).result) || {}
15
+ elsif ENV['DATABASE_URL']
16
+ # Value from ENV['DATABASE_URL'] is set to default database connection
17
+ # by Active Record.
18
+ {}
19
+ end
20
+
21
+ config
22
+ rescue Psych::SyntaxError => e
23
+ raise "YAML syntax error occurred while parsing #{yaml.to_s}. " \
24
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
25
+ "Error: #{e.message}"
26
+ rescue => e
27
+ raise e, "Cannot load `#{path}`:\n#{e.message}", e.backtrace
28
+ end
29
+
30
+ def self.read_config_file(option_file, default)
31
+ file_to_read = nil
32
+ if option_file
33
+ if !File.exist?(option_file)
34
+ $stderr.puts "no such file: #{option_file}"
35
+ exit 1
36
+ end
37
+ file_to_read = option_file
38
+ elsif File.exist?(default)
39
+ file_to_read = default
40
+ end
41
+
42
+ if file_to_read
43
+ YAML.load_file(file_to_read)
44
+ else
45
+ {}
46
+ end
47
+ end
48
+
49
+ def self.make_options_parser(options)
50
+ parser = OptionParser.new do |opts|
51
+ # note that the key to the hash needs to stay the same as the
52
+ # option name since we re-pass them
53
+ opts.on("-h","--host HOST", "sql host") do |h|
54
+ options["host"] = h
55
+ end
56
+
57
+ opts.on("-d","--database DATABASE", "sql database") do |d|
58
+ options["database"] = d
59
+ end
60
+
61
+ opts.on("-u","--username USER", "sql user") do |u|
62
+ options["username"] = u
63
+ end
64
+
65
+ opts.on("-p","--password PASSWORD", "sql password") do |p|
66
+ options["password"] = p
28
67
  end
29
68
 
69
+ opts.on("-c","--config FILE", "location of shiba.yml") do |f|
70
+ options["config"] = f
71
+ end
72
+
73
+ opts.on("-i","--index INDEX", "location of shiba_index.yml") do |i|
74
+ options["index"] = i.to_i
75
+ end
76
+
77
+ opts.on("-l", "--limit NUM", "stop after processing NUM queries") do |l|
78
+ options["limit"] = l.to_i
79
+ end
80
+
81
+ opts.on("-s","--stats FILES", "location of index statistics tsv file") do |f|
82
+ options["stats"] = f
83
+ end
84
+
85
+ opts.on("-f", "--file FILE", "location of file containing queries") do |f|
86
+ options["file"] = f
87
+ end
88
+
89
+ opts.on("-e", "--explain FILE", "write explain JSON to file. default: stdout") do |f|
90
+ options["explain"] = f
91
+ end
92
+
93
+ opts.on("-o", "--output PATH", "path to put generated report in. default: /tmp") do |p|
94
+ options["output"] = p
95
+ end
96
+
97
+ opts.on("-t", "--test", "analyze queries at --file instead of analyzing a process") do |f|
98
+ options["test"] = true
99
+ end
100
+ end
30
101
  end
31
- end
102
+ end
103
+ end
data/lib/shiba/explain.rb CHANGED
@@ -3,10 +3,10 @@ require 'shiba/index'
3
3
 
4
4
  module Shiba
5
5
  class Explain
6
- def initialize(sql, stats, options = {})
6
+ def initialize(sql, stats, backtrace, options = {})
7
7
  @sql = sql
8
+ @backtrace = backtrace
8
9
 
9
- @sql, _, @backtrace = @sql.partition(" /*shiba")
10
10
  if options[:force_key]
11
11
  @sql = @sql.sub(/(FROM\s*\S+)/i, '\1' + " FORCE INDEX(`#{options[:force_key]}`)")
12
12
  end
@@ -20,8 +20,6 @@ module Shiba
20
20
  end
21
21
 
22
22
  def as_json
23
- @backtrace.chomp!("*/")
24
-
25
23
  {
26
24
  sql: @sql,
27
25
  table: get_table,
@@ -30,7 +28,7 @@ module Shiba
30
28
  cost: @cost,
31
29
  used_key_parts: first['used_key_parts'],
32
30
  possible_keys: first['possible_keys'],
33
- backtrace: JSON.parse(@backtrace)
31
+ backtrace: @backtrace
34
32
  }
35
33
  end
36
34
 
@@ -217,7 +215,7 @@ module Shiba
217
215
  end
218
216
 
219
217
  def estimate_row_count_with_key(key)
220
- Explain.new(@sql, @stats, force_key: key).estimate_row_count
218
+ Explain.new(@sql, @stats, @backtrace, force_key: key).estimate_row_count
221
219
  rescue Mysql2::Error => e
222
220
  if /Key .+? doesn't exist in table/ =~ e.message
223
221
  return nil
@@ -226,7 +224,32 @@ module Shiba
226
224
  raise e
227
225
  end
228
226
 
227
+ def ignore?
228
+ !!ignore_line_and_backtrace_line
229
+ end
230
+
231
+ def ignore_line_and_backtrace_line
232
+ ignore_files = Shiba.config['ignore']
233
+ if ignore_files
234
+ ignore_files.each do |i|
235
+ file, method = i.split('#')
236
+ @backtrace.each do |b|
237
+ next unless b.include?(file)
238
+ next if method && !b.include?(method)
239
+ return [i, b]
240
+ end
241
+ end
242
+ end
243
+ nil
244
+ end
245
+
229
246
  def run_checks!
247
+ if ignore?
248
+ @cost = 0
249
+ messages << "ignored"
250
+ return
251
+ end
252
+
230
253
  @cost = estimate_row_count
231
254
  end
232
255
  end
data/lib/shiba/output.rb CHANGED
@@ -5,28 +5,26 @@ require 'erb'
5
5
 
6
6
  module Shiba
7
7
  class Output
8
-
9
8
  OUTPUT_PATH = "/tmp/shiba_results"
10
- JS_PATH = OUTPUT_PATH + "/js"
11
9
 
12
10
  WEB_PATH = File.dirname(__FILE__) + "/../../web"
13
11
  def self.tags
14
12
  @tags ||= YAML.load_file(File.dirname(__FILE__) + "/output/tags.yaml")
15
13
  end
16
14
 
17
- def self.from_file(fname)
18
- queries = []
19
- File.open(fname, "r") do |f|
20
- while line = f.gets
21
- queries << JSON.parse(line)
22
- end
23
- end
24
- new(queries)
15
+ def initialize(queries, options = {})
16
+ @queries = queries
17
+ @options = options
25
18
  end
26
19
 
20
+ def output_path
21
+ path ||= File.join(@options['output'], "shiba_results") if @options['output']
22
+ path ||= Dir.pwd + "/log/shiba_results" if File.exist?(Dir.pwd + "/log")
23
+ path ||= OUTPUT_PATH
24
+ end
27
25
 
28
- def initialize(queries)
29
- @queries = queries
26
+ def js_path
27
+ File.join(output_path, "js")
30
28
  end
31
29
 
32
30
  def remote_url
@@ -40,11 +38,11 @@ module Shiba
40
38
  end
41
39
 
42
40
  def make_web!
43
- FileUtils.mkdir_p(JS_PATH)
41
+ FileUtils.mkdir_p(js_path)
44
42
 
45
43
  js = Dir.glob(WEB_PATH + "/dist/*.js").map { |f| File.basename(f) }
46
44
  js.each do |f|
47
- system("cp #{WEB_PATH}/dist/#{f} #{JS_PATH}")
45
+ system("cp #{WEB_PATH}/dist/#{f} #{js_path}")
48
46
  end
49
47
 
50
48
  data = {
@@ -54,14 +52,14 @@ module Shiba
54
52
  url: remote_url
55
53
  }
56
54
 
57
- system("cp #{WEB_PATH}/*.css #{OUTPUT_PATH}")
55
+ system("cp #{WEB_PATH}/*.css #{output_path}")
58
56
 
59
57
  erb = ERB.new(File.read(WEB_PATH + "/../web/results.html.erb"))
60
- File.open(OUTPUT_PATH + "/results.html", "w+") do |f|
58
+ File.open(output_path + "/results.html", "w+") do |f|
61
59
  f.write(erb.result(binding))
62
60
  end
63
61
 
64
- puts "done, results are in " + "/tmp/shiba_results/results.html"
62
+ puts "done, results are in " + File.join(output_path, "results.html")
65
63
  end
66
64
  end
67
65
  end
@@ -38,7 +38,12 @@ access_type_tablescan:
38
38
  fuzzed_data:
39
39
  title: Guessed Table Size
40
40
  description: |
41
- There wasn't enough data in your local environment to adequately measure this query, so we
42
- pretended this table had a bunch of data in it. To improve results, please dump your
43
- production index statistics into Shiba.
41
+ We're not sure how much data this table will hold in the future, so we've pretended
42
+ there's 6000 rows in it. This can lead to a lot of false positives. To
43
+ improve results, please give shiba your index statistics.
44
+ level: info
45
+ ignored:
46
+ title: Ignored Query
47
+ description: |
48
+ This query matched an "ignore" rule in shiba.yml, so we skipped any further analysis on it.
44
49
  level: info
data/lib/shiba/query.rb CHANGED
@@ -15,7 +15,15 @@ module Shiba
15
15
  end
16
16
 
17
17
  def initialize(sql, stats)
18
- @sql = sql
18
+ @sql, _, @backtrace = sql.partition(" /*shiba")
19
+
20
+ if @backtrace.length > 0
21
+ @backtrace.chomp!("*/")
22
+ @backtrace = JSON.parse(@backtrace)
23
+ else
24
+ @backtrace = []
25
+ end
26
+
19
27
  @stats = stats
20
28
  @@index += 1
21
29
  @index = @@index
@@ -23,12 +31,17 @@ module Shiba
23
31
 
24
32
  attr_reader :sql, :index
25
33
 
34
+
26
35
  def fingerprint
27
36
  @fingerprint ||= self.class.get_fingerprint(@sql)
28
37
  end
29
38
 
30
39
  def explain
31
- Explain.new(@sql, @stats)
40
+ Explain.new(@sql, @stats, @backtrace)
41
+ end
42
+
43
+ def backtrace
44
+ @backtrace
32
45
  end
33
46
  end
34
47
  end
@@ -4,13 +4,14 @@ require 'rails'
4
4
 
5
5
  module Shiba
6
6
  class QueryWatcher
7
- FINGERPRINTS = {}
8
7
  IGNORE = /\.rvm|gem|vendor\/|rbenv|seed|db|shiba|test|spec/
9
8
 
10
9
  def self.watch(file)
11
10
  new(file).watch
12
11
  end
13
12
 
13
+ attr_reader :queries
14
+
14
15
  def initialize(file)
15
16
  @file = file
16
17
  # fixme mem growth on this is kinda nasty
@@ -23,11 +24,13 @@ module Shiba
23
24
  sql = payload[:sql]
24
25
 
25
26
  if sql.start_with?("SELECT")
26
- lines = app_backtrace
27
- if lines && !@queries[sql]
28
- @file.puts("#{sql} /*shiba#{lines}*/" )
27
+ fingerprint = Query.get_fingerprint(sql)
28
+ if !@queries[fingerprint]
29
+ if lines = app_backtrace
30
+ @file.puts("#{sql} /*shiba#{lines}*/")
31
+ end
29
32
  end
30
- @queries[sql] = true
33
+ @queries[fingerprint] = true
31
34
  end
32
35
  end
33
36
  end
data/lib/shiba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Shiba
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
data/web/results.html.erb CHANGED
@@ -94,11 +94,13 @@
94
94
  <query v-for="query in queries" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
95
95
  </div>
96
96
  <div class="row">
97
- <div class="col-12">We also found {{ queriesLow.length }} queries that look fine.</div>
97
+ <div class="col-12">We also found <a href="#" v-on:click.prevent="lowExpanded = !lowExpanded">{{ queriesLow.length }} queries</a> that look fine.</div>
98
98
  </div>
99
- <div class="queries">
99
+ <a name="lowExapnded"></a>
100
+ <div class="queries" v-if="lowExpanded">
100
101
  <query v-for="query in queriesLow" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
101
102
  </div>
103
+ <div style="height:50px"></div>
102
104
  </div>
103
105
  </div>
104
106
 
@@ -188,7 +190,8 @@
188
190
  data: {
189
191
  queries: queriesByTable,
190
192
  queriesLow: queriesByTableLow,
191
- tags: data.tags
193
+ tags: data.tags,
194
+ lowExpanded: false
192
195
  },
193
196
  methods: { }
194
197
  });
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Osheroff