@oneuptime/common 12.0.2 → 12.0.3

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.
Files changed (228) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Index.ts +2 -0
  3. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  4. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  5. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  6. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  7. package/Server/API/BaseAPI.ts +13 -2
  8. package/Server/API/DashboardAPI.ts +39 -0
  9. package/Server/EnvironmentConfig.ts +31 -1
  10. package/Server/Infrastructure/GlobalCache.ts +115 -0
  11. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  14. package/Server/Services/AlertStateTimelineService.ts +20 -0
  15. package/Server/Services/CephClusterService.ts +26 -86
  16. package/Server/Services/CloudResourceService.ts +23 -83
  17. package/Server/Services/DatabaseService.ts +111 -0
  18. package/Server/Services/DockerHostService.ts +19 -79
  19. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  20. package/Server/Services/HostService.ts +46 -99
  21. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  22. package/Server/Services/Index.ts +2 -0
  23. package/Server/Services/IoTFleetService.ts +19 -79
  24. package/Server/Services/KubernetesClusterService.ts +17 -78
  25. package/Server/Services/LabelService.ts +57 -10
  26. package/Server/Services/MetricTypeService.ts +84 -0
  27. package/Server/Services/MonitorService.ts +89 -14
  28. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  29. package/Server/Services/MonitorTemplateService.ts +65 -0
  30. package/Server/Services/PodmanHostService.ts +19 -79
  31. package/Server/Services/ProxmoxClusterService.ts +23 -83
  32. package/Server/Services/RumApplicationService.ts +19 -80
  33. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  34. package/Server/Services/ServerlessFunctionService.ts +24 -85
  35. package/Server/Services/ServiceService.ts +299 -73
  36. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  37. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  38. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  39. package/Server/Services/TelemetryEntityService.ts +1 -0
  40. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  41. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  44. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  45. package/Server/Utils/SingleFlight.ts +83 -0
  46. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  47. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  48. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  49. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  50. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  51. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  52. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  53. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  54. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  55. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  56. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  57. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  58. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  59. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  60. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  61. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  62. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  63. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  64. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  65. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  66. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  67. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  68. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  69. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  70. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  71. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  72. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  73. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  74. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  75. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  76. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  77. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  78. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  79. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  80. package/Tests/Types/Decimal.test.ts +37 -0
  81. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  82. package/Tests/Types/Port.test.ts +38 -0
  83. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  84. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  85. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  86. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  87. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  88. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  89. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  90. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  91. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  92. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  93. package/Types/Database/DatabaseProperty.ts +26 -0
  94. package/Types/Database/NumericColumnValue.ts +128 -0
  95. package/Types/Decimal.ts +16 -4
  96. package/Types/Permission.ts +48 -0
  97. package/Types/Port.ts +16 -4
  98. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  99. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  100. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  101. package/Utils/Dashboard/Components/Index.ts +7 -0
  102. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  103. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  105. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  107. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  109. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  110. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  111. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  113. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  114. package/build/dist/Server/API/BaseAPI.js +11 -1
  115. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  116. package/build/dist/Server/API/DashboardAPI.js +39 -0
  117. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  118. package/build/dist/Server/EnvironmentConfig.js +27 -1
  119. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  120. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  121. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  122. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  123. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  128. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  129. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  130. package/build/dist/Server/Services/CephClusterService.js +26 -76
  131. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  132. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  133. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  134. package/build/dist/Server/Services/DatabaseService.js +86 -0
  135. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  136. package/build/dist/Server/Services/DockerHostService.js +19 -69
  137. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  138. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  139. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  140. package/build/dist/Server/Services/HostService.js +58 -102
  141. package/build/dist/Server/Services/HostService.js.map +1 -1
  142. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  143. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  144. package/build/dist/Server/Services/Index.js +2 -0
  145. package/build/dist/Server/Services/Index.js.map +1 -1
  146. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  147. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  148. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  149. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  150. package/build/dist/Server/Services/LabelService.js +42 -7
  151. package/build/dist/Server/Services/LabelService.js.map +1 -1
  152. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  153. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  154. package/build/dist/Server/Services/MonitorService.js +81 -9
  155. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  156. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  157. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  158. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  159. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  160. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  161. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  162. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  163. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  164. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  165. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  166. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  167. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  168. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  169. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  170. package/build/dist/Server/Services/ServiceService.js +236 -61
  171. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  172. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  173. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  174. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  175. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  176. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  177. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  178. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  179. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  180. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  181. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  182. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  183. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  184. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  185. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  186. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  187. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  190. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  191. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  192. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  193. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  194. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  195. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  196. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  197. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  198. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  199. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  200. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  201. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  202. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  203. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  204. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  205. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  206. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  207. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  208. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  209. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  210. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  211. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  212. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  213. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  214. package/build/dist/Types/Decimal.js +14 -3
  215. package/build/dist/Types/Decimal.js.map +1 -1
  216. package/build/dist/Types/Permission.js +42 -0
  217. package/build/dist/Types/Permission.js.map +1 -1
  218. package/build/dist/Types/Port.js +15 -5
  219. package/build/dist/Types/Port.js.map +1 -1
  220. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  221. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  222. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  223. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  224. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  225. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  226. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  227. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  228. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SingleFlight.js","sourceRoot":"","sources":["../../../../Server/Utils/SingleFlight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAqB,YAAY;IAM/B;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,IAAsB;QAC5D,MAAM,QAAQ,GAAiC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEtE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,QAAQ,CAAM,CAAC;QAC/B,CAAC;QAED;;;;WAIG;QACH,MAAM,OAAO,GAAe,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC,CAAC,EAAE,CAAC;QAEL,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAEhC,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT;;;;;eAKG;YACH,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC;gBACvC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,2EAA2E;IACpE,MAAM,CAAC,aAAa;QACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,4CAA4C;IACrC,MAAM,CAAC,KAAK;QACjB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;;AAtDc,qBAAQ,GAAkC,IAAI,GAAG,EAG7D,CAAC;eAJe,YAAY"}
@@ -0,0 +1,29 @@
1
+ import BadDataException from "../../../Types/Exception/BadDataException";
2
+ import RulePatternMatchUtil from "../../../Utils/Rules/RulePatternMatchUtil";
3
+ /*
4
+ * Write-time validation for the monitor name / description patterns on status
5
+ * page monitor rules.
6
+ *
7
+ * The rule engine accepts a case-insensitive regex or a '*' wildcard glob, the
8
+ * same two syntaxes the network device rules take. Anything else - `api-(01`,
9
+ * `[unclosed` - compiles to nothing and matches nothing, and the engine can
10
+ * only log about it long after the user left the form. Reject it at the write
11
+ * instead, exactly as NetworkDeviceRulePatternValidator does
12
+ * (OneUptime/oneuptime#2940).
13
+ */
14
+ export default class StatusPageMonitorRulePatternValidator {
15
+ static validate(data) {
16
+ StatusPageMonitorRulePatternValidator.validateOne("Monitor Name Pattern", data.namePattern);
17
+ StatusPageMonitorRulePatternValidator.validateOne("Monitor Description Pattern", data.descriptionPattern);
18
+ }
19
+ static validateOne(title, pattern) {
20
+ if (!pattern) {
21
+ return;
22
+ }
23
+ if (RulePatternMatchUtil.isSupportedPattern(pattern)) {
24
+ return;
25
+ }
26
+ throw new BadDataException(`${title} "${pattern}" is not a valid regular expression, and contains no '*' wildcard to fall back on, so it would never match a monitor. Use a regex such as api-.* or a wildcard such as *api*.`);
27
+ }
28
+ }
29
+ //# sourceMappingURL=MonitorRulePatternValidator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MonitorRulePatternValidator.js","sourceRoot":"","sources":["../../../../../Server/Utils/StatusPage/MonitorRulePatternValidator.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,2CAA2C,CAAC;AACzE,OAAO,oBAAoB,MAAM,2CAA2C,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,OAAO,qCAAqC;IACjD,MAAM,CAAC,QAAQ,CAAC,IAGtB;QACC,qCAAqC,CAAC,WAAW,CAC/C,sBAAsB,EACtB,IAAI,CAAC,WAAW,CACjB,CAAC;QACF,qCAAqC,CAAC,WAAW,CAC/C,6BAA6B,EAC7B,IAAI,CAAC,kBAAkB,CACxB,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,WAAW,CACxB,KAAa,EACb,OAAkC;QAElC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,IAAI,oBAAoB,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YACrD,OAAO;QACT,CAAC;QAED,MAAM,IAAI,gBAAgB,CACxB,GAAG,KAAK,KAAK,OAAO,+KAA+K,CACpM,CAAC;IACJ,CAAC;CACF"}
@@ -4,6 +4,7 @@ import logger from "../Logger";
4
4
  import TelemetryEntityService from "../../Services/TelemetryEntityService";
5
5
  import TelemetryEntityRelationshipService from "../../Services/TelemetryEntityRelationshipService";
6
6
  import { deriveRelationships, } from "../../../Utils/Telemetry/EntityRelationship";
7
+ import crypto from "crypto";
7
8
  /*
8
9
  * Shared entity-registry reconciliation machinery (phases 2 + 5 of the
9
10
  * entity model — see Internal/Docs/OpenTelemetryEntities.md). Lives in
@@ -65,24 +66,66 @@ export const REGISTRY_PROMOTED_TYPES = new Set(Object.values(EntityType).filter(
65
66
  */
66
67
  const FENCE_NAMESPACE = "otel-maintenance-fence";
67
68
  const FENCE_SCOPE = "entity-reconcile";
69
+ const ROW_FENCE_SCOPE = "entity-reconcile-row";
68
70
  const FENCE_TTL_SECONDS = 5 * 60; // 5 minutes
71
+ /*
72
+ * Atomic claim. A read-then-write here admits every worker that reads the
73
+ * absent key in the same instant, which at ingest concurrency is all of them —
74
+ * the defect behind the row-lock convoy documented in
75
+ * DatabaseService.updateColumnsByIdIfUnlockedWithoutHooks. TTLs are jittered
76
+ * so a fleet-wide restart cannot re-synchronise the windows and rebuild the
77
+ * herd on a fixed period.
78
+ */
69
79
  async function shouldReconcile(fenceId) {
70
80
  try {
71
- const key = `${FENCE_SCOPE}:${fenceId}`;
72
- const seen = await GlobalCache.getString(FENCE_NAMESPACE, key);
73
- if (seen) {
74
- return false;
75
- }
76
- await GlobalCache.setString(FENCE_NAMESPACE, key, "1", {
77
- expiresInSeconds: FENCE_TTL_SECONDS,
78
- });
81
+ return await GlobalCache.setStringIfNotExists(FENCE_NAMESPACE, `${FENCE_SCOPE}:${hashFenceId(fenceId)}`, "1", { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) });
82
+ }
83
+ catch (_a) {
84
+ // If the cache is down, default to running the reconcile.
79
85
  return true;
80
86
  }
87
+ }
88
+ /*
89
+ * The set-level fence id concatenates every promoted entity key, so it is
90
+ * unbounded in length — a pod with many entities would otherwise produce a
91
+ * multi-kilobyte Redis key. Hashing keeps it fixed-width without changing which
92
+ * sets collide.
93
+ */
94
+ function hashFenceId(fenceId) {
95
+ return crypto.createHash("sha1").update(fenceId).digest("hex");
96
+ }
97
+ /*
98
+ * Per-ROW fence, sitting inside the set-level one.
99
+ *
100
+ * The set-level fence keys on (project + the whole promoted entity set), which
101
+ * in practice is unique per POD — a pod's own key is in the set. But the writes
102
+ * it gates are per ROW: the single TelemetryEntity row for a Kubernetes cluster
103
+ * takes one UPDATE per pod in that cluster per window, the namespace row one
104
+ * per pod in the namespace, and so on. Throttle granularity finer than write
105
+ * granularity means the throttle does not bound the writes at all, and the
106
+ * mismatch factor is the pod count — the same shape of defect as the Service
107
+ * heartbeat, one layer down.
108
+ *
109
+ * Claiming per row before the lookup also removes the redundant SELECT, not
110
+ * just the redundant UPDATE.
111
+ */
112
+ async function shouldReconcileRow(rowFenceId) {
113
+ try {
114
+ return await GlobalCache.setStringIfNotExists(FENCE_NAMESPACE, `${ROW_FENCE_SCOPE}:${hashFenceId(rowFenceId)}`, "1", { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) });
115
+ }
81
116
  catch (_a) {
82
117
  // If the cache is down, default to running the reconcile.
83
118
  return true;
84
119
  }
85
120
  }
121
+ async function releaseRowFence(rowFenceId) {
122
+ try {
123
+ await GlobalCache.deleteKey(FENCE_NAMESPACE, `${ROW_FENCE_SCOPE}:${hashFenceId(rowFenceId)}`);
124
+ }
125
+ catch (_a) {
126
+ // Best effort — losing the release costs one window of staleness.
127
+ }
128
+ }
86
129
  /*
87
130
  * Over-budget skips happen per row, but the warn log is fenced to once per
88
131
  * (project, entityType) per fence window — an over-budget project would
@@ -90,15 +133,7 @@ async function shouldReconcile(fenceId) {
90
133
  */
91
134
  export async function shouldWarnEntityBudgetOnce(data) {
92
135
  try {
93
- const key = `entity-budget-warn:${data.projectId.toString()}:${data.entityType}`;
94
- const seen = await GlobalCache.getString(FENCE_NAMESPACE, key);
95
- if (seen) {
96
- return false;
97
- }
98
- await GlobalCache.setString(FENCE_NAMESPACE, key, "1", {
99
- expiresInSeconds: FENCE_TTL_SECONDS,
100
- });
101
- return true;
136
+ return await GlobalCache.setStringIfNotExists(FENCE_NAMESPACE, `entity-budget-warn:${data.projectId.toString()}:${data.entityType}`, "1", { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) });
102
137
  }
103
138
  catch (_a) {
104
139
  // If the cache is down, default to warning (visibility over silence).
@@ -181,6 +216,9 @@ export async function reconcileByNaturalKey(data) {
181
216
  // Unresolved generic mapped type — TS cannot prove overlap directly.
182
217
  return Object.assign({ lastSeenAt: data.lastSeenAt }, (data.buildUpdate ? data.buildUpdate(existing) : {}));
183
218
  };
219
+ if (!(await shouldReconcileRow(data.rowFenceId))) {
220
+ return;
221
+ }
184
222
  const existing = await data.service.findOneBy({
185
223
  query: data.query,
186
224
  select,
@@ -194,14 +232,29 @@ export async function reconcileByNaturalKey(data) {
194
232
  * audit). buildBump returns only plain values — lastSeenAt plus, at most,
195
233
  * the descriptiveAttributes / labels JSON columns — which the primitive
196
234
  * persists via the driver transformer path. See ServiceService.updateLastSeen.
235
+ *
236
+ * SKIP LOCKED: a contended bump yields rather than queueing behind the
237
+ * writer that already holds the row. Registry rows for shared entities (a
238
+ * cluster, a namespace) are written by every pod that reports them, so
239
+ * this is exactly the hot-row shape that convoyed on Service.
197
240
  */
198
- await data.service.updateColumnsByIdWithoutHooks({
241
+ const wrote = await data.service.updateColumnsByIdIfUnlockedWithoutHooks({
199
242
  id: existing.id,
200
243
  data: buildBump(existing),
201
244
  });
245
+ if (!wrote) {
246
+ // Skipped under contention — re-open the window so the next batch retries.
247
+ await releaseRowFence(data.rowFenceId);
248
+ }
202
249
  return;
203
250
  }
204
251
  if (data.beforeCreate && !(await data.beforeCreate())) {
252
+ /*
253
+ * Over budget: release the row fence so the gate is re-evaluated next
254
+ * batch rather than being suppressed for a full window by a decision that
255
+ * wrote nothing.
256
+ */
257
+ await releaseRowFence(data.rowFenceId);
205
258
  return;
206
259
  }
207
260
  try {
@@ -224,12 +277,18 @@ export async function reconcileByNaturalKey(data) {
224
277
  });
225
278
  if (winner) {
226
279
  logger.debug(`EntityRegistry: create raced for ${data.describe} (concurrent insert); bumping lastSeenAt on the winning row.`);
227
- await data.service.updateColumnsByIdWithoutHooks({
280
+ await data.service.updateColumnsByIdIfUnlockedWithoutHooks({
228
281
  id: winner.id,
229
282
  data: buildBump(winner),
230
283
  });
231
284
  return;
232
285
  }
286
+ /*
287
+ * The insert itself was invalid. Re-open the row window so the next batch
288
+ * retries rather than being silently suppressed for the TTL — a genuinely
289
+ * broken row should keep surfacing this warning, not go quiet.
290
+ */
291
+ await releaseRowFence(data.rowFenceId);
233
292
  logger.warn(`EntityRegistry: create failed for ${data.describe}:`);
234
293
  logger.warn(err);
235
294
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityRegistry.js","sourceRoot":"","sources":["../../../../../Server/Utils/Telemetry/EntityRegistry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityRegistry.js","sourceRoot":"","sources":["../../../../../Server/Utils/Telemetry/EntityRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ import GlobalCache from "../../Infrastructure/GlobalCache";
2
+ import SingleFlight from "../SingleFlight";
3
+ import logger from "../Logger";
4
+ /** Derived from the model's liveness namespace — see cacheNamespace. */
5
+ function fingerprintNamespace(cacheNamespace) {
6
+ return `${cacheNamespace}-fingerprint`;
7
+ }
8
+ function writeWindowNamespace(cacheNamespace) {
9
+ return `${cacheNamespace}-write-window`;
10
+ }
11
+ export default class ResourceHeartbeat {
12
+ static async write(input) {
13
+ const cacheKey = input.id.toString();
14
+ /*
15
+ * Collapse duplicate concurrent callers inside THIS process first. One
16
+ * OTLP batch resolves the same resource once per contained resource — the
17
+ * host-metrics collector config OneUptime ships emits hundreds per batch —
18
+ * and without this every one of them independently asks Redis the same
19
+ * question and throws the answer away.
20
+ */
21
+ await SingleFlight.run(`${input.cacheNamespace}:${cacheKey}:${input.fingerprint}`, async () => {
22
+ await this.writeOnce(input, cacheKey);
23
+ });
24
+ }
25
+ static async writeOnce(input, cacheKey) {
26
+ var _a, _b;
27
+ /*
28
+ * Liveness gate. Presence-only and jittered: a fleet-wide restart would
29
+ * otherwise re-synchronise every resource's window and rebuild the herd on
30
+ * a fixed period.
31
+ */
32
+ let writeLiveness = true;
33
+ try {
34
+ writeLiveness = await GlobalCache.setStringIfNotExists(input.cacheNamespace, cacheKey, "1", {
35
+ expiresInSeconds: GlobalCache.withJitter(input.throttleInSeconds),
36
+ });
37
+ }
38
+ catch (_c) {
39
+ /*
40
+ * Cache unavailable — fail OPEN and refresh liveness anyway. Suppressing
41
+ * the heartbeat during a Redis outage is how a healthy resource is shown
42
+ * as disconnected while its data keeps arriving. Affordable only because
43
+ * the write below cannot queue.
44
+ */
45
+ writeLiveness = true;
46
+ }
47
+ const hasMetadata = Boolean(input.metadata) && Object.keys(input.metadata).length > 0;
48
+ /*
49
+ * Enrichment gate 1: has the payload actually changed? Compare-and-claim,
50
+ * so a genuinely new value is written promptly rather than waiting out a
51
+ * window.
52
+ */
53
+ let metadataChanged = false;
54
+ if (hasMetadata) {
55
+ try {
56
+ metadataChanged = await GlobalCache.setStringIfChanged(fingerprintNamespace(input.cacheNamespace), cacheKey, input.fingerprint, {
57
+ expiresInSeconds: GlobalCache.withJitter((_a = input.enrichmentThrottleInSeconds) !== null && _a !== void 0 ? _a : input.throttleInSeconds),
58
+ });
59
+ }
60
+ catch (_d) {
61
+ /*
62
+ * Fail CLOSED for enrichment, unlike liveness. These columns are
63
+ * descriptive; nothing is mis-reported by writing them a little later,
64
+ * and letting a Redis outage open this gate restores the per-batch
65
+ * write storm this exists to prevent.
66
+ */
67
+ metadataChanged = false;
68
+ }
69
+ }
70
+ /*
71
+ * Enrichment gate 2: the rate limiter. Change detection cannot tell a
72
+ * meaningful update apart from a gauge that moves every scrape — both
73
+ * present as "it changed". Since the churning case is unbounded, this
74
+ * bounds both.
75
+ */
76
+ let writeMetadata = false;
77
+ if (metadataChanged) {
78
+ try {
79
+ writeMetadata = await GlobalCache.setStringIfNotExists(writeWindowNamespace(input.cacheNamespace), cacheKey, "1", {
80
+ expiresInSeconds: GlobalCache.withJitter((_b = input.enrichmentThrottleInSeconds) !== null && _b !== void 0 ? _b : input.throttleInSeconds),
81
+ });
82
+ }
83
+ catch (_e) {
84
+ writeMetadata = false;
85
+ }
86
+ }
87
+ if (!writeLiveness && !writeMetadata) {
88
+ // Nothing to say. Issue no statement at all.
89
+ return;
90
+ }
91
+ /*
92
+ * Liveness rides along even on an enrichment-only write: refreshing it
93
+ * early is harmless (it is a floor, not a sample), and splitting the two
94
+ * into separate statements would double the writes we just halved.
95
+ */
96
+ const data = Object.assign(Object.assign({}, input.liveness), (writeMetadata
97
+ ? (input.metadata || {})
98
+ : {}));
99
+ let wrote = false;
100
+ try {
101
+ wrote = await input.service.updateColumnsByIdIfUnlockedWithoutHooks({
102
+ id: input.id,
103
+ data: data,
104
+ });
105
+ }
106
+ catch (err) {
107
+ /*
108
+ * Liveness must survive bad metadata. Every enrichment value is a
109
+ * collector-supplied attribute, so if one of them makes Postgres reject
110
+ * the statement it takes `lastSeenAt` down with it and the resource
111
+ * silently stops looking alive — the disconnection sweep then flips a
112
+ * perfectly healthy resource 15 minutes later with telemetry arriving
113
+ * the whole time. That was issue #3006. Retry with liveness only, so
114
+ * enrichment is what gets dropped, never the heartbeat.
115
+ */
116
+ logger.warn(`ResourceHeartbeat: metadata write failed for ${input.describe}, falling back to a liveness-only update: ${err instanceof Error ? err.message : String(err)}`);
117
+ await this.releaseGates(input.cacheNamespace, cacheKey, writeMetadata);
118
+ await input.service.updateColumnsByIdIfUnlockedWithoutHooks({
119
+ id: input.id,
120
+ data: input.liveness,
121
+ });
122
+ return;
123
+ }
124
+ if (!wrote && writeMetadata) {
125
+ /*
126
+ * The row was locked, so this write was SKIPPED rather than applied. For
127
+ * liveness that is a non-event — whoever holds the lock is storing the
128
+ * same timestamp. Enrichment is different: the holder may be writing an
129
+ * older payload, and the gates just claimed would suppress ours for the
130
+ * rest of the window. Release them so the next batch re-attempts.
131
+ *
132
+ * This cannot become a retry storm: each attempt is a non-blocking
133
+ * statement that returns immediately when contended.
134
+ */
135
+ await this.releaseGates(input.cacheNamespace, cacheKey, true);
136
+ }
137
+ }
138
+ /**
139
+ * Re-open the gates so the next batch retries. When enrichment was at stake
140
+ * those are the gates that must not stay claimed after a write that never
141
+ * happened; otherwise it is the liveness gate.
142
+ */
143
+ static async releaseGates(cacheNamespace, cacheKey, hadMetadata) {
144
+ const namespaces = hadMetadata
145
+ ? [
146
+ fingerprintNamespace(cacheNamespace),
147
+ writeWindowNamespace(cacheNamespace),
148
+ ]
149
+ : [cacheNamespace];
150
+ for (const namespace of namespaces) {
151
+ try {
152
+ await GlobalCache.deleteKey(namespace, cacheKey);
153
+ }
154
+ catch (_a) {
155
+ // Best effort — the retry is what actually matters.
156
+ }
157
+ }
158
+ }
159
+ }
160
+ //# sourceMappingURL=ResourceHeartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceHeartbeat.js","sourceRoot":"","sources":["../../../../../Server/Utils/Telemetry/ResourceHeartbeat.ts"],"names":[],"mappings":"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"}