builder_apm 0.2.0
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 +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +28 -0
- data/app/controllers/builder_apm/dashboard_controller.rb +8 -0
- data/app/controllers/builder_apm/error_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/n_plus_one_controller.rb +8 -0
- data/app/controllers/builder_apm/recent_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/request_analysis_controller.rb +8 -0
- data/app/controllers/builder_apm/request_data_controller.rb +41 -0
- data/app/controllers/builder_apm/request_details_controller.rb +9 -0
- data/app/controllers/builder_apm/slow_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/wip_controller.rb +8 -0
- data/app/views/builder_apm/css/_dark.html.erb +119 -0
- data/app/views/builder_apm/css/_main.html.erb +268 -0
- data/app/views/builder_apm/dashboard/index.html.erb +10 -0
- data/app/views/builder_apm/error_requests/index.html.erb +23 -0
- data/app/views/builder_apm/js/_compress.html.erb +93 -0
- data/app/views/builder_apm/js/_dashboard.html.erb +199 -0
- data/app/views/builder_apm/js/_data_fetcher.html.erb +254 -0
- data/app/views/builder_apm/js/_error_requests.html.erb +65 -0
- data/app/views/builder_apm/js/_lzma.html.erb +2670 -0
- data/app/views/builder_apm/js/_n_plus_one.html.erb +79 -0
- data/app/views/builder_apm/js/_recent_requests.html.erb +82 -0
- data/app/views/builder_apm/js/_request_analysis.html.erb +77 -0
- data/app/views/builder_apm/js/_request_details.html.erb +204 -0
- data/app/views/builder_apm/js/_slow_requests.html.erb +74 -0
- data/app/views/builder_apm/n_plus_one/index.html.erb +21 -0
- data/app/views/builder_apm/recent_requests/index.html.erb +21 -0
- data/app/views/builder_apm/request_analysis/index.html.erb +24 -0
- data/app/views/builder_apm/request_details/index.html.erb +7 -0
- data/app/views/builder_apm/shared/_footer.html.erb +3 -0
- data/app/views/builder_apm/shared/_header.html.erb +55 -0
- data/app/views/builder_apm/slow_requests/index.html.erb +21 -0
- data/app/views/builder_apm/wip/index.html.erb +5 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/builder_apm.gemspec +23 -0
- data/config/routes.rb +12 -0
- data/lib/builder_apm/configuration.rb +15 -0
- data/lib/builder_apm/controllers/instrumenter.rb +88 -0
- data/lib/builder_apm/engine.rb +17 -0
- data/lib/builder_apm/methods/instrumenter.rb +79 -0
- data/lib/builder_apm/middleware/timing.rb +56 -0
- data/lib/builder_apm/models/instrumenter.rb +82 -0
- data/lib/builder_apm/railtie.rb +9 -0
- data/lib/builder_apm/redis_client.rb +11 -0
- data/lib/builder_apm/version.rb +3 -0
- data/lib/builder_apm.rb +22 -0
- data/lib/generators/builder_apm/install_generator.rb +21 -0
- data/lib/generators/builder_apm/templates/builder_apm_config.rb +6 -0
- data/lib/generators/builder_apm/templates/create_builder_apm_requests.rb +21 -0
- data/lib/generators/builder_apm/templates/create_builder_apm_sql_queries.rb +17 -0
- 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>
|