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,79 @@
|
|
1
|
+
<script>
|
2
|
+
$(document).ready(function() {
|
3
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
4
|
+
renderTable(updatedData);
|
5
|
+
});
|
6
|
+
|
7
|
+
setInterval(function() {
|
8
|
+
var autoUpdate = $('#autoUpdate').is(':checked');
|
9
|
+
if (autoUpdate) {
|
10
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
11
|
+
renderTable(updatedData);
|
12
|
+
});
|
13
|
+
}
|
14
|
+
}, 5000);
|
15
|
+
|
16
|
+
addSortingClick();
|
17
|
+
});
|
18
|
+
|
19
|
+
function renderTable(data, target = null) {
|
20
|
+
data = detectNPlusOne(data);
|
21
|
+
data = sortDataBy(current_sort_field, data);
|
22
|
+
|
23
|
+
// Get a reference to the table body
|
24
|
+
var tableBody = target || $('table tbody');
|
25
|
+
|
26
|
+
// Clear the table body
|
27
|
+
tableBody.empty();
|
28
|
+
|
29
|
+
// Create new table rows for each data item
|
30
|
+
data.forEach(function(item) {
|
31
|
+
var row = $('<tr>');
|
32
|
+
|
33
|
+
$('<td>').text(new Date(item['start_time']).toLocaleString()).appendTo(row);
|
34
|
+
$('<td>').text(item['controller'] + '#' + item['action']).appendTo(row);
|
35
|
+
$('<td>').text(item['status']).appendTo(row);
|
36
|
+
$('<td>').append(renderDuration(item['duration'])).appendTo(row);
|
37
|
+
$('<td>').append(renderDuration(item['db_runtime'])).appendTo(row);
|
38
|
+
$('<td>').append(renderDuration(item['view_runtime'])).appendTo(row);
|
39
|
+
// Action column
|
40
|
+
var actionTd = $('<td>');
|
41
|
+
var actionButton = $('<button>').text('Details');
|
42
|
+
actionButton.on('click', function() {
|
43
|
+
// Replace 'your-details-url' with the actual URL where the details are to be fetched.
|
44
|
+
// It's assumed the ID is required as a URL parameter, modify as per your requirements.
|
45
|
+
window.location.href = '<%= request_details_path %>?request_id=' + item['request_id'];
|
46
|
+
});
|
47
|
+
actionButton.appendTo(actionTd);
|
48
|
+
actionTd.appendTo(row);
|
49
|
+
|
50
|
+
// Append the row to the table body
|
51
|
+
tableBody.append(row);
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
function renderDuration(duration) {
|
56
|
+
var durationClass;
|
57
|
+
if (duration > 0 && duration < 500) {
|
58
|
+
durationClass = "duration-circle green-circle";
|
59
|
+
} else if (duration >= 500 && duration < 1000) {
|
60
|
+
durationClass = "duration-circle amber-circle";
|
61
|
+
} else if (duration >= 1000){
|
62
|
+
durationClass = "duration-circle red-circle";
|
63
|
+
}
|
64
|
+
|
65
|
+
var formattedDuration = duration == null || duration == 0 ? 'n/a' : parseFloat(parseFloat(duration).toFixed(3)) + 'ms';
|
66
|
+
|
67
|
+
// Create the duration span with the circle and the duration value
|
68
|
+
return $("<span>").addClass("duration").append(
|
69
|
+
$("<span>").addClass(durationClass),
|
70
|
+
`${formattedDuration}`
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
74
|
+
function detectNPlusOne(requests) {
|
75
|
+
tagQueriesWithNPlusOne(requests);
|
76
|
+
return getNPlusOneRequests(requests);
|
77
|
+
}
|
78
|
+
|
79
|
+
</script>
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<script>
|
2
|
+
|
3
|
+
$(document).ready(function() {
|
4
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
5
|
+
// Render the table with the updated data
|
6
|
+
renderTable(updatedData);
|
7
|
+
});
|
8
|
+
|
9
|
+
|
10
|
+
setInterval(function() {
|
11
|
+
var autoUpdate = $('#autoUpdate').is(':checked');
|
12
|
+
if (autoUpdate) {
|
13
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
14
|
+
// Render the table with the updated data
|
15
|
+
renderTable(updatedData);
|
16
|
+
});
|
17
|
+
}
|
18
|
+
}, 5000);
|
19
|
+
|
20
|
+
addSortingClick();
|
21
|
+
});
|
22
|
+
|
23
|
+
|
24
|
+
function renderTable(data, target = null) {
|
25
|
+
data = sortDataBy(current_sort_field, data);
|
26
|
+
|
27
|
+
// Get a reference to the table body
|
28
|
+
var tableBody = target || $('table tbody');
|
29
|
+
|
30
|
+
// Clear the table body
|
31
|
+
tableBody.empty();
|
32
|
+
|
33
|
+
// Create new table rows for each data item
|
34
|
+
data.forEach(function(item) {
|
35
|
+
var row = $('<tr>');
|
36
|
+
|
37
|
+
$('<td>').text(new Date(item['start_time']).toLocaleString()).appendTo(row);
|
38
|
+
$('<td>').text(item['controller'] + '#' + item['action']).appendTo(row);
|
39
|
+
$('<td>').text(item['status']).appendTo(row);
|
40
|
+
$('<td>').append(renderDuration(item['real_duration_time'])).appendTo(row);
|
41
|
+
$('<td>').append(renderDuration(item['calc_db_runtime'])).appendTo(row);
|
42
|
+
$('<td>').append(renderDuration(item['view_runtime'])).appendTo(row);
|
43
|
+
// Action column
|
44
|
+
var actionTd = $('<td>');
|
45
|
+
|
46
|
+
if(item.stack && item.stack.length > 0) {
|
47
|
+
|
48
|
+
var actionButton = $('<button>').text('Details');
|
49
|
+
actionButton.on('click', function() {
|
50
|
+
// Replace 'your-details-url' with the actual URL where the details are to be fetched.
|
51
|
+
// It's assumed the ID is required as a URL parameter, modify as per your requirements.
|
52
|
+
window.location.href = '<%= request_details_path %>?request_id=' + item['request_id'];
|
53
|
+
});
|
54
|
+
actionButton.appendTo(actionTd);
|
55
|
+
}
|
56
|
+
actionTd.appendTo(row);
|
57
|
+
|
58
|
+
// Append the row to the table body
|
59
|
+
tableBody.append(row);
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
function renderDuration(duration) {
|
64
|
+
var durationClass;
|
65
|
+
if (duration > 0 && duration < 500) {
|
66
|
+
durationClass = "duration-circle green-circle";
|
67
|
+
} else if (duration >= 500 && duration < 1000) {
|
68
|
+
durationClass = "duration-circle amber-circle";
|
69
|
+
} else if (duration >= 1000){
|
70
|
+
durationClass = "duration-circle red-circle";
|
71
|
+
}
|
72
|
+
|
73
|
+
var formattedDuration = duration == null || duration == 0 ? 'n/a' : parseFloat(parseFloat(duration).toFixed(3)) + 'ms';
|
74
|
+
|
75
|
+
// Create the duration span with the circle and the duration value
|
76
|
+
return $("<span>").addClass("duration").append(
|
77
|
+
$("<span>").addClass(durationClass),
|
78
|
+
`${formattedDuration}`
|
79
|
+
);
|
80
|
+
}
|
81
|
+
|
82
|
+
</script>
|
@@ -0,0 +1,77 @@
|
|
1
|
+
<script>
|
2
|
+
$(document).ready(function() {
|
3
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
4
|
+
renderTable(updatedData);
|
5
|
+
});
|
6
|
+
|
7
|
+
setInterval(function() {
|
8
|
+
var autoUpdate = $('#autoUpdate').is(':checked');
|
9
|
+
if (autoUpdate) {
|
10
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
11
|
+
renderTable(updatedData);
|
12
|
+
});
|
13
|
+
}
|
14
|
+
}, 5000);
|
15
|
+
|
16
|
+
addSortingClick();
|
17
|
+
});
|
18
|
+
|
19
|
+
function renderTable(data, target = null) {
|
20
|
+
data = aggregateRequests(data);
|
21
|
+
data = sortDataBy(current_sort_field, data);
|
22
|
+
|
23
|
+
// Get a reference to the table body
|
24
|
+
var tableBody = target || $('table tbody');
|
25
|
+
|
26
|
+
// Clear the table body
|
27
|
+
tableBody.empty();
|
28
|
+
|
29
|
+
// Create new table rows for each data item
|
30
|
+
data.forEach(function(item) {
|
31
|
+
var row = $('<tr>');
|
32
|
+
|
33
|
+
$('<td>').text(item['controller']).appendTo(row);
|
34
|
+
$('<td>').text(item['path']).appendTo(row);
|
35
|
+
$('<td>').text(item['count']).appendTo(row);
|
36
|
+
$('<td>').append(renderDuration(item['averageDuration'])).appendTo(row);
|
37
|
+
$('<td>').append(renderDuration(item['averageDbRuntime'])).appendTo(row);
|
38
|
+
$('<td>').append(renderDuration(item['averageViewRuntime'])).appendTo(row);
|
39
|
+
$('<td>').append(renderDuration(item['slowestDuration'])).appendTo(row);
|
40
|
+
$('<td>').append(renderDuration(item['slowestDbRuntime'])).appendTo(row);
|
41
|
+
$('<td>').append(renderDuration(item['slowestViewRuntime'])).appendTo(row);
|
42
|
+
// // Action column
|
43
|
+
// var actionTd = $('<td>');
|
44
|
+
// var actionButton = $('<button>').text('Details');
|
45
|
+
// actionButton.on('click', function() {
|
46
|
+
// // Replace 'your-details-url' with the actual URL where the details are to be fetched.
|
47
|
+
// // It's assumed the ID is required as a URL parameter, modify as per your requirements.
|
48
|
+
// window.location.href = '<%= request_details_path %>?request_id=' + item['request_id'];
|
49
|
+
// });
|
50
|
+
// actionButton.appendTo(actionTd);
|
51
|
+
// actionTd.appendTo(row);
|
52
|
+
|
53
|
+
// Append the row to the table body
|
54
|
+
tableBody.append(row);
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
function renderDuration(duration) {
|
59
|
+
var durationClass;
|
60
|
+
if (duration > 0 && duration < 500) {
|
61
|
+
durationClass = "duration-circle green-circle";
|
62
|
+
} else if (duration >= 500 && duration < 1000) {
|
63
|
+
durationClass = "duration-circle amber-circle";
|
64
|
+
} else if (duration >= 1000){
|
65
|
+
durationClass = "duration-circle red-circle";
|
66
|
+
}
|
67
|
+
|
68
|
+
var formattedDuration = duration == null || duration == 0 ? 'n/a' : parseFloat(parseFloat(duration).toFixed(3)) + 'ms';
|
69
|
+
|
70
|
+
// Create the duration span with the circle and the duration value
|
71
|
+
return $("<span>").addClass("duration").append(
|
72
|
+
$("<span>").addClass(durationClass),
|
73
|
+
`${formattedDuration}`
|
74
|
+
);
|
75
|
+
}
|
76
|
+
|
77
|
+
</script>
|
@@ -0,0 +1,204 @@
|
|
1
|
+
<script>
|
2
|
+
var request_id = '<%= @request_id %>';
|
3
|
+
var current_index = 0;
|
4
|
+
|
5
|
+
$(document).ready(function() {
|
6
|
+
prepPage();
|
7
|
+
loadLocalData(function(){
|
8
|
+
var request = loadRequest(request_id);
|
9
|
+
tagQueriesWithNPlusOne(request);
|
10
|
+
const preparedData = flattenData(request.stack);
|
11
|
+
|
12
|
+
$("#details_div").empty()
|
13
|
+
if(request.exception_message) {
|
14
|
+
$("#details_div").append(errorDetails(request));
|
15
|
+
}
|
16
|
+
$("#details_div").append(renderDetails(preparedData));
|
17
|
+
|
18
|
+
// if(request.has_n_plus_one){
|
19
|
+
// // Find all div elements with 'possible_n_plus_one' class
|
20
|
+
// $('div.possible_n_plus_one').each(function() {
|
21
|
+
// // Traverse up the parent hierarchy
|
22
|
+
// $(this).parents().each(function() {
|
23
|
+
// // If the parent has the 'has_children' class, trigger a click event
|
24
|
+
// $(this).show();
|
25
|
+
// });
|
26
|
+
// });
|
27
|
+
// }
|
28
|
+
});
|
29
|
+
});
|
30
|
+
|
31
|
+
function prepPage(){
|
32
|
+
$("#details_div").text("Loading Request Now...");
|
33
|
+
$("#options").empty();
|
34
|
+
|
35
|
+
// Create a new checkbox
|
36
|
+
var checkbox = $('<input />', {
|
37
|
+
type: 'checkbox',
|
38
|
+
id: 'chkShowMinorCalls',
|
39
|
+
value: 'showMinorCalls'
|
40
|
+
}).on('change', function() {
|
41
|
+
// Show/hide '.minor_call' elements based on checkbox status
|
42
|
+
if(this.checked) {
|
43
|
+
$('.minor_call').show();
|
44
|
+
} else {
|
45
|
+
$('.minor_call').hide();
|
46
|
+
}
|
47
|
+
});
|
48
|
+
|
49
|
+
// Create a label for the checkbox
|
50
|
+
var label = $('<label />', {
|
51
|
+
'for': 'chkShowMinorCalls',
|
52
|
+
text: 'Show Calls with less than 0.1ms duration',
|
53
|
+
});
|
54
|
+
|
55
|
+
// Append the checkbox and label to the div
|
56
|
+
$("#options").append(checkbox).append(label);
|
57
|
+
}
|
58
|
+
|
59
|
+
function flattenData(stackData, level = 0) {
|
60
|
+
var result = [];
|
61
|
+
|
62
|
+
stackData.forEach(function(stackItem, level) {
|
63
|
+
var start = new Date(stackItem.start_time);
|
64
|
+
var end = new Date(stackItem.end_time);
|
65
|
+
|
66
|
+
if(stackItem.sql) {
|
67
|
+
result.push({
|
68
|
+
start: start,
|
69
|
+
duration: stackItem.duration,
|
70
|
+
sql: stackItem.sql,
|
71
|
+
cached: stackItem.cached,
|
72
|
+
record_count: stackItem.record_count,
|
73
|
+
triggering_line: stackItem.triggering_line,
|
74
|
+
params: stackItem.params,
|
75
|
+
level: level,
|
76
|
+
possibleNPlusOne: stackItem.possibleNPlusOne
|
77
|
+
});
|
78
|
+
} else {
|
79
|
+
var children = [];
|
80
|
+
if (stackItem.sql_events && stackItem.sql_events.length > 0) {
|
81
|
+
children = children.concat(flattenData(stackItem.sql_events, level+1));
|
82
|
+
}
|
83
|
+
|
84
|
+
if (stackItem.children && stackItem.children.length > 0) {
|
85
|
+
children = children.concat(flattenData(stackItem.children, level+1));
|
86
|
+
}
|
87
|
+
result.push({
|
88
|
+
start: start,
|
89
|
+
duration: stackItem.duration,
|
90
|
+
method: stackItem.method,
|
91
|
+
method_line: stackItem.method_line,
|
92
|
+
triggering_line: stackItem.triggering_line,
|
93
|
+
children: children,
|
94
|
+
level
|
95
|
+
});
|
96
|
+
}
|
97
|
+
});
|
98
|
+
|
99
|
+
// sort by start_time
|
100
|
+
result.sort((a, b) => a.start - b.start);
|
101
|
+
|
102
|
+
return result;
|
103
|
+
}
|
104
|
+
function getDurationClass(duration) {
|
105
|
+
if (duration < 500) {
|
106
|
+
return "green-circle";
|
107
|
+
} else if (duration >= 500 && duration < 1000) {
|
108
|
+
return "amber-circle";
|
109
|
+
} else {
|
110
|
+
return "red-circle";
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
function getFormattedDate(date) {
|
115
|
+
return `${date.toLocaleDateString('en-GB')} ${date.toLocaleTimeString('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })}`;
|
116
|
+
}
|
117
|
+
|
118
|
+
function renderDetails(stackData, level = 0) {
|
119
|
+
return stackData.map((item, index) => {
|
120
|
+
var itemDiv = $("<div>").addClass('bounding-box');
|
121
|
+
|
122
|
+
var formattedDate = getFormattedDate(item.start);
|
123
|
+
var formattedDuration = parseFloat(parseFloat(item.duration).toFixed(3)) + 'ms';
|
124
|
+
var durationClass = getDurationClass(item.duration);
|
125
|
+
|
126
|
+
var durationSpan = $("<span>").addClass("duration").append(
|
127
|
+
$("<span>").addClass("duration-circle " + durationClass),
|
128
|
+
`${formattedDuration}`
|
129
|
+
);
|
130
|
+
|
131
|
+
if (item.method) { // If it's a method.
|
132
|
+
if(item.duration < 0.1 && level > 0){
|
133
|
+
itemDiv.addClass('minor_call');
|
134
|
+
}
|
135
|
+
itemDiv.addClass("method")
|
136
|
+
.append($('<span>').addClass('date').text(formattedDate), durationSpan,
|
137
|
+
$('<span>').addClass('description').text(item.method),
|
138
|
+
$('<span>').addClass('method_line').text(item.method_line),
|
139
|
+
$('<span>').addClass('trigger_line').text(item.triggering_line));
|
140
|
+
|
141
|
+
if(item.children && item.children.length > 0) {
|
142
|
+
itemDiv.prepend($("<span>").addClass('has_children').text("+"))
|
143
|
+
.append(
|
144
|
+
$("<div>").addClass("children")
|
145
|
+
.append(renderDetails(item.children, level +1))
|
146
|
+
.hide() // Hide the children initially
|
147
|
+
);
|
148
|
+
}
|
149
|
+
} else { // If it's an SQL event.
|
150
|
+
itemDiv.addClass("sql-event")
|
151
|
+
.append($('<span>').addClass('date').text(formattedDate), durationSpan,
|
152
|
+
$('<span>').addClass('record_count').text(item.record_count + ' Results'),
|
153
|
+
$('<span>').addClass('cached').text(item.cached ? 'Cached' : ''),
|
154
|
+
$('<span>').addClass('n_plus_one').text(item.possibleNPlusOne ? 'Possible N+1' : ''),
|
155
|
+
$('<span>').addClass('trigger_line').text(item.triggering_line),
|
156
|
+
$('<span>').addClass('sql').text(item.sql));
|
157
|
+
|
158
|
+
if(item.possibleNPlusOne) {
|
159
|
+
itemDiv.addClass('possible_n_plus_one');
|
160
|
+
}
|
161
|
+
if(item.params && item.params.length > 0) {
|
162
|
+
itemDiv.append($('<span>').addClass('params').text('Params: ' + item.params));
|
163
|
+
}
|
164
|
+
}
|
165
|
+
return itemDiv;
|
166
|
+
});
|
167
|
+
}
|
168
|
+
|
169
|
+
$(document).on('click', '.bounding-box', function(e) {
|
170
|
+
e.stopPropagation();
|
171
|
+
var toggleElement = $(this).find(".has_children:first");
|
172
|
+
if (toggleElement.length > 0) {
|
173
|
+
if (toggleElement.text() === "+") {
|
174
|
+
toggleElement.text("-");
|
175
|
+
} else {
|
176
|
+
toggleElement.text("+");
|
177
|
+
}
|
178
|
+
$(this).find(".children:first").slideToggle();
|
179
|
+
}
|
180
|
+
});
|
181
|
+
|
182
|
+
|
183
|
+
function errorDetails(request) {
|
184
|
+
// Create a container for the error details
|
185
|
+
var errorContainer = $('<div>').addClass('error_status');
|
186
|
+
|
187
|
+
// Create and append elements for the exception class and message
|
188
|
+
var exceptionClassElement = $('<h2>').text(request.exception_class).css('margin', '0');
|
189
|
+
var exceptionMessageElement = $('<p>').text(request.exception_message);
|
190
|
+
errorContainer.append(exceptionClassElement, exceptionMessageElement);
|
191
|
+
|
192
|
+
// Create and append an element for the backtrace
|
193
|
+
var backtraceElement = $('<ol>');
|
194
|
+
request.exception_backtrace.forEach(function(line) {
|
195
|
+
var lineElement = $('<li>').text(line);
|
196
|
+
backtraceElement.append(lineElement);
|
197
|
+
});
|
198
|
+
errorContainer.append(backtraceElement);
|
199
|
+
|
200
|
+
// Append the error details container to the body of the page
|
201
|
+
return errorContainer;
|
202
|
+
}
|
203
|
+
|
204
|
+
</script>
|
@@ -0,0 +1,74 @@
|
|
1
|
+
<script>
|
2
|
+
$(document).ready(function() {
|
3
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
4
|
+
renderTable(updatedData);
|
5
|
+
});
|
6
|
+
|
7
|
+
setInterval(function() {
|
8
|
+
var autoUpdate = $('#autoUpdate').is(':checked');
|
9
|
+
if (autoUpdate) {
|
10
|
+
fetchDataAndUpdateStorage(function(updatedData) {
|
11
|
+
renderTable(updatedData);
|
12
|
+
});
|
13
|
+
}
|
14
|
+
}, 5000);
|
15
|
+
|
16
|
+
addSortingClick();
|
17
|
+
});
|
18
|
+
|
19
|
+
function renderTable(data, target = null) {
|
20
|
+
data = data.filter(item => item.duration > 1500);
|
21
|
+
data = sortDataBy(current_sort_field, data);
|
22
|
+
|
23
|
+
// Get a reference to the table body
|
24
|
+
var tableBody = target || $('table tbody');
|
25
|
+
|
26
|
+
// Clear the table body
|
27
|
+
tableBody.empty();
|
28
|
+
|
29
|
+
// Create new table rows for each data item
|
30
|
+
data.forEach(function(item) {
|
31
|
+
var row = $('<tr>');
|
32
|
+
|
33
|
+
$('<td>').text(new Date(item['start_time']).toLocaleString()).appendTo(row);
|
34
|
+
$('<td>').text(item['controller'] + '#' + item['action']).appendTo(row);
|
35
|
+
$('<td>').text(item['status']).appendTo(row);
|
36
|
+
$('<td>').append(renderDuration(item['duration'])).appendTo(row);
|
37
|
+
$('<td>').append(renderDuration(item['db_runtime'])).appendTo(row);
|
38
|
+
$('<td>').append(renderDuration(item['view_runtime'])).appendTo(row);
|
39
|
+
// Action column
|
40
|
+
var actionTd = $('<td>');
|
41
|
+
var actionButton = $('<button>').text('Details');
|
42
|
+
actionButton.on('click', function() {
|
43
|
+
// Replace 'your-details-url' with the actual URL where the details are to be fetched.
|
44
|
+
// It's assumed the ID is required as a URL parameter, modify as per your requirements.
|
45
|
+
window.location.href = '<%= request_details_path %>?request_id=' + item['request_id'];
|
46
|
+
});
|
47
|
+
actionButton.appendTo(actionTd);
|
48
|
+
actionTd.appendTo(row);
|
49
|
+
|
50
|
+
// Append the row to the table body
|
51
|
+
tableBody.append(row);
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
function renderDuration(duration) {
|
56
|
+
var durationClass;
|
57
|
+
if (duration > 0 && duration < 500) {
|
58
|
+
durationClass = "duration-circle green-circle";
|
59
|
+
} else if (duration >= 500 && duration < 1000) {
|
60
|
+
durationClass = "duration-circle amber-circle";
|
61
|
+
} else if (duration >= 1000){
|
62
|
+
durationClass = "duration-circle red-circle";
|
63
|
+
}
|
64
|
+
|
65
|
+
var formattedDuration = duration == null || duration == 0 ? 'n/a' : parseFloat(parseFloat(duration).toFixed(3)) + 'ms';
|
66
|
+
|
67
|
+
// Create the duration span with the circle and the duration value
|
68
|
+
return $("<span>").addClass("duration").append(
|
69
|
+
$("<span>").addClass(durationClass),
|
70
|
+
`${formattedDuration}`
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
74
|
+
</script>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%= render 'builder_apm/shared/header' %>
|
2
|
+
|
3
|
+
<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="real_duration_time">Duration (ms)</th>
|
10
|
+
<th class="sortable" data-field="calc_db_runtime">DB Runtime (ms)</th>
|
11
|
+
<th class="sortable" data-field="view_runtime">View Runtime (ms)</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<!-- Table content will be populated here by JavaScript -->
|
16
|
+
</tbody>
|
17
|
+
</table>
|
18
|
+
|
19
|
+
<%= render 'builder_apm/js/n_plus_one' %>
|
20
|
+
|
21
|
+
<%= render 'builder_apm/shared/footer' %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%= render 'builder_apm/shared/header' %>
|
2
|
+
|
3
|
+
<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="real_duration_time">Duration (ms)</th>
|
10
|
+
<th class="sortable" data-field="calc_db_runtime">DB Runtime (ms)</th>
|
11
|
+
<th class="sortable" data-field="view_runtime">View Runtime (ms)</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<!-- Table content will be populated here by JavaScript -->
|
16
|
+
</tbody>
|
17
|
+
</table>
|
18
|
+
|
19
|
+
<%= render 'builder_apm/js/recent_requests' %>
|
20
|
+
|
21
|
+
<%= render 'builder_apm/shared/footer' %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= render 'builder_apm/shared/header' %>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th class="sortable" data-field="controller">Controller#Action</th>
|
7
|
+
<th class="sortable" data-field="path">Url</th>
|
8
|
+
<th class="sortable" data-field="count">Requests</th>
|
9
|
+
<th class="sortable" data-field="averageDuration">Avg Duration (ms)</th>
|
10
|
+
<th class="sortable" data-field="averageDbRuntime">Avg DB Runtime (ms)</th>
|
11
|
+
<th class="sortable" data-field="averageViewRuntime">Avg View Runtime (ms)</th>
|
12
|
+
<th class="sortable" data-field="slowestDuration">Slowest Duration (ms)</th>
|
13
|
+
<th class="sortable" data-field="slowestDbRuntime">Slowest DB Runtime (ms)</th>
|
14
|
+
<th class="sortable" data-field="slowestViewRuntime">Slowest View Runtime (ms)</th>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
<!-- Table content will be populated here by JavaScript -->
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
|
22
|
+
<%= render 'builder_apm/js/request_analysis' %>
|
23
|
+
|
24
|
+
<%= render 'builder_apm/shared/footer' %>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>BuilderApm Dashboard</title>
|
5
|
+
<%= render 'builder_apm/css/main' %>
|
6
|
+
<%= render 'builder_apm/css/dark' %>
|
7
|
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
8
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.js"></script>
|
9
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.css" />
|
10
|
+
<%= render 'builder_apm/js/data_fetcher' %>
|
11
|
+
<script>
|
12
|
+
$(document).ready(function() {
|
13
|
+
|
14
|
+
// Check for saved 'darkMode' in localStorage
|
15
|
+
if (localStorage.getItem('darkMode') === 'true') {
|
16
|
+
$('body').addClass('dark');
|
17
|
+
}
|
18
|
+
|
19
|
+
$('#clearData').on('click', function() {
|
20
|
+
localStorage.removeItem('builder_apm_requests');
|
21
|
+
localStorage.removeItem('builder_apm_cursor');
|
22
|
+
alert('Local Data Cleared');
|
23
|
+
});
|
24
|
+
$('#darkModeToggle').on('click', function() {
|
25
|
+
$('body').toggleClass('dark');
|
26
|
+
|
27
|
+
// Save the current mode in localStorage
|
28
|
+
if ($('body').hasClass('dark')) {
|
29
|
+
localStorage.setItem('darkMode', 'true');
|
30
|
+
} else {
|
31
|
+
localStorage.setItem('darkMode', 'false');
|
32
|
+
}
|
33
|
+
});
|
34
|
+
$('body').show();
|
35
|
+
});
|
36
|
+
</script>
|
37
|
+
</head>
|
38
|
+
<body>
|
39
|
+
<h1 id="header">BuilderApm Dashboard</h1>
|
40
|
+
<nav id="navbar">
|
41
|
+
<ul>
|
42
|
+
<li><%= link_to 'Dashboard', dashboard_path, class: ("active" if current_page?(dashboard_path)) %></li>
|
43
|
+
<li><%= link_to 'Request Analysis', request_analysis_path, class: ("active" if current_page?(request_analysis_path)) %></li>
|
44
|
+
<li><%= link_to '500 Errors', errors_500_path, class: ("active" if current_page?(errors_500_path)) %></li>
|
45
|
+
<li><%= link_to 'Recent Requests', recent_requests_path, class: ("active" if current_page?(recent_requests_path)) %></li>
|
46
|
+
<li><%= link_to 'Slow Requests', slow_requests_path, class: ("active" if current_page?(slow_requests_path)) %></li>
|
47
|
+
<li><%= link_to 'N+1', n_plus_one_path, class: ("active" if current_page?(n_plus_one_path)) %></li>
|
48
|
+
<li id="dark-mode-toggle"><button id="darkModeToggle" class="nav-button">Toggle Dark Mode</button></li>
|
49
|
+
<li id="dark-mode-toggle"><button id="clearData" class="nav-button">Clear Data</button></li>
|
50
|
+
</ul>
|
51
|
+
</nav>
|
52
|
+
<div id="options">
|
53
|
+
<input type="checkbox" id="autoUpdate" name="autoUpdate">
|
54
|
+
<label for="autoUpdate">Auto-Update</label>
|
55
|
+
</div>
|