@juice-js/observability 1.1.0-alpha.18 → 1.1.0-alpha.21
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.
- package/README.md +4 -2
- package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs +349 -0
- package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs.map +1 -0
- package/fesm2022/juice-js-observability-service.page-BrcID0Bs.mjs +1255 -0
- package/fesm2022/juice-js-observability-service.page-BrcID0Bs.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-topology.page-aMWEXyMB.mjs → juice-js-observability-topology.page-CUayiRVT.mjs} +55 -3
- package/fesm2022/juice-js-observability-topology.page-CUayiRVT.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-trace.page-DUCWnZL5.mjs → juice-js-observability-trace.page-ByxGFLpE.mjs} +9 -7
- package/fesm2022/juice-js-observability-trace.page-ByxGFLpE.mjs.map +1 -0
- package/fesm2022/juice-js-observability.mjs +23 -4
- package/fesm2022/juice-js-observability.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_observability.scss +22 -3
- package/types/juice-js-observability.d.ts +94 -0
- package/fesm2022/juice-js-observability-service.page-Dt8bN2Ry.mjs +0 -726
- package/fesm2022/juice-js-observability-service.page-Dt8bN2Ry.mjs.map +0 -1
- package/fesm2022/juice-js-observability-topology.page-aMWEXyMB.mjs.map +0 -1
- package/fesm2022/juice-js-observability-trace.page-DUCWnZL5.mjs.map +0 -1
|
@@ -117,7 +117,11 @@
|
|
|
117
117
|
.jc-list-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--jc-border); }
|
|
118
118
|
.jc-list-row:last-child { border-bottom: 0; }
|
|
119
119
|
.jc-summary { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; margin-bottom: 8px; }
|
|
120
|
-
.jc-actions { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
|
|
120
|
+
.jc-actions { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; min-width: 0; }
|
|
121
|
+
// A button label can carry a span name (a whole SQL statement, say): keep each button within the row and cut its label
|
|
122
|
+
// with an ellipsis; the full text is on the button's title.
|
|
123
|
+
.jc-actions > .mat-mdc-button-base { max-width: 100%; min-width: 0; }
|
|
124
|
+
.jc-actions .mdc-button__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
121
125
|
// The filters, the context bar and every other field the pages size label each field. An app theme denser than -1 hides a fill field's label (and
|
|
122
126
|
// these fields have no placeholder to fall back on), so keep them at -1 whatever the app's density.
|
|
123
127
|
.jc-context, .jc-filter-form, mat-form-field[class*='jc-field-'] { @include mat.form-field-density(-1); }
|
|
@@ -155,12 +159,23 @@
|
|
|
155
159
|
.jc-health-card.jc-selected { border-color: var(--jc-accent); }
|
|
156
160
|
|
|
157
161
|
.jc-chart-title { font-size: 13px; color: var(--jc-muted); margin: 4px 0; }
|
|
162
|
+
.jc-capacity-inputs { display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 16px; }
|
|
163
|
+
.jc-capacity-inputs mat-form-field { width: 170px; }
|
|
164
|
+
.jc-capacity-result { display: flex; flex-wrap: wrap; gap: 12px 32px; margin-bottom: 12px; }
|
|
165
|
+
.jc-capacity-result div { display: flex; flex-direction: column; }
|
|
166
|
+
.jc-capacity-result strong { font-size: 24px; font-weight: 500; line-height: 1.2; }
|
|
167
|
+
.jc-capacity-notes { margin: 12px 0 0; padding-left: 18px; font-size: 13px; }
|
|
168
|
+
.jc-capacity-measured th { width: 240px; }
|
|
169
|
+
.jc-load-steps { margin-top: 12px; }
|
|
170
|
+
.jc-load-steps summary { cursor: pointer; font-size: 13px; }
|
|
171
|
+
|
|
158
172
|
.jc-chart { height: 180px; width: 100%; background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 6px; }
|
|
159
173
|
.jc-chart-empty { display: flex; align-items: center; justify-content: center; color: var(--jc-muted); font-size: 13px; }
|
|
160
174
|
|
|
161
175
|
// ─── Waterfall ───
|
|
162
176
|
.jc-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; margin-bottom: 6px; i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; } }
|
|
163
|
-
|
|
177
|
+
// minmax(0, …), not a bare 1fr: a 1fr column grows to fit its longest unbreakable line (a span name in a button label) instead of letting it clip.
|
|
178
|
+
.jc-trace { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
|
164
179
|
.jc-trace.jc-with-detail { grid-template-columns: minmax(0, 1.6fr) minmax(340px, 1fr); }
|
|
165
180
|
.jc-waterfall { background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 8px; font-size: 12px; overflow: hidden; }
|
|
166
181
|
.jc-wf-head, .jc-wf-row { display: grid; grid-template-columns: minmax(260px, 40%) 1fr; align-items: center; }
|
|
@@ -187,6 +202,7 @@
|
|
|
187
202
|
.jc-usage-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
|
|
188
203
|
.jc-fill-page > .jc-trace > .jc-waterfall, .jc-fill-page > .jc-trace > .jc-span-detail { overflow: auto; max-height: none; position: static; }
|
|
189
204
|
.jc-fill-page .jc-wf-head { position: sticky; top: 0; z-index: 1; background: var(--jc-surface); }
|
|
205
|
+
.jc-span-detail h2 { min-width: 0; overflow-wrap: anywhere; }
|
|
190
206
|
.jc-span-facts { display: flex; gap: 12px; align-items: center; margin-bottom: 6px; }
|
|
191
207
|
.jc-event { border-left: 3px solid var(--jc-border); padding-left: 8px; margin: 6px 0; }
|
|
192
208
|
.jc-event.jc-wf-error { border-left-color: var(--jc-bad); }
|
|
@@ -197,10 +213,13 @@
|
|
|
197
213
|
.jc-graph-legend { display: flex; gap: 16px; align-items: center; font-size: 12px; color: var(--jc-muted); padding: 4px; }
|
|
198
214
|
.jc-line { display: inline-block; width: 24px; border-top: 2px solid var(--jc-muted); vertical-align: middle; }
|
|
199
215
|
.jc-line.jc-dashed { border-top-style: dashed; }
|
|
216
|
+
.jc-line.jc-line-warn { border-top: 3px solid var(--jc-warn); }
|
|
217
|
+
.jc-line.jc-line-bad { border-top: 3px solid var(--jc-bad); }
|
|
218
|
+
.jc-line.jc-line-idle { opacity: 0.35; }
|
|
200
219
|
.jc-side { align-self: start; }
|
|
201
220
|
|
|
202
221
|
@media (max-width: 900px) {
|
|
203
|
-
.jc-trace.jc-with-detail, .jc-topology { grid-template-columns: 1fr; }
|
|
222
|
+
.jc-trace.jc-with-detail, .jc-topology { grid-template-columns: minmax(0, 1fr); }
|
|
204
223
|
// Stacked panes: the page scrolls as a whole again.
|
|
205
224
|
mat-sidenav-content > .jc-fill-page { height: auto; display: block; overflow: visible; }
|
|
206
225
|
.jc-fill-page > .jc-trace > .jc-waterfall, .jc-fill-page > .jc-trace > .jc-span-detail { overflow: visible; }
|
|
@@ -299,6 +299,96 @@ export interface ServiceStats {
|
|
|
299
299
|
stepSeconds: number;
|
|
300
300
|
points: ServiceStatsPoint[];
|
|
301
301
|
}
|
|
302
|
+
/** What a service was measured to need, per instance. A unit of work is a request or a message. */
|
|
303
|
+
export interface Capacity {
|
|
304
|
+
stepSeconds: number;
|
|
305
|
+
samples: number;
|
|
306
|
+
instances: number;
|
|
307
|
+
truncated: boolean;
|
|
308
|
+
requestSource: 'metrics' | 'spans' | 'none';
|
|
309
|
+
hosts: string[];
|
|
310
|
+
coresPerInstance: number | null;
|
|
311
|
+
peakPerSecond: number;
|
|
312
|
+
responseMs: number | null;
|
|
313
|
+
responseLowLoadMs: number | null;
|
|
314
|
+
responseHighLoadMs: number | null;
|
|
315
|
+
peakQueue: number | null;
|
|
316
|
+
/** `fitted` separates idle CPU from CPU per unit; `average` is CPU over work in the busiest samples, so it can read high. */
|
|
317
|
+
cpuModel: 'fitted' | 'average' | 'none';
|
|
318
|
+
cpuSecondsPerUnit: number | null;
|
|
319
|
+
idleCores: number | null;
|
|
320
|
+
peakCores: number | null;
|
|
321
|
+
/** `fitted` is idle plus growth per unit in flight; `peak` is only the highest seen. */
|
|
322
|
+
memoryModel: 'fitted' | 'peak' | 'none';
|
|
323
|
+
idleMemoryBytes: number | null;
|
|
324
|
+
memoryBytesPerInFlight: number | null;
|
|
325
|
+
peakMemoryBytes: number | null;
|
|
326
|
+
/** Near 1, a consumer was busy nearly all the time: if it handles one message at a time, it was at its limit whatever CPU says. */
|
|
327
|
+
peakMessagesPerSecond: number;
|
|
328
|
+
peakMessagesInFlight: number;
|
|
329
|
+
/** Null when the service created and delivered no outbox messages in the window. */
|
|
330
|
+
outbox: OutboxBacklog | null;
|
|
331
|
+
}
|
|
332
|
+
/** `undelivered` is created minus delivered in the window, so it includes rolled-back messages. The delay is the p95 from creating to delivering, in the newest minutes. */
|
|
333
|
+
export interface OutboxBacklog {
|
|
334
|
+
createdPerSecond: number;
|
|
335
|
+
deliveredPerSecond: number;
|
|
336
|
+
undelivered: number;
|
|
337
|
+
recentDelaySeconds: number | null;
|
|
338
|
+
fallingBehind: boolean;
|
|
339
|
+
}
|
|
340
|
+
export interface LoadRun {
|
|
341
|
+
id: string;
|
|
342
|
+
name: string;
|
|
343
|
+
environment: string | null;
|
|
344
|
+
startedAt: string;
|
|
345
|
+
endedAt: string | null;
|
|
346
|
+
startedBy: string;
|
|
347
|
+
}
|
|
348
|
+
export interface LoadRunPoint {
|
|
349
|
+
at: string;
|
|
350
|
+
perSecond: number;
|
|
351
|
+
errorRate: number;
|
|
352
|
+
p95Ms: number | null;
|
|
353
|
+
queue: number | null;
|
|
354
|
+
consumerBusy: number;
|
|
355
|
+
created: number;
|
|
356
|
+
delivered: number;
|
|
357
|
+
backlog: number;
|
|
358
|
+
}
|
|
359
|
+
/** Where a service started to struggle. */
|
|
360
|
+
export interface LoadRunLimit {
|
|
361
|
+
reason: string;
|
|
362
|
+
at: string;
|
|
363
|
+
perSecond: number;
|
|
364
|
+
value: number;
|
|
365
|
+
baseline: number | null;
|
|
366
|
+
}
|
|
367
|
+
export interface LoadRunService {
|
|
368
|
+
serviceId: string | null;
|
|
369
|
+
name: string;
|
|
370
|
+
points: LoadRunPoint[];
|
|
371
|
+
peakPerSecond: number;
|
|
372
|
+
p95AtPeakMs: number | null;
|
|
373
|
+
errorRateAtPeak: number;
|
|
374
|
+
baselineP95Ms: number | null;
|
|
375
|
+
limit: LoadRunLimit | null;
|
|
376
|
+
}
|
|
377
|
+
export interface LoadRunResults {
|
|
378
|
+
run: LoadRun;
|
|
379
|
+
stepSeconds: number;
|
|
380
|
+
services: LoadRunService[];
|
|
381
|
+
truncated: boolean;
|
|
382
|
+
}
|
|
383
|
+
export interface DependencyStats {
|
|
384
|
+
dependencyId: string;
|
|
385
|
+
count: number;
|
|
386
|
+
errorRate: number;
|
|
387
|
+
}
|
|
388
|
+
export interface DependencyStatsPage {
|
|
389
|
+
items: DependencyStats[];
|
|
390
|
+
truncated: boolean;
|
|
391
|
+
}
|
|
302
392
|
export interface DependencyEvidence {
|
|
303
393
|
dependencyId: string;
|
|
304
394
|
relation: string;
|
|
@@ -349,6 +439,10 @@ export declare class ApiService {
|
|
|
349
439
|
metrics(query: Query): Observable<TelemetryPage<MetricInfo>>;
|
|
350
440
|
series(query: Query): Observable<Series>;
|
|
351
441
|
serviceStats(query: Query): Observable<ServiceStats>;
|
|
442
|
+
capacity(query: Query): Observable<Capacity>;
|
|
443
|
+
loadRuns(query: Query): Observable<Page<LoadRun>>;
|
|
444
|
+
loadRunResults(id: string): Observable<LoadRunResults>;
|
|
445
|
+
dependencyStats(query: Query): Observable<DependencyStatsPage>;
|
|
352
446
|
dependency(id: string, query: Query): Observable<DependencyEvidence>;
|
|
353
447
|
search(q: string): Observable<TelemetryPage<SearchHit>>;
|
|
354
448
|
static ɵfac: i0.ɵɵFactoryDeclaration<ApiService, never>;
|