mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Synthetics] Use msearch for multiple queries !! (#192065)
## Summary Use msearch for multiple queries for overview status !! Also refactored to create separate query for status alert rule instead of reusing !!
This commit is contained in:
parent
69665cecd0
commit
c07e946e0c
5 changed files with 783 additions and 502 deletions
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import pMap from 'p-map';
|
||||
import times from 'lodash/times';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { cloneDeep, intersection } from 'lodash';
|
||||
import { createEsParams, SyntheticsEsClient } from '../../lib';
|
||||
import {
|
||||
OverviewPendingStatusMetaData,
|
||||
OverviewPing,
|
||||
OverviewStatus,
|
||||
OverviewStatusMetaData,
|
||||
} from '../../../common/runtime_types';
|
||||
import { FINAL_SUMMARY_FILTER } from '../../../common/constants/client_defaults';
|
||||
|
||||
const DEFAULT_MAX_ES_BUCKET_SIZE = 10000;
|
||||
|
||||
const fields = [
|
||||
'@timestamp',
|
||||
'summary',
|
||||
'monitor',
|
||||
'observer',
|
||||
'config_id',
|
||||
'error',
|
||||
'agent',
|
||||
'url',
|
||||
'state',
|
||||
'tags',
|
||||
];
|
||||
|
||||
export async function queryMonitorStatusForAlert(
|
||||
esClient: SyntheticsEsClient,
|
||||
monitorLocationIds: string[],
|
||||
range: { from: string; to: string },
|
||||
monitorQueryIds: string[],
|
||||
monitorLocationsMap: Record<string, string[]>,
|
||||
monitorQueryIdToConfigIdMap: Record<string, string>
|
||||
): Promise<
|
||||
Omit<
|
||||
OverviewStatus,
|
||||
| 'disabledCount'
|
||||
| 'allMonitorsCount'
|
||||
| 'disabledMonitorsCount'
|
||||
| 'projectMonitorsCount'
|
||||
| 'disabledMonitorQueryIds'
|
||||
| 'allIds'
|
||||
>
|
||||
> {
|
||||
const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / monitorLocationIds.length || 1);
|
||||
const pageCount = Math.ceil(monitorQueryIds.length / idSize);
|
||||
let up = 0;
|
||||
let down = 0;
|
||||
const upConfigs: Record<string, OverviewStatusMetaData> = {};
|
||||
const downConfigs: Record<string, OverviewStatusMetaData> = {};
|
||||
const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap)));
|
||||
const pendingConfigs: Record<string, OverviewPendingStatusMetaData> = {};
|
||||
|
||||
await pMap(
|
||||
times(pageCount),
|
||||
async (i) => {
|
||||
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
|
||||
const params = createEsParams({
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
FINAL_SUMMARY_FILTER,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: range.from,
|
||||
lte: range.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': idsToQuery,
|
||||
},
|
||||
},
|
||||
] as QueryDslQueryContainer[],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
size: idSize,
|
||||
},
|
||||
aggs: {
|
||||
location: {
|
||||
terms: {
|
||||
field: 'observer.name',
|
||||
size: monitorLocationIds.length || 100,
|
||||
},
|
||||
aggs: {
|
||||
status: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
_source: {
|
||||
includes: fields,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (monitorLocationIds.length > 0) {
|
||||
params.body.query.bool.filter.push({
|
||||
terms: {
|
||||
'observer.name': monitorLocationIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { body: result } = await esClient.search<OverviewPing, typeof params>(
|
||||
params,
|
||||
'getCurrentStatusOverview' + i
|
||||
);
|
||||
|
||||
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
|
||||
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
|
||||
const ping = status.hits.hits[0]._source;
|
||||
return { location: locationName, ping };
|
||||
});
|
||||
|
||||
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
|
||||
// in monitorLocationsMap but not in listOfLocations
|
||||
const monLocations = monitorLocationsMap?.[queryId];
|
||||
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
|
||||
monQueriedLocations?.forEach((monLocation) => {
|
||||
const locationSummary = locationSummaries.find(
|
||||
(summary) => summary.location === monLocation
|
||||
);
|
||||
|
||||
if (locationSummary) {
|
||||
const { ping } = locationSummary;
|
||||
const downCount = ping.summary?.down ?? 0;
|
||||
const upCount = ping.summary?.up ?? 0;
|
||||
const configId = ping.config_id;
|
||||
const monitorQueryId = ping.monitor.id;
|
||||
|
||||
const meta = {
|
||||
ping,
|
||||
configId,
|
||||
monitorQueryId,
|
||||
locationId: monLocation,
|
||||
timestamp: ping['@timestamp'],
|
||||
};
|
||||
|
||||
if (downCount > 0) {
|
||||
down += 1;
|
||||
downConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'down',
|
||||
};
|
||||
} else if (upCount > 0) {
|
||||
up += 1;
|
||||
upConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'up',
|
||||
};
|
||||
}
|
||||
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
|
||||
monitorsWithoutData.set(
|
||||
monitorQueryId,
|
||||
monitorsMissingData?.filter((loc) => loc !== monLocation)
|
||||
);
|
||||
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
|
||||
monitorsWithoutData.delete(monitorQueryId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
{ concurrency: 5 }
|
||||
);
|
||||
|
||||
// identify the remaining monitors without data, to determine pending monitors
|
||||
for (const [queryId, locs] of monitorsWithoutData) {
|
||||
locs.forEach((loc) => {
|
||||
pendingConfigs[`${monitorQueryIdToConfigIdMap[queryId]}-${loc}`] = {
|
||||
configId: `${monitorQueryIdToConfigIdMap[queryId]}`,
|
||||
monitorQueryId: queryId,
|
||||
status: 'unknown',
|
||||
locationId: loc,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
up,
|
||||
down,
|
||||
pending: Object.values(pendingConfigs).length,
|
||||
upConfigs,
|
||||
downConfigs,
|
||||
pendingConfigs,
|
||||
enabledMonitorQueryIds: monitorQueryIds,
|
||||
};
|
||||
}
|
|
@ -10,6 +10,7 @@ import {
|
|||
SavedObjectsFindResult,
|
||||
} from '@kbn/core-saved-objects-api-server';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { queryMonitorStatusForAlert } from './query_monitor_status_alert';
|
||||
import { SyntheticsServerSetup } from '../../types';
|
||||
import { SyntheticsEsClient } from '../../lib';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
|
||||
|
@ -17,7 +18,6 @@ import {
|
|||
getAllMonitors,
|
||||
processMonitors,
|
||||
} from '../../saved_objects/synthetics_monitor/get_all_monitors';
|
||||
import { queryMonitorStatus } from '../../queries/query_monitor_status';
|
||||
import { StatusRuleParams } from '../../../common/rules/status_rule';
|
||||
import {
|
||||
ConfigKey,
|
||||
|
@ -107,7 +107,7 @@ export class StatusRuleExecutor {
|
|||
: 'now-2m';
|
||||
|
||||
if (enabledMonitorQueryIds.length > 0) {
|
||||
const currentStatus = await queryMonitorStatus(
|
||||
const currentStatus = await queryMonitorStatusForAlert(
|
||||
this.esClient,
|
||||
monitorLocationIds,
|
||||
{
|
||||
|
|
|
@ -78,7 +78,6 @@ export class SyntheticsEsClient {
|
|||
|
||||
const esParams = { index: SYNTHETICS_INDEX_PATTERN, ...params };
|
||||
const startTime = process.hrtime();
|
||||
|
||||
const startTimeNow = Date.now();
|
||||
|
||||
let esRequestStatus: RequestStatus = RequestStatus.PENDING;
|
||||
|
@ -90,7 +89,8 @@ export class SyntheticsEsClient {
|
|||
esError = e;
|
||||
esRequestStatus = RequestStatus.ERROR;
|
||||
}
|
||||
if (this.request) {
|
||||
const isInspectorEnabled = await this.getInspectEnabled();
|
||||
if (isInspectorEnabled && this.request) {
|
||||
this.inspectableEsQueries.push(
|
||||
getInspectResponse({
|
||||
esError,
|
||||
|
@ -102,9 +102,7 @@ export class SyntheticsEsClient {
|
|||
startTime: startTimeNow,
|
||||
})
|
||||
);
|
||||
}
|
||||
const isInspectorEnabled = await this.getInspectEnabled();
|
||||
if (isInspectorEnabled && this.request) {
|
||||
|
||||
debugESCall({
|
||||
startTime,
|
||||
request: this.request,
|
||||
|
@ -125,7 +123,8 @@ export class SyntheticsEsClient {
|
|||
TSearchRequest extends estypes.SearchRequest = estypes.SearchRequest,
|
||||
TDocument = unknown
|
||||
>(
|
||||
requests: MsearchMultisearchBody[]
|
||||
requests: MsearchMultisearchBody[],
|
||||
operationName?: string
|
||||
): Promise<{ responses: Array<InferSearchResponseOf<TDocument, TSearchRequest>> }> {
|
||||
const searches: Array<MsearchMultisearchHeader | MsearchMultisearchBody> = [];
|
||||
for (const request of requests) {
|
||||
|
@ -133,15 +132,41 @@ export class SyntheticsEsClient {
|
|||
searches.push(request);
|
||||
}
|
||||
|
||||
const results = await this.baseESClient.msearch(
|
||||
{
|
||||
searches,
|
||||
},
|
||||
{ meta: true }
|
||||
);
|
||||
const startTimeNow = Date.now();
|
||||
|
||||
let res: any;
|
||||
let esError: any;
|
||||
|
||||
try {
|
||||
res = await this.baseESClient.msearch(
|
||||
{
|
||||
searches,
|
||||
},
|
||||
{ meta: true }
|
||||
);
|
||||
} catch (e) {
|
||||
esError = e;
|
||||
}
|
||||
|
||||
const isInspectorEnabled = await this.getInspectEnabled();
|
||||
if (isInspectorEnabled && this.request) {
|
||||
requests.forEach((request, index) => {
|
||||
this.inspectableEsQueries.push(
|
||||
getInspectResponse({
|
||||
esError,
|
||||
esRequestParams: { index: SYNTHETICS_INDEX_PATTERN, ...request },
|
||||
esRequestStatus: RequestStatus.OK,
|
||||
esResponse: res.body.responses[index],
|
||||
kibanaRequest: this.request!,
|
||||
operationName: operationName ?? '',
|
||||
startTime: startTimeNow,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
responses: results.body.responses as unknown as Array<
|
||||
responses: res.body.responses as unknown as Array<
|
||||
InferSearchResponseOf<TDocument, TSearchRequest>
|
||||
>,
|
||||
};
|
||||
|
@ -193,6 +218,9 @@ export class SyntheticsEsClient {
|
|||
return {};
|
||||
}
|
||||
async getInspectEnabled() {
|
||||
if (this.isDev) {
|
||||
return true;
|
||||
}
|
||||
if (!this.uiSettings) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import pMap from 'p-map';
|
||||
import times from 'lodash/times';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { cloneDeep, intersection } from 'lodash';
|
||||
import { MsearchMultisearchBody } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { FINAL_SUMMARY_FILTER } from '../../common/constants/client_defaults';
|
||||
import {
|
||||
OverviewPendingStatusMetaData,
|
||||
|
@ -33,6 +33,99 @@ const fields = [
|
|||
'tags',
|
||||
];
|
||||
|
||||
const getStatusQuery = ({
|
||||
idsToQuery,
|
||||
range,
|
||||
monitorLocationIds,
|
||||
idSize,
|
||||
}: {
|
||||
idSize: number;
|
||||
monitorLocationIds: string[];
|
||||
range: { from: string; to: string };
|
||||
idsToQuery: string[];
|
||||
monitorLocationsMap: Record<string, string[]>;
|
||||
monitorQueryIdToConfigIdMap: Record<string, string>;
|
||||
}) => {
|
||||
const params = createEsParams({
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
FINAL_SUMMARY_FILTER,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: range.from,
|
||||
lte: range.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': idsToQuery,
|
||||
},
|
||||
},
|
||||
] as QueryDslQueryContainer[],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
size: idSize,
|
||||
},
|
||||
aggs: {
|
||||
location: {
|
||||
terms: {
|
||||
field: 'observer.name',
|
||||
size: monitorLocationIds.length || 100,
|
||||
},
|
||||
aggs: {
|
||||
status: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
_source: {
|
||||
includes: fields,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (monitorLocationIds.length > 0) {
|
||||
params.body.query?.bool?.filter.push({
|
||||
terms: {
|
||||
'observer.name': monitorLocationIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
type StatusQueryParams = ReturnType<typeof getStatusQuery>;
|
||||
type OverviewStatusResponse = Omit<
|
||||
OverviewStatus,
|
||||
| 'disabledCount'
|
||||
| 'allMonitorsCount'
|
||||
| 'disabledMonitorsCount'
|
||||
| 'projectMonitorsCount'
|
||||
| 'disabledMonitorQueryIds'
|
||||
| 'allIds'
|
||||
>;
|
||||
|
||||
export async function queryMonitorStatus(
|
||||
esClient: SyntheticsEsClient,
|
||||
monitorLocationIds: string[],
|
||||
|
@ -40,17 +133,7 @@ export async function queryMonitorStatus(
|
|||
monitorQueryIds: string[],
|
||||
monitorLocationsMap: Record<string, string[]>,
|
||||
monitorQueryIdToConfigIdMap: Record<string, string>
|
||||
): Promise<
|
||||
Omit<
|
||||
OverviewStatus,
|
||||
| 'disabledCount'
|
||||
| 'allMonitorsCount'
|
||||
| 'disabledMonitorsCount'
|
||||
| 'projectMonitorsCount'
|
||||
| 'disabledMonitorQueryIds'
|
||||
| 'allIds'
|
||||
>
|
||||
> {
|
||||
): Promise<OverviewStatusResponse> {
|
||||
const idSize = Math.trunc(DEFAULT_MAX_ES_BUCKET_SIZE / monitorLocationIds.length || 1);
|
||||
const pageCount = Math.ceil(monitorQueryIds.length / idSize);
|
||||
let up = 0;
|
||||
|
@ -60,140 +143,80 @@ export async function queryMonitorStatus(
|
|||
const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap)));
|
||||
const pendingConfigs: Record<string, OverviewPendingStatusMetaData> = {};
|
||||
|
||||
await pMap(
|
||||
times(pageCount),
|
||||
async (i) => {
|
||||
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
|
||||
const params = createEsParams({
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
FINAL_SUMMARY_FILTER,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: range.from,
|
||||
lte: range.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': idsToQuery,
|
||||
},
|
||||
},
|
||||
] as QueryDslQueryContainer[],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
size: idSize,
|
||||
},
|
||||
aggs: {
|
||||
location: {
|
||||
terms: {
|
||||
field: 'observer.name',
|
||||
size: monitorLocationIds.length || 100,
|
||||
},
|
||||
aggs: {
|
||||
status: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
_source: {
|
||||
includes: fields,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const queries: MsearchMultisearchBody[] = times(pageCount).map((i) => {
|
||||
const idsToQuery = (monitorQueryIds as string[]).slice(i * idSize, i * idSize + idSize);
|
||||
return getStatusQuery({
|
||||
idSize,
|
||||
monitorLocationIds,
|
||||
range,
|
||||
idsToQuery,
|
||||
monitorLocationsMap,
|
||||
monitorQueryIdToConfigIdMap,
|
||||
}).body;
|
||||
});
|
||||
|
||||
if (monitorLocationIds.length > 0) {
|
||||
params.body.query.bool.filter.push({
|
||||
terms: {
|
||||
'observer.name': monitorLocationIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { body: result } = await esClient.search<OverviewPing, typeof params>(
|
||||
params,
|
||||
'getCurrentStatusOverview' + i
|
||||
);
|
||||
|
||||
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
|
||||
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
|
||||
const ping = status.hits.hits[0]._source;
|
||||
return { location: locationName, ping };
|
||||
});
|
||||
|
||||
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
|
||||
// in monitorLocationsMap but not in listOfLocations
|
||||
const monLocations = monitorLocationsMap?.[queryId];
|
||||
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
|
||||
monQueriedLocations?.forEach((monLocation) => {
|
||||
const locationSummary = locationSummaries.find(
|
||||
(summary) => summary.location === monLocation
|
||||
);
|
||||
|
||||
if (locationSummary) {
|
||||
const { ping } = locationSummary;
|
||||
const downCount = ping.summary?.down ?? 0;
|
||||
const upCount = ping.summary?.up ?? 0;
|
||||
const configId = ping.config_id;
|
||||
const monitorQueryId = ping.monitor.id;
|
||||
|
||||
const meta = {
|
||||
ping,
|
||||
configId,
|
||||
monitorQueryId,
|
||||
locationId: monLocation,
|
||||
timestamp: ping['@timestamp'],
|
||||
};
|
||||
|
||||
if (downCount > 0) {
|
||||
down += 1;
|
||||
downConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'down',
|
||||
};
|
||||
} else if (upCount > 0) {
|
||||
up += 1;
|
||||
upConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'up',
|
||||
};
|
||||
}
|
||||
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
|
||||
monitorsWithoutData.set(
|
||||
monitorQueryId,
|
||||
monitorsMissingData?.filter((loc) => loc !== monLocation)
|
||||
);
|
||||
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
|
||||
monitorsWithoutData.delete(monitorQueryId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
{ concurrency: 5 }
|
||||
const { responses } = await esClient.msearch<StatusQueryParams, OverviewPing>(
|
||||
queries,
|
||||
'getCurrentStatusOverview'
|
||||
);
|
||||
|
||||
responses.forEach((result) => {
|
||||
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
|
||||
const locationSummaries = location.buckets.map(({ status, key: locationName }) => {
|
||||
const ping = status.hits.hits[0]._source;
|
||||
return { location: locationName, ping };
|
||||
});
|
||||
|
||||
// discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are
|
||||
// in monitorLocationsMap but not in listOfLocations
|
||||
const monLocations = monitorLocationsMap?.[queryId];
|
||||
const monQueriedLocations = intersection(monLocations, monitorLocationIds);
|
||||
monQueriedLocations?.forEach((monLocation) => {
|
||||
const locationSummary = locationSummaries.find(
|
||||
(summary) => summary.location === monLocation
|
||||
);
|
||||
|
||||
if (locationSummary) {
|
||||
const { ping } = locationSummary;
|
||||
const downCount = ping.summary?.down ?? 0;
|
||||
const upCount = ping.summary?.up ?? 0;
|
||||
const configId = ping.config_id;
|
||||
const monitorQueryId = ping.monitor.id;
|
||||
|
||||
const meta = {
|
||||
ping,
|
||||
configId,
|
||||
monitorQueryId,
|
||||
locationId: monLocation,
|
||||
timestamp: ping['@timestamp'],
|
||||
};
|
||||
|
||||
if (downCount > 0) {
|
||||
down += 1;
|
||||
downConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'down',
|
||||
};
|
||||
} else if (upCount > 0) {
|
||||
up += 1;
|
||||
upConfigs[`${configId}-${monLocation}`] = {
|
||||
...meta,
|
||||
status: 'up',
|
||||
};
|
||||
}
|
||||
const monitorsMissingData = monitorsWithoutData.get(monitorQueryId) || [];
|
||||
monitorsWithoutData.set(
|
||||
monitorQueryId,
|
||||
monitorsMissingData?.filter((loc) => loc !== monLocation)
|
||||
);
|
||||
if (!monitorsWithoutData.get(monitorQueryId)?.length) {
|
||||
monitorsWithoutData.delete(monitorQueryId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// identify the remaining monitors without data, to determine pending monitors
|
||||
for (const [queryId, locs] of monitorsWithoutData) {
|
||||
locs.forEach((loc) => {
|
||||
|
|
|
@ -108,108 +108,111 @@ describe('current status route', () => {
|
|||
describe('queryMonitorStatus', () => {
|
||||
it('parses expected agg fields', async () => {
|
||||
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
|
||||
esClient.search.mockResponseOnce(
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
esClient.msearch.mockResponseOnce({
|
||||
responses: [
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
]),
|
||||
],
|
||||
took: 605,
|
||||
});
|
||||
expect(
|
||||
await queryMonitorStatus(
|
||||
syntheticsEsClient,
|
||||
|
@ -261,108 +264,111 @@ describe('current status route', () => {
|
|||
|
||||
it('handles limits with multiple requests', async () => {
|
||||
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
|
||||
esClient.search.mockResponseOnce(
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
esClient.msearch.mockResponseOnce({
|
||||
responses: [
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 0,
|
||||
down: 1,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 0,
|
||||
down: 1,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
]),
|
||||
],
|
||||
took: 605,
|
||||
});
|
||||
|
||||
/**
|
||||
* By passing the function a location count of 10k, it forces the query to paginate once,
|
||||
|
@ -422,122 +428,125 @@ describe('current status route', () => {
|
|||
},
|
||||
pendingConfigs: {},
|
||||
});
|
||||
expect(esClient.search).toHaveBeenCalledTimes(2);
|
||||
expect(esClient.msearch).toHaveBeenCalledTimes(1);
|
||||
// These assertions are to ensure that we are paginating through the IDs we use for filtering
|
||||
expect(
|
||||
// @ts-expect-error mock search is not lining up with expected type
|
||||
esClient.search.mock.calls[0][0].body.query.bool.filter[2].terms['monitor.id']
|
||||
esClient.msearch.mock.calls[0][0].searches[1].query.bool.filter[2].terms['monitor.id']
|
||||
).toEqual(['id1']);
|
||||
expect(
|
||||
// @ts-expect-error mock search is not lining up with expected type
|
||||
esClient.search.mock.calls[1][0].body.query.bool.filter[2].terms['monitor.id']
|
||||
esClient.msearch.mock.calls[0][0].searches[3].query.bool.filter[2].terms['monitor.id']
|
||||
).toEqual(['id2']);
|
||||
});
|
||||
|
||||
it('handles pending configs', async () => {
|
||||
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
|
||||
esClient.search.mockResponseOnce(
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
esClient.msearch.mockResponseOnce({
|
||||
responses: [
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
]),
|
||||
],
|
||||
took: 605,
|
||||
});
|
||||
expect(
|
||||
await queryMonitorStatus(
|
||||
syntheticsEsClient,
|
||||
|
@ -668,108 +677,111 @@ describe('current status route', () => {
|
|||
} as unknown as SavedObjectsFindResult<EncryptedSyntheticsMonitorAttributes>,
|
||||
]);
|
||||
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
|
||||
esClient.search.mockResponseOnce(
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
esClient.msearch.mockResponseOnce({
|
||||
responses: [
|
||||
getEsResponse([
|
||||
{
|
||||
key: 'id1',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:08:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id1',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id1',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
{
|
||||
key: 'id2',
|
||||
location: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'Asia/Pacific - Japan',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:09:16.724Z',
|
||||
monitor: {
|
||||
status: 'up',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Asia/Pacific - Japan',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
{
|
||||
key: 'Europe - Germany',
|
||||
status: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-09-15T16:19:16.724Z',
|
||||
monitor: {
|
||||
status: 'down',
|
||||
id: 'id2',
|
||||
},
|
||||
summary: {
|
||||
down: 1,
|
||||
up: 0,
|
||||
},
|
||||
config_id: 'id2',
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'Europe - Germany',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
]),
|
||||
],
|
||||
took: 605,
|
||||
});
|
||||
const result = await getStatus(
|
||||
{
|
||||
syntheticsEsClient,
|
||||
|
@ -834,7 +846,7 @@ describe('current status route', () => {
|
|||
} as unknown as SavedObjectsFindResult<EncryptedSyntheticsMonitorAttributes>,
|
||||
]);
|
||||
const { esClient, syntheticsEsClient } = getUptimeESMockClient();
|
||||
esClient.search.mockResponseOnce(getEsResponse([]));
|
||||
esClient.msearch.mockResponseOnce({ responses: [getEsResponse([])], took: 605 });
|
||||
expect(
|
||||
await getStatus(
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue