@oneuptime/common 11.3.9 → 11.3.10
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/MutableMetric.ts +333 -0
- package/Server/Services/AlertService.ts +167 -206
- package/Server/Services/AlertStateTimelineService.ts +121 -1
- package/Server/Services/AnalyticsDatabaseService.ts +19 -23
- package/Server/Services/IncidentService.ts +179 -252
- package/Server/Services/IncidentStateTimelineService.ts +120 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MetricAggregationService.ts +73 -3
- package/Server/Services/MetricService.ts +228 -0
- package/Server/Services/MutableMetricService.ts +275 -0
- package/Server/Services/TelemetryAttributeService.ts +183 -1
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +2 -0
- package/Server/Utils/AnalyticsDatabase/Statement.ts +2 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +143 -0
- package/Tests/Types/Monitor/MonitorStepEntityScope.test.ts +0 -56
- package/Types/AnalyticsDatabase/AnalyticsTableEngine.ts +1 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +1 -0
- package/Types/Monitor/MonitorStepMetricMonitor.ts +0 -27
- package/build/dist/Models/AnalyticsModels/MutableMetric.js +275 -0
- package/build/dist/Models/AnalyticsModels/MutableMetric.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +133 -170
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +97 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +16 -15
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +142 -207
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +97 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +49 -3
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +155 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MutableMetricService.js +182 -0
- package/build/dist/Server/Services/MutableMetricService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js +131 -6
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +2 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +2 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +0 -13
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import CreateBy from "../Types/Database/CreateBy";
|
|
2
2
|
import DeleteBy from "../Types/Database/DeleteBy";
|
|
3
|
-
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
3
|
+
import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
|
|
4
4
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
5
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
5
6
|
import DatabaseService from "./DatabaseService";
|
|
6
7
|
import IncidentPublicNoteService from "./IncidentPublicNoteService";
|
|
7
8
|
import IncidentService from "./IncidentService";
|
|
@@ -643,6 +644,113 @@ ${createdItem.rootCause}`,
|
|
|
643
644
|
return createdItem;
|
|
644
645
|
}
|
|
645
646
|
|
|
647
|
+
@CaptureSpan()
|
|
648
|
+
protected override async onBeforeUpdate(
|
|
649
|
+
updateBy: UpdateBy<IncidentStateTimeline>,
|
|
650
|
+
): Promise<OnUpdate<IncidentStateTimeline>> {
|
|
651
|
+
const incidentIds: Array<ObjectID> =
|
|
652
|
+
await this.getIncidentIdsForTimelineQuery(updateBy);
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
updateBy,
|
|
656
|
+
carryForward: incidentIds,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
@CaptureSpan()
|
|
661
|
+
protected override async onUpdateSuccess(
|
|
662
|
+
onUpdate: OnUpdate<IncidentStateTimeline>,
|
|
663
|
+
updatedItemIds: ObjectID[],
|
|
664
|
+
): Promise<OnUpdate<IncidentStateTimeline>> {
|
|
665
|
+
const incidentIds: Array<ObjectID> = [
|
|
666
|
+
...(Array.isArray(onUpdate.carryForward)
|
|
667
|
+
? (onUpdate.carryForward as Array<ObjectID>)
|
|
668
|
+
: []),
|
|
669
|
+
];
|
|
670
|
+
|
|
671
|
+
if (updatedItemIds.length > 0) {
|
|
672
|
+
const updatedTimelines: Array<IncidentStateTimeline> = await this.findBy({
|
|
673
|
+
query: {
|
|
674
|
+
_id: QueryHelper.any(updatedItemIds),
|
|
675
|
+
},
|
|
676
|
+
select: {
|
|
677
|
+
incidentId: true,
|
|
678
|
+
},
|
|
679
|
+
skip: 0,
|
|
680
|
+
limit: LIMIT_PER_PROJECT,
|
|
681
|
+
props: {
|
|
682
|
+
isRoot: true,
|
|
683
|
+
ignoreHooks: true,
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
incidentIds.push(...this.getIncidentIdsFromTimelines(updatedTimelines));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this.refreshIncidentMetricsForIncidentIds(incidentIds);
|
|
691
|
+
|
|
692
|
+
return onUpdate;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private async getIncidentIdsForTimelineQuery(
|
|
696
|
+
updateBy: UpdateBy<IncidentStateTimeline>,
|
|
697
|
+
): Promise<Array<ObjectID>> {
|
|
698
|
+
const timelines: Array<IncidentStateTimeline> = await this.findBy({
|
|
699
|
+
query: updateBy.query,
|
|
700
|
+
select: {
|
|
701
|
+
incidentId: true,
|
|
702
|
+
},
|
|
703
|
+
skip: updateBy.skip,
|
|
704
|
+
limit: updateBy.limit,
|
|
705
|
+
props: {
|
|
706
|
+
isRoot: true,
|
|
707
|
+
ignoreHooks: true,
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
return this.getIncidentIdsFromTimelines(timelines);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private getIncidentIdsFromTimelines(
|
|
715
|
+
timelines: Array<IncidentStateTimeline>,
|
|
716
|
+
): Array<ObjectID> {
|
|
717
|
+
const incidentIds: Map<string, ObjectID> = new Map<string, ObjectID>();
|
|
718
|
+
|
|
719
|
+
for (const timeline of timelines) {
|
|
720
|
+
if (timeline.incidentId) {
|
|
721
|
+
incidentIds.set(timeline.incidentId.toString(), timeline.incidentId);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return Array.from(incidentIds.values());
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private refreshIncidentMetricsForIncidentIds(
|
|
729
|
+
incidentIds: Array<ObjectID>,
|
|
730
|
+
): void {
|
|
731
|
+
const uniqueIncidentIds: Map<string, ObjectID> = new Map<
|
|
732
|
+
string,
|
|
733
|
+
ObjectID
|
|
734
|
+
>();
|
|
735
|
+
|
|
736
|
+
for (const incidentId of incidentIds) {
|
|
737
|
+
uniqueIncidentIds.set(incidentId.toString(), incidentId);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
for (const incidentId of uniqueIncidentIds.values()) {
|
|
741
|
+
IncidentService.refreshIncidentMetrics({
|
|
742
|
+
incidentId: incidentId,
|
|
743
|
+
}).catch((error: Error) => {
|
|
744
|
+
logger.error(`Error while refreshing incident metrics:`, {
|
|
745
|
+
incidentId: incidentId.toString(),
|
|
746
|
+
} as LogAttributes);
|
|
747
|
+
logger.error(error, {
|
|
748
|
+
incidentId: incidentId.toString(),
|
|
749
|
+
} as LogAttributes);
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
646
754
|
private async isLastIncidentState(data: {
|
|
647
755
|
projectId: ObjectID;
|
|
648
756
|
incidentStateId: ObjectID;
|
|
@@ -1027,6 +1135,17 @@ ${createdItem.rootCause}`,
|
|
|
1027
1135
|
},
|
|
1028
1136
|
});
|
|
1029
1137
|
}
|
|
1138
|
+
|
|
1139
|
+
IncidentService.refreshIncidentMetrics({
|
|
1140
|
+
incidentId: incidentId,
|
|
1141
|
+
}).catch((error: Error) => {
|
|
1142
|
+
logger.error(`Error while refreshing incident metrics:`, {
|
|
1143
|
+
incidentId: incidentId?.toString(),
|
|
1144
|
+
} as LogAttributes);
|
|
1145
|
+
logger.error(error, {
|
|
1146
|
+
incidentId: incidentId?.toString(),
|
|
1147
|
+
} as LogAttributes);
|
|
1148
|
+
});
|
|
1030
1149
|
}
|
|
1031
1150
|
|
|
1032
1151
|
return onDelete;
|
package/Server/Services/Index.ts
CHANGED
|
@@ -61,6 +61,7 @@ import MailService from "./MailService";
|
|
|
61
61
|
import MetricService from "./MetricService";
|
|
62
62
|
import MetricItemAggMV1mService from "./MetricItemAggMV1mService";
|
|
63
63
|
import MetricItemAggMV1mByHostV2Service from "./MetricItemAggMV1mByHostV2Service";
|
|
64
|
+
import MutableMetricService from "./MutableMetricService";
|
|
64
65
|
import MetricBaselineService from "./MetricBaselineService";
|
|
65
66
|
import MonitorCustomFieldService from "./MonitorCustomFieldService";
|
|
66
67
|
import MonitorGroupOwnerTeamService from "./MonitorGroupOwnerTeamService";
|
|
@@ -499,6 +500,7 @@ export const AnalyticsServices: Array<
|
|
|
499
500
|
LogService,
|
|
500
501
|
SpanService,
|
|
501
502
|
MetricService,
|
|
503
|
+
MutableMetricService,
|
|
502
504
|
/*
|
|
503
505
|
* Materialized-view target tables. The auto-create flow runs
|
|
504
506
|
* `CREATE TABLE IF NOT EXISTS` for these, idempotent with the legacy
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
|
|
2
2
|
import { getQuerySettings } from "../Utils/AnalyticsDatabase/QuerySettingsHelper";
|
|
3
3
|
import MetricService from "./MetricService";
|
|
4
|
+
import { MutableMetricService as MutableMetricServiceClass } from "./MutableMetricService";
|
|
4
5
|
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
|
|
5
6
|
import { JSONObject } from "../../Types/JSON";
|
|
6
7
|
import ObjectID from "../../Types/ObjectID";
|
|
@@ -40,6 +41,8 @@ export interface FacetRequest extends MetricFilters {
|
|
|
40
41
|
export class MetricAggregationService {
|
|
41
42
|
private static readonly DEFAULT_FACET_LIMIT: number = 500;
|
|
42
43
|
private static readonly TABLE_NAME: string = AnalyticsTableName.Metric;
|
|
44
|
+
private static readonly MUTABLE_TABLE_NAME: string =
|
|
45
|
+
AnalyticsTableName.MutableMetric;
|
|
43
46
|
private static readonly TOP_LEVEL_COLUMNS: Set<string> = new Set([
|
|
44
47
|
"primaryEntityId",
|
|
45
48
|
"name",
|
|
@@ -102,26 +105,34 @@ export class MetricAggregationService {
|
|
|
102
105
|
const isTopLevelColumn: boolean =
|
|
103
106
|
isResourceFacet ||
|
|
104
107
|
MetricAggregationService.isTopLevelColumn(request.facetKey);
|
|
108
|
+
const useMutableMetricTable: boolean =
|
|
109
|
+
MetricAggregationService.shouldUseMutableMetricTable(request);
|
|
105
110
|
|
|
106
111
|
const statement: Statement = new Statement();
|
|
107
112
|
|
|
108
113
|
if (isResourceFacet) {
|
|
109
114
|
statement.append(
|
|
110
|
-
SQL`SELECT toString(primaryEntityId) AS val, count() AS cnt
|
|
115
|
+
SQL`SELECT toString(primaryEntityId) AS val, count() AS cnt`,
|
|
111
116
|
);
|
|
112
117
|
} else if (isTopLevelColumn) {
|
|
113
118
|
statement.append(
|
|
114
|
-
SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt
|
|
119
|
+
SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt`,
|
|
115
120
|
);
|
|
116
121
|
} else {
|
|
117
122
|
statement.append(
|
|
118
123
|
SQL`SELECT attributes[${{
|
|
119
124
|
type: TableColumnType.Text,
|
|
120
125
|
value: request.facetKey,
|
|
121
|
-
}}] AS val, count() AS cnt
|
|
126
|
+
}}] AS val, count() AS cnt`,
|
|
122
127
|
);
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
MetricAggregationService.appendFacetSourceTable(
|
|
131
|
+
statement,
|
|
132
|
+
request,
|
|
133
|
+
useMutableMetricTable,
|
|
134
|
+
);
|
|
135
|
+
|
|
125
136
|
statement.append(
|
|
126
137
|
SQL` WHERE projectId = ${{
|
|
127
138
|
type: TableColumnType.ObjectID,
|
|
@@ -135,6 +146,10 @@ export class MetricAggregationService {
|
|
|
135
146
|
}}`,
|
|
136
147
|
);
|
|
137
148
|
|
|
149
|
+
if (useMutableMetricTable) {
|
|
150
|
+
statement.append(SQL` AND isDeleted = false`);
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
if (isResourceFacet) {
|
|
139
154
|
statement.append(
|
|
140
155
|
SQL` AND primaryEntityType = ${{
|
|
@@ -187,6 +202,61 @@ export class MetricAggregationService {
|
|
|
187
202
|
return statement;
|
|
188
203
|
}
|
|
189
204
|
|
|
205
|
+
private static shouldUseMutableMetricTable(request: FacetRequest): boolean {
|
|
206
|
+
if (!request.metricNames || request.metricNames.length === 0) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return request.metricNames.every((metricName: string): boolean => {
|
|
211
|
+
return MutableMetricServiceClass.isMutableMetricName(metricName);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private static appendFacetSourceTable(
|
|
216
|
+
statement: Statement,
|
|
217
|
+
request: FacetRequest,
|
|
218
|
+
useMutableMetricTable: boolean,
|
|
219
|
+
): void {
|
|
220
|
+
if (!useMutableMetricTable) {
|
|
221
|
+
statement.append(SQL` FROM ${MetricAggregationService.TABLE_NAME}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
statement.append(
|
|
226
|
+
SQL` FROM (
|
|
227
|
+
SELECT
|
|
228
|
+
projectId,
|
|
229
|
+
name,
|
|
230
|
+
primaryEntityId,
|
|
231
|
+
primaryEntityType,
|
|
232
|
+
metricPointId,
|
|
233
|
+
argMax(time, version) AS time,
|
|
234
|
+
argMax(attributes, version) AS attributes,
|
|
235
|
+
argMax(retentionDate, version) AS retentionDate,
|
|
236
|
+
argMax(isDeleted, version) AS isDeleted
|
|
237
|
+
FROM ${MetricAggregationService.MUTABLE_TABLE_NAME}
|
|
238
|
+
WHERE projectId = ${{
|
|
239
|
+
type: TableColumnType.ObjectID,
|
|
240
|
+
value: request.projectId,
|
|
241
|
+
}}`,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (request.metricNames && request.metricNames.length > 0) {
|
|
245
|
+
statement.append(
|
|
246
|
+
SQL` AND name IN (${{
|
|
247
|
+
type: TableColumnType.Text,
|
|
248
|
+
value: new Includes(request.metricNames),
|
|
249
|
+
}})`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
statement.append(
|
|
254
|
+
SQL`
|
|
255
|
+
GROUP BY projectId, name, primaryEntityId, primaryEntityType, metricPointId
|
|
256
|
+
)`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
190
260
|
private static appendCommonFilters(
|
|
191
261
|
statement: Statement,
|
|
192
262
|
request: MetricFilters,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase";
|
|
2
2
|
import AnalyticsDatabaseService from "./AnalyticsDatabaseService";
|
|
3
|
+
import { MutableMetricService as MutableMetricServiceClass } from "./MutableMetricService";
|
|
3
4
|
import Metric from "../../Models/AnalyticsModels/Metric";
|
|
4
5
|
import AggregateBy, {
|
|
5
6
|
AggregateUtil,
|
|
@@ -172,6 +173,15 @@ export class MetricService extends AnalyticsDatabaseService<Metric> {
|
|
|
172
173
|
statement: Statement;
|
|
173
174
|
columns: Array<string>;
|
|
174
175
|
} {
|
|
176
|
+
const mutableMetricStatement: {
|
|
177
|
+
statement: Statement;
|
|
178
|
+
columns: Array<string>;
|
|
179
|
+
} | null = this.tryBuildMutableMetricAggregateStatement(aggregateBy);
|
|
180
|
+
|
|
181
|
+
if (mutableMetricStatement) {
|
|
182
|
+
return mutableMetricStatement;
|
|
183
|
+
}
|
|
184
|
+
|
|
175
185
|
if (!isPercentileAggregation(aggregateBy.aggregationType)) {
|
|
176
186
|
/*
|
|
177
187
|
* Try the per-host MV first — host detail pages are the
|
|
@@ -387,6 +397,224 @@ export class MetricService extends AnalyticsDatabaseService<Metric> {
|
|
|
387
397
|
return { statement, columns };
|
|
388
398
|
}
|
|
389
399
|
|
|
400
|
+
private tryBuildMutableMetricAggregateStatement(
|
|
401
|
+
aggregateBy: AggregateBy<Metric>,
|
|
402
|
+
): { statement: Statement; columns: Array<string> } | null {
|
|
403
|
+
const metricName: string | null = this.getExactMetricNameFromQuery(
|
|
404
|
+
aggregateBy.query,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (
|
|
408
|
+
!MutableMetricServiceClass.isMutableMetricName(metricName || undefined)
|
|
409
|
+
) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!this.canRouteAggregateToMutableMetricTable(aggregateBy)) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!this.database) {
|
|
418
|
+
this.useDefaultDatabase();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const databaseName: string = this.database.getDatasourceOptions().database!;
|
|
422
|
+
|
|
423
|
+
const select: { statement: Statement; columns: Array<string> } =
|
|
424
|
+
this.statementGenerator.toAggregateSelectStatement(aggregateBy);
|
|
425
|
+
|
|
426
|
+
const innerWhereStatement: Statement =
|
|
427
|
+
this.statementGenerator.toWhereStatement(
|
|
428
|
+
this.getMutableMetricInnerQuery(aggregateBy.query),
|
|
429
|
+
);
|
|
430
|
+
const outerWhereStatement: Statement =
|
|
431
|
+
this.statementGenerator.toWhereStatement(aggregateBy.query);
|
|
432
|
+
const sortStatement: Statement = this.statementGenerator.toSortStatement(
|
|
433
|
+
aggregateBy.sort!,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const statement: Statement = SQL``;
|
|
437
|
+
|
|
438
|
+
statement
|
|
439
|
+
.append(SQL`SELECT `)
|
|
440
|
+
.append(select.statement)
|
|
441
|
+
.append(
|
|
442
|
+
SQL`
|
|
443
|
+
FROM (
|
|
444
|
+
SELECT
|
|
445
|
+
projectId,
|
|
446
|
+
name,
|
|
447
|
+
primaryEntityId,
|
|
448
|
+
primaryEntityType,
|
|
449
|
+
metricPointId,
|
|
450
|
+
argMax(metricPointType, version) AS metricPointType,
|
|
451
|
+
argMax(time, version) AS time,
|
|
452
|
+
argMax(timeUnixNano, version) AS timeUnixNano,
|
|
453
|
+
argMax(attributes, version) AS attributes,
|
|
454
|
+
argMax(attributeKeys, version) AS attributeKeys,
|
|
455
|
+
argMax(value, version) AS value,
|
|
456
|
+
argMax(retentionDate, version) AS retentionDate,
|
|
457
|
+
argMax(isDeleted, version) AS isDeleted
|
|
458
|
+
FROM ${databaseName}.${AnalyticsTableName.MutableMetric}
|
|
459
|
+
WHERE TRUE
|
|
460
|
+
`,
|
|
461
|
+
)
|
|
462
|
+
.append(innerWhereStatement)
|
|
463
|
+
.append(
|
|
464
|
+
SQL`
|
|
465
|
+
GROUP BY
|
|
466
|
+
projectId,
|
|
467
|
+
name,
|
|
468
|
+
primaryEntityId,
|
|
469
|
+
primaryEntityType,
|
|
470
|
+
metricPointId
|
|
471
|
+
)
|
|
472
|
+
WHERE isDeleted = false
|
|
473
|
+
`,
|
|
474
|
+
)
|
|
475
|
+
.append(outerWhereStatement)
|
|
476
|
+
.append(SQL` AND retentionDate >= now()`);
|
|
477
|
+
|
|
478
|
+
statement
|
|
479
|
+
.append(SQL` GROUP BY `)
|
|
480
|
+
.append(`${aggregateBy.aggregationTimestampColumnName.toString()}`);
|
|
481
|
+
|
|
482
|
+
if (aggregateBy.groupBy && Object.keys(aggregateBy.groupBy).length > 0) {
|
|
483
|
+
statement
|
|
484
|
+
.append(SQL` , `)
|
|
485
|
+
.append(
|
|
486
|
+
this.statementGenerator.toGroupByStatement(aggregateBy.groupBy),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
statement.append(SQL` ORDER BY `).append(sortStatement);
|
|
491
|
+
|
|
492
|
+
statement.append(
|
|
493
|
+
SQL` LIMIT ${{
|
|
494
|
+
value: Number(aggregateBy.limit),
|
|
495
|
+
type: TableColumnType.Number,
|
|
496
|
+
}}`,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
statement.append(
|
|
500
|
+
SQL` OFFSET ${{
|
|
501
|
+
value: Number(aggregateBy.skip),
|
|
502
|
+
type: TableColumnType.Number,
|
|
503
|
+
}}`,
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
statement.append(
|
|
507
|
+
getQuerySettings({
|
|
508
|
+
additionalSettings: {
|
|
509
|
+
optimize_move_to_prewhere: 1,
|
|
510
|
+
max_threads: 4,
|
|
511
|
+
},
|
|
512
|
+
}),
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
logger.debug("Mutable metric aggregate statement", {
|
|
516
|
+
metricName: metricName || "",
|
|
517
|
+
} as LogAttributes);
|
|
518
|
+
logger.debug(statement, {
|
|
519
|
+
metricName: metricName || "",
|
|
520
|
+
} as LogAttributes);
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
statement,
|
|
524
|
+
columns: select.columns,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private getMutableMetricInnerQuery(query: Query<Metric>): Query<Metric> {
|
|
529
|
+
const queryRecord: Record<string, unknown> = query as unknown as Record<
|
|
530
|
+
string,
|
|
531
|
+
unknown
|
|
532
|
+
>;
|
|
533
|
+
const allowedInnerKeys: Array<string> = [
|
|
534
|
+
"projectId",
|
|
535
|
+
"name",
|
|
536
|
+
"primaryEntityId",
|
|
537
|
+
"primaryEntityType",
|
|
538
|
+
];
|
|
539
|
+
const innerQuery: Record<string, unknown> = {};
|
|
540
|
+
|
|
541
|
+
for (const key of allowedInnerKeys) {
|
|
542
|
+
if (queryRecord[key] !== undefined) {
|
|
543
|
+
innerQuery[key] = queryRecord[key];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return innerQuery as unknown as Query<Metric>;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private getExactMetricNameFromQuery(query: Query<Metric>): string | null {
|
|
551
|
+
const queryRecord: Record<string, unknown> = query as unknown as Record<
|
|
552
|
+
string,
|
|
553
|
+
unknown
|
|
554
|
+
>;
|
|
555
|
+
const metricName: unknown = queryRecord["name"];
|
|
556
|
+
|
|
557
|
+
if (typeof metricName !== "string") {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return metricName;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
private canRouteAggregateToMutableMetricTable(
|
|
565
|
+
aggregateBy: AggregateBy<Metric>,
|
|
566
|
+
): boolean {
|
|
567
|
+
const supportedColumns: Set<string> = new Set<string>([
|
|
568
|
+
"projectId",
|
|
569
|
+
"name",
|
|
570
|
+
"primaryEntityId",
|
|
571
|
+
"primaryEntityType",
|
|
572
|
+
"metricPointType",
|
|
573
|
+
"time",
|
|
574
|
+
"timeUnixNano",
|
|
575
|
+
"attributes",
|
|
576
|
+
"attributeKeys",
|
|
577
|
+
"value",
|
|
578
|
+
"retentionDate",
|
|
579
|
+
]);
|
|
580
|
+
|
|
581
|
+
const queryRecord: Record<string, unknown> =
|
|
582
|
+
aggregateBy.query as unknown as Record<string, unknown>;
|
|
583
|
+
|
|
584
|
+
for (const key of Object.keys(queryRecord)) {
|
|
585
|
+
if (!supportedColumns.has(key)) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (
|
|
591
|
+
!supportedColumns.has(aggregateBy.aggregateColumnName.toString()) ||
|
|
592
|
+
!supportedColumns.has(
|
|
593
|
+
aggregateBy.aggregationTimestampColumnName.toString(),
|
|
594
|
+
)
|
|
595
|
+
) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (aggregateBy.groupBy) {
|
|
600
|
+
for (const key of Object.keys(aggregateBy.groupBy)) {
|
|
601
|
+
if (!supportedColumns.has(key)) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (aggregateBy.sort) {
|
|
608
|
+
for (const key of Object.keys(aggregateBy.sort)) {
|
|
609
|
+
if (!supportedColumns.has(key)) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
|
|
390
618
|
/*
|
|
391
619
|
* Materialized-view fast path for scalar aggregations.
|
|
392
620
|
*
|