Skip to content

Commit

Permalink
[Infrastructure UI] New Infra api (#155084)
Browse files Browse the repository at this point in the history
closes: [#152103](#152103)
fixes: #151768

## Summary

This PR adds a new API to return host metrics for the Hosts View page. 

The difference between this API and Snapshot API is that this one runs a
terms aggregation and the API can return a range of 1 to 500 hosts,
preventing it from returning all data.

It uses Inventory Model aggregations, so the performance should be
similar to that of the Snapshot API. The `limit` parameter is what will
allow the client to try to get a better response time from the API.

#### Snapshot API

Returns all 500 hosts

15 minutes
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/230073865-d0d98ce8-76ff-4295-be9b-94f5b342c9f7.png">

1 day
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/230076350-affbad3d-a498-481e-bcba-bcec019ab739.png">

1 week
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/230077049-320d90c8-4f3e-47ba-bd16-1c5878a83254.png">


#### Hosts API

100 hosts limit

15 minutes
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/233349080-50acdf55-d269-49f7-be97-3fc259021020.png">

1 day
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/233349614-826a791a-aef5-4e7a-b103-2900c4f64f49.png">

1 week
<img width="317" alt="image"
src="https://user-images.githubusercontent.com/2767137/233350318-388ae9b3-8b7b-4365-9f32-7dcbc2342480.png">

### How to test
```bash
curl --location -u elastic:changeme 'http://0.0.0.0:5601/ftw/api/metrics/infra' \
--header 'kbn-xsrf: xxxx' \
--header 'Content-Type: application/json' \
--data '{
    "type": "host",
    "limit": 100,
    "metrics": [
        {
            "type": "rx"
        },
        {
            "type": "tx"
        },
        {
            "type": "memory"
        },
        {
            "type": "cpu"
        },
        {
            "type": "diskLatency"
        },
        {
            "type": "memoryTotal"
        }
    ],
    "query": {
        "bool": {
            "must": [],
            "filter": [],
            "should": [],
            "must_not": []
        }
    },
    "range": {
        "from": "2023-04-18T11:15:31.407Z",
        "to":   "2023-04-18T11:30:31.407Z"
    },
    "sourceId": "default"
}'
```

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
crespocarlos and kibanamachine authored Apr 20, 2023
1 parent d44eaaa commit ef218cd
Show file tree
Hide file tree
Showing 27 changed files with 1,457 additions and 1 deletion.
8 changes: 8 additions & 0 deletions packages/kbn-io-ts-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ export { toBooleanRt } from './src/to_boolean_rt';
export { toJsonSchema } from './src/to_json_schema';
export { nonEmptyStringRt } from './src/non_empty_string_rt';
export { createLiteralValueFromUndefinedRT } from './src/literal_value_from_undefined_rt';
export { createRouteValidationFunction } from './src/route_validation';
export { inRangeRt, type InRangeBrand, type InRange } from './src/in_range_rt';
export { dateRt } from './src/date_rt';
export {
isGreaterOrEqualRt,
type IsGreaterOrEqualBrand,
type IsGreaterOrEqual,
} from './src/is_greater_or_equal';
24 changes: 24 additions & 0 deletions packages/kbn-io-ts-utils/src/date_rt/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { dateRt } from '.';
import { isRight } from 'fp-ts/lib/Either';

describe('dateRt', () => {
it('passes if it is a valide date/time', () => {
expect(isRight(dateRt.decode('2019-08-20T11:18:31.407Z'))).toBe(true);
});

it('passes if it is a valide date', () => {
expect(isRight(dateRt.decode('2019-08-20'))).toBe(true);
});

it('fails if it is an invalide date/time', () => {
expect(isRight(dateRt.decode('2019-02-30T11:18:31.407Z'))).toBe(false);
});
});
22 changes: 22 additions & 0 deletions packages/kbn-io-ts-utils/src/date_rt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as rt from 'io-ts';
import moment from 'moment';

export interface DateBrand {
readonly Date: unique symbol;
}

export type Date = rt.Branded<string, DateBrand>;

export const dateRt = rt.brand(
rt.string,
(date): date is Date => moment(date, true).isValid(),
'Date'
);
24 changes: 24 additions & 0 deletions packages/kbn-io-ts-utils/src/in_range_rt/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { inRangeRt } from '.';
import { isRight } from 'fp-ts/lib/Either';

describe('inRangeRT', () => {
test('passes if value is within range', () => {
expect(isRight(inRangeRt(1, 100).decode(50))).toBe(true);
});

test('fails if value above the range', () => {
expect(isRight(inRangeRt(1, 100).decode(101))).toBe(false);
});

test('fails if value below the range', () => {
expect(isRight(inRangeRt(1, 100).decode(0))).toBe(false);
});
});
23 changes: 23 additions & 0 deletions packages/kbn-io-ts-utils/src/in_range_rt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as rt from 'io-ts';

export interface InRangeBrand {
readonly InRange: unique symbol;
}

export type InRange = rt.Branded<number, InRangeBrand>;

export const inRangeRt = (start: number, end: number) =>
rt.brand(
rt.number, // codec
(n): n is InRange => n >= start && n <= end,
// refinement of the number type
'InRange' // name of this codec
);
24 changes: 24 additions & 0 deletions packages/kbn-io-ts-utils/src/is_greater_or_equal/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { isGreaterOrEqualRt } from '.';
import { isRight } from 'fp-ts/lib/Either';

describe('inRangeRT', () => {
test('passes if value is a positive number', () => {
expect(isRight(isGreaterOrEqualRt(0).decode(1))).toBe(true);
});

test('passes if value is 0', () => {
expect(isRight(isGreaterOrEqualRt(0).decode(0))).toBe(true);
});

test('fails if value is a negative number', () => {
expect(isRight(isGreaterOrEqualRt(0).decode(-1))).toBe(false);
});
});
23 changes: 23 additions & 0 deletions packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as rt from 'io-ts';

export interface IsGreaterOrEqualBrand {
readonly IsGreaterOrEqual: unique symbol;
}

export type IsGreaterOrEqual = rt.Branded<number, IsGreaterOrEqualBrand>;

export const isGreaterOrEqualRt = (value: number) =>
rt.brand(
rt.number, // codec
(n): n is IsGreaterOrEqual => n >= value,
// refinement of the number type
'IsGreaterOrEqual' // name of this codec
);
53 changes: 53 additions & 0 deletions packages/kbn-io-ts-utils/src/route_validation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { RouteValidationFunction } from '@kbn/core/server';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts';

type ValdidationResult<Value> = ReturnType<RouteValidationFunction<Value>>;

export const createRouteValidationFunction =
<DecodedValue, EncodedValue, InputValue>(
runtimeType: Type<DecodedValue, EncodedValue, InputValue>
): RouteValidationFunction<DecodedValue> =>
(inputValue, { badRequest, ok }) =>
pipe(
runtimeType.decode(inputValue),
fold<Errors, DecodedValue, ValdidationResult<DecodedValue>>(
(errors: Errors) => badRequest(formatErrors(errors)),
(result: DecodedValue) => ok(result)
)
);

const getErrorPath = ([first, ...rest]: Context): string[] => {
if (typeof first === 'undefined') {
return [];
} else if (first.type instanceof IntersectionType) {
const [, ...next] = rest;
return getErrorPath(next);
} else if (first.type instanceof UnionType) {
const [, ...next] = rest;
return [first.key, ...getErrorPath(next)];
}

return [first.key, ...getErrorPath(rest)];
};

const getErrorType = ({ context }: ValidationError) =>
context[context.length - 1]?.type?.name ?? 'unknown';

const formatError = (error: ValidationError) =>
error.message ??
`in ${getErrorPath(error.context).join('/')}: ${JSON.stringify(
error.value
)} does not match expected type ${getErrorType(error)}`;

export const formatErrors = (errors: ValidationError[]) =>
`Failed to validate: \n${errors.map((error) => ` ${formatError(error)}`).join('\n')}`;
3 changes: 2 additions & 1 deletion packages/kbn-io-ts-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"**/*.ts"
],
"kbn_references": [
"@kbn/config-schema"
"@kbn/config-schema",
"@kbn/core"
],
"exclude": [
"target/**/*",
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/infra/common/http_api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './metrics_api';
export * from './log_alerts';
export * from './snapshot_api';
export * from './host_details';
export * from './infra';
80 changes: 80 additions & 0 deletions x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { createLiteralValueFromUndefinedRT, inRangeRt, dateRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';

export const InfraMetricTypeRT = rt.keyof({
cpu: null,
diskLatency: null,
memory: null,
memoryTotal: null,
rx: null,
tx: null,
});

export const RangeRT = rt.type({
from: dateRt,
to: dateRt,
});

export const InfraAssetMetadataTypeRT = rt.keyof({
'host.os.name': null,
'cloud.provider': null,
});

export const InfraAssetMetricsRT = rt.type({
name: InfraMetricTypeRT,
value: rt.union([rt.number, rt.null]),
});

export const InfraAssetMetadataRT = rt.type({
// keep the actual field name from the index mappings
name: InfraAssetMetadataTypeRT,
value: rt.union([rt.string, rt.number, rt.null]),
});

export const GetInfraMetricsRequestBodyPayloadRT = rt.intersection([
rt.partial({
query: rt.UnknownRecord,
}),
rt.type({
type: rt.literal('host'),
limit: rt.union([inRangeRt(1, 500), createLiteralValueFromUndefinedRT(20)]),
metrics: rt.array(rt.type({ type: InfraMetricTypeRT })),
sourceId: rt.string,
range: RangeRT,
}),
]);

export const InfraAssetMetricsItemRT = rt.type({
name: rt.string,
metrics: rt.array(InfraAssetMetricsRT),
metadata: rt.array(InfraAssetMetadataRT),
});

export const GetInfraMetricsResponsePayloadRT = rt.type({
type: rt.literal('host'),
nodes: rt.array(InfraAssetMetricsItemRT),
});

export type InfraAssetMetrics = rt.TypeOf<typeof InfraAssetMetricsRT>;
export type InfraAssetMetadata = rt.TypeOf<typeof InfraAssetMetadataRT>;
export type InfraAssetMetricType = rt.TypeOf<typeof InfraMetricTypeRT>;
export type InfraAssetMetricsItem = rt.TypeOf<typeof InfraAssetMetricsItemRT>;

export type GetInfraMetricsRequestBodyPayload = Omit<
rt.TypeOf<typeof GetInfraMetricsRequestBodyPayloadRT>,
'limit' | 'range'
> & {
limit?: number;
range: {
from: string;
to: string;
};
};
export type GetInfraMetricsResponsePayload = rt.TypeOf<typeof GetInfraMetricsResponsePayloadRT>;
8 changes: 8 additions & 0 deletions x-pack/plugins/infra/common/http_api/infra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './get_infra_metrics';
2 changes: 2 additions & 0 deletions x-pack/plugins/infra/server/infra_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { initNodeDetailsRoute } from './routes/node_details';
import { initOverviewRoute } from './routes/overview';
import { initProcessListRoute } from './routes/process_list';
import { initSnapshotRoute } from './routes/snapshot';
import { initInfraMetricsRoute } from './routes/infra';

export const initInfraServer = (libs: InfraBackendLibs) => {
initIpToHostName(libs);
Expand Down Expand Up @@ -63,4 +64,5 @@ export const initInfraServer = (libs: InfraBackendLibs) => {
initGetLogAlertsChartPreviewDataRoute(libs);
initProcessListRoute(libs);
initOverviewRoute(libs);
initInfraMetricsRoute(libs);
};
Loading

0 comments on commit ef218cd

Please sign in to comment.