shiba 0.6.0 → 0.6.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.
data/web/main.css DELETED
@@ -1,70 +0,0 @@
1
-
2
- .query-info-box {
3
- border: 1px solid black;
4
- padding: 10px;
5
- margin: 5px;
6
- }
7
-
8
- .backtrace {
9
- font-family: monospace;
10
- background-color: #EEEEEE;
11
- padding: 5px;
12
- margin: 10px
13
- }
14
-
15
- .sql {
16
- font-family: monospace;
17
- }
18
-
19
- .badge {
20
- color: black;
21
- background-color: white;
22
- border-style: solid;
23
- border-width: 2px;
24
- margin-right: 5px;
25
- width: 100px;
26
- }
27
-
28
- .shiba-badge-info {
29
- border-color: #78b0ec;
30
- }
31
-
32
- .shiba-badge-danger {
33
- border-color: #ff655d;
34
- }
35
-
36
- .shiba-badge-success {
37
- border-color: green;
38
- }
39
-
40
- .shiba-badge-warning {
41
- border-color: #ffb100;
42
- }
43
-
44
- .shiba-badge-td {
45
- width: 100px;
46
- }
47
-
48
- .shiba-messages {
49
- margin: 0px;
50
- margin-top: 10px;
51
- width: 100%;
52
- }
53
-
54
- .shiba-messages td {
55
- padding-top: 5px;
56
- }
57
-
58
- .shiba-message {
59
- padding-right: 10px;
60
- width: 90%;
61
- }
62
-
63
- .running-totals {
64
- align: right;
65
- font-family: monospace;
66
- }
67
-
68
-
69
- [v-cloak] { display: none }
70
-
data/web/main.js DELETED
@@ -1,6 +0,0 @@
1
- import Vue from 'vue';
2
- import VModal from 'vue-js-modal';
3
-
4
- Vue.use(VModal, { dialog: true });
5
- window.Vue = Vue;
6
- window._ = require('lodash');
data/web/results.html.erb DELETED
@@ -1,412 +0,0 @@
1
- <html>
2
- <head>
3
- <title>Shiba results for <%= Time.now %></title>
4
- <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
5
- </head>
6
- <body>
7
- <% data[:js].each do |js| %>
8
- <% if ENV['SHIBA_DEBUG'] %>
9
- <script type="text/javascript" src="file://<%= js %>"></script>
10
- <% else %>
11
- <script type="text/javascript">
12
- <%= File.read(js) %>
13
- </script>
14
- <% end %>
15
- <% end %>
16
-
17
- <% data[:css].each do |css| %>
18
- <style type="text/css">
19
- <%= File.read(css) %>
20
- </style>
21
- <% end %>
22
-
23
- <script language="javascript">
24
- var data = <%= data.to_json %>;
25
- var queriesByTable = [];
26
- var queriesByTableLow = [];
27
- var queriesHaveFuzzed = false;
28
- var severityIndexes = { high: 1, medium: 2, low: 3, none: 4 };
29
-
30
- function sortByFunc(fields) {
31
- return function(a, b) {
32
- for ( var i = 0 ; i < fields.length; i++ ) {
33
- if ( a[fields[i]] < b[fields[i]] )
34
- return -1;
35
- else if ( a[fields[i]] > b[fields[i]] )
36
- return 1;
37
- }
38
- return 0;
39
- }
40
- }
41
-
42
- function Query(obj) {
43
- Object.assign(this, obj);
44
- this.severityIndex = severityIndexes[this.severity];
45
- this.splitSQL();
46
- this.makeSearchString();
47
- };
48
-
49
- Query.prototype = {
50
- makeSearchString: function() {
51
- var arr = [this.sql];
52
- arr = arr.concat(this.messages.map(function(m) { return m.tag }).join(':'));
53
- arr = arr.concat(this.backtrace.join(':'));
54
-
55
- this.searchString = arr.join(':').toLowerCase();
56
- },
57
- hasTag: function(tag) {
58
- return this.messages.find(function(m) {
59
- return m.tag == tag;
60
- });
61
- },
62
- splitSQL: function() {
63
- this.sqlFragments = this.sql.match(/(SELECT\s)(.*?)(\s+FROM .*)/i);
64
- },
65
- select: function () {
66
- return this.sqlFragments[1];
67
- },
68
- fields: function () {
69
- return this.sqlFragments[2];
70
- },
71
- rest: function(index) {
72
- return this.sqlFragments.slice(index).join('');
73
- }
74
- };
75
-
76
- data.queries.forEach(function(query) {
77
- var q = new Query(query);
78
-
79
- if ( q.severity == "none" ) {
80
- queriesByTableLow.push(q);
81
- } else {
82
- queriesByTable.push(q);
83
- }
84
-
85
- if ( q.hasTag("fuzzed_data") )
86
- queriesHaveFuzzed = true;
87
-
88
- q.expandSelect = false;
89
-
90
- var rCost = 0;
91
- q.messages.forEach(function(m) {
92
- if ( m.cost && m.cost != 0) {
93
- rCost += m.cost;
94
- m.running_cost = rCost;
95
- } else {
96
- m.running_cost = undefined;
97
- }
98
- });
99
- });
100
-
101
- var f = sortByFunc(['severityIndex', 'table']);
102
- queriesByTable = queriesByTable.sort(f);
103
- queriesByTableLow = queriesByTableLow.sort(f);
104
- </script>
105
-
106
- <script type="text/x-template" id="query-template">
107
- <div class="query">
108
- <div class="row">
109
- <div class="col-3">
110
- <a href="#" v-on:click="expandToggle">
111
- <span stlye="text-align: right">{{ expandText }}</span>
112
- </a>
113
- {{ query.table }}
114
- </div>
115
- <div class="col-5">{{ truncate(query.sql, 50) }}</div>
116
- <div class="col-3" v-html="makeURL(query.backtrace[0], shortLocation(query))"></div>
117
- <div class="col-1">{{ query.severity }}</div>
118
- </div>
119
- <div class="row" v-if="expanded">
120
- <div class="col-12">
121
- <div class="query-info-box">
122
- <query-sql v-bind:query="query"></query-sql>
123
- <div v-if="query.backtrace && query.backtrace.length > 0">
124
- Stack Trace:<br>
125
- <div class="backtrace">
126
- <div v-for="backtrace in query.backtrace" v-html="makeURL(backtrace, backtrace)"></div>
127
- </div>
128
- </div>
129
- <table class="shiba-messages">
130
- <component v-for="message in query.messages" v-bind:is="'tag-' + message.tag" v-bind="message"></component>
131
- </table>
132
- <% if ENV['SHIBA_DEBUG'] %>
133
- <div style="font-size: 10px">md5: {{ query.md5 }}</div>
134
- <% end %>
135
- <div v-if="!rawExpanded">
136
- <a href="#" v-on:click.prevent="rawExpanded = !rawExpanded">See full EXPLAIN</a>
137
- </div>
138
- <div v-else>
139
- <a href="#" v-on:click.prevent="rawExpanded = !rawExpanded">hide EXPLAIN</a>
140
- <pre class="backtrace">{{ JSON.stringify(query.raw_explain, null, 2) }}</pre>
141
- </div>
142
- </div>
143
- </div>
144
- </div>
145
- </div>
146
- </script>
147
-
148
- <script>
149
- var greenToRedGradient = [
150
- '#57bb8a','#63b682', '#73b87e', '#84bb7b', '#94bd77', '#a4c073', '#b0be6e',
151
- '#c4c56d', '#d4c86a', '#e2c965', '#f5ce62', '#f3c563', '#e9b861', '#e6ad61',
152
- '#ecac67', '#e9a268', '#e79a69', '#e5926b', '#e2886c', '#e0816d', '#dd776e'];
153
-
154
- var templateComputedFunctions = {
155
- key_parts: function() {
156
- if ( this.index_used && this.index_used.length > 0 )
157
- return this.index_used.join(',');
158
- else
159
- return "";
160
- },
161
- fuzz_table_sizes: function() {
162
- var h = {};
163
- var tables = this.tables;
164
-
165
- Object.keys(tables).forEach(function(k) {
166
- console.log(k);
167
- var size = tables[k];
168
- if ( !h[size] )
169
- h[size] = [];
170
-
171
- h[size].push(k);
172
- });
173
-
174
- var sizesDesc = Object.keys(h).sort(function(a, b) { return b - a });
175
- var str = "";
176
-
177
- sizesDesc.forEach(function(size) {
178
- str = str + h[size].join(", ") + ": " + size.toLocaleString() + " rows. ";
179
- });
180
-
181
- return str;
182
- },
183
- formatted_cost: function() {
184
- var readPercentage = (this.rows_read / this.table_size) * 100.0;
185
- if ( this.rows_read > 100 && readPercentage > 1 ) // todo: make better
186
- return `${readPercentage.toFixed()}% (${this.rows_read.toLocaleString()}) of the`;
187
- else
188
- return this.rows_read.toLocaleString();
189
- },
190
- costToColor: function() {
191
- var goodColor = [34, 160, 60];
192
- var endColor = [255, 0, 0];
193
- var costScale = this.cost ? this.cost / 0.5 : 0;
194
-
195
- if ( costScale > 1 )
196
- costScale = 1;
197
-
198
- var pos = (costScale * (greenToRedGradient.length - 1)).toFixed();
199
-
200
- return "border-color: " + greenToRedGradient[pos];
201
- },
202
- formattedRunningCost: function() {
203
- if ( this.running_cost === undefined )
204
- return "-";
205
- else if ( this.running_cost < 1.0 )
206
- return (this.running_cost * 100).toFixed() + "ms";
207
- else
208
- return this.running_cost.toFixed(1) + "s";
209
- },
210
- formatted_result: function() {
211
- var rb = this.result_bytes;
212
- var result;
213
- if ( rb == 0 )
214
- return "" + this.result_size + " rows";
215
- else if ( rb < 1000 )
216
- result = rb + " bytes ";
217
- else if ( rb < 1000000 )
218
- result = (rb / 1000).toFixed() + "kb ";
219
- else
220
- result = (rb / 1000000 ).toFixed(1) + "mb ";
221
-
222
- return result + " (" + this.result_size.toLocaleString() + " rows)";
223
- }
224
- }
225
- </script>
226
- <% data[:tags].each do |tag, h| %>
227
- <script type="text/x-template" id="tag-<%= tag %>-template">
228
- <tr>
229
- <td class="shiba-badge-td">
230
- <a class="badge" v-bind:style="costToColor"><%= h['title'] %></a>
231
- </td>
232
- <td class="shiba-message">
233
- <%= h['summary'] %>
234
- </td>
235
- <td class="running-totals">
236
- {{ formattedRunningCost }}
237
- </td>
238
- </tr>
239
- </script>
240
- <script>
241
- Vue.component('tag-<%= tag %>', {
242
- template: '#tag-<%= tag %>-template',
243
- props: [ 'table_size', 'result_size', 'table', 'cost', 'index', 'join_to', 'index_used', 'running_cost', 'tables', 'rows_read', 'result_bytes' ],
244
- computed: templateComputedFunctions,
245
- data: function () {
246
- return { lastRunnningCost: undefined };
247
- }
248
- });
249
- </script>
250
- <% end %>
251
-
252
-
253
- <script type="text/x-template" id="sql-template">
254
- <div class="sql">
255
- <span>{{ query.select() }}</span>
256
- <span v-if="!expandFields && query.fields().length > 80">
257
- <a href="#" v-on:click.prevent="expandFields = !expandFields">...</a>
258
- </span>
259
- <span v-else>{{ query.fields() }}</span>
260
- <span>{{ query.rest(3) }}</span>
261
- </div>
262
- </script>
263
-
264
- <script>
265
- Vue.component('query-sql', {
266
- template: '#sql-template',
267
- props: ['query'],
268
- data: function () {
269
- return { expandFields: false }
270
- }
271
- });
272
- </script>
273
-
274
- <div id="app" v-cloak>
275
- <v-dialog :width="600"></v-dialog>
276
- <div class="container" style="">
277
- <div class="row" v-if="hasFuzzed">
278
- <div class="alert alert-warning" role="alert">
279
- This query analysis was generated using estimated table sizes.
280
- To improve these results and find other problem queries beyond missing indexes, we'll need more stats.<br/>
281
- <a target="_blank" href="https://github.com/burrito-brothers/shiba/blob/master/README.md#going-beyond-table-scans">Find out how to get a more accurate analysis by feeding Shiba index stats</a>
282
- </div>
283
- </div>
284
- <div class="row">
285
- <div class="col-10"></div>
286
- <div class="col-2"><input :value="search" @input="updateSearch" placeholder="search..."></div>
287
- </div>
288
- <div class="row">
289
- <div class="col-12">We found {{ queries.length }} queries that
290
- <span v-if="search == ''">deserve your attention:</span>
291
- <span v-else=>match your search term</span>
292
- </div>
293
- </div>
294
- <div class="row">
295
- <div class="col-3">Table</div>
296
- <div class="col-5">Query</div>
297
- <div class="col-3">Source</div>
298
- <div class="col-1">Severity</div>
299
- </div>
300
- <div class="queries">
301
- <query v-for="query in queries" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
302
- </div>
303
-
304
- <div v-if="search == ''">
305
- <div class="row">
306
- <div class="col-12">We also found <a href="#" v-on:click.prevent="lowExpanded = !lowExpanded">{{ queriesLow.length }} queries</a> that look fine.</div>
307
- </div>
308
- <a name="lowExapnded"></a>
309
- <div class="queries" v-if="lowExpanded">
310
- <query v-for="query in queriesLow" v-bind:query="query" v-bind:key="query.sql" v-bind:tags="tags"></query>
311
- </div>
312
- </div>
313
- <div style="height:50px"></div>
314
- </div>
315
- </div>
316
-
317
- <script>
318
- Vue.component('query', {
319
- template: '#query-template',
320
- props: ['query', 'tags', 'github'],
321
- data: function () {
322
- return {
323
- expanded: false,
324
- rawExpanded: false
325
- };
326
- },
327
- methods: {
328
- truncate: function (string, len) {
329
- if ( string.length > len ) {
330
- return string.substring(0, len - 3) + "...";
331
- } else {
332
- return string;
333
- }
334
- },
335
- expandInfo: function(tag, event) {
336
- this.$modal.show('dialog', {
337
- title: this.tags[tag].title,
338
- text: this.tags[tag].description,
339
- buttons: [
340
- {
341
- title: 'Close'
342
- }
343
- ]
344
- })
345
- event.preventDefault();
346
- },
347
- expandToggle: function(event) {
348
- if (event) event.preventDefault()
349
- this.expanded = !this.expanded;
350
- },
351
- shortLocation: function(query) {
352
- if ( !query.backtrace || query.backtrace.length == 0 )
353
- return null;
354
- var location = query.backtrace[0];
355
- return location.match(/([^\/]+:\d+):/)[1];
356
- },
357
- makeURL: function(line, content) {
358
- if ( !data.url || !line )
359
- return content;
360
-
361
- var matches = line.match(/(.+):(\d+):/);
362
- var file = matches[1].replace(/^\/+/, '');
363
- var line = matches[2];
364
-
365
- return `<a href='${data.url}/${file}#L${line}' target='_new'>${content}</a>`;
366
- }
367
- },
368
- computed: {
369
- expandText: function() {
370
- return this.expanded ? "-" : "+";
371
- }
372
- }
373
- });
374
-
375
- var app = new Vue({
376
- el: '#app',
377
- data: {
378
- highQ: queriesByTable,
379
- lowQ: queriesByTableLow,
380
- tags: data.tags,
381
- lowExpanded: false,
382
- hasFuzzed: queriesHaveFuzzed,
383
- search: ''
384
- },
385
- methods: {
386
- updateSearch: _.debounce(function (e) {
387
- this.search = e.target.value;
388
- }, 500)
389
- },
390
- computed: {
391
- queries: function() {
392
- if ( this.search != '' ) {
393
- var filtered = [];
394
- var lcSearch = this.search.toLowerCase();
395
- this.highQ.concat(this.lowQ).forEach(function(q) {
396
- if ( q.searchString.includes(lcSearch) )
397
- filtered.push(q);
398
- });
399
- return filtered;
400
- } else
401
- return this.highQ;
402
- },
403
- queriesLow: function() {
404
- return this.lowQ;
405
- }
406
- }
407
- });
408
-
409
-
410
- </script>
411
- </body>
412
- </html>