builder_apm 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +17 -0
  7. data/Gemfile.lock +196 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +50 -0
  10. data/Rakefile +28 -0
  11. data/app/controllers/builder_apm/dashboard_controller.rb +8 -0
  12. data/app/controllers/builder_apm/error_requests_controller.rb +8 -0
  13. data/app/controllers/builder_apm/n_plus_one_controller.rb +8 -0
  14. data/app/controllers/builder_apm/recent_requests_controller.rb +8 -0
  15. data/app/controllers/builder_apm/request_analysis_controller.rb +8 -0
  16. data/app/controllers/builder_apm/request_data_controller.rb +41 -0
  17. data/app/controllers/builder_apm/request_details_controller.rb +9 -0
  18. data/app/controllers/builder_apm/slow_requests_controller.rb +8 -0
  19. data/app/controllers/builder_apm/wip_controller.rb +8 -0
  20. data/app/views/builder_apm/css/_dark.html.erb +119 -0
  21. data/app/views/builder_apm/css/_main.html.erb +268 -0
  22. data/app/views/builder_apm/dashboard/index.html.erb +10 -0
  23. data/app/views/builder_apm/error_requests/index.html.erb +23 -0
  24. data/app/views/builder_apm/js/_compress.html.erb +93 -0
  25. data/app/views/builder_apm/js/_dashboard.html.erb +199 -0
  26. data/app/views/builder_apm/js/_data_fetcher.html.erb +254 -0
  27. data/app/views/builder_apm/js/_error_requests.html.erb +65 -0
  28. data/app/views/builder_apm/js/_lzma.html.erb +2670 -0
  29. data/app/views/builder_apm/js/_n_plus_one.html.erb +79 -0
  30. data/app/views/builder_apm/js/_recent_requests.html.erb +82 -0
  31. data/app/views/builder_apm/js/_request_analysis.html.erb +77 -0
  32. data/app/views/builder_apm/js/_request_details.html.erb +204 -0
  33. data/app/views/builder_apm/js/_slow_requests.html.erb +74 -0
  34. data/app/views/builder_apm/n_plus_one/index.html.erb +21 -0
  35. data/app/views/builder_apm/recent_requests/index.html.erb +21 -0
  36. data/app/views/builder_apm/request_analysis/index.html.erb +24 -0
  37. data/app/views/builder_apm/request_details/index.html.erb +7 -0
  38. data/app/views/builder_apm/shared/_footer.html.erb +3 -0
  39. data/app/views/builder_apm/shared/_header.html.erb +55 -0
  40. data/app/views/builder_apm/slow_requests/index.html.erb +21 -0
  41. data/app/views/builder_apm/wip/index.html.erb +5 -0
  42. data/bin/console +14 -0
  43. data/bin/setup +8 -0
  44. data/builder_apm.gemspec +23 -0
  45. data/config/routes.rb +12 -0
  46. data/lib/builder_apm/configuration.rb +15 -0
  47. data/lib/builder_apm/controllers/instrumenter.rb +88 -0
  48. data/lib/builder_apm/engine.rb +17 -0
  49. data/lib/builder_apm/methods/instrumenter.rb +79 -0
  50. data/lib/builder_apm/middleware/timing.rb +56 -0
  51. data/lib/builder_apm/models/instrumenter.rb +82 -0
  52. data/lib/builder_apm/railtie.rb +9 -0
  53. data/lib/builder_apm/redis_client.rb +11 -0
  54. data/lib/builder_apm/version.rb +3 -0
  55. data/lib/builder_apm.rb +22 -0
  56. data/lib/generators/builder_apm/install_generator.rb +21 -0
  57. data/lib/generators/builder_apm/templates/builder_apm_config.rb +6 -0
  58. data/lib/generators/builder_apm/templates/create_builder_apm_requests.rb +21 -0
  59. data/lib/generators/builder_apm/templates/create_builder_apm_sql_queries.rb +17 -0
  60. metadata +135 -0
@@ -0,0 +1,268 @@
1
+ <style>
2
+ body {
3
+ font-family: Arial, sans-serif;
4
+ display:none;
5
+ }
6
+
7
+ #header {
8
+ background-color: #4CAF50;
9
+ color: white;
10
+ text-align: center;
11
+ padding: 10px;
12
+ margin-bottom: 20px;
13
+ }
14
+
15
+ #navbar {
16
+ background-color: #333;
17
+ overflow: hidden;
18
+ list-style-type: none;
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ #navbar ul {
24
+ margin: 0;
25
+ padding: 0;
26
+ overflow: hidden;
27
+ background-color: #333;
28
+ }
29
+
30
+ #navbar li {
31
+ float: left;
32
+ display:block;
33
+ }
34
+
35
+ #navbar li a {
36
+ display: block;
37
+ color: #f2f2f2;
38
+ text-align: center;
39
+ padding: 14px 16px;
40
+ text-decoration: none;
41
+ }
42
+
43
+ #navbar li a:hover {
44
+ background-color: #ddd;
45
+ color: black;
46
+ }
47
+
48
+ #navbar li a.active {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ #navbar li a,
53
+ #navbar .nav-button {
54
+ display: block;
55
+ color: #f2f2f2;
56
+ text-align: center;
57
+ padding: 14px 16px;
58
+ text-decoration: none;
59
+ background-color: #333;
60
+ border: none; /* Removes the default button border */
61
+ cursor: pointer; /* Makes the button cursor appear as a hand */
62
+ font-size: 1em; /* Adjust to match your anchors */
63
+ transition: background-color 0.3s ease, color 0.3s ease; /* Smooth transition */
64
+ }
65
+
66
+ th.sortable {
67
+ cursor: pointer; /* Makes the button cursor appear as a hand */
68
+ }
69
+ #navbar #dark-mode-toggle {
70
+ float:right;
71
+ }
72
+
73
+ #navbar li a:hover,
74
+ #navbar .nav-button:hover {
75
+ background-color: #ddd;
76
+ color: black;
77
+ }
78
+
79
+ #navbar li a.active,
80
+ #navbar .nav-button.active {
81
+ background-color: #4CAF50;
82
+ color: white;
83
+ }
84
+
85
+ table {
86
+ border-collapse: collapse;
87
+ width: 100%;
88
+ margin-top: 20px;
89
+ }
90
+ th, td {
91
+ border: 1px solid #ddd;
92
+ padding: 8px;
93
+ text-align: left;
94
+ }
95
+ tr:nth-child(even) {
96
+ background-color: #f2f2f2;
97
+ }
98
+ th {
99
+ background-color: #4CAF50;
100
+ color: white;
101
+ padding-top: 12px;
102
+ padding-bottom: 12px;
103
+ }
104
+
105
+ #gannt_div {
106
+ overflow:display;
107
+ height:2000px;
108
+ width:100%;
109
+ }
110
+ #details_div .bounding-box {
111
+ border: 1px solid #333;
112
+ margin: 2px -1px 3px 0px;
113
+ padding: 10px 0 10px 10px;
114
+ box-shadow: 0px 0px 5px rgba(0,0,0,0.2);
115
+ border-radius: 5px;
116
+ }
117
+
118
+ #details_div div.has-children {
119
+ cursor: pointer;
120
+ color: #444;
121
+ font-weight: bold;
122
+ }
123
+
124
+ #details_div span {
125
+ display: inline-block;
126
+ }
127
+
128
+ #details_div div {
129
+ padding:5px 0 5px 5px;
130
+ font-size: 14px;
131
+ line-height: 1.5;
132
+ color: #333;
133
+ background-color: #f9f9f9;
134
+ }
135
+
136
+ #details_div .sql-event {
137
+ background-color: #e1f5fe;
138
+ color: #01579b;
139
+ }
140
+
141
+ #details_div .children {
142
+ display:none;
143
+ margin-top: 10px;
144
+ border-left: 1px solid #ccc;
145
+ padding-left: 10px;
146
+ }
147
+
148
+ #details_div .has_children {
149
+ border: 1px solid #aaa;
150
+ width: 20px;
151
+ height: 20px;
152
+ text-align: center;
153
+ margin-right: 10px;
154
+ }
155
+
156
+ #details_div .date {
157
+ color: #607d8b;
158
+ margin-right: 10px;
159
+ width: 135px;
160
+ text-align: right;
161
+ }
162
+
163
+ #details_div .duration {
164
+ color: #009688;
165
+ margin-right: 10px;
166
+ width: 100px;
167
+ }
168
+
169
+ #details_div .description,
170
+ #details_div .sql {
171
+ color: #3f51b5;
172
+ margin-right: 10px;
173
+ }
174
+
175
+ #details_div .method_line,
176
+ #details_div .trigger_line {
177
+ color: #8d6e63;
178
+ margin-right: 10px;
179
+ width: 100%;
180
+ }
181
+
182
+ #details_div .cached,
183
+ #details_div .record_count,
184
+ #details_div .params {
185
+ color: #3f51b5;
186
+ margin-right: 10px;
187
+ width: 100px;
188
+ }
189
+
190
+ #details_div .minor_call {
191
+ display:none;
192
+ background: #e5efe5;
193
+ }
194
+
195
+ #details_div .params {
196
+ width: auto;
197
+ }
198
+ #details_div .error_status {
199
+ border: 1px solid red;
200
+ padding: 10px;
201
+ margin-bottom: 10px;
202
+ background-color: rgb(255, 238, 238);
203
+ }
204
+
205
+ .duration-circle {
206
+ display: inline-block;
207
+ width: 10px;
208
+ height: 10px;
209
+ border-radius: 50%;
210
+ border: 1px solid #333;
211
+ margin-right: 5px;
212
+ }
213
+
214
+ .green-circle {
215
+ background-color: #0f0;
216
+ border-color:#0c0;
217
+ }
218
+
219
+ .amber-circle {
220
+ background-color: #ff0;
221
+ border-color:#cc0;
222
+ }
223
+
224
+ .red-circle {
225
+ background-color: #f00;
226
+ border-color:#c00;
227
+ }
228
+
229
+ .image-container {
230
+ position: absolute;
231
+ width: 600px; /* Change to the width of your image */
232
+ height: 600px; /* Change to the height of your image */
233
+ overflow: hidden;
234
+ left:50%;
235
+ margin-left:-300px;
236
+
237
+ }
238
+
239
+ .image-container::before {
240
+ content: '';
241
+ position: absolute;
242
+ top: 0;
243
+ right: 0;
244
+ bottom: 0;
245
+ left: 0;
246
+ background: radial-gradient(ellipse at center, rgba(255,255,255,0) 57%, rgba(255,255,255,1) 71%);
247
+ }
248
+
249
+ .image-container img {
250
+ width: 100%;
251
+ height: 100%;
252
+ }
253
+
254
+ #error_table td p {
255
+ font-size: 10px;
256
+ margin:2px;
257
+ }
258
+
259
+ @keyframes blink {
260
+ 0% {opacity: 1;}
261
+ 50% {opacity: 0;}
262
+ 100% {opacity: 1;}
263
+ }
264
+
265
+ #details_div .n_plus_one {
266
+ color: #ff6347;
267
+ }
268
+ </style>
@@ -0,0 +1,10 @@
1
+ <%= render 'builder_apm/shared/header' %>
2
+
3
+ <div id="request_count_chart" style="height:400px;"></div>
4
+ <div id="avg_duration_chart" style="width:100%; height:400px;"></div>
5
+
6
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
7
+
8
+ <%= render 'builder_apm/js/dashboard' %>
9
+
10
+ <%= render 'builder_apm/shared/footer' %>
@@ -0,0 +1,23 @@
1
+ <%= render 'builder_apm/shared/header' %>
2
+
3
+ <table id="error_table">
4
+ <thead>
5
+ <tr>
6
+ <th class="sortable" data-field="start_time">Time</th>
7
+ <th class="sortable" data-field="controller">Controller#Action</th>
8
+ <th class="sortable" data-field="status">Status</th>
9
+ <th class="sortable" data-field="exception_class">Error Class</th>
10
+ <th class="sortable" data-field="exception_message">Error Message</th>
11
+ <th class="sortable" data-field="exception_backtrace">Trace</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <!-- Table content will be populated here by JavaScript -->
16
+ </tbody>
17
+ </table>
18
+
19
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.js"></script>
20
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.css" />
21
+ <%= render 'builder_apm/js/error_requests' %>
22
+
23
+ <%= render 'builder_apm/shared/footer' %>
@@ -0,0 +1,93 @@
1
+ <script>
2
+
3
+ var compression_mode = 1;
4
+ var my_lzma = LZMA; /// lzma_worker.js creates a global LZMA object. We store it as a new variable just to match simple_demo.html.
5
+
6
+ function convert_bytes_to_hex(byte_arr) {
7
+ var hex_str = "",
8
+ i,
9
+ len,
10
+ tmp_hex;
11
+
12
+ len = byte_arr.length;
13
+
14
+ for (i = 0; i < len; ++i) {
15
+ if (byte_arr[i] < 0) {
16
+ byte_arr[i] = byte_arr[i] + 256;
17
+ }
18
+ if (byte_arr[i] === undefined) {
19
+ alert("Boom " + i);
20
+ byte_arr[i] = 0;
21
+ }
22
+ tmp_hex = byte_arr[i].toString(16);
23
+
24
+ // Add leading zero.
25
+ if (tmp_hex.length == 1) tmp_hex = "0" + tmp_hex;
26
+
27
+ hex_str += tmp_hex;
28
+ }
29
+
30
+ return hex_str.trim();
31
+ }
32
+
33
+ function convert_hex_to_bytes(hex_str) {
34
+ var count = 0,
35
+ hex_arr,
36
+ hex_data = [],
37
+ hex_len,
38
+ i;
39
+
40
+ if (hex_str.trim() == "") return [];
41
+
42
+ /// Check for invalid hex characters.
43
+ if (/[^0-9a-fA-F\s]/.test(hex_str)) {
44
+ return false;
45
+ }
46
+
47
+ hex_arr = hex_str.split(/([0-9a-fA-F]{2})/g);
48
+ hex_len = hex_arr.length;
49
+
50
+ for (i = 0; i < hex_len; ++i) {
51
+ if (hex_arr[i].trim() == "") {
52
+ continue;
53
+ }
54
+ hex_data[count++] = parseInt(hex_arr[i], 16);
55
+ }
56
+
57
+ return hex_data;
58
+ }
59
+
60
+ function compress(data, completeCallback){
61
+
62
+ my_lzma.compress(data, compression_mode, function on_compress_complete(result, error) {
63
+ if(result != null) {
64
+ result = convert_bytes_to_hex(result);
65
+ }
66
+ completeCallback(result, error);
67
+ }, function on_compress_progress_update(percent) {
68
+ console.log("Compressing: " + (percent * 100) + "%");
69
+ });
70
+
71
+ }
72
+
73
+ function decompress(data, completeCallback) {
74
+ var data = convert_hex_to_bytes(data);
75
+
76
+ my_lzma.decompress(data, completeCallback,
77
+ function on_decompress_progress_update(percent) {
78
+ /// Decompressing progress code goes here.
79
+ console.log("Decompressing: " + (percent * 100) + "%");
80
+ });
81
+ }
82
+
83
+ function getSizeOfLocalStorageKey(key) {
84
+ const value = localStorage.getItem(key);
85
+ if(value) {
86
+ // Each character takes 2 bytes when stored in local storage
87
+ return ((value.length * 2) / 1024 / 1024) + 'MB'; // return size in MB
88
+ } else {
89
+ return 0;
90
+ }
91
+ }
92
+
93
+ </script>
@@ -0,0 +1,199 @@
1
+ <script>
2
+ var aggregationInterval = 1; // Change this to modify the interval
3
+ // Load the Google Charts library
4
+ google.charts.load('current', {packages: ['corechart', 'line']});
5
+ google.charts.setOnLoadCallback(drawCharts);
6
+
7
+ function drawCharts() {
8
+ prepareChartData(aggregationInterval);
9
+ setInterval(function() {
10
+ var autoUpdate = $("#autoUpdate").prop('checked');
11
+ if (autoUpdate) {
12
+ prepareChartData(aggregationInterval);
13
+ }
14
+ }, 5000);
15
+ }
16
+ function prepareChartData(aggregationInterval) {
17
+ fetchDataAndUpdateStorage(function(updatedData) {
18
+
19
+ // Continue with data aggregation and charting...
20
+ // Aggregate data into specified interval
21
+ var aggregatedData = {};
22
+ updatedData.forEach(function(item) {
23
+ // Round start_time down to nearest interval
24
+ var time = new Date(item['start_time']);
25
+ time.setSeconds(0, 0);
26
+ time.setMinutes(Math.floor(time.getMinutes() / aggregationInterval) * aggregationInterval, 0, 0);
27
+
28
+ var timeKey = time.getTime();
29
+
30
+ // Initialize data for this interval if it doesn't already exist
31
+ if (!aggregatedData[timeKey]) {
32
+ aggregatedData[timeKey] = {
33
+ count: 0,
34
+ totalDuration: 0,
35
+ totalDbRuntime: 0,
36
+ totalViewRuntime: 0,
37
+ totalOther: 0,
38
+ // durations: []
39
+ };
40
+ }
41
+
42
+ // Add current request data to interval totals
43
+ aggregatedData[timeKey]['count'] += 1;
44
+ aggregatedData[timeKey]['totalDuration'] += (item['real_duration_time']);
45
+ aggregatedData[timeKey]['totalDbRuntime'] += item['calc_db_runtime'];
46
+ aggregatedData[timeKey]['totalViewRuntime'] += item['view_runtime'];
47
+ aggregatedData[timeKey]['totalOther'] += item['real_duration_time'] - item['calc_db_runtime'] - item['view_runtime'];
48
+ // aggregatedData[timeKey]['durations'].push(item['duration']);
49
+ // aggregatedData[timeKey]['totalDbRuntime'] += item['real_duration_time']*0.35;
50
+ // aggregatedData[timeKey]['totalViewRuntime'] += item['real_duration_time']*0.45;
51
+ // aggregatedData[timeKey]['totalOther'] += item['real_duration_time']*0.2;
52
+ });
53
+
54
+
55
+ // Create a list of all possible time intervals
56
+ let keys = Object.keys(aggregatedData).sort();
57
+ let firstTime = keys[0] * 1;
58
+ let lastTime = keys[keys.length - 1] * 1;
59
+
60
+ var timeIntervals = [];
61
+ for (var time = firstTime; time < lastTime; time += (60 * aggregationInterval * 1000)) {
62
+ timeIntervals.push(time);
63
+ }
64
+
65
+ // Fill in missing data points with zeroes
66
+ timeIntervals.forEach(function(timeKey) {
67
+ if (!aggregatedData[timeKey]) {
68
+ aggregatedData[timeKey] = {
69
+ count: 0,
70
+ totalDuration: 0,
71
+ totalDbRuntime: 0,
72
+ totalViewRuntime: 0,
73
+ totalOther: 0
74
+ // durations: []
75
+ };
76
+ }
77
+ });
78
+
79
+ // Convert aggregated data into Dygraphs-friendly format
80
+ var requestCountData = [];
81
+ var avgDurationData = [];
82
+ timeIntervals.forEach(function(timeKey) {
83
+ var time = new Date(timeKey);
84
+ var data = aggregatedData[timeKey];
85
+
86
+ requestCountData.push([
87
+ time,
88
+ data['count'],
89
+ data['count'] === 0 ? 0 : (data['totalDuration'] / data['count'])
90
+ ]);
91
+ avgDurationData.push([
92
+ time,
93
+ // data['count'] === 0 ? 0 : data['totalDuration'] / data['count'],
94
+ data['count'] === 0 ? 0 : data['totalDbRuntime'] / data['count'],
95
+ data['count'] === 0 ? 0 : data['totalViewRuntime'] / data['count'],
96
+ data['count'] === 0 ? 0 : data['totalOther'] / data['count'],
97
+ // percentile(data['durations'], 0.95)
98
+ ]);
99
+ });
100
+
101
+ // Render charts
102
+ renderRequestCountChart(requestCountData, aggregationInterval);
103
+ renderAvgDurationChart(avgDurationData, aggregationInterval);
104
+ });
105
+ }
106
+
107
+ function percentile(arr, p) {
108
+ if (arr.length === 0) return 0;
109
+ if (p <= 0) return arr[0];
110
+ if (p >= 1) return arr[arr.length - 1];
111
+
112
+ arr.sort(function (a, b) { return a - b; });
113
+ var index = arr.length * p;
114
+ var lower = Math.floor(index);
115
+ var upper = lower + 1;
116
+ var weight = index % 1;
117
+
118
+ if (upper >= arr.length) return arr[lower];
119
+ return arr[lower] * (1 - weight) + arr[upper] * weight;
120
+ }
121
+
122
+ function renderRequestCountChart(data, aggregationInterval) {
123
+ var dataTable = new google.visualization.DataTable();
124
+ dataTable.addColumn('datetime', 'Time');
125
+ dataTable.addColumn('number', 'Requests');
126
+ dataTable.addColumn('number', 'Avg. Duration');
127
+
128
+ data.forEach(function(row) {
129
+ dataTable.addRow(row);
130
+ });
131
+
132
+ var options = {
133
+ title: 'Number of requests over time',
134
+ hAxis: {
135
+ title: 'Time'
136
+ },
137
+ series: {
138
+ 0: { targetAxisIndex: 0 },
139
+ 1: { targetAxisIndex: 1 }
140
+ },
141
+ vAxes: {
142
+ 0: { title: 'Requests' },
143
+ 1: { title: 'Duration (ms)' }
144
+ },
145
+ height: 400,
146
+ explorer: {
147
+ actions: ['dragToZoom', 'rightClickToReset'],
148
+ axis: 'horizontal',
149
+ keepInBounds: true,
150
+ maxZoomIn: 4.0
151
+ }
152
+ };
153
+
154
+ var chart = new google.visualization.LineChart(document.getElementById('request_count_chart'));
155
+ chart.draw(dataTable, options);
156
+ }
157
+
158
+
159
+ function renderAvgDurationChart(data, aggregationInterval) {
160
+ var dataTable = new google.visualization.DataTable();
161
+ dataTable.addColumn('datetime', 'Time');
162
+ // dataTable.addColumn('number', 'Duration');
163
+ dataTable.addColumn('number', 'DB Runtime');
164
+ dataTable.addColumn('number', 'View Runtime');
165
+ dataTable.addColumn('number', 'Contoller Time');
166
+
167
+ data.forEach(function(row) {
168
+ dataTable.addRow(row);
169
+ });
170
+
171
+ var options = {
172
+ title: 'Average durations over time',
173
+ hAxis: {
174
+ title: 'Time'
175
+ },
176
+ vAxis: {
177
+ title: 'Duration (ms)'
178
+ },
179
+ isStacked: true,
180
+ height: 400,
181
+ explorer: {
182
+ actions: ['dragToZoom', 'rightClickToReset'],
183
+ axis: 'horizontal',
184
+ keepInBounds: true,
185
+ maxZoomIn: 4.0
186
+ }
187
+ };
188
+
189
+ var chart = new google.visualization.AreaChart(document.getElementById('avg_duration_chart'));
190
+ chart.draw(dataTable, options);
191
+
192
+ google.visualization.events.addListener(chart, 'onmouseover', function(e) {
193
+ console.log('A table row was selected');
194
+ });
195
+
196
+ }
197
+
198
+
199
+ </script>