shiba 0.9.1 → 0.9.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 +4 -4
- data/Gemfile.lock +1 -1
- data/bin/explain +1 -1
- data/lib/shiba/activerecord_integration.rb +19 -7
- data/lib/shiba/console.rb +1 -3
- data/lib/shiba/explain.rb +7 -0
- data/lib/shiba/index_stats.rb +10 -6
- data/lib/shiba/output/tags.yaml +8 -8
- data/lib/shiba/review/cli.rb +1 -0
- data/lib/shiba/review/comment_renderer.rb +3 -1
- data/lib/shiba/version.rb +1 -1
- data/web/src/App.vue +3 -1
- data/web/src/components/Message.js +1 -1
- data/web/src/components/Query.vue +8 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12302c0e4fc56d8c8fc0d14dc6e10a5ae84aeaac2c328937b78b9e14b8122faa
|
4
|
+
data.tar.gz: d0ae437286d3c4975630c5dab8dc5aa109eb38f3ed0f6e606cded20a1201b09f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92d5a42d2b68381a8f07025dec47e289d75c642a2d53823658966c8032e0e1f2a826f6285b1834747dda798fa25968f27f05c00029f60345303dddc9e2b20c2a
|
7
|
+
data.tar.gz: f4911585fe1a38695b587e11b908265d480523b6e2fcb8966780d80f34ab9612c1a798f612f0a3262f77349d5686a27f4c3da3886824753b36021354c33b267e
|
data/Gemfile.lock
CHANGED
data/bin/explain
CHANGED
@@ -42,7 +42,7 @@ file = $stdin if file.nil?
|
|
42
42
|
|
43
43
|
if options["verbose"]
|
44
44
|
$stderr.puts "Reading queries from '#{file.inspect}'..."
|
45
|
-
$stderr.puts "Analyzing SQL to '#{json.inspect}'..."
|
45
|
+
$stderr.puts "Analyzing SQL to '#{options["json"].inspect}'..."
|
46
46
|
end
|
47
47
|
|
48
48
|
table_stats = Shiba::TableStats.new(Shiba.index_config, Shiba.connection, {})
|
@@ -39,7 +39,7 @@ module Shiba
|
|
39
39
|
{
|
40
40
|
'host' => c[:host],
|
41
41
|
'database' => c[:database],
|
42
|
-
'
|
42
|
+
'username' => c[:username],
|
43
43
|
'password' => c[:password],
|
44
44
|
'port' => c[:port],
|
45
45
|
'server' => c[:server],
|
@@ -72,16 +72,28 @@ module Shiba
|
|
72
72
|
def self.when_done
|
73
73
|
return false if @done_hook
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
# define both minitest and rspec hooks -- it can be
|
76
|
+
# unclear in some envs which one is active. maybe even both could run in one process? not sure.
|
77
|
+
@shiba_done = false
|
78
|
+
if defined?(Minitest.after_run)
|
79
|
+
MiniTest.after_run do
|
80
|
+
yield unless @shiba_done
|
81
|
+
@shiba_done = true
|
82
|
+
end
|
78
83
|
@done_hook = :minitest
|
79
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
if defined?(RSpec.configure)
|
80
87
|
RSpec.configure do |config|
|
81
|
-
config.after(:suite)
|
88
|
+
config.after(:suite) do
|
89
|
+
yield unless @shiba_done
|
90
|
+
@shiba_done = true
|
91
|
+
end
|
82
92
|
end
|
83
93
|
@done_hook = :rspec
|
84
|
-
|
94
|
+
end
|
95
|
+
|
96
|
+
if !@done_hook
|
85
97
|
$stderr.puts "Warning: shiba could not find Minitest or RSpec."
|
86
98
|
$stderr.puts "If tests are running with one of these libraries, ensure shiba is required after them."
|
87
99
|
at_exit { yield }
|
data/lib/shiba/console.rb
CHANGED
@@ -7,9 +7,7 @@ require 'shiba/reviewer'
|
|
7
7
|
|
8
8
|
module Shiba
|
9
9
|
# Provides a 'shiba' command to analyze queries from the console.
|
10
|
-
# If required in IRB or Pry, the shiba command will
|
11
|
-
# as it's injected into those consoles at the bottom of this file.
|
12
|
-
#
|
10
|
+
# If required in IRB or Pry, the 'shiba' command will become available.
|
13
11
|
# Example:
|
14
12
|
# require 'shiba/console'
|
15
13
|
#
|
data/lib/shiba/explain.rb
CHANGED
@@ -48,6 +48,7 @@ module Shiba
|
|
48
48
|
table: @query.from_table,
|
49
49
|
md5: @query.md5,
|
50
50
|
messages: @result.messages,
|
51
|
+
global: global,
|
51
52
|
cost: @result.cost,
|
52
53
|
severity: severity,
|
53
54
|
raw_explain: humanized_explain,
|
@@ -55,6 +56,12 @@ module Shiba
|
|
55
56
|
}
|
56
57
|
end
|
57
58
|
|
59
|
+
def global
|
60
|
+
{
|
61
|
+
server: Shiba.connection.mysql? ? 'mysql' : 'postgres'
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
58
65
|
def messages
|
59
66
|
@result.messages
|
60
67
|
end
|
data/lib/shiba/index_stats.rb
CHANGED
@@ -15,14 +15,14 @@ module Shiba
|
|
15
15
|
|
16
16
|
Table = Struct.new(:name, :count, :indexes) do
|
17
17
|
def encode_with(coder)
|
18
|
-
coder.map = self.to_h.stringify_keys
|
19
|
-
coder.map.delete('name')
|
20
|
-
|
21
18
|
if self.count.nil?
|
22
19
|
#uuuugly. No unique keys. we'll take our best guess.
|
23
20
|
self.count = indexes.map { |i, parts| parts.columns.map { |v| v.raw_cardinality } }.flatten.max
|
24
21
|
end
|
25
22
|
|
23
|
+
coder.map = self.to_h.stringify_keys
|
24
|
+
coder.map.delete('name')
|
25
|
+
|
26
26
|
coder.map['column_sizes'] = column_sizes
|
27
27
|
coder.tag = nil
|
28
28
|
end
|
@@ -110,7 +110,7 @@ module Shiba
|
|
110
110
|
ratio_threshold = 0.1
|
111
111
|
elsif count <= 1_000_000
|
112
112
|
ratio_threshold = 0.01
|
113
|
-
|
113
|
+
else
|
114
114
|
ratio_threshold = 0.001
|
115
115
|
end
|
116
116
|
|
@@ -165,9 +165,13 @@ module Shiba
|
|
165
165
|
end
|
166
166
|
|
167
167
|
def set_column_size(table_name, column, size)
|
168
|
-
|
169
|
-
|
168
|
+
if !@tables[table_name]
|
169
|
+
# we get here when a table has no indices. Should really do something smarter long term,
|
170
|
+
# as we'll have zero-counts, stuff like that.
|
171
|
+
build_table(table_name)
|
172
|
+
end
|
170
173
|
|
174
|
+
table = @tables[table_name]
|
171
175
|
table.column_sizes[column] = size
|
172
176
|
end
|
173
177
|
|
data/lib/shiba/output/tags.yaml
CHANGED
@@ -15,12 +15,12 @@ possible_key_check:
|
|
15
15
|
key possible. Sometimes "possible_keys" will be inaccurate and no keys were possible.
|
16
16
|
access_type_const:
|
17
17
|
title: One row
|
18
|
-
summary:
|
18
|
+
summary: "{{ server }} reads one row from <b>{{table}}</b>."
|
19
19
|
description: |
|
20
20
|
This query selects at *most* one row, which is about as good as things get.
|
21
21
|
access_type_ref:
|
22
22
|
title: Index Scan
|
23
|
-
summary:
|
23
|
+
summary: "{{ server }} reads {{ formatted_cost }} rows from <b>{{ table }}</b> via <i>{{ index }}</i> ({{ key_parts }})."
|
24
24
|
description: |
|
25
25
|
This query uses an index to find rows that match a single value. Often this
|
26
26
|
has very good performance, but it depends on how many rows match that value.
|
@@ -31,8 +31,8 @@ join_type_ref:
|
|
31
31
|
title: Indexed Join
|
32
32
|
summary: <b>{{ table }}</b> is joined to <b>{{ join_to }}</b> via <i>{{ index }}</i>, reading {{ formatted_cost }} rows per joined item.
|
33
33
|
access_type_range:
|
34
|
-
title:
|
35
|
-
summary:
|
34
|
+
title: Range Scan
|
35
|
+
summary: "{{ server }} reads more than {{ formatted_cost }} rows from {{ table }} via <i>{{ index }}</i> ({{ key_parts }})"
|
36
36
|
description: |
|
37
37
|
This query uses an index to find rows that match a range of values, for instance
|
38
38
|
`WHERE indexed_value in (1,2,5,6)` or `WHERE indexed_value >= 5 AND indexed_value <= 15`.
|
@@ -40,10 +40,10 @@ access_type_range:
|
|
40
40
|
upped the formatted_cost of this query.
|
41
41
|
access_type_index:
|
42
42
|
title: Index Scan
|
43
|
-
summary:
|
43
|
+
summary: "{{ server }} reads {{ formatted_cost }} of the rows in <b>{{ table }}</b> via <i>{{ index }}</i>"
|
44
44
|
access_type_tablescan:
|
45
45
|
title: Table Scan
|
46
|
-
summary:
|
46
|
+
summary: "{{ server }} reads {{ formatted_cost }} of the rows in <b>{{ table }}</b>, skipping any indexes."
|
47
47
|
description: |
|
48
48
|
This query doesn't use any indexes to find data, meaning this query will need to evaluate
|
49
49
|
every single row in the table. This is about the worst of all possible worlds.
|
@@ -53,7 +53,7 @@ access_type_tablescan:
|
|
53
53
|
a world of pain.
|
54
54
|
limited_scan:
|
55
55
|
title: Limited Scan
|
56
|
-
summary:
|
56
|
+
summary: "{{ server }} reads {{ formatted_cost }} rows from {{ table }}."
|
57
57
|
description: |
|
58
58
|
This query doesn't use any indexes to find data, but since it doesn't care about
|
59
59
|
ordering and it doesn't have any conditions, it only ever reads {{ formatted_cost }} rows.
|
@@ -71,4 +71,4 @@ index_walk:
|
|
71
71
|
level: success
|
72
72
|
retsize:
|
73
73
|
title: Results
|
74
|
-
summary:
|
74
|
+
summary: "{{ server }} returns {{ formatted_result }} to the client."
|
data/lib/shiba/review/cli.rb
CHANGED
@@ -67,6 +67,7 @@ module Shiba
|
|
67
67
|
begin
|
68
68
|
explains = explain_file.each_line.map { |json| JSON.parse(json) }
|
69
69
|
bad = explains.select { |explain| explain["severity"] && explain["severity"] != 'none' }
|
70
|
+
bad = bad.sort_by { |explain| explain["cost"] }
|
70
71
|
bad.map { |explain| [ "#{explain["sql"]}:-2", explain ] }
|
71
72
|
rescue Interrupt
|
72
73
|
@err.puts "SIGINT: Canceled reading from STDIN. To read from an explain log, provide the --file option."
|
@@ -16,13 +16,15 @@ module Shiba
|
|
16
16
|
explain["messages"].each do |message|
|
17
17
|
tag = message['tag']
|
18
18
|
data = present(message)
|
19
|
+
data.merge!(explain["global"])
|
20
|
+
body << " * "
|
19
21
|
body << @templates[tag]["title"]
|
20
22
|
body << ": "
|
21
23
|
body << render_template(@templates[tag]["summary"], data)
|
22
24
|
body << "\n"
|
23
25
|
end
|
24
26
|
|
25
|
-
body << "Estimated query time: %.2fs" % explain['cost']
|
27
|
+
body << " * Estimated query time: %.2fs" % explain['cost']
|
26
28
|
body
|
27
29
|
end
|
28
30
|
|
data/lib/shiba/version.rb
CHANGED
data/web/src/App.vue
CHANGED
@@ -34,7 +34,7 @@
|
|
34
34
|
</div>
|
35
35
|
<a name="lowExapnded"></a>
|
36
36
|
<div class="queries" v-if="lowExpanded">
|
37
|
-
<query v-for="query in queriesLow" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags" v-bind:url="url"></query>
|
37
|
+
<query v-for="query in queriesLow" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags" v-bind:url="url" v-bind:global="global"></query>
|
38
38
|
</div>
|
39
39
|
</div>
|
40
40
|
<div style="height:50px"></div>
|
@@ -90,6 +90,7 @@ export default {
|
|
90
90
|
highQ: [],
|
91
91
|
lowQ: [],
|
92
92
|
tags: {},
|
93
|
+
global: {},
|
93
94
|
lowExpanded: false,
|
94
95
|
hasFuzzed: false,
|
95
96
|
search: '',
|
@@ -112,6 +113,7 @@ export default {
|
|
112
113
|
setupData: function(data) {
|
113
114
|
this.url = data.url;
|
114
115
|
this.tags = data.tags;
|
116
|
+
this.global = data.global;
|
115
117
|
|
116
118
|
Object.keys(this.tags).forEach((k) => {
|
117
119
|
registerMessage(k, this.tags[k].title, this.tags[k].summary);
|
@@ -94,7 +94,7 @@ export default function (tag, title, summary) {
|
|
94
94
|
|
95
95
|
Vue.component(`tag-${tag}`, {
|
96
96
|
template: tmpl,
|
97
|
-
props: [ 'table_size', 'result_size', 'table', 'cost', 'index', 'join_to', 'index_used', 'running_cost', 'tables', 'rows_read', 'result_bytes' ],
|
97
|
+
props: [ 'table_size', 'result_size', 'table', 'cost', 'index', 'join_to', 'index_used', 'running_cost', 'tables', 'rows_read', 'result_bytes', 'server' ],
|
98
98
|
computed: templateComputedFunctions,
|
99
99
|
data: function () {
|
100
100
|
return { lastRunnningCost: undefined };
|
@@ -19,7 +19,7 @@
|
|
19
19
|
<backtrace v-bind:backtrace="query.backtrace" v-bind:url="url"></backtrace>
|
20
20
|
</div>
|
21
21
|
<table class="shiba-messages">
|
22
|
-
<component v-for="message in
|
22
|
+
<component v-for="message in messages" v-bind:is="'tag-' + message.tag" v-bind="message" v-bind:key="query.md5 + ':' + message.tag"></component>
|
23
23
|
</table>
|
24
24
|
<div v-if="debug" style="font-size: 10px">md5: {{ query.md5 }}</div>
|
25
25
|
<div v-if="!rawExpanded">
|
@@ -42,7 +42,7 @@
|
|
42
42
|
export default {
|
43
43
|
name: 'Query',
|
44
44
|
template: '#query-template',
|
45
|
-
props: ['query', 'tags', 'url', 'debug'],
|
45
|
+
props: ['query', 'tags', 'url', 'debug', 'global'],
|
46
46
|
data: function () {
|
47
47
|
return {
|
48
48
|
expanded: false,
|
@@ -95,6 +95,12 @@
|
|
95
95
|
},
|
96
96
|
},
|
97
97
|
computed: {
|
98
|
+
messages: function () {
|
99
|
+
var array = this.query.messages.map((m) =>
|
100
|
+
Object.assign({}, { server: 'MySQL' }, m)
|
101
|
+
);
|
102
|
+
return array;
|
103
|
+
},
|
98
104
|
expandText: function() {
|
99
105
|
return this.expanded ? "-" : "+";
|
100
106
|
}
|
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.9.
|
4
|
+
version: 0.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Osheroff
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-03-
|
12
|
+
date: 2019-03-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -159,8 +159,8 @@ files:
|
|
159
159
|
- web/babel.config.js
|
160
160
|
- web/dist/example_data.json
|
161
161
|
- web/dist/index.html
|
162
|
-
- web/dist/js/app.
|
163
|
-
- web/dist/js/app.
|
162
|
+
- web/dist/js/app.91a7529a.js
|
163
|
+
- web/dist/js/app.91a7529a.js.map
|
164
164
|
- web/package-lock.json
|
165
165
|
- web/package.json
|
166
166
|
- web/public/index.html
|