shiba 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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>