@@ -48,6 +48,7 @@ export function getRootPageHtml(): string {
4848 <p><a href="https://app.roussev.com/grafana/d/items-service-metrics/items-service-metrics" target="_blank">Grafana</a> - Metrics dashboard</p>
4949 <p><a href="/jaeger" target="_blank">Jaeger</a> - Distributed tracing</p>
5050 <p><a href="/items/metrics" target="_blank">Metrics Endpoint</a> - Raw Prometheus metrics</p>
51+ <p><a href="/items/prometheus-queries" target="_blank">Prometheus Queries</a> - Preconfigured metric queries</p>
5152 <p><a href="/prometheus" target="_blank">Prometheus</a> - Metrics collection</p>
5253
5354 <p><strong>Documentation:</strong></p>
@@ -62,3 +63,231 @@ export function getRootPageHtml(): string {
6263</html>` ;
6364}
6465
66+ export function getPrometheusQueriesHtml ( ) : string {
67+ return `<!DOCTYPE html>
68+ <html lang="en">
69+ <head>
70+ <meta charset="UTF-8">
71+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
72+ <title>Items Service - Prometheus Metrics</title>
73+ <style>
74+ body {
75+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
76+ margin: 0;
77+ padding: 20px;
78+ background: #f5f5f5;
79+ }
80+ .container {
81+ max-width: 1400px;
82+ margin: 0 auto;
83+ }
84+ h1 {
85+ color: #333;
86+ margin-bottom: 10px;
87+ }
88+ .subtitle {
89+ color: #666;
90+ margin-bottom: 30px;
91+ }
92+ .metrics-grid {
93+ display: grid;
94+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
95+ gap: 20px;
96+ margin-bottom: 30px;
97+ }
98+ .metric-card {
99+ background: white;
100+ border-radius: 8px;
101+ padding: 20px;
102+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
103+ }
104+ .metric-card h3 {
105+ margin: 0 0 10px 0;
106+ color: #333;
107+ font-size: 16px;
108+ }
109+ .metric-card p {
110+ margin: 0 0 15px 0;
111+ color: #666;
112+ font-size: 14px;
113+ }
114+ .query-box {
115+ background: #f8f9fa;
116+ border: 1px solid #e1e4e8;
117+ border-radius: 4px;
118+ padding: 10px;
119+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
120+ font-size: 13px;
121+ margin-bottom: 10px;
122+ word-break: break-all;
123+ }
124+ .btn {
125+ display: inline-block;
126+ padding: 8px 16px;
127+ background: #e85d04;
128+ color: white;
129+ text-decoration: none;
130+ border-radius: 4px;
131+ font-size: 14px;
132+ transition: background 0.2s;
133+ }
134+ .btn:hover {
135+ background: #dc2f02;
136+ }
137+ .section {
138+ background: white;
139+ border-radius: 8px;
140+ padding: 20px;
141+ margin-bottom: 20px;
142+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
143+ }
144+ .section h2 {
145+ margin-top: 0;
146+ color: #333;
147+ border-bottom: 2px solid #e85d04;
148+ padding-bottom: 10px;
149+ }
150+ code {
151+ background: #f8f9fa;
152+ padding: 2px 6px;
153+ border-radius: 3px;
154+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
155+ font-size: 13px;
156+ }
157+ </style>
158+ </head>
159+ <body>
160+ <div class="container">
161+ <h1>Items Service - Prometheus Metrics Dashboard</h1>
162+
163+ <h2 style="margin-top: 30px; color: #333;">Basic Metrics</h2>
164+ <div class="metrics-grid">
165+ <div class="metric-card">
166+ <h3>Service Health</h3>
167+ <p>Check if items-service is up and being scraped</p>
168+ <div class="query-box">up{job="items-service"}</div>
169+ <a href="/prometheus/graph?g0.expr=up%7Bjob%3D%22items-service%22%7D&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
170+ class="btn" target="_blank">Open</a>
171+ </div>
172+
173+ <div class="metric-card">
174+ <h3>Total HTTP Requests</h3>
175+ <p>All HTTP requests received by the service</p>
176+ <div class="query-box">http_server_requests_total</div>
177+ <a href="/prometheus/graph?g0.expr=http_server_requests_total&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
178+ class="btn" target="_blank">Open</a>
179+ </div>
180+
181+ <div class="metric-card">
182+ <h3>Requests by Endpoint</h3>
183+ <p>HTTP requests grouped by route</p>
184+ <div class="query-box">http_server_requests_total{route="/v1/items"}</div>
185+ <a href="/prometheus/graph?g0.expr=http_server_requests_total%7Broute%3D%22%2Fv1%2Fitems%22%7D&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
186+ class="btn" target="_blank">Open</a>
187+ </div>
188+ </div>
189+
190+ <h2 style="margin-top: 30px; color: #333;">📈 Request Rate (RPS)</h2>
191+ <div class="metrics-grid">
192+ <div class="metric-card">
193+ <h3>Overall Request Rate</h3>
194+ <p>Requests per second over the last 5 minutes</p>
195+ <div class="query-box">rate(http_server_requests_total[5m])</div>
196+ <a href="/prometheus/graph?g0.expr=rate(http_server_requests_total%5B5m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
197+ class="btn" target="_blank">Open</a>
198+ </div>
199+
200+ <div class="metric-card">
201+ <h3>Request Rate by Endpoint</h3>
202+ <p>RPS grouped by route and method</p>
203+ <div class="query-box">sum by(route, method) (rate(http_server_requests_total[5m]))</div>
204+ <a href="/prometheus/graph?g0.expr=sum%20by(route%2C%20method)%20(rate(http_server_requests_total%5B5m%5D))&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
205+ class="btn" target="_blank">Open</a>
206+ </div>
207+
208+ <div class="metric-card">
209+ <h3>Request Rate by Status Code</h3>
210+ <p>RPS grouped by HTTP status code</p>
211+ <div class="query-box">sum by(status_code) (rate(http_server_requests_total[5m]))</div>
212+ <a href="/prometheus/graph?g0.expr=sum%20by(status_code)%20(rate(http_server_requests_total%5B5m%5D))&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
213+ class="btn" target="_blank">Open</a>
214+ </div>
215+ </div>
216+
217+ <h2 style="margin-top: 30px; color: #333;">⏱️ Latency Metrics</h2>
218+ <div class="metrics-grid">
219+ <div class="metric-card">
220+ <h3>Average Response Time</h3>
221+ <p>Mean request duration in milliseconds</p>
222+ <div class="query-box">rate(http_server_duration_sum[5m]) / rate(http_server_duration_count[5m])</div>
223+ <a href="/prometheus/graph?g0.expr=rate(http_server_duration_sum%5B5m%5D)%20%2F%20rate(http_server_duration_count%5B5m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
224+ class="btn" target="_blank">Open</a>
225+ </div>
226+
227+ <div class="metric-card">
228+ <h3>P95 Latency</h3>
229+ <p>95th percentile response time</p>
230+ <div class="query-box">histogram_quantile(0.95, rate(http_server_duration_bucket[5m]))</div>
231+ <a href="/prometheus/graph?g0.expr=histogram_quantile(0.95%2C%20rate(http_server_duration_bucket%5B5m%5D))&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
232+ class="btn" target="_blank">Open</a>
233+ </div>
234+
235+ <div class="metric-card">
236+ <h3>P99 Latency</h3>
237+ <p>99th percentile response time</p>
238+ <div class="query-box">histogram_quantile(0.99, rate(http_server_duration_bucket[5m]))</div>
239+ <a href="/prometheus/graph?g0.expr=histogram_quantile(0.99%2C%20rate(http_server_duration_bucket%5B5m%5D))&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
240+ class="btn" target="_blank">Open</a>
241+ </div>
242+
243+ <div class="metric-card">
244+ <h3>P50 Latency (Median)</h3>
245+ <p>50th percentile response time</p>
246+ <div class="query-box">histogram_quantile(0.50, rate(http_server_duration_bucket[5m]))</div>
247+ <a href="/prometheus/graph?g0.expr=histogram_quantile(0.50%2C%20rate(http_server_duration_bucket%5B5m%5D))&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
248+ class="btn" target="_blank">Open</a>
249+ </div>
250+ </div>
251+
252+ <h2 style="margin-top: 30px; color: #333;">Error Monitoring</h2>
253+ <div class="metrics-grid">
254+ <div class="metric-card">
255+ <h3>Error Rate (5xx)</h3>
256+ <p>Rate of server errors</p>
257+ <div class="query-box">rate(http_server_requests_total{status_code=~"5.."}[5m])</div>
258+ <a href="/prometheus/graph?g0.expr=rate(http_server_requests_total%7Bstatus_code%3D~%225..%22%7D%5B5m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
259+ class="btn" target="_blank">Open</a>
260+ </div>
261+
262+ <div class="metric-card">
263+ <h3>Client Error Rate (4xx)</h3>
264+ <p>Rate of client errors</p>
265+ <div class="query-box">rate(http_server_requests_total{status_code=~"4.."}[5m])</div>
266+ <a href="/prometheus/graph?g0.expr=rate(http_server_requests_total%7Bstatus_code%3D~%224..%22%7D%5B5m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
267+ class="btn" target="_blank">Open</a>
268+ </div>
269+
270+ <div class="metric-card">
271+ <h3>Error Percentage</h3>
272+ <p>Percentage of requests that failed</p>
273+ <div class="query-box">sum(rate(http_server_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_server_requests_total[5m])) * 100</div>
274+ <a href="/prometheus/graph?g0.expr=sum(rate(http_server_requests_total%7Bstatus_code%3D~%225..%22%7D%5B5m%5D))%20%2F%20sum(rate(http_server_requests_total%5B5m%5D))%20*%20100&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h"
275+ class="btn" target="_blank">Open</a>
276+ </div>
277+ </div>
278+
279+ <div class="section" style="margin-top: 30px;">
280+ <h2>🧪 Generate Test Traffic</h2>
281+ <p>Before viewing metrics, generate some traffic:</p>
282+ <pre style="background: #f8f9fa; padding: 15px; border-radius: 4px; overflow-x: auto;"><code>for i in {1..50}; do
283+ curl -s https://app.roussev.com/items/v1/health > /dev/null
284+ curl -s https://app.roussev.com/items/v1/items > /dev/null
285+ sleep 0.5
286+ done</code></pre>
287+ </div>
288+
289+ </div>
290+ </body>
291+ </html>` ;
292+ }
293+
0 commit comments