@oneuptime/common 10.5.9 → 10.5.18

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 (164) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +1 -1
  2. package/Models/AnalyticsModels/Log.ts +1 -1
  3. package/Models/AnalyticsModels/Metric.ts +1 -1
  4. package/Models/AnalyticsModels/Profile.ts +1 -1
  5. package/Models/AnalyticsModels/ProfileSample.ts +1 -1
  6. package/Models/AnalyticsModels/Span.ts +1 -1
  7. package/Models/DatabaseModels/TelemetryException.ts +46 -34
  8. package/Models/DatabaseModels/TelemetryUsageBilling.ts +35 -2
  9. package/Server/API/AIAgentDataAPI.ts +25 -7
  10. package/Server/API/TelemetryAPI.ts +6 -0
  11. package/Server/API/TelemetryExceptionAPI.ts +6 -2
  12. package/Server/EnvironmentConfig.ts +27 -0
  13. package/Server/Infrastructure/ClickhouseDatabase.ts +21 -1
  14. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +19 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.ts +28 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.ts +24 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.ts +47 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.ts +34 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  20. package/Server/Infrastructure/PostgresDatabase.ts +27 -1
  21. package/Server/Infrastructure/QueueWorker.ts +54 -4
  22. package/Server/Infrastructure/Redis.ts +11 -0
  23. package/Server/Services/AnalyticsDatabaseService.ts +87 -0
  24. package/Server/Services/DatabaseService.ts +73 -0
  25. package/Server/Services/TelemetryAttributeService.ts +38 -2
  26. package/Server/Services/TelemetryExceptionService.ts +24 -49
  27. package/Server/Services/TelemetryUsageBillingService.ts +289 -166
  28. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +102 -72
  29. package/Server/Types/Database/Permissions/OwnedScopePermission.ts +81 -60
  30. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +67 -0
  31. package/Server/Utils/Express.ts +32 -0
  32. package/Server/Utils/GracefulShutdown.ts +194 -0
  33. package/Server/Utils/Logger.ts +12 -1
  34. package/Server/Utils/Monitor/MonitorLogUtil.ts +22 -17
  35. package/Server/Utils/Profiling.ts +14 -6
  36. package/Server/Utils/StartServer.ts +13 -5
  37. package/Server/Utils/Telemetry/ContextSpanProcessor.ts +48 -0
  38. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +289 -0
  39. package/Server/Utils/Telemetry/SpanUtil.ts +16 -35
  40. package/Server/Utils/Telemetry/StackTraceParser.ts +423 -0
  41. package/Server/Utils/Telemetry/TelemetryContext.ts +190 -0
  42. package/Server/Utils/Telemetry.ts +33 -7
  43. package/Tests/Server/Services/TelemetryAttributeService.test.ts +83 -0
  44. package/Tests/Server/Utils/Telemetry/LogExceptionExtractor.test.ts +0 -0
  45. package/Types/Database/AccessControl/OwnedThrough.ts +31 -3
  46. package/Types/Telemetry/ServiceType.ts +10 -0
  47. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -1
  48. package/UI/Components/Dictionary/Dictionary.tsx +19 -0
  49. package/UI/Components/Filters/FiltersForm.tsx +1 -0
  50. package/UI/Components/Filters/JSONFilter.tsx +2 -0
  51. package/UI/Components/Filters/Types/Filter.ts +1 -0
  52. package/UI/Components/LogsViewer/LogsViewer.tsx +16 -0
  53. package/UI/Utils/Project.ts +6 -0
  54. package/UI/Utils/Telemetry/Telemetry.ts +65 -0
  55. package/UI/Utils/TelemetryService.ts +150 -0
  56. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -1
  57. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  58. package/build/dist/Models/AnalyticsModels/Log.js +1 -1
  59. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  60. package/build/dist/Models/AnalyticsModels/Metric.js +1 -1
  61. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  62. package/build/dist/Models/AnalyticsModels/Profile.js +1 -1
  63. package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
  64. package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -1
  65. package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
  66. package/build/dist/Models/AnalyticsModels/Span.js +1 -1
  67. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  68. package/build/dist/Models/DatabaseModels/TelemetryException.js +47 -33
  69. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  70. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +36 -2
  71. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
  72. package/build/dist/Server/API/AIAgentDataAPI.js +24 -8
  73. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  74. package/build/dist/Server/API/TelemetryAPI.js +4 -0
  75. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  76. package/build/dist/Server/API/TelemetryExceptionAPI.js +6 -2
  77. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  78. package/build/dist/Server/EnvironmentConfig.js +19 -0
  79. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  80. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +16 -2
  81. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  82. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +10 -9
  83. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  84. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.js +23 -0
  85. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.js.map +1 -0
  86. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.js +19 -0
  87. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.js.map +1 -0
  88. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.js +22 -0
  89. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.js.map +1 -0
  90. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.js +25 -0
  91. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.js.map +1 -0
  92. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  93. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  94. package/build/dist/Server/Infrastructure/PostgresDatabase.js +20 -1
  95. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  96. package/build/dist/Server/Infrastructure/QueueWorker.js +40 -3
  97. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  98. package/build/dist/Server/Infrastructure/Redis.js +5 -0
  99. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  100. package/build/dist/Server/Services/AnalyticsDatabaseService.js +59 -0
  101. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  102. package/build/dist/Server/Services/DatabaseService.js +62 -0
  103. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  104. package/build/dist/Server/Services/TelemetryAttributeService.js +23 -1
  105. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  106. package/build/dist/Server/Services/TelemetryExceptionService.js +16 -41
  107. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  108. package/build/dist/Server/Services/TelemetryUsageBillingService.js +211 -147
  109. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  110. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +84 -63
  111. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  112. package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js +67 -49
  113. package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js.map +1 -1
  114. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +51 -0
  115. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  116. package/build/dist/Server/Utils/Express.js +23 -0
  117. package/build/dist/Server/Utils/Express.js.map +1 -1
  118. package/build/dist/Server/Utils/GracefulShutdown.js +145 -0
  119. package/build/dist/Server/Utils/GracefulShutdown.js.map +1 -0
  120. package/build/dist/Server/Utils/Logger.js +8 -1
  121. package/build/dist/Server/Utils/Logger.js.map +1 -1
  122. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -10
  123. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  124. package/build/dist/Server/Utils/Profiling.js +8 -3
  125. package/build/dist/Server/Utils/Profiling.js.map +1 -1
  126. package/build/dist/Server/Utils/StartServer.js +12 -4
  127. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  128. package/build/dist/Server/Utils/Telemetry/ContextSpanProcessor.js +37 -0
  129. package/build/dist/Server/Utils/Telemetry/ContextSpanProcessor.js.map +1 -0
  130. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +214 -0
  131. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -0
  132. package/build/dist/Server/Utils/Telemetry/SpanUtil.js +15 -24
  133. package/build/dist/Server/Utils/Telemetry/SpanUtil.js.map +1 -1
  134. package/build/dist/Server/Utils/Telemetry/StackTraceParser.js +365 -0
  135. package/build/dist/Server/Utils/Telemetry/StackTraceParser.js.map +1 -0
  136. package/build/dist/Server/Utils/Telemetry/TelemetryContext.js +124 -0
  137. package/build/dist/Server/Utils/Telemetry/TelemetryContext.js.map +1 -0
  138. package/build/dist/Server/Utils/Telemetry.js +22 -5
  139. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  140. package/build/dist/Tests/Server/Services/TelemetryAttributeService.test.js +50 -0
  141. package/build/dist/Tests/Server/Services/TelemetryAttributeService.test.js.map +1 -0
  142. package/build/dist/Tests/Server/Utils/Telemetry/LogExceptionExtractor.test.js +0 -0
  143. package/build/dist/Tests/Server/Utils/Telemetry/LogExceptionExtractor.test.js.map +1 -0
  144. package/build/dist/Types/Database/AccessControl/OwnedThrough.js +7 -2
  145. package/build/dist/Types/Database/AccessControl/OwnedThrough.js.map +1 -1
  146. package/build/dist/Types/Telemetry/ServiceType.js +10 -0
  147. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  148. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +7 -1
  149. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  150. package/build/dist/UI/Components/Dictionary/Dictionary.js +10 -0
  151. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  152. package/build/dist/UI/Components/Filters/FiltersForm.js +1 -1
  153. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  154. package/build/dist/UI/Components/Filters/JSONFilter.js +1 -1
  155. package/build/dist/UI/Components/Filters/JSONFilter.js.map +1 -1
  156. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +15 -0
  157. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  158. package/build/dist/UI/Utils/Project.js +5 -0
  159. package/build/dist/UI/Utils/Project.js.map +1 -1
  160. package/build/dist/UI/Utils/Telemetry/Telemetry.js +44 -0
  161. package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
  162. package/build/dist/UI/Utils/TelemetryService.js +113 -0
  163. package/build/dist/UI/Utils/TelemetryService.js.map +1 -0
  164. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { MeteredPlanUtil } from "../Types/Billing/MeteredPlan/AllMeteredPlans";
2
2
  import TelemetryMeteredPlan from "../Types/Billing/MeteredPlan/TelemetryMeteredPlan";
3
3
  import DatabaseService from "./DatabaseService";
4
4
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
5
- import LIMIT_MAX, { LIMIT_INFINITY } from "../../Types/Database/LimitMax";
5
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
6
6
  import OneUptimeDate from "../../Types/Date";
7
7
  import Decimal from "../../Types/Decimal";
8
8
  import BadDataException from "../../Types/Exception/BadDataException";
@@ -12,17 +12,24 @@ import Model, {
12
12
  DEFAULT_RETENTION_IN_DAYS,
13
13
  } from "../../Models/DatabaseModels/TelemetryUsageBilling";
14
14
  import ServiceService from "./ServiceService";
15
+ import ProjectService from "./ProjectService";
16
+ import Project from "../../Models/DatabaseModels/Project";
15
17
  import SpanService from "./SpanService";
16
18
  import LogService from "./LogService";
17
19
  import MetricService from "./MetricService";
18
20
  import ExceptionInstanceService from "./ExceptionInstanceService";
19
21
  import ProfileService from "./ProfileService";
20
22
  import ProfileSampleService from "./ProfileSampleService";
21
- import AnalyticsQueryHelper from "../Types/AnalyticsDatabase/QueryHelper";
22
23
  import DiskSize from "../../Types/DiskSize";
23
24
  import logger from "../Utils/Logger";
24
- import PositiveNumber from "../../Types/PositiveNumber";
25
25
  import ServiceModel from "../../Models/DatabaseModels/Service";
26
+ import HostService from "./HostService";
27
+ import DockerHostService from "./DockerHostService";
28
+ import KubernetesClusterService from "./KubernetesClusterService";
29
+ import Host from "../../Models/DatabaseModels/Host";
30
+ import DockerHost from "../../Models/DatabaseModels/DockerHost";
31
+ import KubernetesCluster from "../../Models/DatabaseModels/KubernetesCluster";
32
+ import ServiceType from "../../Types/Telemetry/ServiceType";
26
33
  import {
27
34
  AverageSpanRowSizeInBytes,
28
35
  AverageLogRowSizeInBytes,
@@ -116,13 +123,157 @@ export class Service extends DatabaseService<Model> {
116
123
  const startOfDay: Date = OneUptimeDate.getStartOfDay(usageDate);
117
124
  const endOfDay: Date = OneUptimeDate.getEndOfDay(usageDate);
118
125
 
119
- const services: Array<ServiceModel> = await ServiceService.findBy({
126
+ /*
127
+ * Enumerate usage from ClickHouse by (serviceId, serviceType) in a
128
+ * single aggregation scan per analytics table. Unlike the old
129
+ * per-Service-row loop, this surfaces EVERY resource that emitted
130
+ * telemetry — real Services AND Hosts / Docker hosts / Kubernetes
131
+ * clusters / unattributed (serviceId = projectId) — so infrastructure
132
+ * telemetry is no longer ingested for free. `byteSize(*)` gives the
133
+ * estimated ingested bytes per group; the configured average row size
134
+ * is only a fallback if the engine returns 0.
135
+ */
136
+ type ServiceUsage = {
137
+ serviceType: string | null;
138
+ bytes: number;
139
+ };
140
+ const usageByServiceId: Map<string, ServiceUsage> = new Map();
141
+
142
+ const addUsage: (
143
+ rows: Array<{
144
+ serviceId: string;
145
+ serviceType: string | null;
146
+ rowCount: number;
147
+ estimatedBytes: number;
148
+ }>,
149
+ fallbackRowSizeInBytes: number,
150
+ ) => void = (
151
+ rows: Array<{
152
+ serviceId: string;
153
+ serviceType: string | null;
154
+ rowCount: number;
155
+ estimatedBytes: number;
156
+ }>,
157
+ fallbackRowSizeInBytes: number,
158
+ ): void => {
159
+ for (const row of rows) {
160
+ const bytes: number =
161
+ row.estimatedBytes > 0
162
+ ? row.estimatedBytes
163
+ : row.rowCount * fallbackRowSizeInBytes;
164
+ const existing: ServiceUsage | undefined = usageByServiceId.get(
165
+ row.serviceId,
166
+ );
167
+ if (existing) {
168
+ existing.bytes += bytes;
169
+ if (!existing.serviceType && row.serviceType) {
170
+ existing.serviceType = row.serviceType;
171
+ }
172
+ } else {
173
+ usageByServiceId.set(row.serviceId, {
174
+ serviceType: row.serviceType,
175
+ bytes,
176
+ });
177
+ }
178
+ }
179
+ };
180
+
181
+ try {
182
+ if (data.productType === ProductType.Traces) {
183
+ addUsage(
184
+ await SpanService.groupTelemetryUsageByService({
185
+ projectId: data.projectId,
186
+ timestampColumnName: "startTime",
187
+ startDate: startOfDay,
188
+ endDate: endOfDay,
189
+ }),
190
+ averageRowSizeInBytes,
191
+ );
192
+ addUsage(
193
+ await ExceptionInstanceService.groupTelemetryUsageByService({
194
+ projectId: data.projectId,
195
+ timestampColumnName: "time",
196
+ startDate: startOfDay,
197
+ endDate: endOfDay,
198
+ }),
199
+ averageExceptionRowSizeInBytes,
200
+ );
201
+ } else if (data.productType === ProductType.Logs) {
202
+ addUsage(
203
+ await LogService.groupTelemetryUsageByService({
204
+ projectId: data.projectId,
205
+ timestampColumnName: "time",
206
+ startDate: startOfDay,
207
+ endDate: endOfDay,
208
+ }),
209
+ averageRowSizeInBytes,
210
+ );
211
+ } else if (data.productType === ProductType.Metrics) {
212
+ addUsage(
213
+ await MetricService.groupTelemetryUsageByService({
214
+ projectId: data.projectId,
215
+ timestampColumnName: "time",
216
+ startDate: startOfDay,
217
+ endDate: endOfDay,
218
+ }),
219
+ averageRowSizeInBytes,
220
+ );
221
+ } else if (data.productType === ProductType.Profiles) {
222
+ addUsage(
223
+ await ProfileService.groupTelemetryUsageByService({
224
+ projectId: data.projectId,
225
+ timestampColumnName: "startTime",
226
+ startDate: startOfDay,
227
+ endDate: endOfDay,
228
+ }),
229
+ averageRowSizeInBytes,
230
+ );
231
+ addUsage(
232
+ await ProfileSampleService.groupTelemetryUsageByService({
233
+ projectId: data.projectId,
234
+ timestampColumnName: "time",
235
+ startDate: startOfDay,
236
+ endDate: endOfDay,
237
+ }),
238
+ averageProfileSampleRowSizeInBytes,
239
+ );
240
+ }
241
+ } catch (error) {
242
+ logger.error(
243
+ `Failed to aggregate telemetry usage for project ${data.projectId.toString()} (${data.productType}):`,
244
+ );
245
+ logger.error(error as Error);
246
+ return;
247
+ }
248
+
249
+ if (usageByServiceId.size === 0) {
250
+ return;
251
+ }
252
+
253
+ /*
254
+ * Resolve retention per resource (Service / Host / DockerHost /
255
+ * KubernetesCluster overrides), defaulting to the project default for
256
+ * everything else — including the unattributed bucket. Retention
257
+ * scales the billed cost, so we honor per-resource overrides.
258
+ */
259
+ const retentionByServiceId: Map<string, number> =
260
+ await this.buildTelemetryRetentionMap(data.projectId);
261
+ const projectDefaultRetentionInDays: number =
262
+ await this.getProjectDefaultRetentionInDays(data.projectId);
263
+
264
+ /*
265
+ * Skip serviceIds already staged for this day so re-runs of the cron
266
+ * don't double-count (updateUsageBilling accumulates into the day's
267
+ * row). One query instead of a findOneBy per serviceId.
268
+ */
269
+ const alreadyStaged: Array<Model> = await this.findBy({
120
270
  query: {
121
271
  projectId: data.projectId,
272
+ productType: data.productType,
273
+ day: usageDayString,
122
274
  },
123
275
  select: {
124
- _id: true,
125
- retainTelemetryDataForDays: true,
276
+ serviceId: true,
126
277
  },
127
278
  skip: 0,
128
279
  limit: LIMIT_MAX,
@@ -130,187 +281,155 @@ export class Service extends DatabaseService<Model> {
130
281
  isRoot: true,
131
282
  },
132
283
  });
284
+ const stagedServiceIds: Set<string> = new Set(
285
+ alreadyStaged
286
+ .map((row: Model) => {
287
+ return row.serviceId?.toString();
288
+ })
289
+ .filter((id: string | undefined): id is string => {
290
+ return Boolean(id);
291
+ }),
292
+ );
133
293
 
134
- if (!services || services.length === 0) {
135
- return;
136
- }
137
-
138
- for (const service of services) {
139
- if (!service?.id) {
294
+ for (const [serviceIdStr, usage] of usageByServiceId) {
295
+ /*
296
+ * Monitor telemetry is billed via the Active Monitoring plan, and
297
+ * Alert/Incident telemetry is OneUptime's own operational data —
298
+ * neither is charged as ingested telemetry volume.
299
+ */
300
+ if (
301
+ usage.serviceType === ServiceType.Monitor ||
302
+ usage.serviceType === ServiceType.Alert ||
303
+ usage.serviceType === ServiceType.Incident
304
+ ) {
140
305
  continue;
141
306
  }
142
307
 
143
- const existingEntry: Model | null = await this.findOneBy({
144
- query: {
145
- projectId: data.projectId,
146
- productType: data.productType,
147
- serviceId: service.id,
148
- day: usageDayString,
149
- },
150
- select: {
151
- _id: true,
152
- },
153
- props: {
154
- isRoot: true,
155
- },
156
- });
157
-
158
- if (existingEntry) {
308
+ if (stagedServiceIds.has(serviceIdStr)) {
159
309
  continue;
160
310
  }
161
311
 
162
- let estimatedBytes: number = 0;
163
-
164
- try {
165
- if (data.productType === ProductType.Traces) {
166
- const spanCount: PositiveNumber = await SpanService.countBy({
167
- query: {
168
- projectId: data.projectId,
169
- serviceId: service.id,
170
- startTime: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
171
- },
172
- skip: 0,
173
- limit: LIMIT_INFINITY,
174
- props: {
175
- isRoot: true,
176
- },
177
- });
178
-
179
- const exceptionCount: PositiveNumber =
180
- await ExceptionInstanceService.countBy({
181
- query: {
182
- projectId: data.projectId,
183
- serviceId: service.id,
184
- time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
185
- },
186
- skip: 0,
187
- limit: LIMIT_INFINITY,
188
- props: {
189
- isRoot: true,
190
- },
191
- });
192
-
193
- const totalSpanCount: number = spanCount.toNumber();
194
- const totalExceptionCount: number = exceptionCount.toNumber();
195
-
196
- if (totalSpanCount <= 0 && totalExceptionCount <= 0) {
197
- continue;
198
- }
199
-
200
- estimatedBytes =
201
- totalSpanCount * averageRowSizeInBytes +
202
- totalExceptionCount * averageExceptionRowSizeInBytes;
203
- } else if (data.productType === ProductType.Logs) {
204
- const count: PositiveNumber = await LogService.countBy({
205
- query: {
206
- projectId: data.projectId,
207
- serviceId: service.id,
208
- time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
209
- },
210
- skip: 0,
211
- limit: LIMIT_INFINITY,
212
- props: {
213
- isRoot: true,
214
- },
215
- });
216
-
217
- const totalRowCount: number = count.toNumber();
218
-
219
- if (totalRowCount <= 0) {
220
- continue;
221
- }
312
+ const estimatedGigabytes: number = DiskSize.byteSizeToGB(usage.bytes);
313
+ if (!Number.isFinite(estimatedGigabytes) || estimatedGigabytes <= 0) {
314
+ continue;
315
+ }
222
316
 
223
- estimatedBytes = totalRowCount * averageRowSizeInBytes;
224
- } else if (data.productType === ProductType.Metrics) {
225
- const count: PositiveNumber = await MetricService.countBy({
226
- query: {
227
- projectId: data.projectId,
228
- serviceId: service.id,
229
- time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
230
- },
231
- skip: 0,
232
- limit: LIMIT_INFINITY,
233
- props: {
234
- isRoot: true,
235
- },
236
- });
317
+ const retentionInDays: number =
318
+ retentionByServiceId.get(serviceIdStr) ?? projectDefaultRetentionInDays;
237
319
 
238
- const totalRowCount: number = count.toNumber();
320
+ /*
321
+ * Legacy rows (pre-discriminator) and any null serviceType are
322
+ * treated as OpenTelemetry — historically every billed serviceId was
323
+ * a real Service.
324
+ */
325
+ const serviceType: ServiceType =
326
+ (usage.serviceType as ServiceType | null) ?? ServiceType.OpenTelemetry;
239
327
 
240
- if (totalRowCount <= 0) {
241
- continue;
242
- }
243
-
244
- estimatedBytes = totalRowCount * averageRowSizeInBytes;
245
- } else if (data.productType === ProductType.Profiles) {
246
- const profileCount: PositiveNumber = await ProfileService.countBy({
247
- query: {
248
- projectId: data.projectId,
249
- serviceId: service.id,
250
- startTime: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
251
- },
252
- skip: 0,
253
- limit: LIMIT_INFINITY,
254
- props: {
255
- isRoot: true,
256
- },
257
- });
328
+ await this.updateUsageBilling({
329
+ projectId: data.projectId,
330
+ productType: data.productType,
331
+ serviceId: new ObjectID(serviceIdStr),
332
+ serviceType: serviceType,
333
+ dataIngestedInGB: estimatedGigabytes,
334
+ retentionInDays: retentionInDays,
335
+ usageDate: usageDate,
336
+ });
337
+ }
338
+ }
258
339
 
259
- const profileSampleCount: PositiveNumber =
260
- await ProfileSampleService.countBy({
261
- query: {
262
- projectId: data.projectId,
263
- serviceId: service.id,
264
- time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
265
- },
266
- skip: 0,
267
- limit: LIMIT_INFINITY,
268
- props: {
269
- isRoot: true,
270
- },
271
- });
272
-
273
- const totalProfileCount: number = profileCount.toNumber();
274
- const totalProfileSampleCount: number = profileSampleCount.toNumber();
275
-
276
- if (totalProfileCount <= 0 && totalProfileSampleCount <= 0) {
277
- continue;
278
- }
340
+ /*
341
+ * Map of resourceId -> retainTelemetryDataForDays for every resource in
342
+ * the project that can own telemetry (Service, Host, DockerHost,
343
+ * KubernetesCluster). Used to scale billed cost by the actual retention
344
+ * applied to each resource's telemetry. Resources without an override
345
+ * (and the unattributed bucket) fall back to the project default.
346
+ */
347
+ @CaptureSpan()
348
+ private async buildTelemetryRetentionMap(
349
+ projectId: ObjectID,
350
+ ): Promise<Map<string, number>> {
351
+ const retentionByServiceId: Map<string, number> = new Map();
279
352
 
280
- estimatedBytes =
281
- totalProfileCount * averageRowSizeInBytes +
282
- totalProfileSampleCount * averageProfileSampleRowSizeInBytes;
283
- }
284
- } catch (error) {
285
- logger.error(
286
- `Failed to compute telemetry usage for service ${service.id?.toString()}:`,
353
+ const services: Array<ServiceModel> = await ServiceService.findBy({
354
+ query: { projectId: projectId },
355
+ select: { _id: true, retainTelemetryDataForDays: true },
356
+ skip: 0,
357
+ limit: LIMIT_MAX,
358
+ props: { isRoot: true },
359
+ });
360
+ for (const service of services) {
361
+ if (service.id && service.retainTelemetryDataForDays) {
362
+ retentionByServiceId.set(
363
+ service.id.toString(),
364
+ service.retainTelemetryDataForDays,
287
365
  );
288
- logger.error(error as Error);
289
- continue;
290
366
  }
367
+ }
291
368
 
292
- if (estimatedBytes <= 0) {
293
- continue;
369
+ const hosts: Array<Host> = await HostService.findBy({
370
+ query: { projectId: projectId },
371
+ select: { _id: true, retainTelemetryDataForDays: true },
372
+ skip: 0,
373
+ limit: LIMIT_MAX,
374
+ props: { isRoot: true },
375
+ });
376
+ for (const host of hosts) {
377
+ if (host.id && host.retainTelemetryDataForDays) {
378
+ retentionByServiceId.set(
379
+ host.id.toString(),
380
+ host.retainTelemetryDataForDays,
381
+ );
294
382
  }
383
+ }
295
384
 
296
- const estimatedGigabytes: number = DiskSize.byteSizeToGB(estimatedBytes);
297
-
298
- if (!Number.isFinite(estimatedGigabytes) || estimatedGigabytes <= 0) {
299
- continue;
385
+ const dockerHosts: Array<DockerHost> = await DockerHostService.findBy({
386
+ query: { projectId: projectId },
387
+ select: { _id: true, retainTelemetryDataForDays: true },
388
+ skip: 0,
389
+ limit: LIMIT_MAX,
390
+ props: { isRoot: true },
391
+ });
392
+ for (const dockerHost of dockerHosts) {
393
+ if (dockerHost.id && dockerHost.retainTelemetryDataForDays) {
394
+ retentionByServiceId.set(
395
+ dockerHost.id.toString(),
396
+ dockerHost.retainTelemetryDataForDays,
397
+ );
300
398
  }
399
+ }
301
400
 
302
- const dataRetentionInDays: number =
303
- service.retainTelemetryDataForDays || DEFAULT_RETENTION_IN_DAYS;
304
-
305
- await this.updateUsageBilling({
306
- projectId: data.projectId,
307
- productType: data.productType,
308
- serviceId: service.id,
309
- dataIngestedInGB: estimatedGigabytes,
310
- retentionInDays: dataRetentionInDays,
311
- usageDate: usageDate,
401
+ const clusters: Array<KubernetesCluster> =
402
+ await KubernetesClusterService.findBy({
403
+ query: { projectId: projectId },
404
+ select: { _id: true, retainTelemetryDataForDays: true },
405
+ skip: 0,
406
+ limit: LIMIT_MAX,
407
+ props: { isRoot: true },
312
408
  });
409
+ for (const cluster of clusters) {
410
+ if (cluster.id && cluster.retainTelemetryDataForDays) {
411
+ retentionByServiceId.set(
412
+ cluster.id.toString(),
413
+ cluster.retainTelemetryDataForDays,
414
+ );
415
+ }
313
416
  }
417
+
418
+ return retentionByServiceId;
419
+ }
420
+
421
+ @CaptureSpan()
422
+ private async getProjectDefaultRetentionInDays(
423
+ projectId: ObjectID,
424
+ ): Promise<number> {
425
+ const project: Project | null = await ProjectService.findOneById({
426
+ id: projectId,
427
+ select: { defaultTelemetryRetentionInDays: true },
428
+ props: { isRoot: true },
429
+ });
430
+ return (
431
+ project?.defaultTelemetryRetentionInDays || DEFAULT_RETENTION_IN_DAYS
432
+ );
314
433
  }
315
434
 
316
435
  @CaptureSpan()
@@ -318,6 +437,7 @@ export class Service extends DatabaseService<Model> {
318
437
  projectId: ObjectID;
319
438
  productType: ProductType;
320
439
  serviceId: ObjectID;
440
+ serviceType?: ServiceType | undefined;
321
441
  dataIngestedInGB: number;
322
442
  retentionInDays: number;
323
443
  usageDate?: Date;
@@ -404,6 +524,9 @@ export class Service extends DatabaseService<Model> {
404
524
  usageBilling.productType = data.productType;
405
525
  usageBilling.dataIngestedInGB = new Decimal(data.dataIngestedInGB);
406
526
  usageBilling.serviceId = data.serviceId;
527
+ if (data.serviceType) {
528
+ usageBilling.serviceType = data.serviceType;
529
+ }
407
530
  usageBilling.retainTelemetryDataForDays = data.retentionInDays;
408
531
  usageBilling.isReportedToBillingProvider = false;
409
532
  usageBilling.createdAt = usageDate;