pg_insights 0.3.2 → 0.4.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/pg_insights/application.js +91 -21
- data/app/assets/javascripts/pg_insights/plan_performance.js +53 -0
- data/app/assets/javascripts/pg_insights/query_comparison.js +1129 -0
- data/app/assets/javascripts/pg_insights/results/view_toggles.js +26 -5
- data/app/assets/javascripts/pg_insights/results.js +231 -1
- data/app/assets/stylesheets/pg_insights/analysis.css +2628 -0
- data/app/assets/stylesheets/pg_insights/application.css +51 -1
- data/app/assets/stylesheets/pg_insights/results.css +12 -1
- data/app/controllers/pg_insights/insights_controller.rb +486 -9
- data/app/helpers/pg_insights/application_helper.rb +339 -0
- data/app/helpers/pg_insights/insights_helper.rb +567 -0
- data/app/jobs/pg_insights/query_analysis_job.rb +142 -0
- data/app/models/pg_insights/query_execution.rb +198 -0
- data/app/services/pg_insights/query_analysis_service.rb +269 -0
- data/app/views/layouts/pg_insights/application.html.erb +2 -0
- data/app/views/pg_insights/insights/_compare_view.html.erb +264 -0
- data/app/views/pg_insights/insights/_empty_state.html.erb +9 -0
- data/app/views/pg_insights/insights/_execution_table_view.html.erb +86 -0
- data/app/views/pg_insights/insights/_history_bar.html.erb +33 -0
- data/app/views/pg_insights/insights/_perf_view.html.erb +244 -0
- data/app/views/pg_insights/insights/_plan_nodes.html.erb +12 -0
- data/app/views/pg_insights/insights/_plan_tree.html.erb +30 -0
- data/app/views/pg_insights/insights/_plan_tree_modern.html.erb +12 -0
- data/app/views/pg_insights/insights/_plan_view.html.erb +159 -0
- data/app/views/pg_insights/insights/_query_panel.html.erb +3 -2
- data/app/views/pg_insights/insights/_result.html.erb +19 -4
- data/app/views/pg_insights/insights/_results_info.html.erb +33 -9
- data/app/views/pg_insights/insights/_results_info_empty.html.erb +10 -0
- data/app/views/pg_insights/insights/_results_panel.html.erb +7 -9
- data/app/views/pg_insights/insights/_results_table.html.erb +0 -5
- data/app/views/pg_insights/insights/_visual_view.html.erb +212 -0
- data/app/views/pg_insights/insights/index.html.erb +4 -1
- data/app/views/pg_insights/timeline/compare.html.erb +3 -3
- data/config/routes.rb +6 -0
- data/lib/generators/pg_insights/install_generator.rb +20 -14
- data/lib/generators/pg_insights/templates/db/migrate/create_pg_insights_query_executions.rb +45 -0
- data/lib/pg_insights/engine.rb +8 -0
- data/lib/pg_insights/version.rb +1 -1
- data/lib/pg_insights.rb +30 -2
- metadata +20 -2
@@ -0,0 +1,264 @@
|
|
1
|
+
<div id="compare-view" class="view-content" style="display: none;">
|
2
|
+
<div class="compare-container">
|
3
|
+
<div class="compare-header">
|
4
|
+
<div class="query-selector">
|
5
|
+
<div class="query-card query-a">
|
6
|
+
<div class="query-label">Query A</div>
|
7
|
+
<div class="query-title" id="compare-title-a">Select a query</div>
|
8
|
+
<div class="query-summary" id="compare-summary-a"></div>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="vs-indicator">
|
12
|
+
<span class="vs-text">VS</span>
|
13
|
+
<button class="swap-btn" onclick="swapQueries()" title="Swap queries">
|
14
|
+
🔄
|
15
|
+
</button>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="query-card query-b">
|
19
|
+
<div class="query-label">Query B</div>
|
20
|
+
<div class="query-title" id="compare-title-b">Select a query</div>
|
21
|
+
<div class="query-summary" id="compare-summary-b"></div>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="compare-actions">
|
26
|
+
<button class="btn-analyze" onclick="performComparison()">
|
27
|
+
📊 Analyze Comparison
|
28
|
+
</button>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div id="comparison-results" class="comparison-results" style="display: none;">
|
33
|
+
<div class="performance-comparison">
|
34
|
+
<h4>⚡ Performance Metrics</h4>
|
35
|
+
|
36
|
+
<div class="metrics-overview">
|
37
|
+
<div class="metric-card" id="time-metric">
|
38
|
+
<div class="metric-label">Execution Time</div>
|
39
|
+
<div class="metric-values">
|
40
|
+
<span class="value-a">-</span>
|
41
|
+
<span class="vs-divider">vs</span>
|
42
|
+
<span class="value-b">-</span>
|
43
|
+
</div>
|
44
|
+
<div class="metric-difference"></div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div class="metric-card" id="cost-metric">
|
48
|
+
<div class="metric-label">Query Cost</div>
|
49
|
+
<div class="metric-values">
|
50
|
+
<span class="value-a">-</span>
|
51
|
+
<span class="vs-divider">vs</span>
|
52
|
+
<span class="value-b">-</span>
|
53
|
+
</div>
|
54
|
+
<div class="metric-difference"></div>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<div class="metric-card" id="rows-metric">
|
58
|
+
<div class="metric-label">Rows Processed</div>
|
59
|
+
<div class="metric-values">
|
60
|
+
<span class="value-a">-</span>
|
61
|
+
<span class="vs-divider">vs</span>
|
62
|
+
<span class="value-b">-</span>
|
63
|
+
</div>
|
64
|
+
<div class="metric-difference"></div>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div class="metric-card" id="efficiency-metric">
|
68
|
+
<div class="metric-label">Efficiency Ratio</div>
|
69
|
+
<div class="metric-values">
|
70
|
+
<span class="value-a">-</span>
|
71
|
+
<span class="vs-divider">vs</span>
|
72
|
+
<span class="value-b">-</span>
|
73
|
+
</div>
|
74
|
+
<div class="metric-difference"></div>
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
|
78
|
+
<div class="detailed-metrics">
|
79
|
+
<h5>Detailed Breakdown</h5>
|
80
|
+
<div class="metrics-table-wrapper">
|
81
|
+
<table class="metrics-table">
|
82
|
+
<thead>
|
83
|
+
<tr>
|
84
|
+
<th>Metric</th>
|
85
|
+
<th>Query A</th>
|
86
|
+
<th>Query B</th>
|
87
|
+
<th>Difference</th>
|
88
|
+
<th>Impact</th>
|
89
|
+
</tr>
|
90
|
+
</thead>
|
91
|
+
<tbody id="metrics-table-body">
|
92
|
+
</tbody>
|
93
|
+
</table>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
</div>
|
97
|
+
|
98
|
+
<div class="winner-summary" id="winner-summary">
|
99
|
+
<div class="winner-badge">
|
100
|
+
<span class="winner-icon">🏆</span>
|
101
|
+
<span class="winner-text">Query A performs better overall</span>
|
102
|
+
</div>
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<div class="plans-comparison">
|
106
|
+
<h4>🌳 Execution Plan Analysis</h4>
|
107
|
+
|
108
|
+
<div class="plan-overview">
|
109
|
+
<div class="plan-stats">
|
110
|
+
<div class="stat-item">
|
111
|
+
<span class="stat-label">Plan Nodes:</span>
|
112
|
+
<span class="stat-values">
|
113
|
+
<span id="nodes-a">-</span> vs <span id="nodes-b">-</span>
|
114
|
+
</span>
|
115
|
+
</div>
|
116
|
+
<div class="stat-item">
|
117
|
+
<span class="stat-label">Max Depth:</span>
|
118
|
+
<span class="stat-values">
|
119
|
+
<span id="depth-a">-</span> vs <span id="depth-b">-</span>
|
120
|
+
</span>
|
121
|
+
</div>
|
122
|
+
<div class="stat-item">
|
123
|
+
<span class="stat-label">Scan Types:</span>
|
124
|
+
<span class="stat-values">
|
125
|
+
<span id="scans-a">-</span> vs <span id="scans-b">-</span>
|
126
|
+
</span>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
|
130
|
+
<div class="plan-controls">
|
131
|
+
<button class="plan-toggle active" data-view="side-by-side">Side by Side</button>
|
132
|
+
<button class="plan-toggle" data-view="overlay">Overlay</button>
|
133
|
+
<button class="plan-toggle" data-view="diff">Differences</button>
|
134
|
+
</div>
|
135
|
+
</div>
|
136
|
+
|
137
|
+
<div class="plans-grid" id="side-by-side-view">
|
138
|
+
<div class="plan-column">
|
139
|
+
<div class="plan-header">
|
140
|
+
<h5>Query A Plan</h5>
|
141
|
+
<div class="plan-meta">
|
142
|
+
<span class="plan-efficiency" id="efficiency-a">-</span>
|
143
|
+
<span class="plan-bottleneck" id="bottleneck-a">-</span>
|
144
|
+
</div>
|
145
|
+
</div>
|
146
|
+
<div class="plan-content" id="plan-a-content">
|
147
|
+
</div>
|
148
|
+
</div>
|
149
|
+
<div class="plan-column">
|
150
|
+
<div class="plan-header">
|
151
|
+
<h5>Query B Plan</h5>
|
152
|
+
<div class="plan-meta">
|
153
|
+
<span class="plan-efficiency" id="efficiency-b">-</span>
|
154
|
+
<span class="plan-bottleneck" id="bottleneck-b">-</span>
|
155
|
+
</div>
|
156
|
+
</div>
|
157
|
+
<div class="plan-content" id="plan-b-content">
|
158
|
+
</div>
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
|
162
|
+
<div class="plan-overlay" id="overlay-view" style="display: none;">
|
163
|
+
<div class="overlay-controls">
|
164
|
+
<div class="overlay-legend">
|
165
|
+
<span class="legend-item">
|
166
|
+
<span class="legend-color query-a-color"></span> Query A
|
167
|
+
</span>
|
168
|
+
<span class="legend-item">
|
169
|
+
<span class="legend-color query-b-color"></span> Query B
|
170
|
+
</span>
|
171
|
+
<span class="legend-item">
|
172
|
+
<span class="legend-color common-color"></span> Common
|
173
|
+
</span>
|
174
|
+
</div>
|
175
|
+
<div class="overlay-options">
|
176
|
+
<label>
|
177
|
+
<input type="checkbox" id="highlight-differences" checked> Highlight Differences
|
178
|
+
</label>
|
179
|
+
<label>
|
180
|
+
<input type="checkbox" id="show-metrics" checked> Show Metrics
|
181
|
+
</label>
|
182
|
+
</div>
|
183
|
+
</div>
|
184
|
+
|
185
|
+
<div class="overlay-content" id="overlay-content">
|
186
|
+
</div>
|
187
|
+
</div>
|
188
|
+
|
189
|
+
<div class="plan-differences" id="diff-view" style="display: none;">
|
190
|
+
<div class="diff-summary" id="diff-summary">
|
191
|
+
</div>
|
192
|
+
<div class="diff-details" id="diff-details">
|
193
|
+
</div>
|
194
|
+
</div>
|
195
|
+
</div>
|
196
|
+
|
197
|
+
<div class="insights-comparison" id="insights-section">
|
198
|
+
<h4>💡 Optimization Analysis</h4>
|
199
|
+
|
200
|
+
<div class="optimization-score">
|
201
|
+
<div class="score-card">
|
202
|
+
<div class="score-header">Query A Optimization</div>
|
203
|
+
<div class="score-value" id="score-a">-</div>
|
204
|
+
<div class="score-grade" id="grade-a">-</div>
|
205
|
+
</div>
|
206
|
+
|
207
|
+
<div class="score-comparison">
|
208
|
+
<div class="score-arrow" id="score-arrow">↔</div>
|
209
|
+
<div class="score-improvement" id="score-improvement">-</div>
|
210
|
+
</div>
|
211
|
+
|
212
|
+
<div class="score-card">
|
213
|
+
<div class="score-header">Query B Optimization</div>
|
214
|
+
<div class="score-value" id="score-b">-</div>
|
215
|
+
<div class="score-grade" id="grade-b">-</div>
|
216
|
+
</div>
|
217
|
+
</div>
|
218
|
+
|
219
|
+
<div class="findings-section">
|
220
|
+
<h5>Key Findings</h5>
|
221
|
+
<div class="findings-grid">
|
222
|
+
<div class="finding-category">
|
223
|
+
<h6>🚀 Performance Issues</h6>
|
224
|
+
<ul id="performance-issues">
|
225
|
+
</ul>
|
226
|
+
</div>
|
227
|
+
|
228
|
+
<div class="finding-category">
|
229
|
+
<h6>📊 Index Usage</h6>
|
230
|
+
<ul id="index-findings">
|
231
|
+
</ul>
|
232
|
+
</div>
|
233
|
+
|
234
|
+
<div class="finding-category">
|
235
|
+
<h6>🔧 Optimization Opportunities</h6>
|
236
|
+
<ul id="optimization-opportunities">
|
237
|
+
</ul>
|
238
|
+
</div>
|
239
|
+
</div>
|
240
|
+
</div>
|
241
|
+
|
242
|
+
<div class="recommendations-section">
|
243
|
+
<h5>Recommended Actions</h5>
|
244
|
+
<div class="recommendations-list" id="recommendations-list">
|
245
|
+
</div>
|
246
|
+
</div>
|
247
|
+
|
248
|
+
<div class="insights-list" id="insights-list">
|
249
|
+
</div>
|
250
|
+
</div>
|
251
|
+
</div>
|
252
|
+
|
253
|
+
<div id="comparison-loading" class="comparison-loading" style="display: none;">
|
254
|
+
<div class="loading-spinner"></div>
|
255
|
+
<p>Comparing queries...</p>
|
256
|
+
</div>
|
257
|
+
|
258
|
+
<div id="comparison-empty" class="comparison-empty">
|
259
|
+
<div class="empty-icon">⚖️</div>
|
260
|
+
<h3>Ready to Compare</h3>
|
261
|
+
<p>Run some queries with the "Analyze" button first, then select 2 queries from the history bar to start comparing their performance and execution plans.</p>
|
262
|
+
</div>
|
263
|
+
</div>
|
264
|
+
</div>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<div id="empty-state" class="view-content" style="display: block;">
|
2
|
+
<div class="empty-results">
|
3
|
+
<div class="empty-content">
|
4
|
+
<div class="empty-icon">📊</div>
|
5
|
+
<h3>Ready to Query</h3>
|
6
|
+
<p>Enter a SQL query on the left and click "Execute" to see results here.</p>
|
7
|
+
</div>
|
8
|
+
</div>
|
9
|
+
</div>
|
@@ -0,0 +1,86 @@
|
|
1
|
+
<div id="table-view" class="view-content">
|
2
|
+
<% if execution.has_result_data? %>
|
3
|
+
<div class="table-controls">
|
4
|
+
<div class="control-group">
|
5
|
+
<button id="fitColumns" class="btn btn-sm">↕ Fit Columns</button>
|
6
|
+
<button id="resetTable" class="btn btn-sm">⚙ Reset</button>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div id="columnPanel" class="column-panel" style="display: none;">
|
10
|
+
<div class="panel-header">
|
11
|
+
<h4>Column Visibility</h4>
|
12
|
+
<div class="panel-actions">
|
13
|
+
<button id="showAllColumns" class="btn btn-xs">Show All</button>
|
14
|
+
<button id="hideAllColumns" class="btn btn-xs">Hide All</button>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="column-toggles">
|
19
|
+
<% execution.result_data['columns'].each_with_index do |column, index| %>
|
20
|
+
<label class="column-toggle-label">
|
21
|
+
<input type="checkbox"
|
22
|
+
class="column-toggle"
|
23
|
+
data-column="<%= index + 1 %>"
|
24
|
+
checked>
|
25
|
+
<span class="toggle-text"><%= column %></span>
|
26
|
+
</label>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div id="tableScroll" class="table-scroll" tabindex="0">
|
33
|
+
<table id="resultsTable" class="results-table">
|
34
|
+
<thead>
|
35
|
+
<tr>
|
36
|
+
<th class="row-num">#</th>
|
37
|
+
<% execution.result_data['columns'].each_with_index do |column, index| %>
|
38
|
+
<th data-column="<%= index + 1 %>">
|
39
|
+
<div class="header-content">
|
40
|
+
<span class="header-text"><%= column %></span>
|
41
|
+
<span class="header-type">auto</span>
|
42
|
+
</div>
|
43
|
+
</th>
|
44
|
+
<% end %>
|
45
|
+
</tr>
|
46
|
+
</thead>
|
47
|
+
<tbody>
|
48
|
+
<% execution.result_data['rows'].each_with_index do |row, row_index| %>
|
49
|
+
<tr>
|
50
|
+
<td class="row-num"><%= row_index + 1 %></td>
|
51
|
+
<% row.each_with_index do |cell, col_index| %>
|
52
|
+
<td data-column="<%= col_index + 1 %>">
|
53
|
+
<div class="cell-content">
|
54
|
+
<% if cell.nil? %>
|
55
|
+
<span class="null-value">NULL</span>
|
56
|
+
<% elsif cell.to_s.empty? %>
|
57
|
+
<span class="empty-value">empty</span>
|
58
|
+
<% else %>
|
59
|
+
<%= cell %>
|
60
|
+
<% end %>
|
61
|
+
</div>
|
62
|
+
</td>
|
63
|
+
<% end %>
|
64
|
+
</tr>
|
65
|
+
<% end %>
|
66
|
+
</tbody>
|
67
|
+
</table>
|
68
|
+
</div>
|
69
|
+
|
70
|
+
<div id="scrollIndicatorH" class="scroll-indicator horizontal">
|
71
|
+
<div id="scrollThumbH" class="scroll-thumb"></div>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
<div id="scrollIndicatorV" class="scroll-indicator vertical">
|
75
|
+
<div id="scrollThumbV" class="scroll-thumb"></div>
|
76
|
+
</div>
|
77
|
+
<% else %>
|
78
|
+
<div class="empty-results">
|
79
|
+
<div class="empty-content">
|
80
|
+
<div class="empty-icon">📊</div>
|
81
|
+
<h3>No Result Data</h3>
|
82
|
+
<p>This execution did not return table data.</p>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
<% end %>
|
86
|
+
</div>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<div id="query-history-bar" class="history-bar collapsed">
|
2
|
+
<div class="history-header" onclick="toggleHistoryBar()">
|
3
|
+
<div class="history-title">
|
4
|
+
<span class="history-icon">📊</span>
|
5
|
+
<span class="title-text">Recent Analyses</span>
|
6
|
+
<span class="history-count">(0)</span>
|
7
|
+
</div>
|
8
|
+
<div class="history-controls">
|
9
|
+
<span class="selected-count" style="display: none;">
|
10
|
+
<span id="selected-count">0</span> selected
|
11
|
+
</span>
|
12
|
+
<button id="compare-btn" class="btn-compare" style="display: none;" onclick="triggerCompare(event)">
|
13
|
+
⚖️ Compare
|
14
|
+
</button>
|
15
|
+
<span class="expand-arrow">▼</span>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="history-content">
|
20
|
+
<div class="history-loading">
|
21
|
+
<div class="loading-spinner"></div>
|
22
|
+
<span>Loading recent queries...</span>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="history-items" style="display: none;">
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<div class="history-empty" style="display: none;">
|
29
|
+
<div class="empty-icon">🔍</div>
|
30
|
+
<span>No analyzed queries yet. Run some queries with the "Analyze" button!</span>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
@@ -0,0 +1,244 @@
|
|
1
|
+
<div id="perf-view" class="view-content" style="display: none;">
|
2
|
+
<div class="perf-dashboard">
|
3
|
+
<!-- Performance Status with Context -->
|
4
|
+
<div class="perf-header <%= execution.has_performance_issues? ? 'status-warning-header' : 'status-success-header' %>">
|
5
|
+
<div class="perf-status <%= execution.has_performance_issues? ? 'status-warning' : 'status-success' %>">
|
6
|
+
<span class="status-icon"><%= execution.has_performance_issues? ? '⚠' : '✓' %></span>
|
7
|
+
<div class="status-details">
|
8
|
+
<span class="status-text">
|
9
|
+
<%= execution.has_performance_issues? ? 'Performance Issues Detected' : 'Good Performance' %>
|
10
|
+
</span>
|
11
|
+
<span class="status-context">
|
12
|
+
<%= execution.total_time_ms ? "#{execution.total_time_ms.round(1)}ms execution" : '' %>
|
13
|
+
<% if execution.query_cost %>• Cost: <%= execution.query_cost.round(0) %><% end %>
|
14
|
+
</span>
|
15
|
+
</div>
|
16
|
+
<div class="perf-rating-group">
|
17
|
+
<span class="perf-rating"><%= get_performance_rating(execution.total_time_ms) %></span>
|
18
|
+
<span class="perf-score"><%= calculate_overall_performance_score(execution) %>/100</span>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<!-- Enhanced Metrics Grid -->
|
24
|
+
<div class="perf-metrics-grid">
|
25
|
+
<!-- Enhanced Timing Section -->
|
26
|
+
<div class="metrics-group timing-group">
|
27
|
+
<div class="group-header">
|
28
|
+
<span class="header-title">Execution Timing</span>
|
29
|
+
<span class="header-insight">
|
30
|
+
<% if execution.planning_time_ms && execution.execution_time_ms %>
|
31
|
+
<%= planning_vs_execution_insight(execution.planning_time_ms, execution.execution_time_ms) %>
|
32
|
+
<% end %>
|
33
|
+
</span>
|
34
|
+
</div>
|
35
|
+
<div class="timing-bars">
|
36
|
+
<div class="timing-row">
|
37
|
+
<span class="timing-label">Planning</span>
|
38
|
+
<div class="timing-bar-container">
|
39
|
+
<div class="timing-bar planning-bar" style="width: <%= timing_percentage(execution.planning_time_ms, execution.total_time_ms) %>%"></div>
|
40
|
+
</div>
|
41
|
+
<div class="timing-value-group">
|
42
|
+
<span class="timing-value"><%= execution.planning_time_ms ? "#{execution.planning_time_ms.round(1)}ms" : 'N/A' %></span>
|
43
|
+
<span class="timing-percentage">
|
44
|
+
<%= execution.planning_time_ms && execution.total_time_ms ? "(#{timing_percentage(execution.planning_time_ms, execution.total_time_ms).round(1)}%)" : '' %>
|
45
|
+
</span>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
<div class="timing-row">
|
49
|
+
<span class="timing-label">Execution</span>
|
50
|
+
<div class="timing-bar-container">
|
51
|
+
<div class="timing-bar execution-bar" style="width: <%= timing_percentage(execution.execution_time_ms, execution.total_time_ms) %>%"></div>
|
52
|
+
</div>
|
53
|
+
<div class="timing-value-group">
|
54
|
+
<span class="timing-value"><%= execution.execution_time_ms ? "#{execution.execution_time_ms.round(1)}ms" : 'N/A' %></span>
|
55
|
+
<span class="timing-percentage">
|
56
|
+
<%= execution.execution_time_ms && execution.total_time_ms ? "(#{timing_percentage(execution.execution_time_ms, execution.total_time_ms).round(1)}%)" : '' %>
|
57
|
+
</span>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
<div class="timing-row total-row">
|
61
|
+
<span class="timing-label">Total</span>
|
62
|
+
<span class="timing-total-value">
|
63
|
+
<%= execution.total_time_ms ? "#{execution.total_time_ms.round(1)}ms" : 'N/A' %>
|
64
|
+
<span class="timing-benchmark">
|
65
|
+
<%= performance_benchmark_text(execution.total_time_ms) %>
|
66
|
+
</span>
|
67
|
+
</span>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<!-- Enhanced Key Metrics Section -->
|
73
|
+
<div class="metrics-group">
|
74
|
+
<div class="group-header">
|
75
|
+
<span class="header-title">Key Metrics</span>
|
76
|
+
<span class="header-insight">Query Profile</span>
|
77
|
+
</div>
|
78
|
+
<div class="perf-metrics-table">
|
79
|
+
<div class="metric-row">
|
80
|
+
<span class="metric-label">Query Cost</span>
|
81
|
+
<div class="metric-value-group">
|
82
|
+
<span class="metric-value"><%= execution.query_cost ? number_with_delimiter(execution.query_cost.round(0)) : 'N/A' %></span>
|
83
|
+
<span class="metric-threshold"><%= cost_threshold_indicator(execution.query_cost) %></span>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
<div class="metric-row">
|
87
|
+
<span class="metric-label">Rows Returned</span>
|
88
|
+
<div class="metric-value-group">
|
89
|
+
<span class="metric-value"><%= execution.result_rows_count || extract_rich_plan_metrics(execution)[:rows_returned] || 'N/A' %></span>
|
90
|
+
<% if execution.result_columns_count %>
|
91
|
+
<span class="metric-context"><%= execution.result_columns_count %> cols</span>
|
92
|
+
<% end %>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
<div class="metric-row">
|
96
|
+
<span class="metric-label">Efficiency</span>
|
97
|
+
<div class="metric-value-group">
|
98
|
+
<span class="metric-value"><%= calculate_efficiency_score(execution) %></span>
|
99
|
+
<span class="metric-context">cost/row</span>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
<% if execution.execution_plan.present? %>
|
103
|
+
<% plan_metrics = extract_rich_plan_metrics(execution) %>
|
104
|
+
<% if plan_metrics[:memory_usage_kb] && plan_metrics[:memory_usage_kb] > 0 %>
|
105
|
+
<div class="metric-row">
|
106
|
+
<span class="metric-label">Peak Memory</span>
|
107
|
+
<div class="metric-value-group">
|
108
|
+
<span class="metric-value"><%= format_memory_size(plan_metrics[:memory_usage_kb]) %></span>
|
109
|
+
<span class="metric-threshold"><%= memory_threshold_indicator(plan_metrics[:memory_usage_kb]) %></span>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
<% end %>
|
113
|
+
<% if plan_metrics[:workers_launched] && plan_metrics[:workers_launched] > 0 %>
|
114
|
+
<div class="metric-row">
|
115
|
+
<span class="metric-label">Parallelization</span>
|
116
|
+
<div class="metric-value-group">
|
117
|
+
<span class="metric-value"><%= "#{plan_metrics[:workers_launched]}/#{plan_metrics[:workers_planned]}" %></span>
|
118
|
+
<span class="metric-context">
|
119
|
+
<%= "#{((plan_metrics[:workers_launched].to_f / plan_metrics[:workers_planned]) * 100).round(0)}% util" %>
|
120
|
+
</span>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
<% end %>
|
124
|
+
<div class="metric-row">
|
125
|
+
<span class="metric-label">I/O Efficiency</span>
|
126
|
+
<div class="metric-value-group">
|
127
|
+
<span class="metric-value">
|
128
|
+
<%= plan_metrics[:rows_scanned] && plan_metrics[:rows_returned] ?
|
129
|
+
"#{((plan_metrics[:rows_returned].to_f / plan_metrics[:rows_scanned]) * 100).round(1)}%" : 'N/A' %>
|
130
|
+
</span>
|
131
|
+
<span class="metric-context">
|
132
|
+
<%= plan_metrics[:rows_scanned] ? "#{number_with_delimiter(plan_metrics[:rows_scanned])} scanned" : '' %>
|
133
|
+
</span>
|
134
|
+
</div>
|
135
|
+
</div>
|
136
|
+
<% end %>
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
|
140
|
+
<!-- Enhanced Performance Analysis Section -->
|
141
|
+
<% if execution.execution_plan.present? %>
|
142
|
+
<div class="metrics-group analysis-group">
|
143
|
+
<div class="group-header">
|
144
|
+
<span class="header-title">Performance Analysis</span>
|
145
|
+
<span class="header-insight">Optimization Score</span>
|
146
|
+
</div>
|
147
|
+
<% advanced_metrics = calculate_advanced_metrics(extract_rich_plan_metrics(execution)) %>
|
148
|
+
<div class="analysis-table">
|
149
|
+
<div class="analysis-row">
|
150
|
+
<span class="analysis-metric">I/O Efficiency</span>
|
151
|
+
<span class="analysis-score <%= score_css_class(advanced_metrics[:io_efficiency]) %>"><%= advanced_metrics[:io_efficiency] %></span>
|
152
|
+
<div class="analysis-details">
|
153
|
+
<span class="analysis-desc">Row selectivity</span>
|
154
|
+
<span class="analysis-impact">
|
155
|
+
<%= io_efficiency_impact(extract_rich_plan_metrics(execution)) %>
|
156
|
+
</span>
|
157
|
+
</div>
|
158
|
+
</div>
|
159
|
+
<div class="analysis-row">
|
160
|
+
<span class="analysis-metric">Memory Usage</span>
|
161
|
+
<span class="analysis-score <%= score_css_class(advanced_metrics[:memory_efficiency]) %>"><%= advanced_metrics[:memory_efficiency] %></span>
|
162
|
+
<div class="analysis-details">
|
163
|
+
<span class="analysis-desc">Resource efficiency</span>
|
164
|
+
<span class="analysis-impact">
|
165
|
+
<%= memory_efficiency_impact(extract_rich_plan_metrics(execution)) %>
|
166
|
+
</span>
|
167
|
+
</div>
|
168
|
+
</div>
|
169
|
+
<div class="analysis-row">
|
170
|
+
<span class="analysis-metric">Parallelization</span>
|
171
|
+
<span class="analysis-score <%= score_css_class(advanced_metrics[:parallelization]) %>"><%= advanced_metrics[:parallelization] %></span>
|
172
|
+
<div class="analysis-details">
|
173
|
+
<span class="analysis-desc">Worker utilization</span>
|
174
|
+
<span class="analysis-impact">
|
175
|
+
<%= parallelization_impact(extract_rich_plan_metrics(execution)) %>
|
176
|
+
</span>
|
177
|
+
</div>
|
178
|
+
</div>
|
179
|
+
<div class="analysis-row">
|
180
|
+
<span class="analysis-metric">Index Usage</span>
|
181
|
+
<span class="analysis-score <%= score_css_class(advanced_metrics[:index_utilization]) %>"><%= advanced_metrics[:index_utilization] %></span>
|
182
|
+
<div class="analysis-details">
|
183
|
+
<span class="analysis-desc">Access efficiency</span>
|
184
|
+
<span class="analysis-impact">
|
185
|
+
<%= index_usage_impact(extract_rich_plan_metrics(execution)) %>
|
186
|
+
</span>
|
187
|
+
</div>
|
188
|
+
</div>
|
189
|
+
</div>
|
190
|
+
</div>
|
191
|
+
<% end %>
|
192
|
+
</div>
|
193
|
+
|
194
|
+
<!-- Smart Recommendations Section -->
|
195
|
+
<% if execution.performance_insights.present? && execution.performance_insights['suggestions']&.any? %>
|
196
|
+
<div class="recommendations-section">
|
197
|
+
<div class="recommendations-header">
|
198
|
+
<div class="group-header">
|
199
|
+
<span class="header-title">Optimization Recommendations</span>
|
200
|
+
<span class="header-insight"><%= execution.performance_insights['suggestions'].length %> action items</span>
|
201
|
+
</div>
|
202
|
+
</div>
|
203
|
+
<div class="recommendations-grid">
|
204
|
+
<% execution.performance_insights['suggestions'].each_with_index do |suggestion, index| %>
|
205
|
+
<div class="recommendation-card">
|
206
|
+
<div class="rec-priority">
|
207
|
+
<span class="priority-indicator <%= recommendation_priority_class(suggestion, index) %>">
|
208
|
+
<%= recommendation_priority_text(suggestion, index) %>
|
209
|
+
</span>
|
210
|
+
<span class="rec-impact">
|
211
|
+
<%= recommendation_impact_estimate(suggestion) %>
|
212
|
+
</span>
|
213
|
+
</div>
|
214
|
+
<div class="rec-content">
|
215
|
+
<span class="rec-text"><%= suggestion %></span>
|
216
|
+
<div class="rec-actions">
|
217
|
+
<span class="rec-hint"><%= recommendation_hint(suggestion) %></span>
|
218
|
+
</div>
|
219
|
+
</div>
|
220
|
+
</div>
|
221
|
+
<% end %>
|
222
|
+
</div>
|
223
|
+
</div>
|
224
|
+
<% else %>
|
225
|
+
<div class="no-recommendations-section">
|
226
|
+
<div class="group-header">
|
227
|
+
<span class="header-title">Performance Status</span>
|
228
|
+
<span class="header-insight">Well optimized</span>
|
229
|
+
</div>
|
230
|
+
<div class="no-recommendations-content">
|
231
|
+
<div class="performance-badges">
|
232
|
+
<span class="perf-badge excellent">Fast Execution</span>
|
233
|
+
<span class="perf-badge good">Efficient Resource Usage</span>
|
234
|
+
<span class="perf-badge excellent">Good Query Plan</span>
|
235
|
+
</div>
|
236
|
+
<p class="optimization-summary">
|
237
|
+
This query is performing well with no critical optimization opportunities identified.
|
238
|
+
Monitor execution patterns for any performance degradation over time.
|
239
|
+
</p>
|
240
|
+
</div>
|
241
|
+
</div>
|
242
|
+
<% end %>
|
243
|
+
</div>
|
244
|
+
</div>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<div class="plan-nodes-container">
|
2
|
+
<% if plan_data.is_a?(Array) && plan_data.first&.dig("Plan") %>
|
3
|
+
<%= render_plan_node_compact(plan_data.first["Plan"], 0) %>
|
4
|
+
<% elsif plan_data.is_a?(Hash) && plan_data["Plan"] %>
|
5
|
+
<%= render_plan_node_compact(plan_data["Plan"], 0) %>
|
6
|
+
<% else %>
|
7
|
+
<div class="plan-parse-error">
|
8
|
+
<span class="error-indicator">⚠️</span>
|
9
|
+
<span>Unable to parse execution plan</span>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
12
|
+
</div>
|