@oneuptime/common 11.5.11 → 11.5.12
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/Models/AnalyticsModels/NetworkFlow.ts +12 -1
- package/Server/API/StatusPageAPI.ts +14 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +0 -2
- package/Server/Services/IncidentService.ts +62 -67
- package/Server/Services/MonitorGroupService.ts +15 -2
- package/Server/Services/MonitorService.ts +91 -3
- package/Server/Services/MonitorStatusTimelineService.ts +285 -100
- package/Server/Services/StatusPageService.ts +59 -34
- package/Server/Utils/Monitor/MonitorResource.ts +43 -14
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +32 -7
- package/Server/Utils/Monitor/MonitorStatusTimelineReconciler.ts +362 -0
- package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +235 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineReconciler.test.ts +555 -0
- package/Tests/Types/Database/CompareOperatorWireSerialization.test.ts +216 -0
- package/Tests/Types/Database/GreaterThan.test.ts +5 -1
- package/Tests/Types/Database/GreaterThanOrEqual.test.ts +5 -1
- package/Tests/Types/Database/GreaterThanOrNull.test.ts +24 -1
- package/Tests/Types/Database/LessThan.test.ts +5 -1
- package/Tests/Types/Database/LessThanOrEqual.test.ts +24 -1
- package/Tests/Types/Database/LessThanOrNull.test.ts +5 -1
- package/Tests/Utils/Uptime/UptimeUtil.test.ts +726 -0
- package/Types/BaseDatabase/GreaterThan.ts +8 -1
- package/Types/BaseDatabase/GreaterThanOrEqual.ts +8 -1
- package/Types/BaseDatabase/GreaterThanOrNull.ts +9 -1
- package/Types/BaseDatabase/LessThan.ts +8 -1
- package/Types/BaseDatabase/LessThanOrEqual.ts +9 -1
- package/Types/BaseDatabase/LessThanOrNull.ts +8 -1
- package/Utils/StatusPage/ResourceUptime.ts +11 -1
- package/Utils/Uptime/UptimeUtil.ts +142 -36
- package/build/dist/Models/AnalyticsModels/NetworkFlow.js +12 -1
- package/build/dist/Models/AnalyticsModels/NetworkFlow.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +12 -2
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +0 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +47 -56
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/MonitorGroupService.js +15 -2
- package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +66 -3
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +244 -102
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +57 -33
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +34 -12
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +25 -8
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js +268 -0
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js.map +1 -0
- package/build/dist/Types/BaseDatabase/GreaterThan.js +8 -1
- package/build/dist/Types/BaseDatabase/GreaterThan.js.map +1 -1
- package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js +8 -1
- package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js.map +1 -1
- package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js +9 -1
- package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js.map +1 -1
- package/build/dist/Types/BaseDatabase/LessThan.js +8 -1
- package/build/dist/Types/BaseDatabase/LessThan.js.map +1 -1
- package/build/dist/Types/BaseDatabase/LessThanOrEqual.js +9 -1
- package/build/dist/Types/BaseDatabase/LessThanOrEqual.js.map +1 -1
- package/build/dist/Types/BaseDatabase/LessThanOrNull.js +8 -1
- package/build/dist/Types/BaseDatabase/LessThanOrNull.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js +4 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/Uptime/UptimeUtil.js +86 -33
- package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
- package/package.json +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.ts +0 -101
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js +0 -40
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js.map +0 -1
|
@@ -285,8 +285,19 @@ export default class NetworkFlow extends AnalyticsBaseModel {
|
|
|
285
285
|
* (a retentionDate column computed at ingest, like Log/Metric) is
|
|
286
286
|
* the phase-2 follow-up. Keyed on server-assigned ingestedAt so a
|
|
287
287
|
* device with a wrong clock cannot make rows expire early.
|
|
288
|
+
*
|
|
289
|
+
* toDateTime() is NOT redundant: ingestedAt is DateTime64(9), so
|
|
290
|
+
* `ingestedAt + INTERVAL 30 DAY` stays DateTime64(9), and a TTL
|
|
291
|
+
* expression must evaluate to Date or DateTime. ClickHouse only began
|
|
292
|
+
* accepting DateTime64 there in 25.x; on 24.x it fails the CREATE with
|
|
293
|
+
* BAD_TTL_EXPRESSION, which aborts the whole boot schema-sync (it has
|
|
294
|
+
* no per-table error handling) and takes every table registered after
|
|
295
|
+
* this one down with it. The Helm chart ships `tag: latest` but tells
|
|
296
|
+
* operators to pin for production, so older servers are supported and
|
|
297
|
+
* this must stay version-portable. Narrowing to second precision is
|
|
298
|
+
* lossless for a 30-day retention window.
|
|
288
299
|
*/
|
|
289
|
-
ttlExpression: "ingestedAt + INTERVAL 30 DAY DELETE",
|
|
300
|
+
ttlExpression: "toDateTime(ingestedAt) + INTERVAL 30 DAY DELETE",
|
|
290
301
|
defaultSortColumn: "flowStartAt",
|
|
291
302
|
});
|
|
292
303
|
}
|
|
@@ -88,7 +88,7 @@ import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
|
|
|
88
88
|
import StatusPageResourceUptimeUtil from "../../Utils/StatusPage/ResourceUptime";
|
|
89
89
|
import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
|
|
90
90
|
import { Green } from "../../Types/BrandColors";
|
|
91
|
-
import UptimeUtil from "../../Utils/Uptime/UptimeUtil";
|
|
91
|
+
import UptimeUtil, { UptimeWindow } from "../../Utils/Uptime/UptimeUtil";
|
|
92
92
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
93
93
|
import URL from "../../Types/API/URL";
|
|
94
94
|
import SMS from "../../Types/SMS/SMS";
|
|
@@ -1265,6 +1265,17 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
1265
1265
|
const downtimeMonitorStatuses: Array<MonitorStatus> =
|
|
1266
1266
|
statusPage.downtimeMonitorStatuses || [];
|
|
1267
1267
|
|
|
1268
|
+
/*
|
|
1269
|
+
* this endpoint reports uptime over an explicit [startDate, endDate] range, so events
|
|
1270
|
+
* have to be clipped to it and the denominator has to be the range itself. Without
|
|
1271
|
+
* this an open (endsAt = null) row that started before the range contributes its
|
|
1272
|
+
* entire duration to the downtime total.
|
|
1273
|
+
*/
|
|
1274
|
+
const uptimeWindow: UptimeWindow = {
|
|
1275
|
+
startDate: startDate,
|
|
1276
|
+
endDate: endDate,
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1268
1279
|
type ResourceUptime = {
|
|
1269
1280
|
statusPageResourceId: ObjectID;
|
|
1270
1281
|
uptimePercent: number | null;
|
|
@@ -1369,6 +1380,7 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
1369
1380
|
resourceStatusTimelines,
|
|
1370
1381
|
precision,
|
|
1371
1382
|
downtimeMonitorStatuses,
|
|
1383
|
+
uptimeWindow,
|
|
1372
1384
|
);
|
|
1373
1385
|
|
|
1374
1386
|
resourceUptime.uptimePercent = uptimePercent;
|
|
@@ -1419,6 +1431,7 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
1419
1431
|
resourceStatusTimelines,
|
|
1420
1432
|
precision,
|
|
1421
1433
|
downtimeMonitorStatuses,
|
|
1434
|
+
uptimeWindow,
|
|
1422
1435
|
);
|
|
1423
1436
|
|
|
1424
1437
|
resourceUptime.uptimePercent = uptimePercent;
|
|
@@ -461,7 +461,6 @@ import { AddNetworkDeviceInventoryAndDiscoverySchedule1784211212164 } from "./17
|
|
|
461
461
|
import { AddEnterpriseLicenseNotificationColumns1784218257664 } from "./1784218257664-AddEnterpriseLicenseNotificationColumns";
|
|
462
462
|
import { AddAttributionColumnsToUserAndProject1784293516000 } from "./1784293516000-AddAttributionColumnsToUserAndProject";
|
|
463
463
|
import { AddMarketingConversionTable1784298000000 } from "./1784298000000-AddMarketingConversionTable";
|
|
464
|
-
import { MigrationName1784401962564 } from "./1784401962564-MigrationName";
|
|
465
464
|
|
|
466
465
|
export default [
|
|
467
466
|
InitialMigration,
|
|
@@ -927,5 +926,4 @@ export default [
|
|
|
927
926
|
AddEnterpriseLicenseNotificationColumns1784218257664,
|
|
928
927
|
AddAttributionColumnsToUserAndProject1784293516000,
|
|
929
928
|
AddMarketingConversionTable1784298000000,
|
|
930
|
-
MigrationName1784401962564,
|
|
931
929
|
];
|
|
@@ -15,7 +15,6 @@ import IncidentStateService from "./IncidentStateService";
|
|
|
15
15
|
import IncidentStateTimelineService from "./IncidentStateTimelineService";
|
|
16
16
|
import MonitorService from "./MonitorService";
|
|
17
17
|
import MonitorStatusService from "./MonitorStatusService";
|
|
18
|
-
import MonitorStatusTimelineService from "./MonitorStatusTimelineService";
|
|
19
18
|
import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
|
|
20
19
|
import TeamMemberService from "./TeamMemberService";
|
|
21
20
|
import UserService from "./UserService";
|
|
@@ -43,7 +42,6 @@ import IncidentState from "../../Models/DatabaseModels/IncidentState";
|
|
|
43
42
|
import IncidentStateTimeline from "../../Models/DatabaseModels/IncidentStateTimeline";
|
|
44
43
|
import Monitor from "../../Models/DatabaseModels/Monitor";
|
|
45
44
|
import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
|
|
46
|
-
import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
|
|
47
45
|
import User from "../../Models/DatabaseModels/User";
|
|
48
46
|
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
49
47
|
import MutableMetricService from "./MutableMetricService";
|
|
@@ -2538,84 +2536,81 @@ ${incidentSeverity.name}
|
|
|
2538
2536
|
|
|
2539
2537
|
if (resolvedMonitorState) {
|
|
2540
2538
|
for (const monitor of monitors) {
|
|
2541
|
-
//check state of the monitor.
|
|
2542
|
-
|
|
2543
|
-
const doesMonitorHasMoreActiveManualIncidents: boolean =
|
|
2544
|
-
await this.doesMonitorHasMoreActiveManualIncidents(
|
|
2545
|
-
monitor.id!,
|
|
2546
|
-
projectId!,
|
|
2547
|
-
);
|
|
2548
|
-
|
|
2549
|
-
if (doesMonitorHasMoreActiveManualIncidents) {
|
|
2550
|
-
continue;
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
await MonitorService.updateOneById({
|
|
2554
|
-
id: monitor.id!,
|
|
2555
|
-
data: {
|
|
2556
|
-
disableActiveMonitoringBecauseOfManualIncident: false,
|
|
2557
|
-
},
|
|
2558
|
-
props: {
|
|
2559
|
-
isRoot: true,
|
|
2560
|
-
},
|
|
2561
|
-
});
|
|
2562
|
-
|
|
2563
2539
|
/*
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
2566
|
-
*
|
|
2567
|
-
*
|
|
2540
|
+
* Per-monitor isolation: one monitor failing here (for example the
|
|
2541
|
+
* fail-closed status timeline lock, or a transient DB error) must not
|
|
2542
|
+
* abort the loop and strand the REMAINING monitors with
|
|
2543
|
+
* disableActiveMonitoringBecauseOfManualIncident still true - a
|
|
2544
|
+
* monitor left in that state is skipped by probes and sits in its
|
|
2545
|
+
* down status indefinitely, silently accruing downtime.
|
|
2568
2546
|
*/
|
|
2569
|
-
|
|
2570
|
-
|
|
2547
|
+
try {
|
|
2548
|
+
//check state of the monitor.
|
|
2571
2549
|
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2550
|
+
const doesMonitorHasMoreActiveManualIncidents: boolean =
|
|
2551
|
+
await this.doesMonitorHasMoreActiveManualIncidents(
|
|
2552
|
+
monitor.id!,
|
|
2553
|
+
projectId!,
|
|
2554
|
+
);
|
|
2575
2555
|
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
monitorStatusId: true,
|
|
2556
|
+
if (doesMonitorHasMoreActiveManualIncidents) {
|
|
2557
|
+
continue;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
await MonitorService.updateOneById({
|
|
2561
|
+
id: monitor.id!,
|
|
2562
|
+
data: {
|
|
2563
|
+
disableActiveMonitoringBecauseOfManualIncident: false,
|
|
2585
2564
|
},
|
|
2586
2565
|
props: {
|
|
2587
2566
|
isRoot: true,
|
|
2588
2567
|
},
|
|
2589
|
-
sort: {
|
|
2590
|
-
startsAt: SortOrder.Descending,
|
|
2591
|
-
},
|
|
2592
2568
|
});
|
|
2593
2569
|
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2570
|
+
/*
|
|
2571
|
+
* Don't flip the monitor to operational while other incidents
|
|
2572
|
+
* are still open on it — e.g. a metric monitor with group-by
|
|
2573
|
+
* may have one incident per series, and resolving one series
|
|
2574
|
+
* shouldn't claim the whole monitor is healthy.
|
|
2575
|
+
*/
|
|
2576
|
+
const hasOtherActiveIncidents: boolean =
|
|
2577
|
+
await this.doesMonitorHaveActiveIncidents(
|
|
2578
|
+
monitor.id!,
|
|
2579
|
+
projectId!,
|
|
2580
|
+
);
|
|
2602
2581
|
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
monitorStatusTimeline.projectId = projectId!;
|
|
2607
|
-
monitorStatusTimeline.monitorStatusId = resolvedMonitorState.id!;
|
|
2582
|
+
if (hasOtherActiveIncidents) {
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2608
2585
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2586
|
+
/*
|
|
2587
|
+
* changeMonitorStatus performs the same latest-status dedupe check
|
|
2588
|
+
* this loop used to do inline, and additionally absorbs the two
|
|
2589
|
+
* recoverable error classes (status already set by a concurrent
|
|
2590
|
+
* writer, lock not acquired after retries) by skipping the write,
|
|
2591
|
+
* so the common failure modes never even reach the catch below.
|
|
2592
|
+
* notifyOwners is true to preserve the previous behavior here: the
|
|
2593
|
+
* created row had isOwnerNotified unset, so owners were notified.
|
|
2594
|
+
*/
|
|
2595
|
+
await MonitorService.changeMonitorStatus(
|
|
2596
|
+
projectId!,
|
|
2597
|
+
[monitor.id!],
|
|
2598
|
+
resolvedMonitorState.id!,
|
|
2599
|
+
true, // notifyOwners - matches the pre-existing behavior of this loop.
|
|
2600
|
+
undefined,
|
|
2601
|
+
undefined,
|
|
2602
|
+
{
|
|
2603
|
+
isRoot: true,
|
|
2604
|
+
},
|
|
2605
|
+
startsAt,
|
|
2606
|
+
);
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
logger.error(
|
|
2609
|
+
`IncidentService.markMonitorsActiveForMonitoring: failed for monitor ${monitor.id?.toString()}; continuing with the remaining monitors.`,
|
|
2610
|
+
);
|
|
2611
|
+
logger.error(err);
|
|
2612
|
+
continue;
|
|
2611
2613
|
}
|
|
2612
|
-
|
|
2613
|
-
await MonitorStatusTimelineService.create({
|
|
2614
|
-
data: monitorStatusTimeline,
|
|
2615
|
-
props: {
|
|
2616
|
-
isRoot: true,
|
|
2617
|
-
},
|
|
2618
|
-
});
|
|
2619
2614
|
}
|
|
2620
2615
|
}
|
|
2621
2616
|
}
|
|
@@ -93,7 +93,16 @@ export class Service extends DatabaseService<MonitorGroup> {
|
|
|
93
93
|
},
|
|
94
94
|
),
|
|
95
95
|
),
|
|
96
|
-
|
|
96
|
+
/*
|
|
97
|
+
* Select every row that OVERLAPS [startDate, endDate] - it started on or before the
|
|
98
|
+
* window ends, and it either ended on or after the window started or is still open
|
|
99
|
+
* (endsAt IS NULL). Filtering by createdAt-in-window missed rows that started before
|
|
100
|
+
* the window but carry downtime into it (e.g. a monitor Offline since before the
|
|
101
|
+
* window rendered as 100% uptime on the group page). Same predicate as
|
|
102
|
+
* StatusPageService.getMonitorStatusTimelineForStatusPage.
|
|
103
|
+
*/
|
|
104
|
+
startsAt: QueryHelper.lessThanEqualTo(endDate),
|
|
105
|
+
endsAt: QueryHelper.greaterThanEqualToOrNull(startDate),
|
|
97
106
|
},
|
|
98
107
|
select: {
|
|
99
108
|
createdAt: true,
|
|
@@ -108,7 +117,11 @@ export class Service extends DatabaseService<MonitorGroup> {
|
|
|
108
117
|
} as any,
|
|
109
118
|
},
|
|
110
119
|
sort: {
|
|
111
|
-
|
|
120
|
+
/*
|
|
121
|
+
* startsAt, not createdAt: they are different clocks (DB now() vs worker moment())
|
|
122
|
+
* with real skew, and startsAt is the order the timeline math relies on.
|
|
123
|
+
*/
|
|
124
|
+
startsAt: SortOrder.Ascending,
|
|
112
125
|
},
|
|
113
126
|
skip: 0,
|
|
114
127
|
limit: LIMIT_MAX, // This can be optimized.
|
|
@@ -14,7 +14,12 @@ import MonitorOwnerTeamService from "./MonitorOwnerTeamService";
|
|
|
14
14
|
import MonitorOwnerUserService from "./MonitorOwnerUserService";
|
|
15
15
|
import MonitorProbeService from "./MonitorProbeService";
|
|
16
16
|
import MonitorStatusService from "./MonitorStatusService";
|
|
17
|
-
import MonitorStatusTimelineService
|
|
17
|
+
import MonitorStatusTimelineService, {
|
|
18
|
+
MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
|
|
19
|
+
MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE,
|
|
20
|
+
} from "./MonitorStatusTimelineService";
|
|
21
|
+
import ServerException from "../../Types/Exception/ServerException";
|
|
22
|
+
import Sleep from "../../Types/Sleep";
|
|
18
23
|
import ProbeService from "./ProbeService";
|
|
19
24
|
import ProjectService, { CurrentPlan } from "./ProjectService";
|
|
20
25
|
import TeamMemberService from "./TeamMemberService";
|
|
@@ -1733,13 +1738,96 @@ ${createdItem.description?.trim() || "No description provided."}
|
|
|
1733
1738
|
statusTimeline.startsAt = startsAt;
|
|
1734
1739
|
}
|
|
1735
1740
|
|
|
1736
|
-
await
|
|
1737
|
-
|
|
1741
|
+
await this.createStatusTimelineWithRetry({
|
|
1742
|
+
statusTimeline: statusTimeline,
|
|
1738
1743
|
props: props,
|
|
1744
|
+
projectId: projectId,
|
|
1745
|
+
monitorId: monitorId,
|
|
1739
1746
|
});
|
|
1740
1747
|
}
|
|
1741
1748
|
}
|
|
1742
1749
|
|
|
1750
|
+
/*
|
|
1751
|
+
* Creates one status timeline row, absorbing the two error classes that are
|
|
1752
|
+
* recoverable per monitor so one monitor cannot abort a caller's loop over
|
|
1753
|
+
* many (incident resolve, scheduled maintenance end, incident create with
|
|
1754
|
+
* changeMonitorStatusTo):
|
|
1755
|
+
*
|
|
1756
|
+
* - "same as previous" from onBeforeCreate's dedupe check: a concurrent
|
|
1757
|
+
* writer (or an earlier backfilled row at this startsAt) already put the
|
|
1758
|
+
* monitor in this status, so the desired state holds. Idempotent no-op.
|
|
1759
|
+
*
|
|
1760
|
+
* - the fail-closed lock error from MonitorStatusTimelineService.create():
|
|
1761
|
+
* retried a few times with a short delay, because the common causes (a
|
|
1762
|
+
* transient Redis blip, or a concurrent writer holding the per-monitor
|
|
1763
|
+
* mutex) clear quickly. This retry is also what protects the INITIAL
|
|
1764
|
+
* status row written from MonitorService.onCreateSuccess - without it a
|
|
1765
|
+
* lock failure at monitor-create time leaves a Manual monitor with no
|
|
1766
|
+
* timeline forever, since Manual monitors are never probed and so never
|
|
1767
|
+
* self-heal. If all attempts fail, log and continue: for probed monitors
|
|
1768
|
+
* the next probe result recreates the transition, and failing the caller
|
|
1769
|
+
* outright would strand its remaining monitors instead.
|
|
1770
|
+
*
|
|
1771
|
+
* Every other error still propagates.
|
|
1772
|
+
*/
|
|
1773
|
+
private async createStatusTimelineWithRetry(data: {
|
|
1774
|
+
statusTimeline: MonitorStatusTimeline;
|
|
1775
|
+
props: DatabaseCommonInteractionProps;
|
|
1776
|
+
projectId: ObjectID;
|
|
1777
|
+
monitorId: ObjectID;
|
|
1778
|
+
}): Promise<void> {
|
|
1779
|
+
const maxAttempts: number = 3;
|
|
1780
|
+
const retryDelayInMs: number = 2000;
|
|
1781
|
+
|
|
1782
|
+
const logAttributes: LogAttributes = {
|
|
1783
|
+
projectId: data.projectId.toString(),
|
|
1784
|
+
monitorId: data.monitorId.toString(),
|
|
1785
|
+
} as LogAttributes;
|
|
1786
|
+
|
|
1787
|
+
for (let attempt: number = 1; attempt <= maxAttempts; attempt++) {
|
|
1788
|
+
try {
|
|
1789
|
+
await MonitorStatusTimelineService.create({
|
|
1790
|
+
data: data.statusTimeline,
|
|
1791
|
+
props: data.props,
|
|
1792
|
+
});
|
|
1793
|
+
return;
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
if (
|
|
1796
|
+
err instanceof BadDataException &&
|
|
1797
|
+
err.message === MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE
|
|
1798
|
+
) {
|
|
1799
|
+
logger.debug(
|
|
1800
|
+
`changeMonitorStatus: monitor ${data.monitorId.toString()} is already in the requested status; skipping duplicate status timeline.`,
|
|
1801
|
+
);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const isLockError: boolean =
|
|
1806
|
+
err instanceof ServerException &&
|
|
1807
|
+
err.message === MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE;
|
|
1808
|
+
|
|
1809
|
+
if (isLockError && attempt < maxAttempts) {
|
|
1810
|
+
logger.warn(
|
|
1811
|
+
`changeMonitorStatus: could not acquire the status timeline lock for monitor ${data.monitorId.toString()} (attempt ${attempt} of ${maxAttempts}); retrying.`,
|
|
1812
|
+
logAttributes,
|
|
1813
|
+
);
|
|
1814
|
+
await Sleep.sleep(retryDelayInMs);
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
if (isLockError) {
|
|
1819
|
+
logger.error(
|
|
1820
|
+
`changeMonitorStatus: could not acquire the status timeline lock for monitor ${data.monitorId.toString()} after ${maxAttempts} attempt(s); skipping this status change. The monitor keeps its current status.`,
|
|
1821
|
+
logAttributes,
|
|
1822
|
+
);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
throw err;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1743
1831
|
@CaptureSpan()
|
|
1744
1832
|
public async getWorkspaceChannelForMonitor(data: {
|
|
1745
1833
|
monitorId: ObjectID;
|