mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime] Query Overhaul + Pagination (#42933)
Summary Adds pagination to the Uptime app's monitor overview page. Also cleans up/removes a lot of unused code that laid the foundation for the states index approach we opted not to take. This PR is somewhat complex due to our need to use composite aggregations with searchAfter style pagination. Understanding our new query approach How the schema works At its simplest, we run a single heartbeat from a single location checking a single endpoint with a single IP.In this case, everytime we run a check we create a new document. # Documents for a single monitor.id # checked from one heartbeat ------ time ---------------> [D] [D] [D] [D] [D] [D] [D] We can also check from multiple geo locations # Documents for a single monitor.id # checked from multiple heartbeats, on in a US-E datacenter, the other in US-W Location | ---------- time -----------> US-E ` [D] [D] [D] [D] [D] [D] [D] US-W ` [D] [D] [D] [D] [D] [D] [D] We can also check multiple ip addresses per check if there are multiple DNS entries. If the endpoint we're checking has 3 DNS entries we will create 3 documents per check. The final document has an extra summary field with two integer values: summary.up and summary.down summarizing how many documents were up vs down across all three documents in the check. All documents also contain a monitor.check_group UUID string field that has a shared value across all three documents. # Documents for a single monitor.id checked from a single location checking 3 IP addresses total # checked from one heartbeat. # Documents with a summary are indicated with an [S] Note the partially written group at the end --------------- time ---------------------> Docs | [D] [D] [S] [D] [D] [S] [D] Groups | |--UUID1--| |--UUID2--| |-UUID3- Putting it all together, we can have multiple geo locations with multiple IPs checked within each location. # Documents for a single monitor.id checked from a single location checking 3 IP addresses total # checked from one heartbeat. # Documents with a summary are indicated with an [S] Location | ------------------time--------------------------> ` US-W ` [D] [D] [S] [D] [D] [S] [D] ` |--UUID1--| |--UUID2--| |-UUID3- ` US-E ` [D] [D] [S] [D] [D] [S] [D] [D] [S] ` |--UUIDA--| |--UUIDB--| |--UUIDC--| The Query Goals Logically, what we want to do when searching is to: Match the most recent complete check group from each location and return all documents from that check group. For any specific fields (say error.message or monitor.ip) consider the monitor matched if any documents in any location have that value. For status filtering consider the monitor up if all documents within the latest check groups for each location are up. Additional properties we'd like this query to have: Be paginatable Be fast Be in a consistent order (required for pagination) At a High Level There are three phases, described in detail below, but the TL;DR is: queryPotentialMatches() Find monitor.id,mostrecent(check_group) tuples that match the query and all filters except status. We eagerly fetch 500 results here knowing that we may discard some of these results in the next phase. refinePotentialMatches() Perform an additional query that pulls in all the most recent check summaries from all locations for the monitor IDs from the last phase. We compare the matched check groups from the last phase to see if what matched previously was out of date. We can now apply the status filter as well. enrich At this point we already have the matching monitor IDs and their status, but we haven't brought in most of their fields. Here we re-query to get all docs from the matching monitor IDs most recent checks, then format them for return via API. queryPotentialMatches() This query returns via a composite agg all monitor IDs matching all query. It also returns the latest matching monitor.check group of all monitor IDs with the correct monitor.status value. Note that the returned monitor.check_group value will only be the most recent value matching the query terms. It may be an old check. So, the output of the check is all monitor.id values that have ever had a document that matched the query. However, these matches may be old. TODO: The PR as it stands doesn't work exactly this way, I added in an optimization for status filtering that actually is incorrect. We can exclude matches that are down if we're looking for things that are up, but nothing more than that. refinePotentialMatches() In this phase we take the monitor.id values from the preceeding phase and query for all the latest check groups per geo-location for each via a terms query plus terms aggs, using a top hit to get the most recent (top hit size 1 is more efficient than a terms agg on a high cardinality string field sorted , I believe due to global ordinals). Then, in Javascript, we can further exclude monitors based on the monitor.status fiter. If the status filter is up we exclude any monitors that have any down checks using the summary fields. By using the summary fields we additionally ensure that we only include the most recent complete check groups, instead of partial ones. We then combine the output of the preceding phase, kicking out results from the initial phase that didn't have their latest complete check groups match. Enrichment We already have the correct monitor IDs, but we don't have all the data from all matching documents inside the check groups. We only got the summary documents (and only returned the monitor.id and check group) in the preceding phase. In this straightforward phase we get all the documents for the latest check groups for each monitor and combine them into the final response. Additionally adding histogram data via an auxiliary query. Pagination The query phases described above omit discussion of how pagination works. We don't want to retrieve all matching monitor.ids, if a user has 30k monitors that can be expensive. Our default page only shows 10 in this patch. By limiting results we increase speed. Additionally, by using "after" style pagination as composite aggregations use, each page load is as fast as the first, we don't have the performance issues traditional page number style pagination has as you go deep into the result set. Internally the code has an Iterator style paginator object covering all phases except the final enrichment phase that has the following functions: next() Gets the next monitor matching the query advances the internal cursor. peek() Gets the next monitor matching the query, does not advance the internal cursor. current() Gets the monitor at the current cursor paginationAfterCurrent() Checks via peek to see if there is more data, and if so returns pagination data to be sent on the next request to get the next page. This powers the 'next' button. paginationBeforeCurrent() Same as preceeding, but gets the pagination info for the page before the current item. TODO add this method instead of the current hacky way with reverse() Internally we overfetch for the initial filter query to provide a buffer for the paginator; we don't want a query per next() invocation. That's currently set to 500 items at the moment, which is pretty snappy. Fixes elastic/uptime#63 Alternative approaches considered Using data frames: We rejected this approach for two reasons: 1.) It's one more thing that a user can accidentally break 2.) Worry over the cost of constantly re-indexing every document with low latency. We'd need to reindex everything once every 5-10s to prevent excessive lag. Kibana Background jobs and a high water mark. This would involve having a background job in Kibana that would compute the most recent documents for each monitor at a given time and then mark them with a high water mark using an update by query. This has the same issues as the data frames in terms of excessive doc writes and scalability. However, there's less to break in a sense in that there's no extra indices. OTOH, users have configure kibana to let it write to uptime indices.
This commit is contained in:
parent
cbc2b1ad52
commit
35a7de1708
92 changed files with 49723 additions and 5794 deletions
|
@ -161,6 +161,17 @@ describe('esArchiver createParseArchiveStreams', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('parses blank files', async () => {
|
||||
const output = await createPromiseFromStreams([
|
||||
createListStream([]),
|
||||
createGzip(),
|
||||
...createParseArchiveStreams(({ gzip: true })),
|
||||
createConcatStream([])
|
||||
]);
|
||||
|
||||
expect(output).to.eql([]);
|
||||
});
|
||||
|
||||
describe('stream errors', () => {
|
||||
it('stops when the input is not valid gzip archive', async () => {
|
||||
try {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { createGunzip } from 'zlib';
|
||||
import { PassThrough } from 'stream';
|
||||
|
||||
import { createFilterStream } from '../../../legacy/utils/streams/filter_stream';
|
||||
import {
|
||||
createSplitStream,
|
||||
createReplaceStream,
|
||||
|
@ -33,6 +33,7 @@ export function createParseArchiveStreams({ gzip = false } = {}) {
|
|||
gzip ? createGunzip() : new PassThrough(),
|
||||
createReplaceStream('\r\n', '\n'),
|
||||
createSplitStream(RECORD_SEPARATOR),
|
||||
createFilterStream(l => l.match(/[^\s]/)),
|
||||
createMapStream(json => JSON.parse(json.trim())),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SortOrder, CursorDirection } from '../graphql/types';
|
||||
|
||||
/**
|
||||
* The Uptime UI utilizes a settings context, the defaults for which are stored here.
|
||||
*/
|
||||
|
@ -28,4 +30,9 @@ export const CONTEXT_DEFAULTS = {
|
|||
* The end of the default date range is now.
|
||||
*/
|
||||
DATE_RANGE_END: 'now',
|
||||
|
||||
CURSOR_PAGINATION: {
|
||||
cursorDirection: CursorDirection.AFTER,
|
||||
sortOrder: SortOrder.ASC,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -27,8 +27,8 @@ export const QUERY = {
|
|||
};
|
||||
|
||||
export const STATES = {
|
||||
// Number of results returned for a legacy states query
|
||||
LEGACY_STATES_QUERY_SIZE: 50,
|
||||
// Number of results returned for a states query
|
||||
LEGACY_STATES_QUERY_SIZE: 10,
|
||||
// The maximum number of monitors that should be supported
|
||||
MAX_MONITORS: 35000,
|
||||
};
|
||||
|
|
|
@ -395,6 +395,12 @@
|
|||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "pagination",
|
||||
"description": "",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "filters",
|
||||
"description": "",
|
||||
|
@ -2599,6 +2605,22 @@
|
|||
"name": "MonitorSummaryResult",
|
||||
"description": "The primary object returned for monitor states.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "prevPagePagination",
|
||||
"description": "Used to go to the next page of results",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nextPagePagination",
|
||||
"description": "Used to go to the previous page of results",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "summaries",
|
||||
"description": "The objects representing the state of a series of heartbeat monitors.",
|
||||
|
@ -4148,6 +4170,32 @@
|
|||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "CursorDirection",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{ "name": "AFTER", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{ "name": "BEFORE", "description": "", "isDeprecated": false, "deprecationReason": null }
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "SortOrder",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{ "name": "ASC", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{ "name": "DESC", "description": "", "isDeprecated": false, "deprecationReason": null }
|
||||
],
|
||||
"possibleTypes": null
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
|
|
|
@ -495,6 +495,10 @@ export interface MonitorPageTitle {
|
|||
}
|
||||
/** The primary object returned for monitor states. */
|
||||
export interface MonitorSummaryResult {
|
||||
/** Used to go to the next page of results */
|
||||
prevPagePagination?: string | null;
|
||||
/** Used to go to the previous page of results */
|
||||
nextPagePagination?: string | null;
|
||||
/** The objects representing the state of a series of heartbeat monitors. */
|
||||
summaries?: MonitorSummary[] | null;
|
||||
/** The number of summaries. */
|
||||
|
@ -757,11 +761,27 @@ export interface GetMonitorStatesQueryArgs {
|
|||
|
||||
dateRangeEnd: string;
|
||||
|
||||
pagination?: string | null;
|
||||
|
||||
filters?: string | null;
|
||||
|
||||
statusFilter?: string | null;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
// Enums
|
||||
// ====================================================
|
||||
|
||||
export enum CursorDirection {
|
||||
AFTER = 'AFTER',
|
||||
BEFORE = 'BEFORE',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
ASC = 'ASC',
|
||||
DESC = 'DESC',
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
// END: Typescript template
|
||||
// ====================================================
|
||||
|
|
|
@ -21,11 +21,8 @@ export const FilterStatusButton = ({ content, value, withNext }: FilterStatusBut
|
|||
<EuiFilterButton
|
||||
hasActiveFilters={urlValue === value}
|
||||
onClick={() => {
|
||||
if (urlValue === value) {
|
||||
setUrlParams({ statusFilter: '' });
|
||||
} else {
|
||||
setUrlParams({ statusFilter: value });
|
||||
}
|
||||
const nextFilter = { statusFilter: urlValue === value ? '' : value, pagination: '' };
|
||||
setUrlParams(nextFilter);
|
||||
}}
|
||||
withNext={withNext}
|
||||
>
|
||||
|
|
|
@ -80,6 +80,25 @@ exports[`MonitorList component renders a no items message when no data is provid
|
|||
noItemsMessage="No uptime monitors found"
|
||||
responsive={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="prev"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
`;
|
||||
|
@ -226,6 +245,25 @@ exports[`MonitorList component renders the monitor list 1`] = `
|
|||
noItemsMessage="No uptime monitors found"
|
||||
responsive={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="prev"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MonitorList component renders a no items message when no data is provided 1`] = `
|
||||
<Fragment>
|
||||
<EuiPanel>
|
||||
<EuiTitle
|
||||
size="xs"
|
||||
>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Monitor status"
|
||||
id="xpack.uptime.monitorList.monitoringStatusTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiBasicTable
|
||||
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 0 items."
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.monitor.status",
|
||||
"name": "Status",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.monitor.name",
|
||||
"name": "Name",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.url.full",
|
||||
"name": "URL",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "histogram.points",
|
||||
"mobileOptions": Object {
|
||||
"show": false,
|
||||
},
|
||||
"name": "Downtime history",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "state",
|
||||
"hasActions": true,
|
||||
"id": "actions",
|
||||
"mobileOptions": Object {
|
||||
"header": false,
|
||||
},
|
||||
"name": "Integrations",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "monitor_id",
|
||||
"isExpander": true,
|
||||
"name": "",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
"width": "40px",
|
||||
},
|
||||
]
|
||||
}
|
||||
hasActions={true}
|
||||
isExpandable={true}
|
||||
itemId="monitor_id"
|
||||
itemIdToExpandedRowMap={Object {}}
|
||||
items={Array []}
|
||||
loading={false}
|
||||
noItemsMessage="No uptime monitors found"
|
||||
responsive={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="prev"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`MonitorList component renders the monitor list 1`] = `
|
||||
<Fragment>
|
||||
<EuiPanel>
|
||||
<EuiTitle
|
||||
size="xs"
|
||||
>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Monitor status"
|
||||
id="xpack.uptime.monitorList.monitoringStatusTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiBasicTable
|
||||
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 2 items."
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.monitor.status",
|
||||
"name": "Status",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.monitor.name",
|
||||
"name": "Name",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "state.url.full",
|
||||
"name": "URL",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "histogram.points",
|
||||
"mobileOptions": Object {
|
||||
"show": false,
|
||||
},
|
||||
"name": "Downtime history",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "state",
|
||||
"hasActions": true,
|
||||
"id": "actions",
|
||||
"mobileOptions": Object {
|
||||
"header": false,
|
||||
},
|
||||
"name": "Integrations",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "left",
|
||||
"field": "monitor_id",
|
||||
"isExpander": true,
|
||||
"name": "",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
"width": "40px",
|
||||
},
|
||||
]
|
||||
}
|
||||
hasActions={true}
|
||||
isExpandable={true}
|
||||
itemId="monitor_id"
|
||||
itemIdToExpandedRowMap={Object {}}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"monitor_id": "foo",
|
||||
"state": Object {
|
||||
"checks": Array [
|
||||
Object {
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.1",
|
||||
"status": "up",
|
||||
},
|
||||
"timestamp": "124",
|
||||
},
|
||||
Object {
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.2",
|
||||
"status": "down",
|
||||
},
|
||||
"timestamp": "125",
|
||||
},
|
||||
Object {
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.3",
|
||||
"status": "down",
|
||||
},
|
||||
"timestamp": "126",
|
||||
},
|
||||
],
|
||||
"summary": Object {
|
||||
"down": 2,
|
||||
"up": 1,
|
||||
},
|
||||
"timestamp": "123",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"monitor_id": "bar",
|
||||
"state": Object {
|
||||
"checks": Array [
|
||||
Object {
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.1",
|
||||
"status": "up",
|
||||
},
|
||||
"timestamp": "125",
|
||||
},
|
||||
Object {
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.2",
|
||||
"status": "up",
|
||||
},
|
||||
"timestamp": "126",
|
||||
},
|
||||
],
|
||||
"summary": Object {
|
||||
"down": 0,
|
||||
"up": 2,
|
||||
},
|
||||
"timestamp": "125",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
loading={false}
|
||||
noItemsMessage="No uptime monitors found"
|
||||
responsive={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="prev"
|
||||
pagination="{\\"cursorKey\\":{\\"monitor_id\\":123},\\"cursorDirection\\":\\"BEFORE\\",\\"sortOrder\\":\\"ASC\\"}"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<OverviewPageLink
|
||||
direction="next"
|
||||
pagination="{\\"cursorKey\\":{\\"monitor_id\\":456},\\"cursorDirection\\":\\"AFTER\\",\\"sortOrder\\":\\"ASC\\"}"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
`;
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import React from 'react';
|
||||
import {
|
||||
CursorDirection,
|
||||
MonitorSummaryResult,
|
||||
SortOrder,
|
||||
} from '../../../../../common/graphql/types';
|
||||
import { MonitorListComponent } from '../monitor_list';
|
||||
|
||||
describe('MonitorList component', () => {
|
||||
let result: MonitorSummaryResult;
|
||||
|
||||
beforeEach(() => {
|
||||
result = {
|
||||
prevPagePagination: JSON.stringify({
|
||||
cursorKey: { monitor_id: 123 },
|
||||
cursorDirection: CursorDirection.BEFORE,
|
||||
sortOrder: SortOrder.ASC,
|
||||
}),
|
||||
nextPagePagination: JSON.stringify({
|
||||
cursorKey: { monitor_id: 456 },
|
||||
cursorDirection: CursorDirection.AFTER,
|
||||
sortOrder: SortOrder.ASC,
|
||||
}),
|
||||
summaries: [
|
||||
{
|
||||
monitor_id: 'foo',
|
||||
state: {
|
||||
checks: [
|
||||
{
|
||||
monitor: {
|
||||
ip: '127.0.0.1',
|
||||
status: 'up',
|
||||
},
|
||||
timestamp: '124',
|
||||
},
|
||||
{
|
||||
monitor: {
|
||||
ip: '127.0.0.2',
|
||||
status: 'down',
|
||||
},
|
||||
timestamp: '125',
|
||||
},
|
||||
{
|
||||
monitor: {
|
||||
ip: '127.0.0.3',
|
||||
status: 'down',
|
||||
},
|
||||
timestamp: '126',
|
||||
},
|
||||
],
|
||||
summary: {
|
||||
up: 1,
|
||||
down: 2,
|
||||
},
|
||||
timestamp: '123',
|
||||
},
|
||||
},
|
||||
{
|
||||
monitor_id: 'bar',
|
||||
state: {
|
||||
checks: [
|
||||
{
|
||||
monitor: {
|
||||
ip: '127.0.0.1',
|
||||
status: 'up',
|
||||
},
|
||||
timestamp: '125',
|
||||
},
|
||||
{
|
||||
monitor: {
|
||||
ip: '127.0.0.2',
|
||||
status: 'up',
|
||||
},
|
||||
timestamp: '126',
|
||||
},
|
||||
],
|
||||
summary: {
|
||||
up: 2,
|
||||
down: 0,
|
||||
},
|
||||
timestamp: '125',
|
||||
},
|
||||
},
|
||||
],
|
||||
totalSummaryCount: {
|
||||
count: 2,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('renders the monitor list', () => {
|
||||
const component = shallowWithIntl(
|
||||
<MonitorListComponent
|
||||
absoluteStartDate={123}
|
||||
absoluteEndDate={125}
|
||||
dangerColor="danger"
|
||||
data={{ monitorStates: result }}
|
||||
loading={false}
|
||||
successColor="primary"
|
||||
hasActiveFilters={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a no items message when no data is provided', () => {
|
||||
const component = shallowWithIntl(
|
||||
<MonitorListComponent
|
||||
absoluteStartDate={123}
|
||||
absoluteEndDate={125}
|
||||
dangerColor="danger"
|
||||
data={{}}
|
||||
loading={false}
|
||||
successColor="primary"
|
||||
hasActiveFilters={false}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -4,8 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiBasicTable, EuiPanel, EuiTitle, EuiButtonIcon, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiFlexGroup,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
EuiButtonIcon,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { get } from 'lodash';
|
||||
|
@ -25,6 +34,7 @@ import { CLIENT_DEFAULTS } from '../../../../common/constants';
|
|||
import { MonitorBarSeries } from '../charts';
|
||||
import { MonitorPageLink } from '../monitor_page_link';
|
||||
import { MonitorListActionsPopover } from './monitor_list_actions_popover';
|
||||
import { OverviewPageLink } from '../overview_page_link';
|
||||
|
||||
interface MonitorListQueryResult {
|
||||
monitorStates?: MonitorSummaryResult;
|
||||
|
@ -37,14 +47,6 @@ interface MonitorListProps {
|
|||
hasActiveFilters: boolean;
|
||||
successColor: string;
|
||||
linkParameters?: string;
|
||||
// TODO: reintegrate pagination in a future release
|
||||
// pageIndex: number;
|
||||
// pageSize: number;
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// onChange: (criteria: Criteria) => void;
|
||||
// TODO: reintegrate sorting in a future release
|
||||
// sortField: string;
|
||||
// sortDirection: string;
|
||||
}
|
||||
|
||||
type Props = UptimeGraphQLQueryProps<MonitorListQueryResult> & MonitorListProps;
|
||||
|
@ -60,37 +62,12 @@ export const MonitorListComponent = (props: Props) => {
|
|||
hasActiveFilters,
|
||||
linkParameters,
|
||||
loading,
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// onChange,
|
||||
// TODO: reintegrate pagination in future release
|
||||
// pageIndex,
|
||||
// pageSize,
|
||||
// TODO: reintegrate sorting in future release
|
||||
// sortDirection,
|
||||
// sortField,
|
||||
} = props;
|
||||
const [drawerIds, updateDrawerIds] = useState<string[]>([]);
|
||||
|
||||
const items = get<MonitorSummary[]>(data, 'monitorStates.summaries', []);
|
||||
// TODO: use with pagination
|
||||
// const count = get<number>(data, 'monitorStates.totalSummaryCount.count', 0);
|
||||
|
||||
// TODO: reintegrate pagination in future release
|
||||
// const pagination: Pagination = {
|
||||
// pageIndex,
|
||||
// pageSize,
|
||||
// pageSizeOptions: [5, 10, 20, 50],
|
||||
// totalItemCount: count,
|
||||
// hidePerPageOptions: false,
|
||||
// };
|
||||
|
||||
// TODO: reintegrate sorting in future release
|
||||
// const sorting = {
|
||||
// sort: {
|
||||
// field: sortField,
|
||||
// direction: sortDirection,
|
||||
// },
|
||||
// };
|
||||
const nextPagePagination = get<string>(data, 'monitorStates.nextPagePagination');
|
||||
const prevPagePagination = get<string>(data, 'monitorStates.prevPagePagination');
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -268,6 +245,15 @@ export const MonitorListComponent = (props: Props) => {
|
|||
},
|
||||
]}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<OverviewPageLink pagination={prevPagePagination} direction="prev" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<OverviewPageLink pagination={nextPagePagination} direction="next" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiLink, EuiIcon, EuiButtonIcon } from '@elastic/eui';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useUrlParams } from '../../hooks';
|
||||
|
||||
interface OverviewPageLinkProps {
|
||||
pagination: string;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
export const OverviewPageLink: FunctionComponent<OverviewPageLinkProps> = ({
|
||||
pagination,
|
||||
direction,
|
||||
}) => {
|
||||
const [, updateUrlParams] = useUrlParams();
|
||||
const icon = direction === 'prev' ? 'arrowLeft' : 'arrowRight';
|
||||
|
||||
const color = pagination ? 'primary' : 'ghost';
|
||||
const ariaLabel =
|
||||
direction === 'next'
|
||||
? i18n.translate('xpack.uptime.overviewPageLink.next.ariaLabel', {
|
||||
defaultMessage: 'Next page of results',
|
||||
})
|
||||
: i18n.translate('xpack.uptime.overviewPageLink.prev.ariaLabel', {
|
||||
defaultMessage: 'Prev page of results',
|
||||
});
|
||||
|
||||
return !!pagination ? (
|
||||
<EuiLink
|
||||
aria-label={ariaLabel}
|
||||
onClick={() => {
|
||||
updateUrlParams({ pagination });
|
||||
}}
|
||||
>
|
||||
<EuiIcon type={icon} color={color} />
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate('xpack.uptime.overviewPageLink.disabled.ariaLabel', {
|
||||
defaultMessage:
|
||||
'A disabled pagination button indicating that there cannot be any further navigation in the monitors list.',
|
||||
})}
|
||||
color={color}
|
||||
disabled={true}
|
||||
iconType={icon}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -34,12 +34,26 @@ export const useUrlParams: UptimeUrlParamsHook = () => {
|
|||
location: { pathname, search },
|
||||
} = refreshContext;
|
||||
const currentParams: any = qs.parse(search[0] === '?' ? search.slice(1) : search);
|
||||
const mergedParams = {
|
||||
...currentParams,
|
||||
...updatedParams,
|
||||
};
|
||||
|
||||
history.push({
|
||||
pathname,
|
||||
search: qs.stringify({
|
||||
...currentParams,
|
||||
...updatedParams,
|
||||
}),
|
||||
search: qs.stringify(
|
||||
// drop any parameters that have no value
|
||||
Object.keys(mergedParams).reduce((params, key) => {
|
||||
const value = mergedParams[key];
|
||||
if (value === undefined || value === '') {
|
||||
return params;
|
||||
}
|
||||
return {
|
||||
...params,
|
||||
[key]: value,
|
||||
};
|
||||
}, {})
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ Object {
|
|||
"dateRangeEnd": "now",
|
||||
"dateRangeStart": "now-15m",
|
||||
"filters": "",
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
|
@ -24,6 +25,7 @@ Object {
|
|||
"dateRangeEnd": "now",
|
||||
"dateRangeStart": "now-15m",
|
||||
"filters": "",
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
|
@ -39,6 +41,7 @@ Object {
|
|||
"dateRangeEnd": "now",
|
||||
"dateRangeStart": "now-15m",
|
||||
"filters": "",
|
||||
"pagination": undefined,
|
||||
"search": "monitor.status: down",
|
||||
"selectedPingStatus": "up",
|
||||
"statusFilter": "",
|
||||
|
@ -54,6 +57,7 @@ Object {
|
|||
"dateRangeEnd": "now",
|
||||
"dateRangeStart": "now-15m",
|
||||
"filters": "",
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
|
@ -69,6 +73,7 @@ Object {
|
|||
"dateRangeEnd": "now",
|
||||
"dateRangeStart": "now-18d",
|
||||
"filters": "",
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
|
|
|
@ -16,12 +16,8 @@ export interface UptimeUrlParams {
|
|||
autorefreshIsPaused: boolean;
|
||||
dateRangeStart: string;
|
||||
dateRangeEnd: string;
|
||||
pagination?: string;
|
||||
filters: string;
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// monitorListPageIndex: number;
|
||||
// monitorListPageSize: number;
|
||||
// monitorListSortDirection: string;
|
||||
// monitorListSortField: string;
|
||||
search: string;
|
||||
selectedPingStatus: string;
|
||||
statusFilter: string;
|
||||
|
@ -34,14 +30,9 @@ const {
|
|||
AUTOREFRESH_IS_PAUSED,
|
||||
DATE_RANGE_START,
|
||||
DATE_RANGE_END,
|
||||
FILTERS,
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// MONITOR_LIST_PAGE_INDEX,
|
||||
// MONITOR_LIST_PAGE_SIZE,
|
||||
// MONITOR_LIST_SORT_DIRECTION,
|
||||
// MONITOR_LIST_SORT_FIELD,
|
||||
SEARCH,
|
||||
SELECTED_PING_LIST_STATUS,
|
||||
FILTERS,
|
||||
STATUS_FILTER,
|
||||
} = CLIENT_DEFAULTS;
|
||||
|
||||
|
@ -80,14 +71,10 @@ export const getSupportedUrlParams = (params: {
|
|||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
filters,
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// monitorListPageIndex,
|
||||
// monitorListPageSize,
|
||||
// monitorListSortDirection,
|
||||
// monitorListSortField,
|
||||
search,
|
||||
selectedPingStatus,
|
||||
statusFilter,
|
||||
pagination,
|
||||
} = filteredParams;
|
||||
|
||||
return {
|
||||
|
@ -104,14 +91,10 @@ export const getSupportedUrlParams = (params: {
|
|||
dateRangeStart: dateRangeStart || DATE_RANGE_START,
|
||||
dateRangeEnd: dateRangeEnd || DATE_RANGE_END,
|
||||
filters: filters || FILTERS,
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// monitorListPageIndex: parseUrlInt(monitorListPageIndex, MONITOR_LIST_PAGE_INDEX),
|
||||
// monitorListPageSize: parseUrlInt(monitorListPageSize, MONITOR_LIST_PAGE_SIZE),
|
||||
// monitorListSortDirection: monitorListSortDirection || MONITOR_LIST_SORT_DIRECTION,
|
||||
// monitorListSortField: monitorListSortField || MONITOR_LIST_SORT_FIELD,
|
||||
search: search || SEARCH,
|
||||
selectedPingStatus:
|
||||
selectedPingStatus === undefined ? SELECTED_PING_LIST_STATUS : selectedPingStatus,
|
||||
statusFilter: statusFilter || STATUS_FILTER,
|
||||
pagination,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -65,7 +65,14 @@ export const OverviewPage = ({
|
|||
const { colors, setHeadingText } = useContext(UptimeSettingsContext);
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams();
|
||||
const { dateRangeStart, dateRangeEnd, filters: urlFilters, search, statusFilter } = params;
|
||||
const {
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
search,
|
||||
pagination,
|
||||
statusFilter,
|
||||
filters: urlFilters,
|
||||
} = params;
|
||||
const [indexPattern, setIndexPattern] = useState<any>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -121,16 +128,6 @@ export const OverviewPage = ({
|
|||
|
||||
const linkParameters = stringifyUrlParams(params);
|
||||
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// const onMonitorListChange = ({ page: { index, size }, sort: { field, direction } }: Criteria) => {
|
||||
// updateUrl({
|
||||
// monitorListPageIndex: index,
|
||||
// monitorListPageSize: size,
|
||||
// monitorListSortDirection: direction,
|
||||
// monitorListSortField: field,
|
||||
// });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EmptyState basePath={basePath} implementsCustomErrorState={true} variables={{}}>
|
||||
|
@ -143,7 +140,7 @@ export const OverviewPage = ({
|
|||
currentFilter={urlFilters}
|
||||
onFilterUpdate={(filtersKuery: string) => {
|
||||
if (urlFilters !== filtersKuery) {
|
||||
updateUrl({ filters: filtersKuery });
|
||||
updateUrl({ filters: filtersKuery, pagination: '' });
|
||||
}
|
||||
}}
|
||||
variables={sharedProps}
|
||||
|
@ -174,22 +171,9 @@ export const OverviewPage = ({
|
|||
implementsCustomErrorState={true}
|
||||
linkParameters={linkParameters}
|
||||
successColor={colors.success}
|
||||
// TODO: reintegrate pagination in future release
|
||||
// pageIndex={monitorListPageIndex}
|
||||
// pageSize={monitorListPageSize}
|
||||
// TODO: reintegrate sorting in future release
|
||||
// sortDirection={monitorListSortDirection}
|
||||
// sortField={monitorListSortField}
|
||||
// TODO: reintroduce for pagination and sorting
|
||||
// onChange={onMonitorListChange}
|
||||
variables={{
|
||||
...sharedProps,
|
||||
// TODO: reintegrate pagination in future release
|
||||
// pageIndex: monitorListPageIndex,
|
||||
// pageSize: monitorListPageSize,
|
||||
// TODO: reintegrate sorting in future release
|
||||
// sortField: monitorListSortField,
|
||||
// sortDirection: monitorListSortDirection,
|
||||
pagination,
|
||||
}}
|
||||
/>
|
||||
</EmptyState>
|
||||
|
|
|
@ -7,13 +7,16 @@
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
export const monitorStatesQueryString = `
|
||||
query MonitorStates($dateRangeStart: String!, $dateRangeEnd: String!, $filters: String, $statusFilter: String) {
|
||||
query MonitorStates($dateRangeStart: String!, $dateRangeEnd: String!, $pagination: String, $filters: String, $statusFilter: String) {
|
||||
monitorStates: getMonitorStates(
|
||||
dateRangeStart: $dateRangeStart
|
||||
dateRangeEnd: $dateRangeEnd
|
||||
pagination: $pagination
|
||||
filters: $filters
|
||||
statusFilter: $statusFilter
|
||||
) {
|
||||
prevPagePagination
|
||||
nextPagePagination
|
||||
totalSummaryCount {
|
||||
count
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
MonitorSummaryResult,
|
||||
StatesIndexStatus,
|
||||
} from '../../../common/graphql/types';
|
||||
import { CONTEXT_DEFAULTS } from '../../../common/constants/context_defaults';
|
||||
|
||||
export type UMGetMonitorStatesResolver = UMResolver<
|
||||
MonitorSummaryResult | Promise<MonitorSummaryResult>,
|
||||
|
@ -39,28 +40,30 @@ export const createMonitorStatesResolvers: CreateUMGraphQLResolvers = (
|
|||
Query: {
|
||||
async getMonitorStates(
|
||||
resolver,
|
||||
{ dateRangeStart, dateRangeEnd, filters, statusFilter },
|
||||
{ dateRangeStart, dateRangeEnd, filters, pagination, statusFilter },
|
||||
{ req }
|
||||
): Promise<MonitorSummaryResult> {
|
||||
const decodedPagination = pagination
|
||||
? JSON.parse(decodeURIComponent(pagination))
|
||||
: CONTEXT_DEFAULTS.CURSOR_PAGINATION;
|
||||
const [
|
||||
// TODO: rely on new summaries adapter function once continuous data frame is available
|
||||
// summaries,
|
||||
totalSummaryCount,
|
||||
legacySummaries,
|
||||
{ summaries, nextPagePagination, prevPagePagination },
|
||||
] = await Promise.all([
|
||||
// TODO: rely on new summaries adapter function once continuous data frame is available
|
||||
// libs.monitorStates.getMonitorStates(req, pageIndex, pageSize, sortField, sortDirection),
|
||||
libs.pings.getDocCount(req),
|
||||
libs.monitorStates.legacyGetMonitorStates(
|
||||
libs.monitorStates.getMonitorStates(
|
||||
req,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
decodedPagination,
|
||||
filters,
|
||||
statusFilter
|
||||
),
|
||||
]);
|
||||
return {
|
||||
summaries: legacySummaries,
|
||||
summaries,
|
||||
nextPagePagination,
|
||||
prevPagePagination,
|
||||
totalSummaryCount,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -149,6 +149,10 @@ export const monitorStatesSchema = gql`
|
|||
|
||||
"The primary object returned for monitor states."
|
||||
type MonitorSummaryResult {
|
||||
"Used to go to the next page of results"
|
||||
prevPagePagination: String
|
||||
"Used to go to the previous page of results"
|
||||
nextPagePagination: String
|
||||
"The objects representing the state of a series of heartbeat monitors."
|
||||
summaries: [MonitorSummary!]
|
||||
"The number of summaries."
|
||||
|
@ -163,11 +167,22 @@ export const monitorStatesSchema = gql`
|
|||
docCount: DocCount
|
||||
}
|
||||
|
||||
enum CursorDirection {
|
||||
AFTER
|
||||
BEFORE
|
||||
}
|
||||
|
||||
enum SortOrder {
|
||||
ASC
|
||||
DESC
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
"Fetches the current state of Uptime monitors for the given parameters."
|
||||
getMonitorStates(
|
||||
dateRangeStart: String!
|
||||
dateRangeEnd: String!
|
||||
pagination: String
|
||||
filters: String
|
||||
statusFilter: String
|
||||
): MonitorSummaryResult
|
||||
|
|
|
@ -1,741 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ElasticsearchMonitorStatesAdapter applies an appropriate filter section to the query based on filters 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
Object {},
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"monitors": Object {
|
||||
"aggs": Object {
|
||||
"top": Object {
|
||||
"top_hits": Object {
|
||||
"_source": Object {
|
||||
"includes": Array [
|
||||
"monitor.check_group",
|
||||
"@timestamp",
|
||||
],
|
||||
},
|
||||
"size": 1,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"composite": Object {
|
||||
"size": 100,
|
||||
"sources": Array [
|
||||
Object {
|
||||
"monitor_id": Object {
|
||||
"terms": Object {
|
||||
"field": "monitor.id",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"location": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing_bucket": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"gte": "now-15m",
|
||||
"lte": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "summary.up",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"monitor.id": "green-0001",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"monitor.name": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0000",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0001",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0003",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0004",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"size": 0,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
"index": "heartbeat-8*",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {},
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"monitors": Object {
|
||||
"aggregations": Object {
|
||||
"state": Object {
|
||||
"scripted_metric": Object {
|
||||
"combine_script": "return state;",
|
||||
"init_script": "
|
||||
// Globals are values that should be identical across all docs
|
||||
// We can cheat a bit by always overwriting these and make the
|
||||
// assumption that there is no variation in these across checks
|
||||
state.globals = new HashMap();
|
||||
// Here we store stuff broken out by agent.id and monitor.id
|
||||
// This should correspond to a unique check.
|
||||
state.checksByAgentIdIP = new HashMap();
|
||||
",
|
||||
"map_script": "
|
||||
Map curCheck = new HashMap();
|
||||
String agentId = doc[\\"agent.id\\"][0];
|
||||
String ip = null;
|
||||
if (doc[\\"monitor.ip\\"].length > 0) {
|
||||
ip = doc[\\"monitor.ip\\"][0];
|
||||
}
|
||||
String agentIdIP = agentId + \\"-\\" + (ip == null ? \\"\\" : ip.toString());
|
||||
def ts = doc[\\"@timestamp\\"][0].toInstant().toEpochMilli();
|
||||
|
||||
def lastCheck = state.checksByAgentIdIP[agentId];
|
||||
Instant lastTs = lastCheck != null ? lastCheck[\\"@timestamp\\"] : null;
|
||||
if (lastTs != null && lastTs > ts) {
|
||||
return;
|
||||
}
|
||||
|
||||
curCheck.put(\\"@timestamp\\", ts);
|
||||
|
||||
Map agent = new HashMap();
|
||||
agent.id = agentId;
|
||||
curCheck.put(\\"agent\\", agent);
|
||||
|
||||
if (state.globals.url == null) {
|
||||
Map url = new HashMap();
|
||||
Collection fields = [\\"full\\", \\"original\\", \\"scheme\\", \\"username\\", \\"password\\", \\"domain\\", \\"port\\", \\"path\\", \\"query\\", \\"fragment\\"];
|
||||
for (field in fields) {
|
||||
String docPath = \\"url.\\" + field;
|
||||
def val = doc[docPath];
|
||||
if (!val.isEmpty()) {
|
||||
url[field] = val[0];
|
||||
}
|
||||
}
|
||||
state.globals.url = url;
|
||||
}
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.status = doc[\\"monitor.status\\"][0];
|
||||
monitor.ip = ip;
|
||||
if (!doc[\\"monitor.name\\"].isEmpty()) {
|
||||
String monitorName = doc[\\"monitor.name\\"][0];
|
||||
if (monitor.name != \\"\\") {
|
||||
monitor.name = monitorName;
|
||||
}
|
||||
}
|
||||
curCheck.monitor = monitor;
|
||||
|
||||
if (curCheck.observer == null) {
|
||||
curCheck.observer = new HashMap();
|
||||
}
|
||||
if (curCheck.observer.geo == null) {
|
||||
curCheck.observer.geo = new HashMap();
|
||||
}
|
||||
if (!doc[\\"observer.geo.name\\"].isEmpty()) {
|
||||
curCheck.observer.geo.name = doc[\\"observer.geo.name\\"][0];
|
||||
}
|
||||
if (!doc[\\"observer.geo.location\\"].isEmpty()) {
|
||||
curCheck.observer.geo.location = doc[\\"observer.geo.location\\"][0];
|
||||
}
|
||||
if (!doc[\\"kubernetes.pod.uid\\"].isEmpty() && curCheck.kubernetes == null) {
|
||||
curCheck.kubernetes = new HashMap();
|
||||
curCheck.kubernetes.pod = new HashMap();
|
||||
curCheck.kubernetes.pod.uid = doc[\\"kubernetes.pod.uid\\"][0];
|
||||
}
|
||||
if (!doc[\\"container.id\\"].isEmpty() && curCheck.container == null) {
|
||||
curCheck.container = new HashMap();
|
||||
curCheck.container.id = doc[\\"container.id\\"][0];
|
||||
}
|
||||
if (curCheck.tls == null) {
|
||||
curCheck.tls = new HashMap();
|
||||
}
|
||||
if (!doc[\\"tls.certificate_not_valid_after\\"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_after = doc[\\"tls.certificate_not_valid_after\\"][0];
|
||||
}
|
||||
if (!doc[\\"tls.certificate_not_valid_before\\"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_before = doc[\\"tls.certificate_not_valid_before\\"][0];
|
||||
}
|
||||
|
||||
state.checksByAgentIdIP[agentIdIP] = curCheck;
|
||||
",
|
||||
"reduce_script": "
|
||||
// The final document
|
||||
Map result = new HashMap();
|
||||
|
||||
Map checks = new HashMap();
|
||||
Instant maxTs = Instant.ofEpochMilli(0);
|
||||
Collection ips = new HashSet();
|
||||
Collection geoNames = new HashSet();
|
||||
Collection podUids = new HashSet();
|
||||
Collection containerIds = new HashSet();
|
||||
Collection tls = new HashSet();
|
||||
String name = null;
|
||||
for (state in states) {
|
||||
result.putAll(state.globals);
|
||||
for (entry in state.checksByAgentIdIP.entrySet()) {
|
||||
def agentIdIP = entry.getKey();
|
||||
def check = entry.getValue();
|
||||
def lastBestCheck = checks.get(agentIdIP);
|
||||
def checkTs = Instant.ofEpochMilli(check.get(\\"@timestamp\\"));
|
||||
|
||||
if (maxTs.isBefore(checkTs)) { maxTs = checkTs}
|
||||
|
||||
if (lastBestCheck == null || lastBestCheck.get(\\"@timestamp\\") < checkTs) {
|
||||
check[\\"@timestamp\\"] = check[\\"@timestamp\\"];
|
||||
checks[agentIdIP] = check
|
||||
}
|
||||
|
||||
if (check.monitor.name != null && check.monitor.name != \\"\\") {
|
||||
name = check.monitor.name;
|
||||
}
|
||||
|
||||
ips.add(check.monitor.ip);
|
||||
if (check.observer != null && check.observer.geo != null && check.observer.geo.name != null) {
|
||||
geoNames.add(check.observer.geo.name);
|
||||
}
|
||||
if (check.kubernetes != null && check.kubernetes.pod != null) {
|
||||
podUids.add(check.kubernetes.pod.uid);
|
||||
}
|
||||
if (check.container != null) {
|
||||
containerIds.add(check.container.id);
|
||||
}
|
||||
if (check.tls != null) {
|
||||
tls.add(check.tls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We just use the values so we can store these as nested docs
|
||||
result.checks = checks.values();
|
||||
result.put(\\"@timestamp\\", maxTs);
|
||||
|
||||
|
||||
Map summary = new HashMap();
|
||||
summary.up = checks.entrySet().stream().filter(c -> c.getValue().monitor.status == \\"up\\").count();
|
||||
summary.down = checks.size() - summary.up;
|
||||
result.summary = summary;
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.ip = ips;
|
||||
monitor.name = name;
|
||||
monitor.status = summary.down > 0 ? (summary.up > 0 ? \\"mixed\\": \\"down\\") : \\"up\\";
|
||||
result.monitor = monitor;
|
||||
|
||||
Map observer = new HashMap();
|
||||
Map geo = new HashMap();
|
||||
observer.geo = geo;
|
||||
geo.name = geoNames;
|
||||
result.observer = observer;
|
||||
|
||||
if (!podUids.isEmpty()) {
|
||||
result.kubernetes = new HashMap();
|
||||
result.kubernetes.pod = new HashMap();
|
||||
result.kubernetes.pod.uid = podUids;
|
||||
}
|
||||
|
||||
if (!containerIds.isEmpty()) {
|
||||
result.container = new HashMap();
|
||||
result.container.id = containerIds;
|
||||
}
|
||||
|
||||
if (!tls.isEmpty()) {
|
||||
result.tls = new HashMap();
|
||||
result.tls = tls;
|
||||
}
|
||||
|
||||
return result;
|
||||
",
|
||||
},
|
||||
},
|
||||
},
|
||||
"composite": Object {
|
||||
"size": 50,
|
||||
"sources": Array [
|
||||
Object {
|
||||
"monitor_id": Object {
|
||||
"terms": Object {
|
||||
"field": "monitor.id",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"gte": "now-15m",
|
||||
"lte": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"terms": Object {
|
||||
"monitor.check_group": Array [
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"monitor.id": "green-0001",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"monitor.name": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0000",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0001",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0002",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0003",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"monitor.id": "green-0004",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"size": 0,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
"index": "heartbeat-8*",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {},
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"by_id": Object {
|
||||
"aggs": Object {
|
||||
"histogram": Object {
|
||||
"aggs": Object {
|
||||
"status": Object {
|
||||
"terms": Object {
|
||||
"field": "monitor.status",
|
||||
"shard_size": 2,
|
||||
"size": 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
"date_histogram": Object {
|
||||
"field": "@timestamp",
|
||||
"fixed_interval": "36000ms",
|
||||
"missing": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"terms": Object {
|
||||
"field": "monitor.id",
|
||||
"size": 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"terms": Object {
|
||||
"monitor.id": Array [
|
||||
"auto-http-0X21EE76EAC459873F",
|
||||
"auto-http-0X2AF1D7DB9C490053",
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"gte": "now-15m",
|
||||
"lte": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"size": 0,
|
||||
},
|
||||
"index": "heartbeat-8*",
|
||||
},
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ElasticsearchMonitorStatesAdapter returns properly formatted objects from raw es documents 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"histogram": undefined,
|
||||
"monitor_id": "auto-http-0X21EE76EAC459873F",
|
||||
"state": Object {
|
||||
"@timestamp": "2019-06-26T13:42:42.535Z",
|
||||
"checks": Array [
|
||||
Object {
|
||||
"@timestamp": 1561556562535,
|
||||
"agent": Object {
|
||||
"id": "5884d7f7-9a49-4b0e-bff2-72a475aa695f",
|
||||
},
|
||||
"monitor": Object {
|
||||
"ip": "127.0.0.1",
|
||||
"name": "test-page",
|
||||
"status": "down",
|
||||
},
|
||||
"observer": Object {
|
||||
"geo": Object {
|
||||
"location": Object {
|
||||
"lat": 39.952599965035915,
|
||||
"lon": 75.1651999913156,
|
||||
},
|
||||
"name": "us-east-2",
|
||||
},
|
||||
},
|
||||
"timestamp": 1561556562535,
|
||||
},
|
||||
],
|
||||
"monitor": Object {
|
||||
"ip": Array [
|
||||
"127.0.0.1",
|
||||
],
|
||||
"status": "down",
|
||||
},
|
||||
"observer": Object {
|
||||
"geo": Object {
|
||||
"name": Array [
|
||||
"us-east-2",
|
||||
],
|
||||
},
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 1,
|
||||
"up": 0,
|
||||
},
|
||||
"timestamp": "2019-06-26T13:42:42.535Z",
|
||||
"url": Object {
|
||||
"domain": "localhost",
|
||||
"full": "http://localhost:12349/test-page",
|
||||
"path": "/test-page",
|
||||
"port": 12349,
|
||||
"scheme": "http",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"histogram": undefined,
|
||||
"monitor_id": "auto-http-0X2AF1D7DB9C490053",
|
||||
"state": Object {
|
||||
"@timestamp": "2019-06-26T13:42:21.536Z",
|
||||
"checks": Array [
|
||||
Object {
|
||||
"@timestamp": 1561556541536,
|
||||
"agent": Object {
|
||||
"id": "5884d7f7-9a49-4b0e-bff2-72a475aa695f",
|
||||
},
|
||||
"monitor": Object {
|
||||
"ip": "35.245.22.113",
|
||||
"name": "prod-site",
|
||||
"status": "down",
|
||||
},
|
||||
"observer": Object {
|
||||
"geo": Object {
|
||||
"location": Object {
|
||||
"lat": 39.952599965035915,
|
||||
"lon": 75.1651999913156,
|
||||
},
|
||||
"name": "us-east-2",
|
||||
},
|
||||
},
|
||||
"timestamp": 1561556541536,
|
||||
},
|
||||
],
|
||||
"monitor": Object {
|
||||
"ip": Array [
|
||||
"35.245.22.113",
|
||||
],
|
||||
"status": "down",
|
||||
},
|
||||
"observer": Object {
|
||||
"geo": Object {
|
||||
"name": Array [
|
||||
"us-east-2",
|
||||
],
|
||||
},
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 1,
|
||||
"up": 0,
|
||||
},
|
||||
"timestamp": "2019-06-26T13:42:21.536Z",
|
||||
"tls": Array [
|
||||
Object {
|
||||
"certificate_not_valid_after": "2019-11-09T18:04:06.000Z",
|
||||
"certificate_not_valid_before": "2019-11-08T17:03:23.000Z",
|
||||
},
|
||||
],
|
||||
"url": Object {
|
||||
"domain": "35.245.22.113",
|
||||
"full": "http://35.245.22.113:12349/",
|
||||
"path": "/",
|
||||
"port": 12349,
|
||||
"scheme": "http",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DatabaseAdapter } from '../../database';
|
||||
import exampleFilter from './example_filter.json';
|
||||
import monitorState from './monitor_states_docs.json';
|
||||
import { ElasticsearchMonitorStatesAdapter } from '../elasticsearch_monitor_states_adapter';
|
||||
import { get, set } from 'lodash';
|
||||
import { assertCloseTo } from '../../../helper';
|
||||
|
||||
describe('ElasticsearchMonitorStatesAdapter', () => {
|
||||
let database: DatabaseAdapter;
|
||||
let searchMock: jest.Mock<Promise<any>, [any, any]>;
|
||||
beforeAll(() => {
|
||||
database = {
|
||||
count: jest.fn(async (request: any, params: any): Promise<any> => 0),
|
||||
search: jest.fn(async (request: any, params: any): Promise<any> => {}),
|
||||
head: jest.fn(async (request: any, params: any): Promise<any> => {}),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
searchMock = jest.fn(async (request: any, params: any): Promise<any> => monitorState);
|
||||
database.search = searchMock;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns properly formatted objects from raw es documents', async () => {
|
||||
expect.assertions(1);
|
||||
const adapter = new ElasticsearchMonitorStatesAdapter(database);
|
||||
const result = await adapter.legacyGetMonitorStates({}, 'now-15m', 'now');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('applies an appropriate filter section to the query based on filters', async () => {
|
||||
expect.assertions(3);
|
||||
const adapter = new ElasticsearchMonitorStatesAdapter(database);
|
||||
await adapter.legacyGetMonitorStates(
|
||||
{},
|
||||
'now-15m',
|
||||
'now',
|
||||
JSON.stringify(exampleFilter),
|
||||
'down'
|
||||
);
|
||||
expect(searchMock).toHaveBeenCalledTimes(3);
|
||||
const fixedInterval = parseInt(
|
||||
get(
|
||||
searchMock.mock.calls[2][1],
|
||||
'body.aggs.by_id.aggs.histogram.date_histogram.fixed_interval',
|
||||
''
|
||||
).split('ms')[0],
|
||||
10
|
||||
);
|
||||
expect(fixedInterval).not.toBeNaN();
|
||||
/**
|
||||
* This value can sometimes be off by 1 as a result of fuzzy calculation.
|
||||
*
|
||||
* It had no implications in practice, but from a test standpoint can cause flaky
|
||||
* snapshot failures.
|
||||
*/
|
||||
assertCloseTo(fixedInterval, 36000, 100);
|
||||
set(
|
||||
searchMock.mock.calls[2][1],
|
||||
'body.aggs.by_id.aggs.histogram.date_histogram.fixed_interval',
|
||||
'36000ms'
|
||||
);
|
||||
expect(searchMock.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,137 +0,0 @@
|
|||
{
|
||||
"took": 71,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"skipped": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": {
|
||||
"value": 6561,
|
||||
"relation": "eq"
|
||||
},
|
||||
"max_score": null,
|
||||
"hits": []
|
||||
},
|
||||
"aggregations": {
|
||||
"monitors": {
|
||||
"after_key": null,
|
||||
"buckets": [
|
||||
{
|
||||
"key": {
|
||||
"monitor_id": "auto-http-0X21EE76EAC459873F"
|
||||
},
|
||||
"doc_count": 899,
|
||||
"state": {
|
||||
"value": {
|
||||
"summary": {
|
||||
"up": 0,
|
||||
"down": 1
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": ["us-east-2"]
|
||||
}
|
||||
},
|
||||
"monitor": {
|
||||
"ip": ["127.0.0.1"],
|
||||
"status": "down"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "us-east-2",
|
||||
"location": {
|
||||
"lat": 39.952599965035915,
|
||||
"lon": 75.1651999913156
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"id": "5884d7f7-9a49-4b0e-bff2-72a475aa695f"
|
||||
},
|
||||
"@timestamp": 1561556562535,
|
||||
"monitor": {
|
||||
"ip": "127.0.0.1",
|
||||
"name": "test-page",
|
||||
"status": "down"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@timestamp": "2019-06-26T13:42:42.535Z",
|
||||
"url": {
|
||||
"path": "/test-page",
|
||||
"scheme": "http",
|
||||
"port": 12349,
|
||||
"domain": "localhost",
|
||||
"full": "http://localhost:12349/test-page"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"monitor_id": "auto-http-0X2AF1D7DB9C490053"
|
||||
},
|
||||
"doc_count": 52,
|
||||
"state": {
|
||||
"value": {
|
||||
"summary": {
|
||||
"up": 0,
|
||||
"down": 1
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": ["us-east-2"]
|
||||
}
|
||||
},
|
||||
"monitor": {
|
||||
"ip": ["35.245.22.113"],
|
||||
"status": "down"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "us-east-2",
|
||||
"location": {
|
||||
"lat": 39.952599965035915,
|
||||
"lon": 75.1651999913156
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"id": "5884d7f7-9a49-4b0e-bff2-72a475aa695f"
|
||||
},
|
||||
"@timestamp": 1561556541536,
|
||||
"monitor": {
|
||||
"ip": "35.245.22.113",
|
||||
"name": "prod-site",
|
||||
"status": "down"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tls": [
|
||||
{
|
||||
"certificate_not_valid_after": "2019-11-09T18:04:06.000Z",
|
||||
"certificate_not_valid_before": "2019-11-08T17:03:23.000Z"
|
||||
}
|
||||
],
|
||||
"@timestamp": "2019-06-26T13:42:21.536Z",
|
||||
"url": {
|
||||
"path": "/",
|
||||
"scheme": "http",
|
||||
"port": 12349,
|
||||
"domain": "35.245.22.113",
|
||||
"full": "http://35.245.22.113:12349/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,33 +4,50 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { MonitorSummary, StatesIndexStatus } from '../../../../common/graphql/types';
|
||||
import {
|
||||
MonitorSummary,
|
||||
StatesIndexStatus,
|
||||
CursorDirection,
|
||||
SortOrder,
|
||||
} from '../../../../common/graphql/types';
|
||||
|
||||
export interface UMMonitorStatesAdapter {
|
||||
getMonitorStates(
|
||||
request: any,
|
||||
pageIndex: number,
|
||||
pageSize: number,
|
||||
sortField?: string | null,
|
||||
sortDirection?: string | null
|
||||
): Promise<MonitorSummary[]>;
|
||||
legacyGetMonitorStates(
|
||||
request: any,
|
||||
dateRangeStart: string,
|
||||
dateRangeEnd: string,
|
||||
pagination?: CursorPagination,
|
||||
filters?: string | null,
|
||||
statusFilter?: string | null
|
||||
): Promise<MonitorSummary[]>;
|
||||
): Promise<GetMonitorStatesResult>;
|
||||
statesIndexExists(request: any): Promise<StatesIndexStatus>;
|
||||
}
|
||||
|
||||
export interface CursorPagination {
|
||||
cursorKey?: any;
|
||||
cursorDirection: CursorDirection;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export interface GetMonitorStatesResult {
|
||||
summaries: MonitorSummary[];
|
||||
nextPagePagination: string | null;
|
||||
prevPagePagination: string | null;
|
||||
}
|
||||
|
||||
export interface LegacyMonitorStatesQueryResult {
|
||||
result: any;
|
||||
statusFilter?: any;
|
||||
afterKey: any | null;
|
||||
searchAfter: any;
|
||||
}
|
||||
|
||||
export interface LegacyMonitorStatesRecentCheckGroupsQueryResult {
|
||||
export interface MonitorStatesCheckGroupsResult {
|
||||
checkGroups: string[];
|
||||
afterKey: any | null;
|
||||
searchAfter: any;
|
||||
}
|
||||
|
||||
export interface EnrichMonitorStatesResult {
|
||||
monitors: any[];
|
||||
nextPagePagination: any | null;
|
||||
prevPagePagination: any | null;
|
||||
}
|
||||
|
|
|
@ -4,607 +4,57 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, flatten, set, sortBy } from 'lodash';
|
||||
import { DatabaseAdapter } from '../database';
|
||||
import {
|
||||
UMMonitorStatesAdapter,
|
||||
LegacyMonitorStatesRecentCheckGroupsQueryResult,
|
||||
LegacyMonitorStatesQueryResult,
|
||||
} from './adapter_types';
|
||||
import {
|
||||
MonitorSummary,
|
||||
SummaryHistogram,
|
||||
Check,
|
||||
StatesIndexStatus,
|
||||
} from '../../../../common/graphql/types';
|
||||
import { INDEX_NAMES, STATES, QUERY } from '../../../../common/constants';
|
||||
import { getHistogramInterval, parseFilterQuery, getFilterClause } from '../../helper';
|
||||
import { UMMonitorStatesAdapter, GetMonitorStatesResult, CursorPagination } from './adapter_types';
|
||||
import { StatesIndexStatus } from '../../../../common/graphql/types';
|
||||
import { INDEX_NAMES, CONTEXT_DEFAULTS } from '../../../../common/constants';
|
||||
import { fetchPage } from './search';
|
||||
|
||||
type SortChecks = (check: Check) => string[];
|
||||
const checksSortBy = (check: Check) => [
|
||||
get<string>(check, 'observer.geo.name'),
|
||||
get<string>(check, 'monitor.ip'),
|
||||
];
|
||||
export interface QueryContext {
|
||||
database: any;
|
||||
request: any;
|
||||
dateRangeStart: string;
|
||||
dateRangeEnd: string;
|
||||
pagination: CursorPagination;
|
||||
filterClause: any | null;
|
||||
size: number;
|
||||
statusFilter?: string;
|
||||
}
|
||||
|
||||
export class ElasticsearchMonitorStatesAdapter implements UMMonitorStatesAdapter {
|
||||
constructor(private readonly database: DatabaseAdapter) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
// This query returns the most recent check groups for a given
|
||||
// monitor ID.
|
||||
private async runLegacyMonitorStatesRecentCheckGroupsQuery(
|
||||
request: any,
|
||||
query: any,
|
||||
dateRangeStart: string,
|
||||
dateRangeEnd: string,
|
||||
searchAfter?: any,
|
||||
size: number = 50
|
||||
): Promise<LegacyMonitorStatesRecentCheckGroupsQueryResult> {
|
||||
const checkGroupsById = new Map<string, string[]>();
|
||||
let afterKey: any = searchAfter;
|
||||
|
||||
const additionalFilters = [
|
||||
{
|
||||
// We check for summary.up to ensure that the check group
|
||||
// is complete. Summary fields are only present on
|
||||
// completed check groups.
|
||||
exists: {
|
||||
field: 'summary.up',
|
||||
},
|
||||
},
|
||||
];
|
||||
if (query) {
|
||||
additionalFilters.push(query);
|
||||
}
|
||||
const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionalFilters);
|
||||
|
||||
const body = {
|
||||
query: {
|
||||
bool: {
|
||||
filter,
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
],
|
||||
size: 0,
|
||||
aggs: {
|
||||
monitors: {
|
||||
composite: {
|
||||
/**
|
||||
* The goal here is to fetch more than enough check groups to reach the target
|
||||
* amount in one query.
|
||||
*
|
||||
* For larger cardinalities, we can only count on being able to fetch max bucket
|
||||
* size, so we will have to run this query multiple times.
|
||||
*
|
||||
* Multiplying `size` by 2 assumes that there will be less than three locations
|
||||
* for the deployment, if needed the query will be run subsequently.
|
||||
*/
|
||||
size: Math.min(size * 2, QUERY.DEFAULT_AGGS_CAP),
|
||||
sources: [
|
||||
{
|
||||
monitor_id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
terms: {
|
||||
field: 'observer.geo.name',
|
||||
missing_bucket: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
aggs: {
|
||||
top: {
|
||||
top_hits: {
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
],
|
||||
_source: {
|
||||
includes: ['monitor.check_group', '@timestamp'],
|
||||
},
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
if (afterKey) {
|
||||
set(body, 'aggs.monitors.composite.after', afterKey);
|
||||
}
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body,
|
||||
};
|
||||
|
||||
const result = await this.database.search(request, params);
|
||||
afterKey = get<any | null>(result, 'aggregations.monitors.after_key', null);
|
||||
get<any>(result, 'aggregations.monitors.buckets', []).forEach((bucket: any) => {
|
||||
const id = get<string>(bucket, 'key.monitor_id');
|
||||
const checkGroup = get<string>(bucket, 'top.hits.hits[0]._source.monitor.check_group');
|
||||
const value = checkGroupsById.get(id);
|
||||
if (!value) {
|
||||
checkGroupsById.set(id, [checkGroup]);
|
||||
} else if (value.indexOf(checkGroup) < 0) {
|
||||
checkGroupsById.set(id, [...value, checkGroup]);
|
||||
}
|
||||
});
|
||||
return {
|
||||
checkGroups: flatten(Array.from(checkGroupsById.values())),
|
||||
afterKey,
|
||||
};
|
||||
}
|
||||
|
||||
private async runLegacyMonitorStatesQuery(
|
||||
request: any,
|
||||
dateRangeStart: string,
|
||||
dateRangeEnd: string,
|
||||
filters?: string | null,
|
||||
searchAfter?: any,
|
||||
size: number = 50
|
||||
): Promise<LegacyMonitorStatesQueryResult> {
|
||||
size = Math.min(size, QUERY.DEFAULT_AGGS_CAP);
|
||||
const query = parseFilterQuery(filters);
|
||||
|
||||
// First we fetch the most recent check groups for this query
|
||||
// This is a critical performance optimization.
|
||||
// Without this the expensive scripted_metric agg below will run
|
||||
// over large numbers of documents.
|
||||
// It only really needs to run over the latest complete check group for each
|
||||
// agent.
|
||||
const { checkGroups, afterKey } = await this.runLegacyMonitorStatesRecentCheckGroupsQuery(
|
||||
request,
|
||||
query,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
searchAfter,
|
||||
size
|
||||
);
|
||||
const additionalFilters = [{ terms: { 'monitor.check_group': checkGroups } }];
|
||||
if (query) {
|
||||
// Even though this work is already done when calculating the groups
|
||||
// this helps the planner
|
||||
additionalFilters.push(query);
|
||||
}
|
||||
const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionalFilters);
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter,
|
||||
},
|
||||
},
|
||||
sort: [{ '@timestamp': 'desc' }],
|
||||
size: 0,
|
||||
aggs: {
|
||||
monitors: {
|
||||
composite: {
|
||||
size,
|
||||
sources: [
|
||||
{
|
||||
monitor_id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
aggregations: {
|
||||
state: {
|
||||
scripted_metric: {
|
||||
init_script: `
|
||||
// Globals are values that should be identical across all docs
|
||||
// We can cheat a bit by always overwriting these and make the
|
||||
// assumption that there is no variation in these across checks
|
||||
state.globals = new HashMap();
|
||||
// Here we store stuff broken out by agent.id and monitor.id
|
||||
// This should correspond to a unique check.
|
||||
state.checksByAgentIdIP = new HashMap();
|
||||
`,
|
||||
map_script: `
|
||||
Map curCheck = new HashMap();
|
||||
String agentId = doc["agent.id"][0];
|
||||
String ip = null;
|
||||
if (doc["monitor.ip"].length > 0) {
|
||||
ip = doc["monitor.ip"][0];
|
||||
}
|
||||
String agentIdIP = agentId + "-" + (ip == null ? "" : ip.toString());
|
||||
def ts = doc["@timestamp"][0].toInstant().toEpochMilli();
|
||||
|
||||
def lastCheck = state.checksByAgentIdIP[agentId];
|
||||
Instant lastTs = lastCheck != null ? lastCheck["@timestamp"] : null;
|
||||
if (lastTs != null && lastTs > ts) {
|
||||
return;
|
||||
}
|
||||
|
||||
curCheck.put("@timestamp", ts);
|
||||
|
||||
Map agent = new HashMap();
|
||||
agent.id = agentId;
|
||||
curCheck.put("agent", agent);
|
||||
|
||||
if (state.globals.url == null) {
|
||||
Map url = new HashMap();
|
||||
Collection fields = ["full", "original", "scheme", "username", "password", "domain", "port", "path", "query", "fragment"];
|
||||
for (field in fields) {
|
||||
String docPath = "url." + field;
|
||||
def val = doc[docPath];
|
||||
if (!val.isEmpty()) {
|
||||
url[field] = val[0];
|
||||
}
|
||||
}
|
||||
state.globals.url = url;
|
||||
}
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.status = doc["monitor.status"][0];
|
||||
monitor.ip = ip;
|
||||
if (!doc["monitor.name"].isEmpty()) {
|
||||
String monitorName = doc["monitor.name"][0];
|
||||
if (monitor.name != "") {
|
||||
monitor.name = monitorName;
|
||||
}
|
||||
}
|
||||
curCheck.monitor = monitor;
|
||||
|
||||
if (curCheck.observer == null) {
|
||||
curCheck.observer = new HashMap();
|
||||
}
|
||||
if (curCheck.observer.geo == null) {
|
||||
curCheck.observer.geo = new HashMap();
|
||||
}
|
||||
if (!doc["observer.geo.name"].isEmpty()) {
|
||||
curCheck.observer.geo.name = doc["observer.geo.name"][0];
|
||||
}
|
||||
if (!doc["observer.geo.location"].isEmpty()) {
|
||||
curCheck.observer.geo.location = doc["observer.geo.location"][0];
|
||||
}
|
||||
if (!doc["kubernetes.pod.uid"].isEmpty() && curCheck.kubernetes == null) {
|
||||
curCheck.kubernetes = new HashMap();
|
||||
curCheck.kubernetes.pod = new HashMap();
|
||||
curCheck.kubernetes.pod.uid = doc["kubernetes.pod.uid"][0];
|
||||
}
|
||||
if (!doc["container.id"].isEmpty() && curCheck.container == null) {
|
||||
curCheck.container = new HashMap();
|
||||
curCheck.container.id = doc["container.id"][0];
|
||||
}
|
||||
if (curCheck.tls == null) {
|
||||
curCheck.tls = new HashMap();
|
||||
}
|
||||
if (!doc["tls.certificate_not_valid_after"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_after = doc["tls.certificate_not_valid_after"][0];
|
||||
}
|
||||
if (!doc["tls.certificate_not_valid_before"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_before = doc["tls.certificate_not_valid_before"][0];
|
||||
}
|
||||
|
||||
state.checksByAgentIdIP[agentIdIP] = curCheck;
|
||||
`,
|
||||
combine_script: 'return state;',
|
||||
reduce_script: `
|
||||
// The final document
|
||||
Map result = new HashMap();
|
||||
|
||||
Map checks = new HashMap();
|
||||
Instant maxTs = Instant.ofEpochMilli(0);
|
||||
Collection ips = new HashSet();
|
||||
Collection geoNames = new HashSet();
|
||||
Collection podUids = new HashSet();
|
||||
Collection containerIds = new HashSet();
|
||||
Collection tls = new HashSet();
|
||||
String name = null;
|
||||
for (state in states) {
|
||||
result.putAll(state.globals);
|
||||
for (entry in state.checksByAgentIdIP.entrySet()) {
|
||||
def agentIdIP = entry.getKey();
|
||||
def check = entry.getValue();
|
||||
def lastBestCheck = checks.get(agentIdIP);
|
||||
def checkTs = Instant.ofEpochMilli(check.get("@timestamp"));
|
||||
|
||||
if (maxTs.isBefore(checkTs)) { maxTs = checkTs}
|
||||
|
||||
if (lastBestCheck == null || lastBestCheck.get("@timestamp") < checkTs) {
|
||||
check["@timestamp"] = check["@timestamp"];
|
||||
checks[agentIdIP] = check
|
||||
}
|
||||
|
||||
if (check.monitor.name != null && check.monitor.name != "") {
|
||||
name = check.monitor.name;
|
||||
}
|
||||
|
||||
ips.add(check.monitor.ip);
|
||||
if (check.observer != null && check.observer.geo != null && check.observer.geo.name != null) {
|
||||
geoNames.add(check.observer.geo.name);
|
||||
}
|
||||
if (check.kubernetes != null && check.kubernetes.pod != null) {
|
||||
podUids.add(check.kubernetes.pod.uid);
|
||||
}
|
||||
if (check.container != null) {
|
||||
containerIds.add(check.container.id);
|
||||
}
|
||||
if (check.tls != null) {
|
||||
tls.add(check.tls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We just use the values so we can store these as nested docs
|
||||
result.checks = checks.values();
|
||||
result.put("@timestamp", maxTs);
|
||||
|
||||
|
||||
Map summary = new HashMap();
|
||||
summary.up = checks.entrySet().stream().filter(c -> c.getValue().monitor.status == "up").count();
|
||||
summary.down = checks.size() - summary.up;
|
||||
result.summary = summary;
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.ip = ips;
|
||||
monitor.name = name;
|
||||
monitor.status = summary.down > 0 ? (summary.up > 0 ? "mixed": "down") : "up";
|
||||
result.monitor = monitor;
|
||||
|
||||
Map observer = new HashMap();
|
||||
Map geo = new HashMap();
|
||||
observer.geo = geo;
|
||||
geo.name = geoNames;
|
||||
result.observer = observer;
|
||||
|
||||
if (!podUids.isEmpty()) {
|
||||
result.kubernetes = new HashMap();
|
||||
result.kubernetes.pod = new HashMap();
|
||||
result.kubernetes.pod.uid = podUids;
|
||||
}
|
||||
|
||||
if (!containerIds.isEmpty()) {
|
||||
result.container = new HashMap();
|
||||
result.container.id = containerIds;
|
||||
}
|
||||
|
||||
if (!tls.isEmpty()) {
|
||||
result.tls = new HashMap();
|
||||
result.tls = tls;
|
||||
}
|
||||
|
||||
return result;
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await this.database.search(request, params);
|
||||
return { afterKey, result };
|
||||
}
|
||||
|
||||
private getMonitorBuckets(queryResult: any, statusFilter?: any) {
|
||||
let monitors = get(queryResult, 'aggregations.monitors.buckets', []);
|
||||
if (statusFilter) {
|
||||
monitors = monitors.filter(
|
||||
(monitor: any) => get(monitor, 'state.value.monitor.status') === statusFilter
|
||||
);
|
||||
}
|
||||
return monitors;
|
||||
}
|
||||
|
||||
public async legacyGetMonitorStates(
|
||||
request: any,
|
||||
dateRangeStart: string,
|
||||
dateRangeEnd: string,
|
||||
filters?: string | null,
|
||||
statusFilter?: string | null
|
||||
): Promise<MonitorSummary[]> {
|
||||
const monitors: any[] = [];
|
||||
let searchAfter: any | null = null;
|
||||
do {
|
||||
const { result, afterKey } = await this.runLegacyMonitorStatesQuery(
|
||||
request,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
filters,
|
||||
searchAfter
|
||||
);
|
||||
monitors.push(...this.getMonitorBuckets(result, statusFilter));
|
||||
searchAfter = afterKey;
|
||||
} while (searchAfter !== null && monitors.length < STATES.LEGACY_STATES_QUERY_SIZE);
|
||||
|
||||
const monitorIds: string[] = [];
|
||||
const summaries: MonitorSummary[] = monitors.map((monitor: any) => {
|
||||
const monitorId = get<string>(monitor, 'key.monitor_id');
|
||||
monitorIds.push(monitorId);
|
||||
let state = get<any>(monitor, 'state.value');
|
||||
state = {
|
||||
...state,
|
||||
timestamp: state['@timestamp'],
|
||||
};
|
||||
const { checks } = state;
|
||||
if (checks) {
|
||||
state.checks = sortBy<SortChecks, Check>(checks, checksSortBy);
|
||||
state.checks = state.checks.map((check: any) => ({
|
||||
...check,
|
||||
timestamp: check['@timestamp'],
|
||||
}));
|
||||
} else {
|
||||
state.checks = [];
|
||||
}
|
||||
return {
|
||||
monitor_id: monitorId,
|
||||
state,
|
||||
};
|
||||
});
|
||||
const histogramMap = await this.getHistogramForMonitors(
|
||||
request,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
monitorIds
|
||||
);
|
||||
return summaries.map(summary => ({
|
||||
...summary,
|
||||
histogram: histogramMap[summary.monitor_id],
|
||||
}));
|
||||
}
|
||||
|
||||
// Gets a page of monitor states.
|
||||
public async getMonitorStates(
|
||||
request: any,
|
||||
pageIndex: number,
|
||||
pageSize: number,
|
||||
sortField?: string | null,
|
||||
sortDirection?: string | null
|
||||
): Promise<MonitorSummary[]> {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT_STATES,
|
||||
body: {
|
||||
from: pageIndex * pageSize,
|
||||
size: pageSize,
|
||||
},
|
||||
};
|
||||
|
||||
if (sortField) {
|
||||
set(params, 'body.sort', [
|
||||
{
|
||||
[sortField]: {
|
||||
order: sortDirection || 'asc',
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const result = await this.database.search(request, params);
|
||||
const hits = get(result, 'hits.hits', []);
|
||||
const monitorIds: string[] = [];
|
||||
const monitorStates = hits.map(({ _source }: any) => {
|
||||
const { monitor_id } = _source;
|
||||
monitorIds.push(monitor_id);
|
||||
const sourceState = get<any>(_source, 'state');
|
||||
const state = {
|
||||
...sourceState,
|
||||
timestamp: sourceState['@timestamp'],
|
||||
};
|
||||
if (state.checks) {
|
||||
state.checks = sortBy<SortChecks, Check>(state.checks, checksSortBy).map(
|
||||
(check: any): Check => ({
|
||||
...check,
|
||||
timestamp: check['@timestamp'],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
state.checks = [];
|
||||
}
|
||||
return {
|
||||
monitor_id,
|
||||
state,
|
||||
};
|
||||
});
|
||||
|
||||
const histogramMap = await this.getHistogramForMonitors(request, 'now-15m', 'now', monitorIds);
|
||||
return monitorStates.map(monitorState => ({
|
||||
...monitorState,
|
||||
histogram: histogramMap[monitorState.monitor_id],
|
||||
}));
|
||||
}
|
||||
|
||||
private async getHistogramForMonitors(
|
||||
request: any,
|
||||
dateRangeStart: string,
|
||||
dateRangeEnd: string,
|
||||
monitorIds: string[]
|
||||
): Promise<{ [key: string]: SummaryHistogram }> {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': monitorIds,
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateRangeStart,
|
||||
lte: dateRangeEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
by_id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
size: STATES.LEGACY_STATES_QUERY_SIZE,
|
||||
},
|
||||
aggs: {
|
||||
histogram: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: getHistogramInterval(dateRangeStart, dateRangeEnd),
|
||||
missing: 0,
|
||||
},
|
||||
aggs: {
|
||||
status: {
|
||||
terms: {
|
||||
field: 'monitor.status',
|
||||
size: 2,
|
||||
shard_size: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: CursorPagination = CONTEXT_DEFAULTS.CURSOR_PAGINATION,
|
||||
filters?: string | null,
|
||||
statusFilter?: string
|
||||
): Promise<GetMonitorStatesResult> {
|
||||
const size = 10;
|
||||
|
||||
const queryContext: QueryContext = {
|
||||
database: this.database,
|
||||
request,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
pagination,
|
||||
filterClause: filters && filters !== '' ? JSON.parse(filters) : null,
|
||||
size,
|
||||
statusFilter,
|
||||
};
|
||||
const result = await this.database.search(request, params);
|
||||
|
||||
const buckets: any[] = get(result, 'aggregations.by_id.buckets', []);
|
||||
return buckets.reduce((map: { [key: string]: any }, item: any) => {
|
||||
const points = get(item, 'histogram.buckets', []).map((histogram: any) => {
|
||||
const status = get(histogram, 'status.buckets', []).reduce(
|
||||
(statuses: { up: number; down: number }, bucket: any) => {
|
||||
if (bucket.key === 'up') {
|
||||
statuses.up = bucket.doc_count;
|
||||
} else if (bucket.key === 'down') {
|
||||
statuses.down = bucket.doc_count;
|
||||
}
|
||||
return statuses;
|
||||
},
|
||||
{ up: 0, down: 0 }
|
||||
);
|
||||
return {
|
||||
timestamp: histogram.key,
|
||||
...status,
|
||||
};
|
||||
});
|
||||
const page = await fetchPage(queryContext);
|
||||
|
||||
map[item.key] = {
|
||||
count: item.doc_count,
|
||||
points,
|
||||
};
|
||||
return map;
|
||||
}, {});
|
||||
return {
|
||||
summaries: page.items,
|
||||
nextPagePagination: jsonifyPagination(page.nextPagePagination),
|
||||
prevPagePagination: jsonifyPagination(page.prevPagePagination),
|
||||
};
|
||||
}
|
||||
|
||||
public async statesIndexExists(request: any): Promise<StatesIndexStatus> {
|
||||
|
@ -621,3 +71,12 @@ export class ElasticsearchMonitorStatesAdapter implements UMMonitorStatesAdapter
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// To simplify the handling of the group of pagination vars they're passed back to the client as a string
|
||||
const jsonifyPagination = (p: any): string | null => {
|
||||
if (!p) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.stringify(p);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
fetchPage,
|
||||
MonitorEnricher,
|
||||
MonitorGroups,
|
||||
MonitorGroupsFetcher,
|
||||
MonitorGroupsPage,
|
||||
} from '../fetch_page';
|
||||
import { QueryContext } from '../../elasticsearch_monitor_states_adapter';
|
||||
import { MonitorSummary } from '../../../../../../common/graphql/types';
|
||||
import { nextPagination, prevPagination, simpleQueryContext } from './test_helpers';
|
||||
|
||||
const simpleFixture: MonitorGroups[] = [
|
||||
{
|
||||
id: 'foo',
|
||||
groups: [
|
||||
{
|
||||
monitorId: 'foo',
|
||||
location: 'foo-loc',
|
||||
checkGroup: 'foo-check',
|
||||
status: 'up',
|
||||
summaryTimestamp: new Date(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'bar',
|
||||
groups: [
|
||||
{
|
||||
monitorId: 'bar',
|
||||
location: 'bar-loc',
|
||||
checkGroup: 'bar-check',
|
||||
status: 'down',
|
||||
summaryTimestamp: new Date(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const simpleFetcher = (monitorGroups: MonitorGroups[]): MonitorGroupsFetcher => {
|
||||
return async (queryContext: QueryContext, size: number): Promise<MonitorGroupsPage> => {
|
||||
return {
|
||||
monitorGroups,
|
||||
prevPagePagination: prevPagination(monitorGroups[0].id),
|
||||
nextPagePagination: nextPagination(monitorGroups[monitorGroups.length - 1].id),
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const simpleEnricher = (monitorGroups: MonitorGroups[]): MonitorEnricher => {
|
||||
return async (queryContext: QueryContext, checkGroups: string[]): Promise<MonitorSummary[]> => {
|
||||
return checkGroups.map(cg => {
|
||||
const monitorGroup = monitorGroups.find(mg => mg.groups.some(g => g.checkGroup === cg))!;
|
||||
return {
|
||||
monitor_id: monitorGroup.id,
|
||||
state: { summary: {}, timestamp: new Date(Date.parse('1999-12-31')).toISOString() },
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
describe('fetching a page', () => {
|
||||
it('returns the enriched groups', async () => {
|
||||
const res = await fetchPage(
|
||||
simpleQueryContext(),
|
||||
simpleFetcher(simpleFixture),
|
||||
simpleEnricher(simpleFixture)
|
||||
);
|
||||
expect(res).toEqual({
|
||||
items: [
|
||||
{
|
||||
monitor_id: 'foo',
|
||||
state: { summary: {}, timestamp: '1999-12-31T00:00:00.000Z' },
|
||||
},
|
||||
{ monitor_id: 'bar', state: { summary: {}, timestamp: '1999-12-31T00:00:00.000Z' } },
|
||||
],
|
||||
nextPagePagination: { cursorDirection: 'AFTER', sortOrder: 'ASC', cursorKey: 'bar' },
|
||||
prevPagePagination: { cursorDirection: 'BEFORE', sortOrder: 'ASC', cursorKey: 'foo' },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
CHUNK_SIZE,
|
||||
ChunkFetcher,
|
||||
ChunkResult,
|
||||
MonitorGroupIterator,
|
||||
} from '../monitor_group_iterator';
|
||||
import { simpleQueryContext } from './test_helpers';
|
||||
import { QueryContext } from '../../elasticsearch_monitor_states_adapter';
|
||||
import { MonitorGroups } from '../fetch_page';
|
||||
|
||||
describe('iteration', () => {
|
||||
let iterator: MonitorGroupIterator | null = null;
|
||||
let fetched: MonitorGroups[];
|
||||
|
||||
const setup = async (numGroups: number) => {
|
||||
fetched = [];
|
||||
const expectedMonitorGroups = makeMonitorGroups(numGroups);
|
||||
const chunkFetcher = mockChunkFetcher(expectedMonitorGroups);
|
||||
iterator = new MonitorGroupIterator(simpleQueryContext(), [], -1, chunkFetcher);
|
||||
|
||||
while (true) {
|
||||
const got = await iterator.next();
|
||||
if (got) {
|
||||
fetched.push(got);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('matching', () => {
|
||||
[
|
||||
{ name: 'zero results', numGroups: 0 },
|
||||
{ name: 'one result', numGroups: 1 },
|
||||
{ name: 'less than chunk', numGroups: CHUNK_SIZE - 1 },
|
||||
{ name: 'multiple full chunks', numGroups: CHUNK_SIZE * 3 },
|
||||
{ name: 'multiple full chunks + partial', numGroups: CHUNK_SIZE * 3 + 3 },
|
||||
].forEach(({ name, numGroups }) => {
|
||||
describe(`scenario given ${name}`, () => {
|
||||
beforeEach(async () => {
|
||||
await setup(numGroups);
|
||||
});
|
||||
|
||||
it('should receive the expected number of results', () => {
|
||||
expect(fetched.length).toEqual(numGroups);
|
||||
});
|
||||
|
||||
it('should have no remaining pages', async () => {
|
||||
expect(await iterator!.paginationAfterCurrent()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const makeMonitorGroups = (count: number): MonitorGroups[] => {
|
||||
const groups: MonitorGroups[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = `monitor-${i}`;
|
||||
|
||||
groups.push({
|
||||
id,
|
||||
groups: [
|
||||
{
|
||||
monitorId: id,
|
||||
location: 'a-location',
|
||||
status: 'up',
|
||||
checkGroup: `check-group-${i}`,
|
||||
summaryTimestamp: new Date(),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
};
|
||||
|
||||
const mockChunkFetcher = (groups: MonitorGroups[]): ChunkFetcher => {
|
||||
const buffer = groups.slice(0); // Clone it since we'll modify it
|
||||
return async (
|
||||
queryContext: QueryContext,
|
||||
searchAfter: any,
|
||||
size: number
|
||||
): Promise<ChunkResult> => {
|
||||
const resultMonitorGroups = buffer.splice(0, size);
|
||||
const resultSearchAfter =
|
||||
buffer.length === 0
|
||||
? null
|
||||
: { monitor_id: resultMonitorGroups[resultMonitorGroups.length - 1].id };
|
||||
return {
|
||||
monitorGroups: resultMonitorGroups,
|
||||
searchAfter: resultSearchAfter,
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CursorPagination } from '../../adapter_types';
|
||||
import { CursorDirection, SortOrder } from '../../../../../../common/graphql/types';
|
||||
import { QueryContext } from '../../elasticsearch_monitor_states_adapter';
|
||||
|
||||
export const prevPagination = (key: any): CursorPagination => {
|
||||
return {
|
||||
cursorDirection: CursorDirection.BEFORE,
|
||||
sortOrder: SortOrder.ASC,
|
||||
cursorKey: key,
|
||||
};
|
||||
};
|
||||
export const nextPagination = (key: any): CursorPagination => {
|
||||
return {
|
||||
cursorDirection: CursorDirection.AFTER,
|
||||
sortOrder: SortOrder.ASC,
|
||||
cursorKey: key,
|
||||
};
|
||||
};
|
||||
export const simpleQueryContext = (): QueryContext => {
|
||||
return {
|
||||
database: undefined,
|
||||
dateRangeEnd: '',
|
||||
dateRangeStart: '',
|
||||
filterClause: undefined,
|
||||
pagination: nextPagination('something'),
|
||||
request: undefined,
|
||||
size: 0,
|
||||
statusFilter: '',
|
||||
};
|
||||
};
|
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, sortBy } from 'lodash';
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { getHistogramInterval } from '../../../helper';
|
||||
import { INDEX_NAMES, STATES } from '../../../../../common/constants';
|
||||
import {
|
||||
MonitorSummary,
|
||||
SummaryHistogram,
|
||||
Check,
|
||||
CursorDirection,
|
||||
SortOrder,
|
||||
} from '../../../../../common/graphql/types';
|
||||
import { MonitorEnricher } from './fetch_page';
|
||||
|
||||
export const enrichMonitorGroups: MonitorEnricher = async (
|
||||
queryContext: QueryContext,
|
||||
checkGroups: string[]
|
||||
): Promise<MonitorSummary[]> => {
|
||||
// TODO the scripted metric query here is totally unnecessary and largely
|
||||
// redundant with the way the code works now. This could be simplified
|
||||
// to a much simpler query + some JS processing.
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{ terms: { 'monitor.check_group': checkGroups } }],
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
aggs: {
|
||||
monitors: {
|
||||
composite: {
|
||||
/**
|
||||
* TODO: extract this to a constant; we can't be looking for _only_
|
||||
* ten monitors, because it's possible our check groups selection will represent more than ten.
|
||||
*
|
||||
* We were previously passing the after key from the check groups query regardless of the number of monitors we had,
|
||||
* it's important that the latest check group from the final monitor we use is what we return, or we will be way ahead in terms
|
||||
* of check groups and end up skipping monitors on subsequent calls.
|
||||
*/
|
||||
size: 500,
|
||||
sources: [
|
||||
{
|
||||
monitor_id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
order: cursorDirectionToOrder(queryContext.pagination.cursorDirection),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
aggregations: {
|
||||
state: {
|
||||
scripted_metric: {
|
||||
init_script: `
|
||||
// Globals are values that should be identical across all docs
|
||||
// We can cheat a bit by always overwriting these and make the
|
||||
// assumption that there is no variation in these across checks
|
||||
state.globals = new HashMap();
|
||||
// Here we store stuff broken out by agent.id and monitor.id
|
||||
// This should correspond to a unique check.
|
||||
state.checksByAgentIdIP = new HashMap();
|
||||
`,
|
||||
map_script: `
|
||||
Map curCheck = new HashMap();
|
||||
String agentId = doc["agent.id"][0];
|
||||
String ip = null;
|
||||
if (doc["monitor.ip"].length > 0) {
|
||||
ip = doc["monitor.ip"][0];
|
||||
}
|
||||
String agentIdIP = agentId + "-" + (ip == null ? "" : ip.toString());
|
||||
def ts = doc["@timestamp"][0].toInstant().toEpochMilli();
|
||||
|
||||
def lastCheck = state.checksByAgentIdIP[agentId];
|
||||
Instant lastTs = lastCheck != null ? lastCheck["@timestamp"] : null;
|
||||
if (lastTs != null && lastTs > ts) {
|
||||
return;
|
||||
}
|
||||
|
||||
curCheck.put("@timestamp", ts);
|
||||
|
||||
Map agent = new HashMap();
|
||||
agent.id = agentId;
|
||||
curCheck.put("agent", agent);
|
||||
|
||||
if (state.globals.url == null) {
|
||||
Map url = new HashMap();
|
||||
Collection fields = ["full", "original", "scheme", "username", "password", "domain", "port", "path", "query", "fragment"];
|
||||
for (field in fields) {
|
||||
String docPath = "url." + field;
|
||||
def val = doc[docPath];
|
||||
if (!val.isEmpty()) {
|
||||
url[field] = val[0];
|
||||
}
|
||||
}
|
||||
state.globals.url = url;
|
||||
}
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.status = doc["monitor.status"][0];
|
||||
monitor.ip = ip;
|
||||
if (!doc["monitor.name"].isEmpty()) {
|
||||
String monitorName = doc["monitor.name"][0];
|
||||
if (monitor.name != "") {
|
||||
monitor.name = monitorName;
|
||||
}
|
||||
}
|
||||
curCheck.monitor = monitor;
|
||||
|
||||
if (curCheck.observer == null) {
|
||||
curCheck.observer = new HashMap();
|
||||
}
|
||||
if (curCheck.observer.geo == null) {
|
||||
curCheck.observer.geo = new HashMap();
|
||||
}
|
||||
if (!doc["observer.geo.name"].isEmpty()) {
|
||||
curCheck.observer.geo.name = doc["observer.geo.name"][0];
|
||||
}
|
||||
if (!doc["observer.geo.location"].isEmpty()) {
|
||||
curCheck.observer.geo.location = doc["observer.geo.location"][0];
|
||||
}
|
||||
if (!doc["kubernetes.pod.uid"].isEmpty() && curCheck.kubernetes == null) {
|
||||
curCheck.kubernetes = new HashMap();
|
||||
curCheck.kubernetes.pod = new HashMap();
|
||||
curCheck.kubernetes.pod.uid = doc["kubernetes.pod.uid"][0];
|
||||
}
|
||||
if (!doc["container.id"].isEmpty() && curCheck.container == null) {
|
||||
curCheck.container = new HashMap();
|
||||
curCheck.container.id = doc["container.id"][0];
|
||||
}
|
||||
if (curCheck.tls == null) {
|
||||
curCheck.tls = new HashMap();
|
||||
}
|
||||
if (!doc["tls.certificate_not_valid_after"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_after = doc["tls.certificate_not_valid_after"][0];
|
||||
}
|
||||
if (!doc["tls.certificate_not_valid_before"].isEmpty()) {
|
||||
curCheck.tls.certificate_not_valid_before = doc["tls.certificate_not_valid_before"][0];
|
||||
}
|
||||
|
||||
state.checksByAgentIdIP[agentIdIP] = curCheck;
|
||||
`,
|
||||
combine_script: 'return state;',
|
||||
reduce_script: `
|
||||
// The final document
|
||||
Map result = new HashMap();
|
||||
|
||||
Map checks = new HashMap();
|
||||
Instant maxTs = Instant.ofEpochMilli(0);
|
||||
Collection ips = new HashSet();
|
||||
Collection geoNames = new HashSet();
|
||||
Collection podUids = new HashSet();
|
||||
Collection containerIds = new HashSet();
|
||||
Collection tls = new HashSet();
|
||||
String name = null;
|
||||
for (state in states) {
|
||||
result.putAll(state.globals);
|
||||
for (entry in state.checksByAgentIdIP.entrySet()) {
|
||||
def agentIdIP = entry.getKey();
|
||||
def check = entry.getValue();
|
||||
def lastBestCheck = checks.get(agentIdIP);
|
||||
def checkTs = Instant.ofEpochMilli(check.get("@timestamp"));
|
||||
|
||||
if (maxTs.isBefore(checkTs)) { maxTs = checkTs}
|
||||
|
||||
if (lastBestCheck == null || lastBestCheck.get("@timestamp") < checkTs) {
|
||||
check["@timestamp"] = check["@timestamp"];
|
||||
checks[agentIdIP] = check
|
||||
}
|
||||
|
||||
if (check.monitor.name != null && check.monitor.name != "") {
|
||||
name = check.monitor.name;
|
||||
}
|
||||
|
||||
ips.add(check.monitor.ip);
|
||||
if (check.observer != null && check.observer.geo != null && check.observer.geo.name != null) {
|
||||
geoNames.add(check.observer.geo.name);
|
||||
}
|
||||
if (check.kubernetes != null && check.kubernetes.pod != null) {
|
||||
podUids.add(check.kubernetes.pod.uid);
|
||||
}
|
||||
if (check.container != null) {
|
||||
containerIds.add(check.container.id);
|
||||
}
|
||||
if (check.tls != null) {
|
||||
tls.add(check.tls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We just use the values so we can store these as nested docs
|
||||
result.checks = checks.values();
|
||||
result.put("@timestamp", maxTs);
|
||||
|
||||
|
||||
Map summary = new HashMap();
|
||||
summary.up = checks.entrySet().stream().filter(c -> c.getValue().monitor.status == "up").count();
|
||||
summary.down = checks.size() - summary.up;
|
||||
result.summary = summary;
|
||||
|
||||
Map monitor = new HashMap();
|
||||
monitor.ip = ips;
|
||||
monitor.name = name;
|
||||
monitor.status = summary.down > 0 ? (summary.up > 0 ? "mixed": "down") : "up";
|
||||
result.monitor = monitor;
|
||||
|
||||
Map observer = new HashMap();
|
||||
Map geo = new HashMap();
|
||||
observer.geo = geo;
|
||||
geo.name = geoNames;
|
||||
result.observer = observer;
|
||||
|
||||
if (!podUids.isEmpty()) {
|
||||
result.kubernetes = new HashMap();
|
||||
result.kubernetes.pod = new HashMap();
|
||||
result.kubernetes.pod.uid = podUids;
|
||||
}
|
||||
|
||||
if (!containerIds.isEmpty()) {
|
||||
result.container = new HashMap();
|
||||
result.container.id = containerIds;
|
||||
}
|
||||
|
||||
if (!tls.isEmpty()) {
|
||||
result.tls = new HashMap();
|
||||
result.tls = tls;
|
||||
}
|
||||
|
||||
return result;
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const items = await queryContext.database.search(queryContext.request, params);
|
||||
|
||||
const monitorBuckets = get(items, 'aggregations.monitors.buckets', []);
|
||||
|
||||
const monitorIds: string[] = [];
|
||||
const summaries: MonitorSummary[] = monitorBuckets.map((monitor: any) => {
|
||||
const monitorId = get<string>(monitor, 'key.monitor_id');
|
||||
monitorIds.push(monitorId);
|
||||
let state = get<any>(monitor, 'state.value');
|
||||
state = {
|
||||
...state,
|
||||
timestamp: state['@timestamp'],
|
||||
};
|
||||
const { checks } = state;
|
||||
if (checks) {
|
||||
state.checks = sortBy<SortChecks, Check>(checks, checksSortBy);
|
||||
state.checks = state.checks.map((check: any) => ({
|
||||
...check,
|
||||
timestamp: check['@timestamp'],
|
||||
}));
|
||||
} else {
|
||||
state.checks = [];
|
||||
}
|
||||
return {
|
||||
monitor_id: monitorId,
|
||||
state,
|
||||
};
|
||||
});
|
||||
|
||||
const histogramMap = await getHistogramForMonitors(queryContext, monitorIds);
|
||||
|
||||
const resItems = summaries.map(summary => ({
|
||||
...summary,
|
||||
histogram: histogramMap[summary.monitor_id],
|
||||
}));
|
||||
|
||||
const sortedResItems: any = sortBy(resItems, 'monitor_id');
|
||||
if (queryContext.pagination.sortOrder === SortOrder.DESC) {
|
||||
sortedResItems.reverse();
|
||||
}
|
||||
|
||||
return sortedResItems;
|
||||
};
|
||||
|
||||
const getHistogramForMonitors = async (
|
||||
queryContext: QueryContext,
|
||||
monitorIds: string[]
|
||||
): Promise<{ [key: string]: SummaryHistogram }> => {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': monitorIds,
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: queryContext.dateRangeStart,
|
||||
lte: queryContext.dateRangeEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
by_id: {
|
||||
terms: {
|
||||
field: 'monitor.id',
|
||||
size: STATES.LEGACY_STATES_QUERY_SIZE,
|
||||
},
|
||||
aggs: {
|
||||
histogram: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: getHistogramInterval(
|
||||
queryContext.dateRangeStart,
|
||||
queryContext.dateRangeEnd
|
||||
),
|
||||
missing: 0,
|
||||
},
|
||||
aggs: {
|
||||
status: {
|
||||
terms: {
|
||||
field: 'monitor.status',
|
||||
size: 2,
|
||||
shard_size: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await queryContext.database.search(queryContext.request, params);
|
||||
|
||||
const buckets: any[] = get(result, 'aggregations.by_id.buckets', []);
|
||||
return buckets.reduce((map: { [key: string]: any }, item: any) => {
|
||||
const points = get(item, 'histogram.buckets', []).map((histogram: any) => {
|
||||
const status = get(histogram, 'status.buckets', []).reduce(
|
||||
(statuses: { up: number; down: number }, bucket: any) => {
|
||||
if (bucket.key === 'up') {
|
||||
statuses.up = bucket.doc_count;
|
||||
} else if (bucket.key === 'down') {
|
||||
statuses.down = bucket.doc_count;
|
||||
}
|
||||
return statuses;
|
||||
},
|
||||
{ up: 0, down: 0 }
|
||||
);
|
||||
return {
|
||||
timestamp: histogram.key,
|
||||
...status,
|
||||
};
|
||||
});
|
||||
|
||||
map[item.key] = {
|
||||
count: item.doc_count,
|
||||
points,
|
||||
};
|
||||
return map;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const cursorDirectionToOrder = (cd: CursorDirection): 'asc' | 'desc' => {
|
||||
return CursorDirection[cd] === CursorDirection.AFTER ? 'asc' : 'desc';
|
||||
};
|
||||
|
||||
type SortChecks = (check: Check) => string[];
|
||||
const checksSortBy = (check: Check) => [
|
||||
get<string>(check, 'observer.geo.name'),
|
||||
get<string>(check, 'monitor.ip'),
|
||||
];
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { refinePotentialMatches } from './refine_potential_matches';
|
||||
import { findPotentialMatches } from './find_potential_matches';
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { ChunkFetcher, ChunkResult } from './monitor_group_iterator';
|
||||
|
||||
/**
|
||||
* Fetches a single 'chunk' of data with a single query, then uses a secondary query to filter out erroneous matches.
|
||||
* Note that all returned data may be erroneous. If `searchAfter` is returned the caller should invoke this function
|
||||
* repeatedly with the new searchAfter value as there may be more matching data in a future chunk. If `searchAfter`
|
||||
* is falsey there is no more data to fetch.
|
||||
* @param queryContext the data and resources needed to perform the query
|
||||
* @param searchAfter indicates where Elasticsearch should continue querying on subsequent requests, if at all
|
||||
* @param size the minimum size of the matches to chunk
|
||||
*/
|
||||
// Note that all returned data may be erroneous. If `searchAfter` is returned the caller should invoke this function
|
||||
// repeatedly with the new searchAfter value as there may be more matching data in a future chunk. If `searchAfter`
|
||||
// is falsey there is no more data to fetch.
|
||||
export const fetchChunk: ChunkFetcher = async (
|
||||
queryContext: QueryContext,
|
||||
searchAfter: any,
|
||||
size: number
|
||||
): Promise<ChunkResult> => {
|
||||
const { monitorIds, checkGroups, searchAfter: foundSearchAfter } = await findPotentialMatches(
|
||||
queryContext,
|
||||
searchAfter,
|
||||
size
|
||||
);
|
||||
const matching = await refinePotentialMatches(queryContext, monitorIds, checkGroups);
|
||||
|
||||
return {
|
||||
monitorGroups: matching,
|
||||
searchAfter: foundSearchAfter,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { flatten } from 'lodash';
|
||||
import { CursorPagination } from '../adapter_types';
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { QUERY } from '../../../../../common/constants';
|
||||
import { CursorDirection, MonitorSummary, SortOrder } from '../../../../../common/graphql/types';
|
||||
import { enrichMonitorGroups } from './enrich_monitor_groups';
|
||||
import { MonitorGroupIterator } from './monitor_group_iterator';
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a single page of results per the settings in the provided queryContext. These results are very minimal,
|
||||
* just monitor IDs and check groups. This takes an optional `MonitorGroupEnricher` that post-processes the minimal
|
||||
* data, decorating it appropriately. The function also takes a fetcher, which does all the actual fetching.
|
||||
* @param queryContext defines the criteria for the data on the current page
|
||||
* @param monitorGroupFetcher performs paginated monitor fetching
|
||||
* @param monitorEnricher decorates check group results with additional data
|
||||
*/
|
||||
// just monitor IDs and check groups. This takes an optional `MonitorGroupEnricher` that post-processes the minimal
|
||||
// data, decorating it appropriately. The function also takes a fetcher, which does all the actual fetching.
|
||||
export const fetchPage = async (
|
||||
queryContext: QueryContext,
|
||||
monitorGroupFetcher: MonitorGroupsFetcher = fetchPageMonitorGroups,
|
||||
monitorEnricher: MonitorEnricher = enrichMonitorGroups
|
||||
): Promise<EnrichedPage> => {
|
||||
const size = Math.min(queryContext.size, QUERY.DEFAULT_AGGS_CAP);
|
||||
const monitorPage = await monitorGroupFetcher(queryContext, size);
|
||||
|
||||
const checkGroups: string[] = flatten(
|
||||
monitorPage.monitorGroups.map(monitorGroups => monitorGroups.groups.map(g => g.checkGroup))
|
||||
);
|
||||
|
||||
const enrichedMonitors = await monitorEnricher(queryContext, checkGroups);
|
||||
|
||||
return {
|
||||
items: enrichedMonitors,
|
||||
nextPagePagination: monitorPage.nextPagePagination,
|
||||
prevPagePagination: monitorPage.prevPagePagination,
|
||||
};
|
||||
};
|
||||
|
||||
// Fetches the most recent monitor groups for the given page,
|
||||
// in the manner demanded by the `queryContext` and return at most `size` results.
|
||||
const fetchPageMonitorGroups: MonitorGroupsFetcher = async (
|
||||
queryContext: QueryContext,
|
||||
size: number
|
||||
): Promise<MonitorGroupsPage> => {
|
||||
const monitorGroups: MonitorGroups[] = [];
|
||||
const iterator = new MonitorGroupIterator(queryContext);
|
||||
|
||||
let paginationBefore: CursorPagination | null = null;
|
||||
while (monitorGroups.length < size) {
|
||||
const monitor = await iterator.next();
|
||||
if (!monitor) {
|
||||
break; // No more items to fetch
|
||||
}
|
||||
monitorGroups.push(monitor);
|
||||
|
||||
// We want the before pagination to be before the first item we encounter
|
||||
if (monitorGroups.length === 1) {
|
||||
paginationBefore = await iterator.paginationBeforeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
// We have to create these objects before checking if we can navigate backward
|
||||
const paginationAfter = await iterator.paginationAfterCurrent();
|
||||
|
||||
const ssAligned = searchSortAligned(queryContext.pagination);
|
||||
|
||||
if (!ssAligned) {
|
||||
monitorGroups.reverse();
|
||||
}
|
||||
|
||||
return {
|
||||
monitorGroups,
|
||||
nextPagePagination: ssAligned ? paginationAfter : paginationBefore,
|
||||
prevPagePagination: ssAligned ? paginationBefore : paginationAfter,
|
||||
};
|
||||
};
|
||||
|
||||
// Returns true if the order returned by the ES query matches the requested sort order.
|
||||
// This useful to determine if the results need to be reversed from their ES results order.
|
||||
// I.E. when navigating backwards using prevPagePagination (CursorDirection.Before) yet using a SortOrder.ASC.
|
||||
const searchSortAligned = (pagination: CursorPagination): boolean => {
|
||||
if (pagination.cursorDirection === CursorDirection.AFTER) {
|
||||
return pagination.sortOrder === SortOrder.ASC;
|
||||
} else {
|
||||
return pagination.sortOrder === SortOrder.DESC;
|
||||
}
|
||||
};
|
||||
|
||||
// Minimal interface representing the most recent set of groups accompanying a MonitorId in a given context.
|
||||
export interface MonitorGroups {
|
||||
id: string;
|
||||
groups: MonitorLocCheckGroup[];
|
||||
}
|
||||
|
||||
// Representation of the data returned when aggregating summary check groups.
|
||||
export interface MonitorLocCheckGroup {
|
||||
monitorId: string;
|
||||
location: string | null;
|
||||
checkGroup: string;
|
||||
status: 'up' | 'down';
|
||||
summaryTimestamp: Date;
|
||||
}
|
||||
|
||||
// Represents a page that has not yet been enriched.
|
||||
export interface MonitorGroupsPage {
|
||||
monitorGroups: MonitorGroups[];
|
||||
nextPagePagination: CursorPagination | null;
|
||||
prevPagePagination: CursorPagination | null;
|
||||
}
|
||||
|
||||
// Representation of a full page of results with pagination data for constructing next/prev links.
|
||||
export interface EnrichedPage {
|
||||
items: MonitorSummary[];
|
||||
nextPagePagination: CursorPagination | null;
|
||||
prevPagePagination: CursorPagination | null;
|
||||
}
|
||||
|
||||
// A function that does the work of matching the minimal set of data for this query, returning just matching fields
|
||||
// that are efficient to access while performing the query.
|
||||
export type MonitorGroupsFetcher = (
|
||||
queryContext: QueryContext,
|
||||
size: number
|
||||
) => Promise<MonitorGroupsPage>;
|
||||
|
||||
// A function that takes a set of check groups and returns richer MonitorSummary objects.
|
||||
export type MonitorEnricher = (
|
||||
queryContext: QueryContext,
|
||||
checkGroups: string[]
|
||||
) => Promise<MonitorSummary[]>;
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, set } from 'lodash';
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { CursorDirection } from '../../../../../common/graphql/types';
|
||||
import { INDEX_NAMES } from '../../../../../common/constants';
|
||||
import { makeDateRangeFilter } from '../../../helper/make_date_rate_filter';
|
||||
|
||||
// This is the first phase of the query. In it, we find the most recent check groups that matched the given query.
|
||||
// Note that these check groups may not be the most recent groups for the matching monitor ID! We'll filter those
|
||||
/**
|
||||
* This is the first phase of the query. In it, we find the most recent check groups that matched the given query.
|
||||
* Note that these check groups may not be the most recent groups for the matching monitor ID. They'll be filtered
|
||||
* out in the next phase.
|
||||
* @param queryContext the data and resources needed to perform the query
|
||||
* @param searchAfter indicates where Elasticsearch should continue querying on subsequent requests, if at all
|
||||
* @param size the minimum size of the matches to chunk
|
||||
*/
|
||||
export const findPotentialMatches = async (
|
||||
queryContext: QueryContext,
|
||||
searchAfter: any,
|
||||
size: number
|
||||
) => {
|
||||
const queryResult = await query(queryContext, searchAfter, size);
|
||||
|
||||
const checkGroups = new Set<string>();
|
||||
const monitorIds: string[] = [];
|
||||
get<any>(queryResult, 'aggregations.monitors.buckets', []).forEach((b: any) => {
|
||||
const monitorId = b.key.monitor_id;
|
||||
monitorIds.push(monitorId);
|
||||
|
||||
// Doc count can be zero if status filter optimization does not match
|
||||
if (b.doc_count > 0) {
|
||||
// Here we grab the most recent 2 check groups per location and add them to the list.
|
||||
// Why 2? Because the most recent one may be a partial result from mode: all, and hence not match a summary doc.
|
||||
b.locations.buckets.forEach((lb: any) => {
|
||||
lb.top.hits.hits.forEach((h: any) => {
|
||||
checkGroups.add(h._source.monitor.check_group);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
monitorIds,
|
||||
checkGroups,
|
||||
searchAfter: queryResult.aggregations.monitors.after_key,
|
||||
};
|
||||
};
|
||||
|
||||
const query = async (queryContext: QueryContext, searchAfter: any, size: number) => {
|
||||
const body = queryBody(queryContext, searchAfter, size);
|
||||
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body,
|
||||
};
|
||||
|
||||
return await queryContext.database.search(queryContext.request, params);
|
||||
};
|
||||
|
||||
const queryBody = (queryContext: QueryContext, searchAfter: any, size: number) => {
|
||||
const compositeOrder = cursorDirectionToOrder(queryContext.pagination.cursorDirection);
|
||||
|
||||
const filters: any[] = [
|
||||
makeDateRangeFilter(queryContext.dateRangeStart, queryContext.dateRangeEnd),
|
||||
];
|
||||
if (queryContext.filterClause) {
|
||||
filters.push(queryContext.filterClause);
|
||||
}
|
||||
if (queryContext.statusFilter) {
|
||||
filters.push({ match: { 'monitor.status': queryContext.statusFilter } });
|
||||
}
|
||||
|
||||
const body = {
|
||||
size: 0,
|
||||
query: { bool: { filter: filters } },
|
||||
aggs: {
|
||||
monitors: {
|
||||
composite: {
|
||||
size,
|
||||
sources: [
|
||||
{
|
||||
monitor_id: { terms: { field: 'monitor.id', order: compositeOrder } },
|
||||
},
|
||||
],
|
||||
},
|
||||
aggs: {
|
||||
// Here we grab the most recent 2 check groups per location.
|
||||
// Why 2? Because the most recent one may not be for a summary, it may be incomplete.
|
||||
locations: {
|
||||
terms: { field: 'observer.geo.name', missing: '__missing__' },
|
||||
aggs: {
|
||||
top: {
|
||||
top_hits: {
|
||||
sort: [{ '@timestamp': 'desc' }],
|
||||
_source: {
|
||||
includes: ['monitor.check_group', '@timestamp'],
|
||||
},
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (searchAfter) {
|
||||
set(body, 'aggs.monitors.composite.after', searchAfter);
|
||||
}
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
const cursorDirectionToOrder = (cd: CursorDirection): 'asc' | 'desc' => {
|
||||
return CursorDirection[cd] === CursorDirection.AFTER ? 'asc' : 'desc';
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { fetchPage, MonitorGroups, MonitorLocCheckGroup, MonitorGroupsPage } from './fetch_page';
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { CursorPagination } from '../adapter_types';
|
||||
import { fetchChunk } from './fetch_chunk';
|
||||
import { CursorDirection } from '../../../../../common/graphql/types';
|
||||
import { MonitorGroups } from './fetch_page';
|
||||
|
||||
// Hardcoded chunk size for how many monitors to fetch at a time when querying
|
||||
export const CHUNK_SIZE = 1000;
|
||||
|
||||
// Function that fetches a chunk of data used in iteration
|
||||
export type ChunkFetcher = (
|
||||
queryContext: QueryContext,
|
||||
searchAfter: any,
|
||||
size: number
|
||||
) => Promise<ChunkResult>;
|
||||
|
||||
// Result of fetching more results from the search.
|
||||
export interface ChunkResult {
|
||||
monitorGroups: MonitorGroups[];
|
||||
searchAfter: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to simplify the process of querying for page data. Because the raw queries responsible for fetching pages
|
||||
* pull data in `chunks`, and those chunks can be full of matches or void of results that would require additional
|
||||
* querying, this class provides a `next` function that is cleaner to call. `next` provides the next matching result,
|
||||
* which may require many subsequent fetches, while keeping the external API clean.
|
||||
*/
|
||||
// matches, or may simple be empty results that tell us a to keep looking for more, this class exists to simplify things.
|
||||
// The idea is that you can call next() on it and receive the next matching result, even if internally we need to fetch
|
||||
// multiple chunks to find that result.
|
||||
export class MonitorGroupIterator {
|
||||
queryContext: QueryContext;
|
||||
// Cache representing pre-fetched query results.
|
||||
// The first item is the CheckGroup this represents.
|
||||
buffer: MonitorGroups[];
|
||||
bufferPos: number;
|
||||
searchAfter: any;
|
||||
chunkFetcher: ChunkFetcher;
|
||||
|
||||
constructor(
|
||||
queryContext: QueryContext,
|
||||
initialBuffer: MonitorGroups[] = [],
|
||||
initialBufferPos: number = -1,
|
||||
chunkFetcher: ChunkFetcher = fetchChunk
|
||||
) {
|
||||
this.queryContext = queryContext;
|
||||
this.buffer = initialBuffer;
|
||||
this.bufferPos = initialBufferPos;
|
||||
this.searchAfter = queryContext.pagination.cursorKey;
|
||||
this.chunkFetcher = chunkFetcher;
|
||||
}
|
||||
|
||||
// Fetch the next matching result.
|
||||
async next(): Promise<MonitorGroups | null> {
|
||||
await this.bufferNext(CHUNK_SIZE);
|
||||
|
||||
const found = this.buffer[this.bufferPos + 1];
|
||||
if (found) {
|
||||
this.bufferPos++;
|
||||
return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look ahead to see if there are additional results.
|
||||
async peek(): Promise<MonitorGroups | null> {
|
||||
await this.bufferNext(CHUNK_SIZE);
|
||||
return this.buffer[this.bufferPos + 1] || null;
|
||||
}
|
||||
|
||||
// Returns the last item fetched with next(). null if no items fetched with
|
||||
// next or if next has not yet been invoked.
|
||||
getCurrent(): MonitorGroups | null {
|
||||
return this.buffer[this.bufferPos] || null;
|
||||
}
|
||||
|
||||
// Attempts to buffer at most `size` number of additional results, stopping when at least one additional
|
||||
// result is buffered or there are no more matching items to be found.
|
||||
async bufferNext(size: number = CHUNK_SIZE): Promise<void> {
|
||||
// The next element is already buffered.
|
||||
if (this.buffer[this.bufferPos + 1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const result = await this.attemptBufferMore(CHUNK_SIZE);
|
||||
if (result.gotHit || !result.hasMore) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to buffer more results fetching a single chunk.
|
||||
// If trim is set to true, which is the default, it will delete all items in the buffer prior to the current item.
|
||||
// to free up space.
|
||||
async attemptBufferMore(
|
||||
size: number = CHUNK_SIZE
|
||||
): Promise<{ hasMore: boolean; gotHit: boolean }> {
|
||||
// Trim the buffer to just the current element since we'll be fetching more
|
||||
const current = this.getCurrent();
|
||||
|
||||
// Trim the buffer to free space before fetching more
|
||||
// We only need to do this if there is actually something in the buffer.
|
||||
// This also behaves correctly in the -1 case for bufferPos, where we don't want to make it 0.
|
||||
if (current) {
|
||||
this.buffer = [current];
|
||||
this.bufferPos = 0;
|
||||
}
|
||||
|
||||
const results = await this.chunkFetcher(this.queryContext, this.searchAfter, size);
|
||||
// If we've hit the end of the stream searchAfter will be empty
|
||||
|
||||
results.monitorGroups.forEach((mig: MonitorGroups) => this.buffer.push(mig));
|
||||
if (results.searchAfter) {
|
||||
this.searchAfter = results.searchAfter;
|
||||
}
|
||||
|
||||
return {
|
||||
gotHit: results.monitorGroups.length > 0,
|
||||
hasMore: !!results.searchAfter,
|
||||
};
|
||||
}
|
||||
|
||||
// Get a CursorPaginator object that will resume after the current() value.
|
||||
async paginationAfterCurrent(): Promise<CursorPagination | null> {
|
||||
const peek = await this.peek();
|
||||
if (!peek) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const current = this.getCurrent();
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
const cursorKey = { monitor_id: current.id };
|
||||
|
||||
return Object.assign({}, this.queryContext.pagination, { cursorKey });
|
||||
}
|
||||
|
||||
// Get a CursorPaginator object that will resume before the current() value.
|
||||
async paginationBeforeCurrent(): Promise<CursorPagination | null> {
|
||||
const reverseFetcher = await this.reverse();
|
||||
return reverseFetcher && (await reverseFetcher.paginationAfterCurrent());
|
||||
}
|
||||
|
||||
// Returns a copy of this fetcher that goes backwards from the current position
|
||||
reverse(): MonitorGroupIterator | null {
|
||||
const reverseContext = Object.assign({}, this.queryContext);
|
||||
const current = this.getCurrent();
|
||||
|
||||
reverseContext.pagination = {
|
||||
cursorKey: current ? { monitor_id: current.id } : null,
|
||||
sortOrder: this.queryContext.pagination.sortOrder,
|
||||
cursorDirection:
|
||||
this.queryContext.pagination.cursorDirection === CursorDirection.AFTER
|
||||
? CursorDirection.BEFORE
|
||||
: CursorDirection.AFTER,
|
||||
};
|
||||
|
||||
return current
|
||||
? new MonitorGroupIterator(reverseContext, [current], 0, this.chunkFetcher)
|
||||
: null;
|
||||
}
|
||||
|
||||
// Returns a copy of this with a shallow copied buffer. Note that the queryContext is still shared!
|
||||
clone() {
|
||||
return new MonitorGroupIterator(this.queryContext, this.buffer.slice(0), this.bufferPos);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { INDEX_NAMES } from '../../../../../common/constants';
|
||||
import { QueryContext } from '../elasticsearch_monitor_states_adapter';
|
||||
import { CursorDirection } from '../../../../../common/graphql/types';
|
||||
import { MonitorGroups, MonitorLocCheckGroup } from './fetch_page';
|
||||
import { makeDateRangeFilter } from '../../../helper/make_date_rate_filter';
|
||||
|
||||
/**
|
||||
* Determines whether the provided check groups are the latest complete check groups for their associated monitor ID's.
|
||||
* If provided check groups are not the latest complete group, they are discarded.
|
||||
* @param queryContext the data and resources needed to perform the query
|
||||
* @param potentialMatchMonitorIDs the monitor ID's of interest
|
||||
* @param potentialMatchCheckGroups the check groups to filter for the latest match per ID
|
||||
*/
|
||||
// check groups for their associated monitor IDs. If not, it discards the result.
|
||||
export const refinePotentialMatches = async (
|
||||
queryContext: QueryContext,
|
||||
potentialMatchMonitorIDs: string[],
|
||||
potentialMatchCheckGroups: Set<string>
|
||||
): Promise<MonitorGroups[]> => {
|
||||
if (potentialMatchMonitorIDs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const recentGroupsMatchingStatus = await fullyMatchingIds(
|
||||
queryContext,
|
||||
potentialMatchMonitorIDs,
|
||||
potentialMatchCheckGroups
|
||||
);
|
||||
|
||||
// Return the monitor groups filtering out potential matches that weren't current
|
||||
const matches: MonitorGroups[] = potentialMatchMonitorIDs
|
||||
.map((id: string) => {
|
||||
return { id, groups: recentGroupsMatchingStatus.get(id) || [] };
|
||||
})
|
||||
.filter(mrg => mrg.groups.length > 0);
|
||||
|
||||
// Sort matches by ID
|
||||
matches.sort((a: MonitorGroups, b: MonitorGroups) => {
|
||||
return a.id === b.id ? 0 : a.id > b.id ? 1 : -1;
|
||||
});
|
||||
|
||||
if (queryContext.pagination.cursorDirection === CursorDirection.BEFORE) {
|
||||
matches.reverse();
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
const fullyMatchingIds = async (
|
||||
queryContext: QueryContext,
|
||||
potentialMatchMonitorIDs: string[],
|
||||
potentialMatchCheckGroups: Set<string>
|
||||
) => {
|
||||
const mostRecentQueryResult = await mostRecentCheckGroups(queryContext, potentialMatchMonitorIDs);
|
||||
|
||||
const matching = new Map<string, MonitorLocCheckGroup[]>();
|
||||
MonitorLoop: for (const monBucket of mostRecentQueryResult.aggregations.monitor.buckets) {
|
||||
const monitorId: string = monBucket.key;
|
||||
const groups: MonitorLocCheckGroup[] = [];
|
||||
|
||||
for (const locBucket of monBucket.location.buckets) {
|
||||
const location = locBucket.key;
|
||||
const topSource = locBucket.top.hits.hits[0]._source;
|
||||
const checkGroup = topSource.monitor.check_group;
|
||||
const status = topSource.summary.down > 0 ? 'down' : 'up';
|
||||
|
||||
// This monitor doesn't match, so just skip ahead and don't add it to the output
|
||||
if (queryContext.statusFilter && queryContext.statusFilter !== status) {
|
||||
continue MonitorLoop;
|
||||
}
|
||||
|
||||
groups.push({
|
||||
monitorId,
|
||||
location,
|
||||
checkGroup,
|
||||
status,
|
||||
summaryTimestamp: topSource['@timestamp'],
|
||||
});
|
||||
}
|
||||
|
||||
// We only truly match the monitor if one of the most recent check groups was found in the potential matches phase
|
||||
if (groups.some(g => potentialMatchCheckGroups.has(g.checkGroup))) {
|
||||
matching.set(monitorId, groups);
|
||||
}
|
||||
}
|
||||
|
||||
return matching;
|
||||
};
|
||||
|
||||
export const mostRecentCheckGroups = async (
|
||||
queryContext: QueryContext,
|
||||
potentialMatchMonitorIDs: string[]
|
||||
) => {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
makeDateRangeFilter(queryContext.dateRangeStart, queryContext.dateRangeEnd),
|
||||
{ terms: { 'monitor.id': potentialMatchMonitorIDs } },
|
||||
// only match summary docs because we only want the latest *complete* check group.
|
||||
{ exists: { field: 'summary' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
monitor: {
|
||||
terms: { field: 'monitor.id', size: potentialMatchMonitorIDs.length },
|
||||
aggs: {
|
||||
location: {
|
||||
terms: { field: 'observer.geo.name', missing: 'N/A', size: 100 },
|
||||
aggs: {
|
||||
top: {
|
||||
top_hits: {
|
||||
sort: [{ '@timestamp': 'desc' }],
|
||||
_source: {
|
||||
includes: ['monitor.check_group', '@timestamp', 'summary.up', 'summary.down'],
|
||||
},
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return await queryContext.database.search(queryContext.request, params);
|
||||
};
|
|
@ -101,6 +101,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
|
|||
};
|
||||
|
||||
const result = await this.database.search(request, params);
|
||||
|
||||
const dateHistogramBuckets = dropLatestBucket(
|
||||
get(result, 'aggregations.timeseries.buckets', [])
|
||||
);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const makeDateRangeFilter = (
|
||||
dateRangeStart: string | number,
|
||||
dateRangeEnd: string | number
|
||||
) => {
|
||||
return {
|
||||
range: { '@timestamp': { gte: dateRangeStart, lte: dateRangeEnd } },
|
||||
};
|
||||
};
|
|
@ -4,12 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { docCountQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import docCount from './fixtures/doc_count';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function ({ getService }) {
|
||||
describe('docCount query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it(`will fetch the index's count`, async () => {
|
||||
|
@ -18,7 +20,7 @@ export default function ({ getService }) {
|
|||
query: docCountQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -28,7 +30,7 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getDocCountQuery });
|
||||
|
||||
expect(data).to.eql(docCount);
|
||||
expectFixtureEql(data, 'doc_count');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
import { filterBarQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
|
||||
export default function ({ getService }) {
|
||||
describe('filterBar query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns the expected filters', async () => {
|
||||
|
@ -17,7 +20,7 @@ export default function ({ getService }) {
|
|||
query: filterBarQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
{ "statesIndexStatus": { "docCount": { "count": 9234 }, "indexExists": true } }
|
||||
{
|
||||
"statesIndexStatus": {
|
||||
"docCount": {
|
||||
"count": 2000
|
||||
},
|
||||
"indexExists": true
|
||||
}
|
||||
}
|
|
@ -1,37 +1,40 @@
|
|||
{
|
||||
"filterBar": {
|
||||
"ids": [
|
||||
"auto-tcp-0X81440A68E839814C",
|
||||
"auto-http-0XD9AE729FC1C1E04A",
|
||||
"auto-http-0XDD2D4E60FD4A61C3",
|
||||
"auto-http-0X3675F89EF0612091",
|
||||
"auto-http-0X131221E73F825974",
|
||||
"auto-http-0X9CB71300ABD5A2A8",
|
||||
"auto-http-0X970CBD2F2102BFA8",
|
||||
"auto-http-0XA8096548ECEB85B7",
|
||||
"auto-http-0XC9CDA429418EDC2B",
|
||||
"auto-http-0XE3B163481423197D"
|
||||
"0000-intermittent",
|
||||
"0001-up",
|
||||
"0002-up",
|
||||
"0003-up",
|
||||
"0004-up",
|
||||
"0005-up",
|
||||
"0006-up",
|
||||
"0007-up",
|
||||
"0008-up",
|
||||
"0009-up",
|
||||
"0010-down",
|
||||
"0011-up",
|
||||
"0012-up",
|
||||
"0013-up",
|
||||
"0014-up",
|
||||
"0015-intermittent",
|
||||
"0016-up",
|
||||
"0017-up",
|
||||
"0018-up",
|
||||
"0019-up"
|
||||
],
|
||||
"locations": [
|
||||
"mpls"
|
||||
],
|
||||
"locations": [],
|
||||
"ports": [
|
||||
9200,
|
||||
12349
|
||||
5678
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"tcp"
|
||||
"http"
|
||||
],
|
||||
"urls": [
|
||||
"tcp://localhost:9200",
|
||||
"http://www.reddit.com/",
|
||||
"https://www.elastic.co",
|
||||
"http://localhost:12349/",
|
||||
"https://www.google.com/",
|
||||
"https://www.github.com/",
|
||||
"http://www.google.com/",
|
||||
"http://www.example.com/",
|
||||
"https://news.google.com/",
|
||||
"https://www.wikipedia.org/"
|
||||
"http://localhost:5678/pattern?r=200x1",
|
||||
"http://localhost:5678/pattern?r=200x5,500x1",
|
||||
"http://localhost:5678/pattern?r=400x1"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -2,48 +2,264 @@
|
|||
"monitorChartsData": {
|
||||
"locationDurationLines": [
|
||||
{
|
||||
"name": "N/A",
|
||||
"name": "mpls",
|
||||
"line": [
|
||||
{ "x": 1548697571840, "y": 790701.3428571429 },
|
||||
{ "x": 1548697764160, "y": 661065.4375 },
|
||||
{ "x": 1548697956480, "y": 806260.8541666666 },
|
||||
{ "x": 1548698148800, "y": 773767.7291666666 },
|
||||
{ "x": 1548698341120, "y": 767945.0208333334 },
|
||||
{ "x": 1548698533440, "y": 744078.7234042553 },
|
||||
{ "x": 1548698725760, "y": 837049.4893617021 },
|
||||
{ "x": 1548698918080, "y": 778723.1041666666 },
|
||||
{ "x": 1548699110400, "y": 723974.1875 },
|
||||
{ "x": 1548699302720, "y": 771744.3191489362 },
|
||||
{ "x": 1548699495040, "y": 718773.081632653 },
|
||||
{ "x": 1548699687360, "y": 724448.6458333334 },
|
||||
{ "x": 1548699879680, "y": 747848.0833333334 },
|
||||
{ "x": 1548700072000, "y": 748932.125 },
|
||||
{ "x": 1548700264320, "y": 753010.2291666666 },
|
||||
{ "x": 1548700456640, "y": 762055.6875 },
|
||||
{ "x": 1548700648960, "y": 745954.7708333334 }
|
||||
{
|
||||
"x": 1568172657286,
|
||||
"y": 16274
|
||||
},
|
||||
{
|
||||
"x": 1568172680087,
|
||||
"y": 16713
|
||||
},
|
||||
{
|
||||
"x": 1568172702888,
|
||||
"y": 34756
|
||||
},
|
||||
{
|
||||
"x": 1568172725689,
|
||||
"y": null
|
||||
},
|
||||
{
|
||||
"x": 1568172748490,
|
||||
"y": 22205
|
||||
},
|
||||
{
|
||||
"x": 1568172771291,
|
||||
"y": 6071
|
||||
},
|
||||
{
|
||||
"x": 1568172794092,
|
||||
"y": 15681
|
||||
},
|
||||
{
|
||||
"x": 1568172816893,
|
||||
"y": null
|
||||
},
|
||||
{
|
||||
"x": 1568172839694,
|
||||
"y": 1669
|
||||
},
|
||||
{
|
||||
"x": 1568172862495,
|
||||
"y": 956
|
||||
},
|
||||
{
|
||||
"x": 1568172885296,
|
||||
"y": 1435
|
||||
},
|
||||
{
|
||||
"x": 1568172908097,
|
||||
"y": null
|
||||
},
|
||||
{
|
||||
"x": 1568172930898,
|
||||
"y": 32906
|
||||
},
|
||||
{
|
||||
"x": 1568172953699,
|
||||
"y": 892
|
||||
},
|
||||
{
|
||||
"x": 1568172976500,
|
||||
"y": 1514
|
||||
},
|
||||
{
|
||||
"x": 1568172999301,
|
||||
"y": null
|
||||
},
|
||||
{
|
||||
"x": 1568173022102,
|
||||
"y": 2367
|
||||
},
|
||||
{
|
||||
"x": 1568173044903,
|
||||
"y": 3389
|
||||
},
|
||||
{
|
||||
"x": 1568173067704,
|
||||
"y": 362
|
||||
},
|
||||
{
|
||||
"x": 1568173090505,
|
||||
"y": null
|
||||
},
|
||||
{
|
||||
"x": 1568173113306,
|
||||
"y": 3066
|
||||
},
|
||||
{
|
||||
"x": 1568173136107,
|
||||
"y": 44513
|
||||
},
|
||||
{
|
||||
"x": 1568173158908,
|
||||
"y": 6417
|
||||
},
|
||||
{
|
||||
"x": 1568173181709,
|
||||
"y": 1416
|
||||
},
|
||||
{
|
||||
"x": 1568173204510,
|
||||
"y": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": [
|
||||
{ "x": 1548697571840, "up": null, "down": null, "total": 35 },
|
||||
{ "x": 1548697764160, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548697956480, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548698148800, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548698341120, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548698533440, "up": null, "down": null, "total": 47 },
|
||||
{ "x": 1548698725760, "up": null, "down": null, "total": 47 },
|
||||
{ "x": 1548698918080, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548699110400, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548699302720, "up": null, "down": null, "total": 47 },
|
||||
{ "x": 1548699495040, "up": null, "down": null, "total": 49 },
|
||||
{ "x": 1548699687360, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548699879680, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548700072000, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548700264320, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548700456640, "up": null, "down": null, "total": 48 },
|
||||
{ "x": 1548700648960, "up": null, "down": null, "total": 48 }
|
||||
{
|
||||
"x": 1568172657286,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172680087,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172702888,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172725689,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
},
|
||||
{
|
||||
"x": 1568172748490,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172771291,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172794092,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172816893,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
},
|
||||
{
|
||||
"x": 1568172839694,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172862495,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172885296,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172908097,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
},
|
||||
{
|
||||
"x": 1568172930898,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172953699,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172976500,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568172999301,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
},
|
||||
{
|
||||
"x": 1568173022102,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173044903,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173067704,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173090505,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
},
|
||||
{
|
||||
"x": 1568173113306,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173136107,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173158908,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173181709,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 1
|
||||
},
|
||||
{
|
||||
"x": 1568173204510,
|
||||
"up": null,
|
||||
"down": null,
|
||||
"total": 0
|
||||
}
|
||||
],
|
||||
"statusMaxCount": 0,
|
||||
"durationMaxValue": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"monitorChartsData": {
|
||||
"locationDurationLines": [],
|
||||
"status": [],
|
||||
"statusMaxCount": 0,
|
||||
"durationMaxValue": 0
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"monitorPageTitle": {
|
||||
"id": "auto-http-0X131221E73F825974",
|
||||
"url": "https://www.google.com/",
|
||||
"id": "0002-up",
|
||||
"url": "http://localhost:5678/pattern?r=200x1",
|
||||
"name": ""
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,102 +1,144 @@
|
|||
{
|
||||
"monitorStates": {
|
||||
"prevPagePagination": null,
|
||||
"nextPagePagination": null,
|
||||
"totalSummaryCount": {
|
||||
"count": 9234
|
||||
"count": 2000
|
||||
},
|
||||
"summaries": [
|
||||
{
|
||||
"monitor_id": "auto-http-0XDD2D4E60FD4A61C3",
|
||||
"monitor_id": "0002-up",
|
||||
"histogram": {
|
||||
"count": 975,
|
||||
"count": 20,
|
||||
"points": [
|
||||
{
|
||||
"timestamp": 1548697571840,
|
||||
"up": 37,
|
||||
"timestamp": 1568172657286,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548697764160,
|
||||
"up": 52,
|
||||
"timestamp": 1568172680087,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548697956480,
|
||||
"up": 51,
|
||||
"timestamp": 1568172702888,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548698148800,
|
||||
"up": 52,
|
||||
"timestamp": 1568172725689,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548698341120,
|
||||
"up": 58,
|
||||
"timestamp": 1568172748490,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548698533440,
|
||||
"up": 58,
|
||||
"timestamp": 1568172771291,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548698725760,
|
||||
"up": 51,
|
||||
"timestamp": 1568172794092,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548698918080,
|
||||
"up": 52,
|
||||
"timestamp": 1568172816893,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548699110400,
|
||||
"up": 61,
|
||||
"timestamp": 1568172839694,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548699302720,
|
||||
"up": 60,
|
||||
"timestamp": 1568172862495,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548699495040,
|
||||
"up": 61,
|
||||
"timestamp": 1568172885296,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548699687360,
|
||||
"up": 63,
|
||||
"timestamp": 1568172908097,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548699879680,
|
||||
"up": 62,
|
||||
"timestamp": 1568172930898,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548700072000,
|
||||
"up": 62,
|
||||
"timestamp": 1568172953699,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548700264320,
|
||||
"up": 52,
|
||||
"timestamp": 1568172976500,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548700456640,
|
||||
"up": 51,
|
||||
"timestamp": 1568172999301,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548700648960,
|
||||
"up": 51,
|
||||
"timestamp": 1568173022102,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1548700841280,
|
||||
"up": 41,
|
||||
"timestamp": 1568173044903,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173067704,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173090505,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173113306,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173136107,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173158908,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173181709,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173204510,
|
||||
"up": 0,
|
||||
"down": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1568173227311,
|
||||
"up": 1,
|
||||
"down": 0
|
||||
}
|
||||
]
|
||||
|
@ -106,28 +148,33 @@
|
|||
"checks": [
|
||||
{
|
||||
"agent": {
|
||||
"id": "5884d7f7-9a49-4b0e-bff2-72a475aa695f"
|
||||
"id": "04e1d082-65bc-4929-8d65-d0768a2621c4"
|
||||
},
|
||||
"container": null,
|
||||
"kubernetes": null,
|
||||
"monitor": {
|
||||
"ip": "151.101.250.217",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "up"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": null,
|
||||
"location": null
|
||||
"name": "mpls",
|
||||
"location": {
|
||||
"lat": 37.926867976784706,
|
||||
"lon": -78.02490200847387
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": "1548700993074"
|
||||
"timestamp": "1568173234371"
|
||||
}
|
||||
],
|
||||
"geo": null,
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": [],
|
||||
"name": [
|
||||
"mpls"
|
||||
],
|
||||
"location": null
|
||||
}
|
||||
},
|
||||
|
@ -143,10 +190,10 @@
|
|||
"geo": null
|
||||
},
|
||||
"url": {
|
||||
"full": "https://www.elastic.co",
|
||||
"domain": "www.elastic.co"
|
||||
"full": "http://localhost:5678/pattern?r=200x1",
|
||||
"domain": "localhost"
|
||||
},
|
||||
"timestamp": 1548700993074
|
||||
"timestamp": 1568173234371
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,20 @@
|
|||
{
|
||||
"monitorStatus": [
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:16.078Z",
|
||||
"monitor": {
|
||||
"status": "up",
|
||||
"duration": {
|
||||
"us": 3328
|
||||
}
|
||||
},
|
||||
"observer": null,
|
||||
"tls": null,
|
||||
"url": {
|
||||
"full": "tcp://localhost:9200"
|
||||
[
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.371Z",
|
||||
"monitor": {
|
||||
"status": "up",
|
||||
"duration": {
|
||||
"us": 24627
|
||||
}
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
},
|
||||
"tls": null,
|
||||
"url": {
|
||||
"full": "http://localhost:5678/pattern?r=200x1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
|
@ -1,207 +1,319 @@
|
|||
{
|
||||
"allPings": {
|
||||
"total": 9234,
|
||||
"total": 2000,
|
||||
"locations": [
|
||||
"N/A"
|
||||
"mpls"
|
||||
],
|
||||
"pings": [
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:16.078Z",
|
||||
"http": null,
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3328
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"http": null,
|
||||
"error": {
|
||||
"message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
|
||||
"type": "io"
|
||||
},
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3331
|
||||
},
|
||||
"id": "auto-http-0X3675F89EF0612091",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "down",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"http": null,
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3292
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"timestamp": "2019-09-11T03:40:34.410Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": null
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 118727
|
||||
"us": 413
|
||||
},
|
||||
"id": "auto-http-0X970CBD2F2102BFA8",
|
||||
"ip": "172.217.12.132",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": null
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 132169
|
||||
},
|
||||
"id": "auto-http-0X131221E73F825974",
|
||||
"ip": "172.217.12.132",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": null
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 247244
|
||||
},
|
||||
"id": "auto-http-0X9CB71300ABD5A2A8",
|
||||
"ip": "192.30.253.112",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:14.080Z",
|
||||
"http": null,
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 2080
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0074-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:13.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:40:34.406Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1921
|
||||
"us": 441
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0073-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
},
|
||||
"observer": null
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:13.074Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 301,
|
||||
"body": null
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 299586
|
||||
},
|
||||
"id": "auto-http-0XD9AE729FC1C1E04A",
|
||||
"ip": "151.101.249.140",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:13.074Z",
|
||||
"timestamp": "2019-09-11T03:40:34.406Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": null
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 850870
|
||||
"us": 482
|
||||
},
|
||||
"id": "auto-http-0XDD2D4E60FD4A61C3",
|
||||
"ip": "151.101.250.217",
|
||||
"id": "0099-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.406Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 558
|
||||
},
|
||||
"id": "0098-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.406Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 304
|
||||
},
|
||||
"id": "0075-intermittent",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.405Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 487
|
||||
},
|
||||
"id": "0097-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.405Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 602
|
||||
},
|
||||
"id": "0049-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.390Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 365
|
||||
},
|
||||
"id": "0047-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.389Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 870
|
||||
},
|
||||
"id": "0077-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.387Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 2808
|
||||
},
|
||||
"id": "0076-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,264 +1,474 @@
|
|||
{
|
||||
"allPings": {
|
||||
"total": 3373,
|
||||
"total": 20,
|
||||
"locations": [
|
||||
"N/A"
|
||||
"mpls"
|
||||
],
|
||||
"pings": [
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:16.078Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:40:34.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3328
|
||||
"us": 35534
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:15.077Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:40:04.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3292
|
||||
"us": 3080
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:14.080Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:39:34.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 2080
|
||||
"us": 7810
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:13.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:39:04.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1921
|
||||
"us": 1575
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:12.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:38:34.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1771
|
||||
"us": 1787
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:11.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:38:04.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 2226
|
||||
"us": 654
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:10.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:37:34.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1076
|
||||
"us": 15915
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:09.079Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:37:04.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1824
|
||||
"us": 2679
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:08.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:36:34.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1404
|
||||
"us": 2104
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:07.078Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:36:04.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3353
|
||||
"us": 5759
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:06.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:35:34.373Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1861
|
||||
"us": 7166
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:05.076Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:35:04.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 2476
|
||||
"us": 26830
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:04.077Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:34:34.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3512
|
||||
"us": 993
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:03.077Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:34:04.381Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 3158
|
||||
"us": 3880
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T18:43:02.080Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:33:34.371Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1889
|
||||
"us": 1604
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,103 +1,164 @@
|
|||
{
|
||||
"allPings": {
|
||||
"total": 3373,
|
||||
"total": 20,
|
||||
"locations": [
|
||||
"N/A"
|
||||
"mpls"
|
||||
],
|
||||
"pings": [
|
||||
{
|
||||
"timestamp": "2019-01-28T17:47:06.077Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:31:04.380Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1452
|
||||
"us": 56940
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T17:47:07.075Z",
|
||||
"http": null,
|
||||
"timestamp": "2019-09-11T03:31:34.366Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1094
|
||||
"us": 9861
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"ip": "127.0.1.0",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "up",
|
||||
"type": "tcp"
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T17:47:07.075Z",
|
||||
"http": null,
|
||||
"error": {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io"
|
||||
"timestamp": "2019-09-11T03:32:04.372Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1094
|
||||
"us": 2924
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "down",
|
||||
"type": "tcp"
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T17:47:08.078Z",
|
||||
"http": null,
|
||||
"error": {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io"
|
||||
"timestamp": "2019-09-11T03:32:34.375Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1430
|
||||
"us": 21665
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "down",
|
||||
"type": "tcp"
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2019-01-28T17:47:09.075Z",
|
||||
"http": null,
|
||||
"error": {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io"
|
||||
"timestamp": "2019-09-11T03:33:04.370Z",
|
||||
"http": {
|
||||
"response": {
|
||||
"status_code": 200,
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf",
|
||||
"content": null,
|
||||
"content_bytes": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"monitor": {
|
||||
"duration": {
|
||||
"us": 1370
|
||||
"us": 2128
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"id": "0001-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"scheme": null,
|
||||
"status": "down",
|
||||
"type": "tcp"
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": null
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"snapshot": {
|
||||
"counts": {
|
||||
"down": 2,
|
||||
"down": 7,
|
||||
"mixed": 0,
|
||||
"up": 8,
|
||||
"total": 10
|
||||
"up": 93,
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"snapshot": {
|
||||
"counts": {
|
||||
"down": 0,
|
||||
"down": 13,
|
||||
"mixed": 0,
|
||||
"up": 0,
|
||||
"total": 0
|
||||
"total": 13
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"snapshot": {
|
||||
"counts": {
|
||||
"down": 2,
|
||||
"down": 13,
|
||||
"mixed": 0,
|
||||
"up": 0,
|
||||
"total": 10
|
||||
"total": 13
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@
|
|||
"counts": {
|
||||
"down": 0,
|
||||
"mixed": 0,
|
||||
"up": 8,
|
||||
"total": 10
|
||||
"up": 94,
|
||||
"total": 94
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +1,178 @@
|
|||
{
|
||||
"histogram": [
|
||||
{
|
||||
"upCount": 175,
|
||||
"downCount": 202,
|
||||
"x": 1548697764160,
|
||||
"x0": 1548697571840,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172680087,
|
||||
"x0": 1568172657286,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 243,
|
||||
"downCount": 277,
|
||||
"x": 1548697956480,
|
||||
"x0": 1548697764160,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172702888,
|
||||
"x0": 1568172680087,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 238,
|
||||
"downCount": 278,
|
||||
"x": 1548698148800,
|
||||
"x0": 1548697956480,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172725689,
|
||||
"x0": 1568172702888,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 244,
|
||||
"downCount": 279,
|
||||
"x": 1548698341120,
|
||||
"x0": 1548698148800,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568172748490,
|
||||
"x0": 1568172725689,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 409,
|
||||
"downCount": 120,
|
||||
"x": 1548698533440,
|
||||
"x0": 1548698341120,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172771291,
|
||||
"x0": 1568172748490,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 444,
|
||||
"downCount": 88,
|
||||
"x": 1548698725760,
|
||||
"x0": 1548698533440,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172794092,
|
||||
"x0": 1568172771291,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 430,
|
||||
"downCount": 76,
|
||||
"x": 1548698918080,
|
||||
"x0": 1548698725760,
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568172816893,
|
||||
"x0": 1568172794092,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 434,
|
||||
"downCount": 78,
|
||||
"x": 1548699110400,
|
||||
"x0": 1548698918080,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568172839694,
|
||||
"x0": 1568172816893,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"downCount": 76,
|
||||
"x": 1548699302720,
|
||||
"x0": 1548699110400,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172862495,
|
||||
"x0": 1568172839694,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 451,
|
||||
"downCount": 85,
|
||||
"x": 1548699495040,
|
||||
"x0": 1548699302720,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172885296,
|
||||
"x0": 1568172862495,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 458,
|
||||
"downCount": 89,
|
||||
"x": 1548699687360,
|
||||
"x0": 1548699495040,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172908097,
|
||||
"x0": 1568172885296,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"downCount": 86,
|
||||
"x": 1548699879680,
|
||||
"x0": 1548699687360,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568172930898,
|
||||
"x0": 1568172908097,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"downCount": 87,
|
||||
"x": 1548700072000,
|
||||
"x0": 1548699879680,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172953699,
|
||||
"x0": 1568172930898,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 459,
|
||||
"downCount": 88,
|
||||
"x": 1548700264320,
|
||||
"x0": 1548700072000,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172976500,
|
||||
"x0": 1568172953699,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 432,
|
||||
"downCount": 87,
|
||||
"x": 1548700456640,
|
||||
"x0": 1548700264320,
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568172999301,
|
||||
"x0": 1568172976500,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 431,
|
||||
"downCount": 86,
|
||||
"x": 1548700648960,
|
||||
"x0": 1548700456640,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173022102,
|
||||
"x0": 1568172999301,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 433,
|
||||
"downCount": 87,
|
||||
"x": 1548700841280,
|
||||
"x0": 1548700648960,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173044903,
|
||||
"x0": 1568173022102,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173067704,
|
||||
"x0": 1568173044903,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173090505,
|
||||
"x0": 1568173067704,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173113306,
|
||||
"x0": 1568173090505,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173136107,
|
||||
"x0": 1568173113306,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173158908,
|
||||
"x0": 1568173136107,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568173181709,
|
||||
"x0": 1568173158908,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173204510,
|
||||
"x0": 1568173181709,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173227311,
|
||||
"x0": 1568173204510,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,122 +1,178 @@
|
|||
{
|
||||
"histogram": [
|
||||
{
|
||||
"upCount": 175,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548697764160,
|
||||
"x0": 1548697571840,
|
||||
"x": 1568172680087,
|
||||
"x0": 1568172657286,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 243,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548697956480,
|
||||
"x0": 1548697764160,
|
||||
"x": 1568172702888,
|
||||
"x0": 1568172680087,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 238,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548698148800,
|
||||
"x0": 1548697956480,
|
||||
"x": 1568172725689,
|
||||
"x0": 1568172702888,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 244,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548698341120,
|
||||
"x0": 1548698148800,
|
||||
"x": 1568172748490,
|
||||
"x0": 1568172725689,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 409,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548698533440,
|
||||
"x0": 1548698341120,
|
||||
"x": 1568172771291,
|
||||
"x0": 1568172748490,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 444,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548698725760,
|
||||
"x0": 1548698533440,
|
||||
"x": 1568172794092,
|
||||
"x0": 1568172771291,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 430,
|
||||
"upCount": 92,
|
||||
"downCount": 0,
|
||||
"x": 1548698918080,
|
||||
"x0": 1548698725760,
|
||||
"x": 1568172816893,
|
||||
"x0": 1568172794092,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 434,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548699110400,
|
||||
"x0": 1548698918080,
|
||||
"x": 1568172839694,
|
||||
"x0": 1568172816893,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548699302720,
|
||||
"x0": 1548699110400,
|
||||
"x": 1568172862495,
|
||||
"x0": 1568172839694,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 451,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548699495040,
|
||||
"x0": 1548699302720,
|
||||
"x": 1568172885296,
|
||||
"x0": 1568172862495,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 458,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548699687360,
|
||||
"x0": 1548699495040,
|
||||
"x": 1568172908097,
|
||||
"x0": 1568172885296,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548699879680,
|
||||
"x0": 1548699687360,
|
||||
"x": 1568172930898,
|
||||
"x0": 1568172908097,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 455,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548700072000,
|
||||
"x0": 1548699879680,
|
||||
"x": 1568172953699,
|
||||
"x0": 1568172930898,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 459,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548700264320,
|
||||
"x0": 1548700072000,
|
||||
"x": 1568172976500,
|
||||
"x0": 1568172953699,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 432,
|
||||
"upCount": 92,
|
||||
"downCount": 0,
|
||||
"x": 1548700456640,
|
||||
"x0": 1548700264320,
|
||||
"x": 1568172999301,
|
||||
"x0": 1568172976500,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 431,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548700648960,
|
||||
"x0": 1548700456640,
|
||||
"x": 1568173022102,
|
||||
"x0": 1568172999301,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 433,
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1548700841280,
|
||||
"x0": 1548700648960,
|
||||
"x": 1568173044903,
|
||||
"x0": 1568173022102,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1568173067704,
|
||||
"x0": 1568173044903,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1568173090505,
|
||||
"x0": 1568173067704,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173113306,
|
||||
"x0": 1568173090505,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1568173136107,
|
||||
"x0": 1568173113306,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1568173158908,
|
||||
"x0": 1568173136107,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 92,
|
||||
"downCount": 0,
|
||||
"x": 1568173181709,
|
||||
"x0": 1568173158908,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 0,
|
||||
"x": 1568173204510,
|
||||
"x0": 1568173181709,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173227311,
|
||||
"x0": 1568173204510,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,122 +1,178 @@
|
|||
{
|
||||
"histogram": [
|
||||
{
|
||||
"upCount": 37,
|
||||
"downCount": 0,
|
||||
"x": 1548697764160,
|
||||
"x0": 1548697571840,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172680087,
|
||||
"x0": 1568172657286,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 52,
|
||||
"downCount": 0,
|
||||
"x": 1548697956480,
|
||||
"x0": 1548697764160,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172702888,
|
||||
"x0": 1568172680087,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 51,
|
||||
"downCount": 0,
|
||||
"x": 1548698148800,
|
||||
"x0": 1548697956480,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172725689,
|
||||
"x0": 1568172702888,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 52,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548698341120,
|
||||
"x0": 1548698148800,
|
||||
"x": 1568172748490,
|
||||
"x0": 1568172725689,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 58,
|
||||
"downCount": 0,
|
||||
"x": 1548698533440,
|
||||
"x0": 1548698341120,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172771291,
|
||||
"x0": 1568172748490,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 58,
|
||||
"downCount": 0,
|
||||
"x": 1548698725760,
|
||||
"x0": 1548698533440,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172794092,
|
||||
"x0": 1568172771291,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 51,
|
||||
"downCount": 0,
|
||||
"x": 1548698918080,
|
||||
"x0": 1548698725760,
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568172816893,
|
||||
"x0": 1568172794092,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 52,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548699110400,
|
||||
"x0": 1548698918080,
|
||||
"x": 1568172839694,
|
||||
"x0": 1568172816893,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 61,
|
||||
"downCount": 0,
|
||||
"x": 1548699302720,
|
||||
"x0": 1548699110400,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172862495,
|
||||
"x0": 1568172839694,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 60,
|
||||
"downCount": 0,
|
||||
"x": 1548699495040,
|
||||
"x0": 1548699302720,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172885296,
|
||||
"x0": 1568172862495,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 61,
|
||||
"downCount": 0,
|
||||
"x": 1548699687360,
|
||||
"x0": 1548699495040,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172908097,
|
||||
"x0": 1568172885296,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 63,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548699879680,
|
||||
"x0": 1548699687360,
|
||||
"x": 1568172930898,
|
||||
"x0": 1568172908097,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 62,
|
||||
"downCount": 0,
|
||||
"x": 1548700072000,
|
||||
"x0": 1548699879680,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172953699,
|
||||
"x0": 1568172930898,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 62,
|
||||
"downCount": 0,
|
||||
"x": 1548700264320,
|
||||
"x0": 1548700072000,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568172976500,
|
||||
"x0": 1568172953699,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 52,
|
||||
"downCount": 0,
|
||||
"x": 1548700456640,
|
||||
"x0": 1548700264320,
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568172999301,
|
||||
"x0": 1568172976500,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 51,
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548700648960,
|
||||
"x0": 1548700456640,
|
||||
"x": 1568173022102,
|
||||
"x0": 1568172999301,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 51,
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173044903,
|
||||
"x0": 1568173022102,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173067704,
|
||||
"x0": 1568173044903,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173090505,
|
||||
"x0": 1568173067704,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1548700841280,
|
||||
"x0": 1548700648960,
|
||||
"x": 1568173113306,
|
||||
"x0": 1568173090505,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173136107,
|
||||
"x0": 1568173113306,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173158908,
|
||||
"x0": 1568173136107,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 92,
|
||||
"downCount": 8,
|
||||
"x": 1568173181709,
|
||||
"x0": 1568173158908,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 93,
|
||||
"downCount": 7,
|
||||
"x": 1568173204510,
|
||||
"x0": 1568173181709,
|
||||
"y": 1
|
||||
},
|
||||
{
|
||||
"upCount": 0,
|
||||
"downCount": 0,
|
||||
"x": 1568173227311,
|
||||
"x0": 1568173204510,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ import fs from 'fs';
|
|||
import { join } from 'path';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
const fixturesDir = join(__dirname, 'fixtures');
|
||||
const fixturesDir = join(__dirname, '..', 'fixtures');
|
||||
|
||||
const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => {
|
||||
const clone = cloneDeep(from);
|
||||
|
@ -22,9 +22,11 @@ const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => {
|
|||
export const expectFixtureEql = <T>(data: T, fixtureName: string, excluder?: (d: T) => void) => {
|
||||
const fixturePath = join(fixturesDir, `${fixtureName}.json`);
|
||||
const dataExcluded = excludeFieldsFrom(data, excluder);
|
||||
expect(dataExcluded).not.to.be(undefined);
|
||||
if (process.env.UPDATE_UPTIME_FIXTURES) {
|
||||
fs.writeFileSync(fixturePath, JSON.stringify(dataExcluded, null, 2));
|
||||
}
|
||||
const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
|
||||
const fileContents = fs.readFileSync(fixturePath, 'utf8');
|
||||
const fixture = JSON.parse(fileContents);
|
||||
expect(dataExcluded).to.eql(excludeFieldsFrom(fixture, excluder));
|
||||
};
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
export const makePing = async (
|
||||
es: any,
|
||||
index: string,
|
||||
monitorId: string,
|
||||
fields: { [key: string]: any },
|
||||
mogrify: (doc: any) => any
|
||||
) => {
|
||||
const baseDoc = {
|
||||
tcp: {
|
||||
rtt: {
|
||||
connect: {
|
||||
us: 14687,
|
||||
},
|
||||
},
|
||||
},
|
||||
observer: {
|
||||
geo: {
|
||||
name: 'mpls',
|
||||
location: '37.926868, -78.024902',
|
||||
},
|
||||
hostname: 'avc-x1e',
|
||||
},
|
||||
agent: {
|
||||
hostname: 'avc-x1e',
|
||||
id: '10730a1a-4cb7-45ce-8524-80c4820476ab',
|
||||
type: 'heartbeat',
|
||||
ephemeral_id: '0d9a8dc6-f604-49e3-86a0-d8f9d6f2cbad',
|
||||
version: '8.0.0',
|
||||
},
|
||||
'@timestamp': new Date().toISOString(),
|
||||
resolve: {
|
||||
rtt: {
|
||||
us: 350,
|
||||
},
|
||||
ip: '127.0.0.1',
|
||||
},
|
||||
ecs: {
|
||||
version: '1.1.0',
|
||||
},
|
||||
host: {
|
||||
name: 'avc-x1e',
|
||||
},
|
||||
http: {
|
||||
rtt: {
|
||||
response_header: {
|
||||
us: 19349,
|
||||
},
|
||||
total: {
|
||||
us: 48954,
|
||||
},
|
||||
write_request: {
|
||||
us: 33,
|
||||
},
|
||||
content: {
|
||||
us: 51,
|
||||
},
|
||||
validate: {
|
||||
us: 19400,
|
||||
},
|
||||
},
|
||||
response: {
|
||||
status_code: 200,
|
||||
body: {
|
||||
bytes: 3,
|
||||
hash: '27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf',
|
||||
},
|
||||
},
|
||||
},
|
||||
monitor: {
|
||||
duration: {
|
||||
us: 49347,
|
||||
},
|
||||
ip: '127.0.0.1',
|
||||
id: monitorId,
|
||||
check_group: uuid.v4(),
|
||||
type: 'http',
|
||||
status: 'up',
|
||||
},
|
||||
event: {
|
||||
dataset: 'uptime',
|
||||
},
|
||||
url: {
|
||||
path: '/pattern',
|
||||
scheme: 'http',
|
||||
port: 5678,
|
||||
domain: 'localhost',
|
||||
query: 'r=200x5,500x1',
|
||||
full: 'http://localhost:5678/pattern?r=200x5,500x1',
|
||||
},
|
||||
};
|
||||
|
||||
const doc = mogrify(merge(baseDoc, fields));
|
||||
|
||||
await es.index({
|
||||
index,
|
||||
refresh: true,
|
||||
body: doc,
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
export const makeCheck = async (
|
||||
es: any,
|
||||
index: string,
|
||||
monitorId: string,
|
||||
numIps: number,
|
||||
fields: { [key: string]: any },
|
||||
mogrify: (doc: any) => any
|
||||
) => {
|
||||
const cgFields = {
|
||||
monitor: {
|
||||
check_group: uuid.v4(),
|
||||
},
|
||||
};
|
||||
|
||||
const docs = [];
|
||||
const summary = {
|
||||
up: 0,
|
||||
down: 0,
|
||||
};
|
||||
for (let i = 0; i < numIps; i++) {
|
||||
const pingFields = merge(fields, cgFields, {
|
||||
monitor: {
|
||||
ip: `127.0.0.${i}`,
|
||||
},
|
||||
});
|
||||
if (i === numIps - 1) {
|
||||
pingFields.summary = summary;
|
||||
}
|
||||
const doc = await makePing(es, index, monitorId, pingFields, mogrify);
|
||||
docs.push(doc);
|
||||
// @ts-ignore
|
||||
summary[doc.monitor.status]++;
|
||||
}
|
||||
return docs;
|
||||
};
|
||||
|
||||
export const makeChecks = async (
|
||||
es: any,
|
||||
index: string,
|
||||
monitorId: string,
|
||||
numChecks: number,
|
||||
numIps: number,
|
||||
fields: { [key: string]: any } = {},
|
||||
mogrify: (doc: any) => any = d => d
|
||||
) => {
|
||||
const checks = [];
|
||||
for (let li = 0; li < numChecks; li++) {
|
||||
checks.push(await makeCheck(es, index, monitorId, numIps, fields, mogrify));
|
||||
}
|
||||
return checks;
|
||||
};
|
|
@ -4,13 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export default function ({ getService, loadTestFile }) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const archive = 'uptime/full_heartbeat';
|
||||
|
||||
export default function ({ loadTestFile }) {
|
||||
describe('graphql', () => {
|
||||
before('load heartbeat data', () => esArchiver.load(archive));
|
||||
after('unload heartbeat index', () => esArchiver.unload(archive));
|
||||
// each of these test files imports a GQL query from
|
||||
// the uptime app and runs it against the live HTTP server,
|
||||
// verifying the pre-loaded documents are returned in a way that
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { monitorChartsQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import monitorCharts from './fixtures/monitor_charts';
|
||||
import monitorChartsEmptySet from './fixtures/monitor_charts_empty_set';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function ({ getService }) {
|
||||
describe('monitorCharts query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('will fetch a series of data points for monitor duration and status', async () => {
|
||||
|
@ -18,9 +19,9 @@ export default function ({ getService }) {
|
|||
operationName: 'MonitorCharts',
|
||||
query: monitorChartsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
monitorId: 'auto-http-0X131221E73F825974',
|
||||
dateRangeStart: '2019-09-11T03:31:04.380Z',
|
||||
dateRangeEnd: '2019-09-11T03:40:34.410Z',
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -29,7 +30,8 @@ export default function ({ getService }) {
|
|||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorChartsQuery });
|
||||
expect(data).to.eql(monitorCharts);
|
||||
|
||||
expectFixtureEql(data, 'monitor_charts');
|
||||
});
|
||||
|
||||
it('will fetch empty sets for a date range with no data', async () => {
|
||||
|
@ -37,9 +39,9 @@ export default function ({ getService }) {
|
|||
operationName: 'MonitorCharts',
|
||||
query: monitorChartsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2002-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2002-01-28T19:00:16.078Z',
|
||||
monitorId: 'auto-http-0X131221E73F825974',
|
||||
dateRangeStart: '1999-09-11T03:31:04.380Z',
|
||||
dateRangeEnd: '1999-09-11T03:40:34.410Z',
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -48,7 +50,9 @@ export default function ({ getService }) {
|
|||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorChartsQuery });
|
||||
expect(data).to.eql(monitorChartsEmptySet);
|
||||
|
||||
|
||||
expectFixtureEql(data, 'monitor_charts_empty_sets');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { monitorPageTitleQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_page_title_query';
|
||||
import monitorPageTitle from './fixtures/monitor_page_title.json';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('monitorPageTitle', () => {
|
||||
describe('monitor_page_title', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('will fetch a title for a given monitorId', async () => {
|
||||
|
@ -18,7 +20,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
operationName: 'MonitorPageTitle',
|
||||
query: monitorPageTitleQueryString,
|
||||
variables: {
|
||||
monitorId: 'auto-http-0X131221E73F825974',
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -29,7 +31,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorTitleQuery });
|
||||
|
||||
expect(data).to.eql(monitorPageTitle);
|
||||
expectFixtureEql(data, 'monitor_page_title');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,54 +4,165 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { monitorStatesQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_states_query';
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { makeChecks } from './helpers/make_checks';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('monitorStates', () => {
|
||||
const supertest = getService('supertest');
|
||||
const supertest = getService('supertest');
|
||||
let dateRangeStart: string;
|
||||
let dateRangeEnd: string;
|
||||
|
||||
it('will fetch monitor state data for the given date range', async () => {
|
||||
const getMonitorStatesQuery = {
|
||||
operationName: 'MonitorStates',
|
||||
query: monitorStatesQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const getMonitorStates = async (variables: { [key: string]: any } = {}) => {
|
||||
const query = {
|
||||
operationName: 'MonitorStates',
|
||||
query: monitorStatesQueryString,
|
||||
variables: {
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
...variables,
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatesQuery });
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...query });
|
||||
|
||||
expectFixtureEql(data, 'monitor_states');
|
||||
return data;
|
||||
};
|
||||
|
||||
describe('monitor states', async () => {
|
||||
describe('with real world data', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () =>
|
||||
getService('esArchiver').unload('uptime/full_heartbeat')
|
||||
);
|
||||
|
||||
before('set start/end', () => {
|
||||
dateRangeStart = '2019-09-11T03:31:04.380Z';
|
||||
dateRangeEnd = '2019-09-11T03:40:34.410Z';
|
||||
});
|
||||
|
||||
it('will fetch monitor state data for the given filters and range', async () => {
|
||||
const data: any = await getMonitorStates({
|
||||
statusFilter: 'up',
|
||||
filters:
|
||||
'{"bool":{"must":[{"match":{"monitor.id":{"query":"0002-up","operator":"and"}}}]}}',
|
||||
});
|
||||
// await new Promise(r => setTimeout(r, 90000));
|
||||
expectFixtureEql(data, 'monitor_states_id_filtered');
|
||||
});
|
||||
|
||||
it('will fetch monitor state data for the given date range', async () => {
|
||||
expectFixtureEql(await getMonitorStates(), 'monitor_states');
|
||||
});
|
||||
|
||||
it('can navigate forward and backward using pagination', async () => {
|
||||
const expectedResultsCount = 100;
|
||||
const expectedPageCount = expectedResultsCount / 10;
|
||||
|
||||
let pagination: string | null = null;
|
||||
for (let page = 1; page <= expectedPageCount; page++) {
|
||||
const data: any = await getMonitorStates({ pagination });
|
||||
pagination = data.monitorStates.nextPagePagination;
|
||||
expectFixtureEql(data, `monitor_states_page_${page}`);
|
||||
|
||||
// Test to see if the previous page pagination works on every page (other than the first)
|
||||
if (page > 1) {
|
||||
const prevData = await getMonitorStates({
|
||||
pagination: data.monitorStates.prevPagePagination,
|
||||
});
|
||||
expectFixtureEql(prevData, `monitor_states_page_${page}_previous`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('will fetch monitor state data for the given filters and range', async () => {
|
||||
const getMonitorStatesQuery = {
|
||||
operationName: 'MonitorStates',
|
||||
query: monitorStatesQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
filters:
|
||||
'{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}},{"match":{"monitor.id":{"query":"auto-http-0XDD2D4E60FD4A61C3","operator":"and"}}}]}}',
|
||||
},
|
||||
};
|
||||
describe('monitor state scoping', async () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/blank'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/blank'));
|
||||
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatesQuery });
|
||||
describe('query document scoping with mismatched check statuses', async () => {
|
||||
let checks: any[] = [];
|
||||
let nonSummaryIp: string | null = null;
|
||||
const testMonitorId = 'scope-test-id';
|
||||
const makeApiParams = (monitorId: string, filterClauses: any[] = []): any => {
|
||||
return {
|
||||
filters: JSON.stringify({
|
||||
bool: {
|
||||
filter: [{ match: { 'monitor.id': monitorId } }, ...filterClauses],
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
expectFixtureEql(data, 'monitor_states_id_filtered');
|
||||
before(async () => {
|
||||
const index = 'heartbeat-8.0.0';
|
||||
|
||||
const es = getService('es');
|
||||
dateRangeStart = new Date().toISOString();
|
||||
checks = await makeChecks(es, index, testMonitorId, 1, 2, {}, d => {
|
||||
if (d.summary) {
|
||||
d.monitor.status = 'down';
|
||||
d.summary.up--;
|
||||
d.summary.down++;
|
||||
}
|
||||
return d;
|
||||
});
|
||||
dateRangeEnd = new Date().toISOString();
|
||||
nonSummaryIp = checks[0][0].monitor.ip;
|
||||
});
|
||||
|
||||
it('should match non summary documents without a status filter', async () => {
|
||||
const params = makeApiParams(testMonitorId, [{ match: { 'monitor.ip': nonSummaryIp } }]);
|
||||
|
||||
const nonSummaryRes = await getMonitorStates(params);
|
||||
expect(nonSummaryRes.monitorStates.summaries.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('should not match non summary documents if the check status does not match the document status', async () => {
|
||||
const params = makeApiParams(testMonitorId, [{ match: { 'monitor.ip': nonSummaryIp } }]);
|
||||
params.statusFilter = 'down';
|
||||
|
||||
const nonSummaryRes = await getMonitorStates(params);
|
||||
expect(nonSummaryRes.monitorStates.summaries.length).to.eql(0);
|
||||
});
|
||||
|
||||
it('should not non match non summary documents if the check status does not match', async () => {
|
||||
const params = makeApiParams(testMonitorId, [{ match: { 'monitor.ip': nonSummaryIp } }]);
|
||||
params.statusFilter = 'up';
|
||||
|
||||
const nonSummaryRes = await getMonitorStates(params);
|
||||
expect(nonSummaryRes.monitorStates.summaries.length).to.eql(0);
|
||||
});
|
||||
|
||||
describe('matching outside of the date range', async () => {
|
||||
before('set date range to future', () => {
|
||||
const futureDate = new Date();
|
||||
|
||||
// Set dateRangeStart to one day from now
|
||||
futureDate.setDate(futureDate.getDate() + 1);
|
||||
dateRangeStart = futureDate.toISOString();
|
||||
|
||||
// Set dateRangeStart to two days from now
|
||||
futureDate.setDate(futureDate.getDate() + 1);
|
||||
dateRangeEnd = futureDate.toISOString();
|
||||
});
|
||||
|
||||
it('should not match any documents', async () => {
|
||||
const params = makeApiParams(testMonitorId);
|
||||
params.statusFilter = 'up';
|
||||
|
||||
const nonSummaryRes = await getMonitorStates(params);
|
||||
expect(nonSummaryRes.monitorStates.summaries.length).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { monitorStatusBarQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function ({ getService }) {
|
||||
describe('monitorStatusBar query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns the status for all monitors with no ID filtering', async () => {
|
||||
|
@ -17,7 +20,7 @@ export default function ({ getService }) {
|
|||
query: monitorStatusBarQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -28,7 +31,8 @@ export default function ({ getService }) {
|
|||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatusBarQuery });
|
||||
expectFixtureEql({ monitorStatus: responseData }, 'monitor_status');
|
||||
|
||||
expectFixtureEql(responseData, 'monitor_status_all', res => res.forEach(i => delete i.millisFromNow));
|
||||
});
|
||||
|
||||
it('returns the status for only the given monitor', async () => {
|
||||
|
@ -37,19 +41,17 @@ export default function ({ getService }) {
|
|||
query: monitorStatusBarQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
monitorId: 'auto-tcp-0X81440A68E839814C',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
const {
|
||||
body: {
|
||||
data: { monitorStatus },
|
||||
},
|
||||
} = await supertest
|
||||
const res = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatusBarQuery });
|
||||
expectFixtureEql({ monitorStatus }, 'monitor_status_by_id');
|
||||
|
||||
|
||||
expectFixtureEql(res.body.data.monitorStatus, 'monitor_status_by_id');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { pingsQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types';
|
||||
|
||||
const expectPingFixtureEql = (data: { allPings: PingResults }, fixtureName: string) => {
|
||||
|
@ -15,6 +15,9 @@ const expectPingFixtureEql = (data: { allPings: PingResults }, fixtureName: stri
|
|||
|
||||
export default function({ getService }: any) {
|
||||
describe('pingList query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns a list of pings for the given date range and default size', async () => {
|
||||
|
@ -23,7 +26,7 @@ export default function({ getService }: any) {
|
|||
query: pingsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -36,6 +39,7 @@ export default function({ getService }: any) {
|
|||
allPings: { pings },
|
||||
} = data;
|
||||
expect(pings).length(10);
|
||||
|
||||
expectPingFixtureEql(data, 'ping_list');
|
||||
});
|
||||
|
||||
|
@ -46,7 +50,7 @@ export default function({ getService }: any) {
|
|||
query: pingsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
size: SIZE,
|
||||
},
|
||||
};
|
||||
|
@ -65,13 +69,13 @@ export default function({ getService }: any) {
|
|||
|
||||
it('returns a list of pings for a monitor ID', async () => {
|
||||
const SIZE = 15;
|
||||
const MONITOR_ID = 'auto-tcp-0X81440A68E839814C';
|
||||
const MONITOR_ID = '0001-up';
|
||||
const getPingsQuery = {
|
||||
operationName: 'PingList',
|
||||
query: pingsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
monitorId: MONITOR_ID,
|
||||
size: SIZE,
|
||||
},
|
||||
|
@ -87,13 +91,13 @@ export default function({ getService }: any) {
|
|||
|
||||
it('returns a list of pings sorted ascending', async () => {
|
||||
const SIZE = 5;
|
||||
const MONITOR_ID = 'auto-tcp-0X81440A68E839814C';
|
||||
const MONITOR_ID = '0001-up';
|
||||
const getPingsQuery = {
|
||||
operationName: 'PingList',
|
||||
query: pingsQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
monitorId: MONITOR_ID,
|
||||
size: SIZE,
|
||||
sort: 'asc',
|
||||
|
@ -105,6 +109,7 @@ export default function({ getService }: any) {
|
|||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getPingsQuery });
|
||||
|
||||
expectPingFixtureEql(data, 'ping_list_sort');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
*/
|
||||
|
||||
import { snapshotQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function ({ getService }) {
|
||||
describe('snapshot query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('will fetch a monitor snapshot summary', async () => {
|
||||
|
@ -17,7 +20,7 @@ export default function ({ getService }) {
|
|||
query: snapshotQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
|
@ -36,7 +39,8 @@ export default function ({ getService }) {
|
|||
query: snapshotQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`,
|
||||
statusFilter: 'down',
|
||||
},
|
||||
};
|
||||
|
@ -56,7 +60,8 @@ export default function ({ getService }) {
|
|||
query: snapshotQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}`,
|
||||
statusFilter: 'up',
|
||||
},
|
||||
};
|
||||
|
@ -77,7 +82,7 @@ export default function ({ getService }) {
|
|||
query: snapshotQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-25T04:30:54.740Z',
|
||||
dateRangeEnd: '2019-01-28T04:50:54.740Z',
|
||||
dateRangeEnd: '2025-01-28T04:50:54.740Z',
|
||||
filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
*/
|
||||
|
||||
import { snapshotHistogramQueryString } from '../../../../../legacy/plugins/uptime/public/queries/snapshot_histogram_query';
|
||||
import { expectFixtureEql } from './expect_fixture_eql';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('snapshotHistogram', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('will fetch histogram data for all monitors', async () => {
|
||||
|
@ -17,8 +20,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
operationName: 'SnapshotHistogram',
|
||||
query: snapshotHistogramQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeStart: '2019-09-11T03:31:04.380Z',
|
||||
dateRangeEnd: '2019-09-11T03:40:34.410Z',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -36,9 +39,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
operationName: 'SnapshotHistogram',
|
||||
query: snapshotHistogramQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
monitorId: 'auto-http-0XDD2D4E60FD4A61C3',
|
||||
dateRangeStart: '2019-09-11T03:31:04.380Z',
|
||||
dateRangeEnd: '2019-09-11T03:40:34.410Z',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -56,8 +58,8 @@ export default function({ getService }: FtrProviderContext) {
|
|||
operationName: 'SnapshotHistogram',
|
||||
query: snapshotHistogramQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2019-01-28T19:00:16.078Z',
|
||||
dateRangeStart: '2019-09-11T03:31:04.380Z',
|
||||
dateRangeEnd: '2019-09-11T03:40:34.410Z',
|
||||
filters:
|
||||
'{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}',
|
||||
},
|
||||
|
|
2381
x-pack/test/functional/es_archives/uptime/blank/mappings.json
Normal file
2381
x-pack/test/functional/es_archives/uptime/blank/mappings.json
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue