diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md index 00e5da4a9a9f9..7b2cbdecd146a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md @@ -25,8 +25,6 @@ import { i18n } from '@kbn/i18n'; async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { const deprecations: DeprecationsDetails[] = []; - - // Example of an api correctiveAction const count = await getFooCount(savedObjectsClient); if (count > 0) { deprecations.push({ @@ -42,12 +40,12 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations level: 'warning', correctiveActions: { manualSteps: [ - i18n.translate('xpack.foo.deprecations.manualStepOneMessage', { - defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".', - }), - i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', { - defaultMessage: 'Select Foo from the "New Visualization" window.', - }), + i18n.translate('xpack.foo.deprecations.manualStepOneMessage', { + defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".', + }), + i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', { + defaultMessage: 'Select Foo from the "New Visualization" window.', + }), ], api: { path: '/internal/security/users/test_dashboard_user', @@ -68,7 +66,6 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations }, }); } - return deprecations; } diff --git a/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md new file mode 100644 index 0000000000000..ae9df8b406be6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) > [(constructor)](./kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md) + +## EventLoopDelaysMonitor.(constructor) + +Creating a new instance from EventLoopDelaysMonitor will automatically start tracking event loop delays. + +Signature: + +```typescript +constructor(); +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.collect.md b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.collect.md new file mode 100644 index 0000000000000..0e07497baf887 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.collect.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) > [collect](./kibana-plugin-core-server.eventloopdelaysmonitor.collect.md) + +## EventLoopDelaysMonitor.collect() method + +Collect gathers event loop delays metrics from nodejs perf\_hooks.monitorEventLoopDelay the histogram calculations start from the last time `reset` was called or this EventLoopDelaysMonitor instance was created. + +Signature: + +```typescript +collect(): IntervalHistogram; +``` +Returns: + +`IntervalHistogram` + +{IntervalHistogram} + diff --git a/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.md b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.md new file mode 100644 index 0000000000000..21bbd8b48840c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) + +## EventLoopDelaysMonitor class + +Signature: + +```typescript +export declare class EventLoopDelaysMonitor +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)()](./kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md) | | Creating a new instance from EventLoopDelaysMonitor will automatically start tracking event loop delays. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [collect()](./kibana-plugin-core-server.eventloopdelaysmonitor.collect.md) | | Collect gathers event loop delays metrics from nodejs perf\_hooks.monitorEventLoopDelay the histogram calculations start from the last time reset was called or this EventLoopDelaysMonitor instance was created. | +| [reset()](./kibana-plugin-core-server.eventloopdelaysmonitor.reset.md) | | Resets the collected histogram data. | +| [stop()](./kibana-plugin-core-server.eventloopdelaysmonitor.stop.md) | | Disables updating the interval timer for collecting new data points. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.reset.md b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.reset.md new file mode 100644 index 0000000000000..fdba7a79ebda0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.reset.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) > [reset](./kibana-plugin-core-server.eventloopdelaysmonitor.reset.md) + +## EventLoopDelaysMonitor.reset() method + +Resets the collected histogram data. + +Signature: + +```typescript +reset(): void; +``` +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.stop.md b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.stop.md new file mode 100644 index 0000000000000..25b61434b0061 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.eventloopdelaysmonitor.stop.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) > [stop](./kibana-plugin-core-server.eventloopdelaysmonitor.stop.md) + +## EventLoopDelaysMonitor.stop() method + +Disables updating the interval timer for collecting new data points. + +Signature: + +```typescript +stop(): void; +``` +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.exceeds.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.exceeds.md new file mode 100644 index 0000000000000..664bdb8f24d7b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.exceeds.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [exceeds](./kibana-plugin-core-server.intervalhistogram.exceeds.md) + +## IntervalHistogram.exceeds property + +Signature: + +```typescript +exceeds: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.fromtimestamp.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.fromtimestamp.md new file mode 100644 index 0000000000000..00fa8dcb84430 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.fromtimestamp.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [fromTimestamp](./kibana-plugin-core-server.intervalhistogram.fromtimestamp.md) + +## IntervalHistogram.fromTimestamp property + +Signature: + +```typescript +fromTimestamp: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.lastupdatedat.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.lastupdatedat.md new file mode 100644 index 0000000000000..58e75fc2ba437 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.lastupdatedat.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [lastUpdatedAt](./kibana-plugin-core-server.intervalhistogram.lastupdatedat.md) + +## IntervalHistogram.lastUpdatedAt property + +Signature: + +```typescript +lastUpdatedAt: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.max.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.max.md new file mode 100644 index 0000000000000..14d7fe6b68c4b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.max.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [max](./kibana-plugin-core-server.intervalhistogram.max.md) + +## IntervalHistogram.max property + +Signature: + +```typescript +max: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.md new file mode 100644 index 0000000000000..d7fb889dce322 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) + +## IntervalHistogram interface + +an IntervalHistogram object that samples and reports the event loop delay over time. The delays will be reported in nanoseconds. + +Signature: + +```typescript +export interface IntervalHistogram +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [exceeds](./kibana-plugin-core-server.intervalhistogram.exceeds.md) | number | | +| [fromTimestamp](./kibana-plugin-core-server.intervalhistogram.fromtimestamp.md) | string | | +| [lastUpdatedAt](./kibana-plugin-core-server.intervalhistogram.lastupdatedat.md) | string | | +| [max](./kibana-plugin-core-server.intervalhistogram.max.md) | number | | +| [mean](./kibana-plugin-core-server.intervalhistogram.mean.md) | number | | +| [min](./kibana-plugin-core-server.intervalhistogram.min.md) | number | | +| [percentiles](./kibana-plugin-core-server.intervalhistogram.percentiles.md) | {
50: number;
75: number;
95: number;
99: number;
} | | +| [stddev](./kibana-plugin-core-server.intervalhistogram.stddev.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.mean.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.mean.md new file mode 100644 index 0000000000000..e6794bfa5fe52 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.mean.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [mean](./kibana-plugin-core-server.intervalhistogram.mean.md) + +## IntervalHistogram.mean property + +Signature: + +```typescript +mean: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.min.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.min.md new file mode 100644 index 0000000000000..d0eb929601f18 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.min.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [min](./kibana-plugin-core-server.intervalhistogram.min.md) + +## IntervalHistogram.min property + +Signature: + +```typescript +min: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.percentiles.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.percentiles.md new file mode 100644 index 0000000000000..b0adc9531c0b1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.percentiles.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [percentiles](./kibana-plugin-core-server.intervalhistogram.percentiles.md) + +## IntervalHistogram.percentiles property + +Signature: + +```typescript +percentiles: { + 50: number; + 75: number; + 95: number; + 99: number; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.stddev.md b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.stddev.md new file mode 100644 index 0000000000000..bca5ab56cb237 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.intervalhistogram.stddev.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) > [stddev](./kibana-plugin-core-server.intervalhistogram.stddev.md) + +## IntervalHistogram.stddev property + +Signature: + +```typescript +stddev: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 96bb82c8968df..66c0299669dc4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -19,6 +19,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [BasePath](./kibana-plugin-core-server.basepath.md) | Access or manipulate the Kibana base path | | [CspConfig](./kibana-plugin-core-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) | Wrapper of config schema. | +| [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) | | | [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [RouteValidationError](./kibana-plugin-core-server.routevalidationerror.md) | Error to return when the validation is not successful. | | [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) | | @@ -97,6 +98,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IExternalUrlPolicy](./kibana-plugin-core-server.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. | | [IKibanaResponse](./kibana-plugin-core-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | +| [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) | an IntervalHistogram object that samples and reports the event loop delay over time. The delays will be reported in nanoseconds. | | [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. | | [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | | diff --git a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md index 9803c0fbd53cc..1572c1ae3131e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md @@ -19,7 +19,8 @@ export interface OpsMetrics | [collected\_at](./kibana-plugin-core-server.opsmetrics.collected_at.md) | Date | Time metrics were recorded at. | | [concurrent\_connections](./kibana-plugin-core-server.opsmetrics.concurrent_connections.md) | OpsServerMetrics['concurrent_connections'] | number of current concurrent connections to the server | | [os](./kibana-plugin-core-server.opsmetrics.os.md) | OpsOsMetrics | OS related metrics | -| [process](./kibana-plugin-core-server.opsmetrics.process.md) | OpsProcessMetrics | Process related metrics | +| [process](./kibana-plugin-core-server.opsmetrics.process.md) | OpsProcessMetrics | Process related metrics. Deprecated in favor of processes field. | +| [processes](./kibana-plugin-core-server.opsmetrics.processes.md) | OpsProcessMetrics[] | Process related metrics. Reports an array of objects for each kibana pid. | | [requests](./kibana-plugin-core-server.opsmetrics.requests.md) | OpsServerMetrics['requests'] | server requests stats | | [response\_times](./kibana-plugin-core-server.opsmetrics.response_times.md) | OpsServerMetrics['response_times'] | server response time stats | diff --git a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.process.md b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.process.md index b3759fadafc0a..9da2c0644dc84 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.process.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.process.md @@ -4,7 +4,11 @@ ## OpsMetrics.process property -Process related metrics +> Warning: This API is now obsolete. +> +> + +Process related metrics. Deprecated in favor of processes field. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.processes.md b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.processes.md new file mode 100644 index 0000000000000..cf1f0a7c54475 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.processes.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) > [processes](./kibana-plugin-core-server.opsmetrics.processes.md) + +## OpsMetrics.processes property + +Process related metrics. Reports an array of objects for each kibana pid. + +Signature: + +```typescript +processes: OpsProcessMetrics[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md index 239f94e37d00e..d626b9cf8f98c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md @@ -4,7 +4,7 @@ ## OpsProcessMetrics.event\_loop\_delay property -node event loop delay +mean event loop delay since last collection Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md new file mode 100644 index 0000000000000..1d870b19f2d1f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OpsProcessMetrics](./kibana-plugin-core-server.opsprocessmetrics.md) > [event\_loop\_delay\_histogram](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md) + +## OpsProcessMetrics.event\_loop\_delay\_histogram property + +node event loop delay histogram since last collection + +Signature: + +```typescript +event_loop_delay_histogram: IntervalHistogram; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.md b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.md index 79763b783470e..198b668afca60 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsprocessmetrics.md @@ -16,7 +16,8 @@ export interface OpsProcessMetrics | Property | Type | Description | | --- | --- | --- | -| [event\_loop\_delay](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md) | number | node event loop delay | +| [event\_loop\_delay\_histogram](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md) | IntervalHistogram | node event loop delay histogram since last collection | +| [event\_loop\_delay](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md) | number | mean event loop delay since last collection | | [memory](./kibana-plugin-core-server.opsprocessmetrics.memory.md) | {
heap: {
total_in_bytes: number;
used_in_bytes: number;
size_limit: number;
};
resident_set_size_in_bytes: number;
} | process memory usage | | [pid](./kibana-plugin-core-server.opsprocessmetrics.pid.md) | number | pid of the kibana process | | [uptime\_in\_millis](./kibana-plugin-core-server.opsprocessmetrics.uptime_in_millis.md) | number | uptime of the kibana process | diff --git a/src/core/public/core_app/status/lib/load_status.test.ts b/src/core/public/core_app/status/lib/load_status.test.ts index e412192ea00ee..73c697c3d55aa 100644 --- a/src/core/public/core_app/status/lib/load_status.test.ts +++ b/src/core/public/core_app/status/lib/load_status.test.ts @@ -9,6 +9,7 @@ import { StatusResponse } from '../../../../types/status'; import { httpServiceMock } from '../../../http/http_service.mock'; import { notificationServiceMock } from '../../../notifications/notifications_service.mock'; +import { mocked } from '../../../../server/metrics/event_loop_delays/event_loop_delays_monitor.mocks'; import { loadStatus } from './load_status'; const mockedResponse: StatusResponse = { @@ -61,6 +62,7 @@ const mockedResponse: StatusResponse = { }, }, process: { + pid: 1, memory: { heap: { size_limit: 1000000, @@ -70,9 +72,25 @@ const mockedResponse: StatusResponse = { resident_set_size_in_bytes: 1, }, event_loop_delay: 1, - pid: 1, + event_loop_delay_histogram: mocked.createHistogram(), uptime_in_millis: 1, }, + processes: [ + { + pid: 1, + memory: { + heap: { + size_limit: 1000000, + used_in_bytes: 100, + total_in_bytes: 0, + }, + resident_set_size_in_bytes: 1, + }, + event_loop_delay: 1, + event_loop_delay_histogram: mocked.createHistogram(), + uptime_in_millis: 1, + }, + ], response_times: { avg_in_millis: 4000, max_in_millis: 8000, diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts index b8a134fbf8cd2..bc981c21ba975 100644 --- a/src/core/server/deprecations/deprecations_service.ts +++ b/src/core/server/deprecations/deprecations_service.ts @@ -39,7 +39,6 @@ import { SavedObjectsClientContract } from '../saved_objects/types'; * const deprecations: DeprecationsDetails[] = []; * const count = await getFooCount(savedObjectsClient); * if (count > 0) { - * // Example of a manual correctiveAction * deprecations.push({ * title: i18n.translate('xpack.foo.deprecations.title', { * defaultMessage: `Foo's are deprecated` diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 1c3a0850d3b79..f2eb7ccd3a253 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -378,7 +378,9 @@ export type { OpsProcessMetrics, MetricsServiceSetup, MetricsServiceStart, + IntervalHistogram, } from './metrics'; +export { EventLoopDelaysMonitor } from './metrics'; export type { I18nServiceSetup } from './i18n'; export type { diff --git a/src/core/server/metrics/collectors/collector.mock.ts b/src/core/server/metrics/collectors/collector.mock.ts index bf45925bf583f..088156fa2ff5e 100644 --- a/src/core/server/metrics/collectors/collector.mock.ts +++ b/src/core/server/metrics/collectors/collector.mock.ts @@ -8,8 +8,10 @@ import { MetricsCollector } from './types'; -const createCollector = (collectReturnValue: any = {}): jest.Mocked> => { - const collector: jest.Mocked> = { +const createCollector = ( + collectReturnValue: any = {} +): jest.Mocked> => { + const collector: jest.Mocked> = { collect: jest.fn().mockResolvedValue(collectReturnValue), reset: jest.fn(), }; diff --git a/src/core/server/metrics/collectors/mocks.ts b/src/core/server/metrics/collectors/mocks.ts index ad8dd9fa57966..425751899ddc9 100644 --- a/src/core/server/metrics/collectors/mocks.ts +++ b/src/core/server/metrics/collectors/mocks.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { MetricsCollector } from './types'; +import type { MetricsCollector } from './types'; +import { createMockOpsProcessMetrics } from './process.mocks'; const createMock = () => { const mocked: jest.Mocked> = { @@ -21,4 +22,5 @@ const createMock = () => { export const collectorMock = { create: createMock, + createOpsProcessMetrics: createMockOpsProcessMetrics, }; diff --git a/src/core/server/metrics/collectors/process.mocks.ts b/src/core/server/metrics/collectors/process.mocks.ts new file mode 100644 index 0000000000000..8ee43394b9251 --- /dev/null +++ b/src/core/server/metrics/collectors/process.mocks.ts @@ -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 { mocked } from '../event_loop_delays/event_loop_delays_monitor.mocks'; +import type { OpsProcessMetrics } from './types'; + +export function createMockOpsProcessMetrics(): OpsProcessMetrics { + const histogram = mocked.createHistogram(); + + return { + memory: { + heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, + resident_set_size_in_bytes: 1, + }, + event_loop_delay: 1, + event_loop_delay_histogram: histogram, + pid: 1, + uptime_in_millis: 1, + }; +} diff --git a/src/core/server/metrics/collectors/process.test.ts b/src/core/server/metrics/collectors/process.test.ts index 0395cd8d597fd..ff861d5c8bef1 100644 --- a/src/core/server/metrics/collectors/process.test.ts +++ b/src/core/server/metrics/collectors/process.test.ts @@ -9,6 +9,7 @@ import v8, { HeapInfo } from 'v8'; import { ProcessMetricsCollector } from './process'; +/* eslint-disable dot-notation */ describe('ProcessMetricsCollector', () => { let collector: ProcessMetricsCollector; @@ -20,28 +21,34 @@ describe('ProcessMetricsCollector', () => { jest.restoreAllMocks(); }); - it('collects pid from the process', async () => { - const metrics = await collector.collect(); + it('collects pid from the process', () => { + const metrics = collector.collect(); - expect(metrics.pid).toEqual(process.pid); + expect(metrics).toHaveLength(1); + expect(metrics[0].pid).toEqual(process.pid); }); - it('collects event loop delay', async () => { - const metrics = await collector.collect(); - - expect(metrics.event_loop_delay).toBeGreaterThan(0); + it('collects event loop delay', () => { + const mockEventLoopDelayMonitor = { collect: jest.fn().mockReturnValue({ mean: 13 }) }; + // @ts-expect-error-next-line readonly private method. + collector['eventLoopDelayMonitor'] = mockEventLoopDelayMonitor; + const metrics = collector.collect(); + expect(metrics).toHaveLength(1); + expect(metrics[0].event_loop_delay).toBe(13); + expect(mockEventLoopDelayMonitor.collect).toBeCalledTimes(1); }); - it('collects uptime info from the process', async () => { + it('collects uptime info from the process', () => { const uptime = 58986; jest.spyOn(process, 'uptime').mockImplementation(() => uptime); - const metrics = await collector.collect(); + const metrics = collector.collect(); - expect(metrics.uptime_in_millis).toEqual(uptime * 1000); + expect(metrics).toHaveLength(1); + expect(metrics[0].uptime_in_millis).toEqual(uptime * 1000); }); - it('collects memory info from the process', async () => { + it('collects memory info from the process', () => { const heapTotal = 58986; const heapUsed = 4688; const heapSizeLimit = 5788; @@ -61,11 +68,12 @@ describe('ProcessMetricsCollector', () => { } as HeapInfo) ); - const metrics = await collector.collect(); + const metrics = collector.collect(); - expect(metrics.memory.heap.total_in_bytes).toEqual(heapTotal); - expect(metrics.memory.heap.used_in_bytes).toEqual(heapUsed); - expect(metrics.memory.heap.size_limit).toEqual(heapSizeLimit); - expect(metrics.memory.resident_set_size_in_bytes).toEqual(rss); + expect(metrics).toHaveLength(1); + expect(metrics[0].memory.heap.total_in_bytes).toEqual(heapTotal); + expect(metrics[0].memory.heap.used_in_bytes).toEqual(heapUsed); + expect(metrics[0].memory.heap.size_limit).toEqual(heapSizeLimit); + expect(metrics[0].memory.resident_set_size_in_bytes).toEqual(rss); }); }); diff --git a/src/core/server/metrics/collectors/process.ts b/src/core/server/metrics/collectors/process.ts index d7ff967114f00..3acfda3e165ee 100644 --- a/src/core/server/metrics/collectors/process.ts +++ b/src/core/server/metrics/collectors/process.ts @@ -7,14 +7,26 @@ */ import v8 from 'v8'; -import { Bench } from '@hapi/hoek'; import { OpsProcessMetrics, MetricsCollector } from './types'; +import { EventLoopDelaysMonitor } from '../event_loop_delays'; -export class ProcessMetricsCollector implements MetricsCollector { - public async collect(): Promise { +export class ProcessMetricsCollector implements MetricsCollector { + static getMainThreadMetrics(processes: OpsProcessMetrics[]): undefined | OpsProcessMetrics { + /** + * Currently Kibana does not support multi-processes. + * Once we have multiple processes we can add a `name` field + * and filter on `name === 'server_worker'` to get the main thread. + */ + return processes[0]; + } + + private readonly eventLoopDelayMonitor = new EventLoopDelaysMonitor(); + + private getCurrentPidMetrics(): OpsProcessMetrics { + const eventLoopDelayHistogram = this.eventLoopDelayMonitor.collect(); const heapStats = v8.getHeapStatistics(); const memoryUsage = process.memoryUsage(); - const [eventLoopDelay] = await Promise.all([getEventLoopDelay()]); + return { memory: { heap: { @@ -25,19 +37,17 @@ export class ProcessMetricsCollector implements MetricsCollector => { - const bench = new Bench(); - return new Promise((resolve) => { - setImmediate(() => { - return resolve(bench.elapsed()); - }); - }); -}; + public reset() { + this.eventLoopDelayMonitor.reset(); + } +} diff --git a/src/core/server/metrics/collectors/types.ts b/src/core/server/metrics/collectors/types.ts index ec9746aaae769..4684e8008f4da 100644 --- a/src/core/server/metrics/collectors/types.ts +++ b/src/core/server/metrics/collectors/types.ts @@ -5,11 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { MaybePromise } from '@kbn/utility-types'; +import type { IntervalHistogram } from '../types'; /** Base interface for all metrics gatherers */ export interface MetricsCollector { /** collect the data currently gathered by the collector */ - collect(): Promise; + collect(): MaybePromise; /** reset the internal state of the collector */ reset(): void; } @@ -19,6 +21,8 @@ export interface MetricsCollector { * @public */ export interface OpsProcessMetrics { + /** pid of the kibana process */ + pid: number; /** process memory usage */ memory: { /** heap memory usage */ @@ -33,10 +37,10 @@ export interface OpsProcessMetrics { /** node rss */ resident_set_size_in_bytes: number; }; - /** node event loop delay */ + /** mean event loop delay since last collection*/ event_loop_delay: number; - /** pid of the kibana process */ - pid: number; + /** node event loop delay histogram since last collection */ + event_loop_delay_histogram: IntervalHistogram; /** uptime of the kibana process */ uptime_in_millis: number; } diff --git a/src/core/server/metrics/event_loop_delays/__mocks__/perf_hooks.ts b/src/core/server/metrics/event_loop_delays/__mocks__/perf_hooks.ts new file mode 100644 index 0000000000000..2a5477a1c4e9e --- /dev/null +++ b/src/core/server/metrics/event_loop_delays/__mocks__/perf_hooks.ts @@ -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 { mocked } from '../event_loop_delays_monitor.mocks'; + +export const monitorEventLoopDelay = jest.fn().mockImplementation(() => { + const mockedHistogram = mocked.createHistogram(); + + return { + ...mockedHistogram, + enable: jest.fn(), + percentile: jest.fn().mockImplementation((percentile: number) => { + return (mockedHistogram.percentiles as Record)[`${percentile}`]; + }), + disable: jest.fn(), + reset: jest.fn(), + }; +}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.mocks.ts b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.mocks.ts similarity index 54% rename from src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.mocks.ts rename to src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.mocks.ts index f266a27a7034f..ee96668cf3e7c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.mocks.ts +++ b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.mocks.ts @@ -6,26 +6,11 @@ * Side Public License, v 1. */ import moment from 'moment'; -import type { IntervalHistogram } from './event_loop_delays'; - -export const mockMonitorEnable = jest.fn(); -export const mockMonitorPercentile = jest.fn(); -export const mockMonitorReset = jest.fn(); -export const mockMonitorDisable = jest.fn(); -export const monitorEventLoopDelay = jest.fn().mockReturnValue({ - enable: mockMonitorEnable, - percentile: mockMonitorPercentile, - disable: mockMonitorDisable, - reset: mockMonitorReset, - ...createMockHistogram(), -}); - -jest.doMock('perf_hooks', () => ({ - monitorEventLoopDelay, -})); +import type { EventLoopDelaysMonitor } from './event_loop_delays_monitor'; +import type { IntervalHistogram } from '../types'; function createMockHistogram(overwrites: Partial = {}): IntervalHistogram { - const now = moment(); + const now = Date.now(); return { min: 9093120, @@ -33,8 +18,8 @@ function createMockHistogram(overwrites: Partial = {}): Inter mean: 11993238.600747818, exceeds: 0, stddev: 1168191.9357543814, - fromTimestamp: now.startOf('day').toISOString(), - lastUpdatedAt: now.toISOString(), + fromTimestamp: moment(now).toISOString(), + lastUpdatedAt: moment(now).toISOString(), percentiles: { '50': 12607487, '75': 12615679, @@ -45,6 +30,22 @@ function createMockHistogram(overwrites: Partial = {}): Inter }; } +function createMockEventLoopDelaysMonitor() { + const mockCollect = jest.fn(); + const MockEventLoopDelaysMonitor: jest.MockedClass< + typeof EventLoopDelaysMonitor + > = jest.fn().mockReturnValue({ + collect: mockCollect, + reset: jest.fn(), + stop: jest.fn(), + }); + + mockCollect.mockReturnValue(createMockHistogram()); + + return new MockEventLoopDelaysMonitor(); +} + export const mocked = { createHistogram: createMockHistogram, + createEventLoopDelaysMonitor: createMockEventLoopDelaysMonitor, }; diff --git a/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.test.ts b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.test.ts new file mode 100644 index 0000000000000..3e88dbca8f4e7 --- /dev/null +++ b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.test.ts @@ -0,0 +1,59 @@ +/* + * 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. + */ + +/* eslint-disable dot-notation */ +jest.mock('perf_hooks'); +import { monitorEventLoopDelay } from 'perf_hooks'; +import { EventLoopDelaysMonitor } from './event_loop_delays_monitor'; +import { mocked } from './event_loop_delays_monitor.mocks'; + +describe('EventLoopDelaysMonitor', () => { + beforeAll(() => { + jest.useFakeTimers('modern'); + const mockNow = jest.getRealSystemTime(); + jest.setSystemTime(mockNow); + }); + afterEach(() => jest.clearAllMocks()); + afterAll(() => jest.useRealTimers()); + + test('#constructor enables monitoring', () => { + const eventLoopDelaysMonitor = new EventLoopDelaysMonitor(); + expect(monitorEventLoopDelay).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(1); + }); + + test('#collect returns event loop delays histogram', () => { + const eventLoopDelaysMonitor = new EventLoopDelaysMonitor(); + expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(0); + expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(1); + const histogramData = eventLoopDelaysMonitor.collect(); + expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(2); + expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(1, 50); + expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(2, 75); + expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(3, 95); + expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(4, 99); + + // mocked perf_hook returns `mocked.createHistogram()`. + // This ensures that the wiring of the `collect` function is correct. + const mockedHistogram = mocked.createHistogram(); + expect(histogramData).toEqual(mockedHistogram); + }); + + test('#reset resets histogram data', () => { + const eventLoopDelaysMonitor = new EventLoopDelaysMonitor(); + eventLoopDelaysMonitor.reset(); + expect(eventLoopDelaysMonitor['loopMonitor'].reset).toBeCalledTimes(1); + }); + test('#stop disables monitoring event loop delays', () => { + const eventLoopDelaysMonitor = new EventLoopDelaysMonitor(); + expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(0); + eventLoopDelaysMonitor.stop(); + expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.ts b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.ts similarity index 58% rename from src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.ts rename to src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.ts index f5de44a061d5a..3dff847f83c9b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.ts +++ b/src/core/server/metrics/event_loop_delays/event_loop_delays_monitor.ts @@ -8,48 +8,41 @@ import type { EventLoopDelayMonitor } from 'perf_hooks'; import { monitorEventLoopDelay } from 'perf_hooks'; -import { MONITOR_EVENT_LOOP_DELAYS_RESOLUTION } from './constants'; +import type { IntervalHistogram } from '../types'; -export interface IntervalHistogram { - fromTimestamp: string; - lastUpdatedAt: string; - min: number; - max: number; - mean: number; - exceeds: number; - stddev: number; - percentiles: { - 50: number; - 75: number; - 95: number; - 99: number; - }; -} - -export class EventLoopDelaysCollector { +export class EventLoopDelaysMonitor { private readonly loopMonitor: EventLoopDelayMonitor; private fromTimestamp: Date; + /** + * Creating a new instance from EventLoopDelaysMonitor will + * automatically start tracking event loop delays. + */ constructor() { - const monitor = monitorEventLoopDelay({ - resolution: MONITOR_EVENT_LOOP_DELAYS_RESOLUTION, - }); + const monitor = monitorEventLoopDelay(); monitor.enable(); this.fromTimestamp = new Date(); this.loopMonitor = monitor; } - + /** + * Collect gathers event loop delays metrics from nodejs perf_hooks.monitorEventLoopDelay + * the histogram calculations start from the last time `reset` was called or this + * EventLoopDelaysMonitor instance was created. + * @returns {IntervalHistogram} + */ public collect(): IntervalHistogram { + const lastUpdated = new Date(); + this.loopMonitor.disable(); const { min, max, mean, exceeds, stddev } = this.loopMonitor; - return { + const collectedData: IntervalHistogram = { min, max, mean, exceeds, stddev, fromTimestamp: this.fromTimestamp.toISOString(), - lastUpdatedAt: new Date().toISOString(), + lastUpdatedAt: lastUpdated.toISOString(), percentiles: { 50: this.loopMonitor.percentile(50), 75: this.loopMonitor.percentile(75), @@ -57,13 +50,22 @@ export class EventLoopDelaysCollector { 99: this.loopMonitor.percentile(99), }, }; + + this.loopMonitor.enable(); + return collectedData; } + /** + * Resets the collected histogram data. + */ public reset() { this.loopMonitor.reset(); this.fromTimestamp = new Date(); } + /** + * Disables updating the interval timer for collecting new data points. + */ public stop() { this.loopMonitor.disable(); } diff --git a/src/core/server/metrics/event_loop_delays/index.ts b/src/core/server/metrics/event_loop_delays/index.ts new file mode 100644 index 0000000000000..bc9cda18d443b --- /dev/null +++ b/src/core/server/metrics/event_loop_delays/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { EventLoopDelaysMonitor } from './event_loop_delays_monitor'; diff --git a/src/core/server/metrics/index.ts b/src/core/server/metrics/index.ts index 0631bb2b35801..797a0ae8c3f00 100644 --- a/src/core/server/metrics/index.ts +++ b/src/core/server/metrics/index.ts @@ -12,8 +12,10 @@ export type { MetricsServiceSetup, MetricsServiceStart, OpsMetrics, + IntervalHistogram, } from './types'; export type { OpsProcessMetrics, OpsServerMetrics, OpsOsMetrics } from './collectors'; export { MetricsService } from './metrics_service'; export { opsConfig } from './ops_config'; export type { OpsConfigType } from './ops_config'; +export { EventLoopDelaysMonitor } from './event_loop_delays'; diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts index e535b9babf92b..2d7a6bebf255e 100644 --- a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts +++ b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts @@ -8,19 +8,15 @@ import { OpsMetrics } from '..'; import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; +import { collectorMock } from '../collectors/mocks'; function createBaseOpsMetrics(): OpsMetrics { + const mockProcess = collectorMock.createOpsProcessMetrics(); + return { collected_at: new Date('2020-01-01 01:00:00'), - process: { - memory: { - heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, - resident_set_size_in_bytes: 1, - }, - event_loop_delay: 1, - pid: 1, - uptime_in_millis: 1, - }, + process: mockProcess, + processes: [mockProcess], os: { platform: 'darwin' as const, platformRelease: 'test', diff --git a/src/core/server/metrics/metrics_service.mock.ts b/src/core/server/metrics/metrics_service.mock.ts index b023824df64d3..5c41e78d8efcb 100644 --- a/src/core/server/metrics/metrics_service.mock.ts +++ b/src/core/server/metrics/metrics_service.mock.ts @@ -8,8 +8,9 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; - import type { MetricsService } from './metrics_service'; +import { collectorMock } from './collectors/mocks'; +import { mocked as eventLoopDelaysMonitorMock } from './event_loop_delays/event_loop_delays_monitor.mocks'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart, @@ -22,18 +23,14 @@ const createInternalSetupContractMock = () => { collectionInterval: 30000, getOpsMetrics$: jest.fn(), }; + + const processMock = collectorMock.createOpsProcessMetrics(); + setupContract.getOpsMetrics$.mockReturnValue( new BehaviorSubject({ collected_at: new Date('2020-01-01 01:00:00'), - process: { - memory: { - heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, - resident_set_size_in_bytes: 1, - }, - event_loop_delay: 1, - pid: 1, - uptime_in_millis: 1, - }, + process: processMock, + processes: [processMock], os: { platform: 'darwin' as const, platformRelease: 'test', @@ -81,4 +78,5 @@ export const metricsServiceMock = { createStartContract: createStartContractMock, createInternalSetupContract: createInternalSetupContractMock, createInternalStartContract: createInternalStartContractMock, + createEventLoopDelaysMonitor: eventLoopDelaysMonitorMock.createEventLoopDelaysMonitor, }; diff --git a/src/core/server/metrics/ops_metrics_collector.test.ts b/src/core/server/metrics/ops_metrics_collector.test.ts index 3faa771db1dae..7d263d8b7d6af 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.ts @@ -28,7 +28,7 @@ describe('OpsMetricsCollector', () => { describe('#collect', () => { it('gathers metrics from the underlying collectors', async () => { mockOsCollector.collect.mockResolvedValue('osMetrics'); - mockProcessCollector.collect.mockResolvedValue('processMetrics'); + mockProcessCollector.collect.mockResolvedValue(['processMetrics']); mockServerCollector.collect.mockResolvedValue({ requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', @@ -43,6 +43,7 @@ describe('OpsMetricsCollector', () => { expect(metrics).toEqual({ collected_at: expect.any(Date), process: 'processMetrics', + processes: ['processMetrics'], os: 'osMetrics', requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', diff --git a/src/core/server/metrics/ops_metrics_collector.ts b/src/core/server/metrics/ops_metrics_collector.ts index 74e8b8246d83c..376b10dcccdda 100644 --- a/src/core/server/metrics/ops_metrics_collector.ts +++ b/src/core/server/metrics/ops_metrics_collector.ts @@ -28,14 +28,21 @@ export class OpsMetricsCollector implements MetricsCollector { } public async collect(): Promise { - const [process, os, server] = await Promise.all([ + const [processes, os, server] = await Promise.all([ this.processCollector.collect(), this.osCollector.collect(), this.serverCollector.collect(), ]); + return { collected_at: new Date(), - process, + /** + * Kibana does not yet support multi-process nodes. + * `processes` is just an Array(1) only returning the current process's data + * which is why we can just use processes[0] for `process` + */ + process: processes[0], + processes, os, ...server, }; diff --git a/src/core/server/metrics/types.ts b/src/core/server/metrics/types.ts index d70b8c9ff4208..550a60d0d295a 100644 --- a/src/core/server/metrics/types.ts +++ b/src/core/server/metrics/types.ts @@ -7,7 +7,7 @@ */ import { Observable } from 'rxjs'; -import { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors'; +import type { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors'; /** * APIs to retrieves metrics gathered and exposed by the core platform. @@ -51,8 +51,13 @@ export type InternalMetricsServiceStart = MetricsServiceStart; export interface OpsMetrics { /** Time metrics were recorded at. */ collected_at: Date; - /** Process related metrics */ + /** + * Process related metrics. + * @deprecated use the processes field instead. + */ process: OpsProcessMetrics; + /** Process related metrics. Reports an array of objects for each kibana pid.*/ + processes: OpsProcessMetrics[]; /** OS related metrics */ os: OpsOsMetrics; /** server response time stats */ @@ -62,3 +67,37 @@ export interface OpsMetrics { /** number of current concurrent connections to the server */ concurrent_connections: OpsServerMetrics['concurrent_connections']; } + +/** + * an IntervalHistogram object that samples and reports the event loop delay over time. + * The delays will be reported in nanoseconds. + * + * @public + */ +export interface IntervalHistogram { + // The first timestamp the interval timer kicked in for collecting data points. + fromTimestamp: string; + // Last timestamp the interval timer kicked in for collecting data points. + lastUpdatedAt: string; + // The minimum recorded event loop delay. + min: number; + // The maximum recorded event loop delay. + max: number; + // The mean of the recorded event loop delays. + mean: number; + // The number of times the event loop delay exceeded the maximum 1 hour event loop delay threshold. + exceeds: number; + // The standard deviation of the recorded event loop delays. + stddev: number; + // An object detailing the accumulated percentile distribution. + percentiles: { + // 50th percentile of delays of the collected data points. + 50: number; + // 75th percentile of delays of the collected data points. + 75: number; + // 95th percentile of delays of the collected data points. + 95: number; + // 99th percentile of delays of the collected data points. + 99: number; + }; +} diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 882694e3dee14..5423625926228 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -915,6 +915,16 @@ export interface ErrorHttpResponseOptions { headers?: ResponseHeaders; } +// Warning: (ae-missing-release-tag) "EventLoopDelaysMonitor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class EventLoopDelaysMonitor { + constructor(); + collect(): IntervalHistogram; + reset(): void; + stop(): void; +} + // @public (undocumented) export interface ExecutionContextSetup { withContext(context: KibanaExecutionContext | undefined, fn: (...args: any[]) => R): R; @@ -1148,6 +1158,31 @@ export interface IKibanaSocket { }): Promise; } +// @public +export interface IntervalHistogram { + // (undocumented) + exceeds: number; + // (undocumented) + fromTimestamp: string; + // (undocumented) + lastUpdatedAt: string; + // (undocumented) + max: number; + // (undocumented) + mean: number; + // (undocumented) + min: number; + // (undocumented) + percentiles: { + 50: number; + 75: number; + 95: number; + 99: number; + }; + // (undocumented) + stddev: number; +} + // @public (undocumented) export interface IRenderOptions { includeUserSettings?: boolean; @@ -1433,7 +1468,9 @@ export interface OpsMetrics { collected_at: Date; concurrent_connections: OpsServerMetrics['concurrent_connections']; os: OpsOsMetrics; + // @deprecated process: OpsProcessMetrics; + processes: OpsProcessMetrics[]; requests: OpsServerMetrics['requests']; response_times: OpsServerMetrics['response_times']; } @@ -1474,6 +1511,7 @@ export interface OpsOsMetrics { // @public export interface OpsProcessMetrics { event_loop_delay: number; + event_loop_delay_histogram: IntervalHistogram; memory: { heap: { total_in_bytes: number; diff --git a/src/core/server/status/routes/status.ts b/src/core/server/status/routes/status.ts index 43a596bd1e0ec..df0300c9fa0c2 100644 --- a/src/core/server/status/routes/status.ts +++ b/src/core/server/status/routes/status.ts @@ -106,6 +106,7 @@ export const registerStatusRoute = ({ router, config, metrics, status }: Deps) = collection_interval_in_millis: metrics.collectionInterval, os: lastMetrics.os, process: lastMetrics.process, + processes: lastMetrics.processes, response_times: lastMetrics.response_times, concurrent_connections: lastMetrics.concurrent_connections, requests: { diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/constants.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/constants.ts index d6201deff5fec..4661441a15a6b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/constants.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/constants.ts @@ -31,11 +31,6 @@ export const MONITOR_EVENT_LOOP_DELAYS_RESET = 24 * 60 * 60 * 1000; */ export const MONITOR_EVENT_LOOP_DELAYS_START = 1 * 60 * 1000; -/** - * Event loop monitoring sampling rate in milliseconds. - */ -export const MONITOR_EVENT_LOOP_DELAYS_RESOLUTION = 10; - /** * Mean event loop delay threshold for logging a warning. */ diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.test.ts deleted file mode 100644 index b40030e210176..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/event_loop_delays.test.ts +++ /dev/null @@ -1,63 +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 - * 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 { - mockMonitorEnable, - mockMonitorPercentile, - monitorEventLoopDelay, - mockMonitorReset, - mockMonitorDisable, -} from './event_loop_delays.mocks'; -import { EventLoopDelaysCollector } from './event_loop_delays'; - -describe('EventLoopDelaysCollector', () => { - jest.useFakeTimers('modern'); - const mockNow = jest.getRealSystemTime(); - jest.setSystemTime(mockNow); - - beforeEach(() => jest.clearAllMocks()); - afterAll(() => jest.useRealTimers()); - - test('#constructor enables monitoring', () => { - new EventLoopDelaysCollector(); - expect(monitorEventLoopDelay).toBeCalledWith({ resolution: 10 }); - expect(mockMonitorEnable).toBeCalledTimes(1); - }); - - test('#collect returns event loop delays histogram', () => { - const eventLoopDelaysCollector = new EventLoopDelaysCollector(); - const histogramData = eventLoopDelaysCollector.collect(); - expect(mockMonitorPercentile).toHaveBeenNthCalledWith(1, 50); - expect(mockMonitorPercentile).toHaveBeenNthCalledWith(2, 75); - expect(mockMonitorPercentile).toHaveBeenNthCalledWith(3, 95); - expect(mockMonitorPercentile).toHaveBeenNthCalledWith(4, 99); - - expect(Object.keys(histogramData)).toMatchInlineSnapshot(` - Array [ - "min", - "max", - "mean", - "exceeds", - "stddev", - "fromTimestamp", - "lastUpdatedAt", - "percentiles", - ] - `); - }); - test('#reset resets histogram data', () => { - const eventLoopDelaysCollector = new EventLoopDelaysCollector(); - eventLoopDelaysCollector.reset(); - expect(mockMonitorReset).toBeCalledTimes(1); - }); - test('#stop disables monitoring event loop delays', () => { - const eventLoopDelaysCollector = new EventLoopDelaysCollector(); - eventLoopDelaysCollector.stop(); - expect(mockMonitorDisable).toBeCalledTimes(1); - }); -}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts index 94840ccfc2748..64668a5f23de1 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts @@ -14,7 +14,7 @@ import { createRootWithCorePlugins, } from '../../../../../../../core/test_helpers/kbn_server'; import { rollDailyData } from '../daily'; -import { mocked } from '../../event_loop_delays.mocks'; +import { metricsServiceMock } from '../../../../../../../core/server/mocks'; import { SAVED_OBJECTS_DAILY_TYPE, @@ -26,18 +26,20 @@ import moment from 'moment'; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), }); - +const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); function createRawObject(date: moment.MomentInput) { const pid = Math.round(Math.random() * 10000); + const instanceUuid = 'mock_instance'; + return { type: SAVED_OBJECTS_DAILY_TYPE, - id: serializeSavedObjectId({ pid, date }), + id: serializeSavedObjectId({ pid, date, instanceUuid }), attributes: { - ...mocked.createHistogram({ - fromTimestamp: moment(date).startOf('day').toISOString(), - lastUpdatedAt: moment(date).toISOString(), - }), + ...eventLoopDelaysMonitor.collect(), + fromTimestamp: moment(date).startOf('day').toISOString(), + lastUpdatedAt: moment(date).toISOString(), processId: pid, + instanceUuid, }, }; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.test.ts index 022040615bd45..ddae0ff302829 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.test.ts @@ -11,21 +11,19 @@ import { serializeSavedObjectId, deleteHistogramSavedObjects, } from './saved_objects'; -import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks'; +import { savedObjectsRepositoryMock, metricsServiceMock } from '../../../../../core/server/mocks'; import type { SavedObjectsFindResponse } from '../../../../../core/server/'; -import { mocked } from './event_loop_delays.mocks'; describe('serializeSavedObjectId', () => { it('returns serialized id', () => { - const id = serializeSavedObjectId({ date: 1623233091278, pid: 123 }); - expect(id).toBe('123::09062021'); + const id = serializeSavedObjectId({ instanceUuid: 'mock_uuid', date: 1623233091278, pid: 123 }); + expect(id).toBe('mock_uuid::123::09062021'); }); }); describe('storeHistogram', () => { - const mockHistogram = mocked.createHistogram(); + const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); const mockInternalRepository = savedObjectsRepositoryMock.create(); - jest.useFakeTimers('modern'); const mockNow = jest.getRealSystemTime(); jest.setSystemTime(mockNow); @@ -34,13 +32,15 @@ describe('storeHistogram', () => { afterAll(() => jest.useRealTimers()); it('stores histogram data in a savedObject', async () => { - await storeHistogram(mockHistogram, mockInternalRepository); + const mockHistogram = eventLoopDelaysMonitor.collect(); + const instanceUuid = 'mock_uuid'; + await storeHistogram(mockHistogram, mockInternalRepository, instanceUuid); const pid = process.pid; - const id = serializeSavedObjectId({ date: mockNow, pid }); + const id = serializeSavedObjectId({ date: mockNow, pid, instanceUuid }); expect(mockInternalRepository.create).toBeCalledWith( 'event_loop_delays_daily', - { ...mockHistogram, processId: pid }, + { ...mockHistogram, processId: pid, instanceUuid }, { id, overwrite: true } ); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.ts index 610a6697da364..57a9bb3b739c0 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/saved_objects.ts @@ -12,12 +12,12 @@ import type { ISavedObjectsRepository, } from 'kibana/server'; import moment from 'moment'; -import type { IntervalHistogram } from './event_loop_delays'; - +import type { IntervalHistogram } from 'kibana/server'; export const SAVED_OBJECTS_DAILY_TYPE = 'event_loop_delays_daily'; export interface EventLoopDelaysDaily extends SavedObjectAttributes, IntervalHistogram { processId: number; + instanceUuid: string; } export function registerSavedObjectTypes(registerType: SavedObjectsServiceSetup['registerType']) { @@ -35,10 +35,18 @@ export function registerSavedObjectTypes(registerType: SavedObjectsServiceSetup[ }); } -export function serializeSavedObjectId({ date, pid }: { date: moment.MomentInput; pid: number }) { +export function serializeSavedObjectId({ + date, + pid, + instanceUuid, +}: { + date: moment.MomentInput; + pid: number; + instanceUuid: string; +}) { const formattedDate = moment(date).format('DDMMYYYY'); - return `${pid}::${formattedDate}`; + return `${instanceUuid}::${pid}::${formattedDate}`; } export async function deleteHistogramSavedObjects( @@ -59,14 +67,15 @@ export async function deleteHistogramSavedObjects( export async function storeHistogram( histogram: IntervalHistogram, - internalRepository: ISavedObjectsRepository + internalRepository: ISavedObjectsRepository, + instanceUuid: string ) { const pid = process.pid; - const id = serializeSavedObjectId({ date: histogram.lastUpdatedAt, pid }); + const id = serializeSavedObjectId({ date: histogram.lastUpdatedAt, pid, instanceUuid }); return await internalRepository.create( SAVED_OBJECTS_DAILY_TYPE, - { ...histogram, processId: pid }, + { ...histogram, processId: pid, instanceUuid }, { id, overwrite: true } ); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/schema.ts index 319e8c77438b8..757e96e5602f0 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/schema.ts @@ -11,6 +11,7 @@ import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; export interface EventLoopDelaysUsageReport { daily: Array<{ processId: number; + instanceUuid: string; lastUpdatedAt: string; fromTimestamp: string; min: number; @@ -37,6 +38,12 @@ export const eventLoopDelaysUsageSchema: MakeSchemaFrom { + const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); const mockInternalRepository = savedObjectsRepositoryMock.create(); const stopMonitoringEventLoop$ = new Subject(); + const instanceUuid = 'mock_uuid'; beforeAll(() => jest.useFakeTimers('modern')); beforeEach(() => jest.clearAllMocks()); afterEach(() => stopMonitoringEventLoop$.next()); - it('initializes EventLoopDelaysCollector and starts timer', () => { + it('collects eventLoopDelaysMonitor metrics after start delay', () => { const collectionStartDelay = 1000; - startTrackingEventLoopDelaysUsage(mockInternalRepository, stopMonitoringEventLoop$, { - collectionStartDelay, - }); + startTrackingEventLoopDelaysUsage( + mockInternalRepository, + instanceUuid, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + collectionStartDelay, + } + ); - expect(monitorEventLoopDelay).toBeCalledTimes(1); - expect(mockMonitorPercentile).toBeCalledTimes(0); + expect(eventLoopDelaysMonitor.collect).toBeCalledTimes(0); jest.advanceTimersByTime(collectionStartDelay); - expect(mockMonitorPercentile).toBeCalled(); + expect(eventLoopDelaysMonitor.collect).toBeCalledTimes(1); }); it('stores event loop delays every collectionInterval duration', () => { const collectionStartDelay = 100; const collectionInterval = 1000; - startTrackingEventLoopDelaysUsage(mockInternalRepository, stopMonitoringEventLoop$, { - collectionStartDelay, - collectionInterval, - }); + startTrackingEventLoopDelaysUsage( + mockInternalRepository, + instanceUuid, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + collectionStartDelay, + collectionInterval, + } + ); expect(mockInternalRepository.create).toBeCalledTimes(0); jest.advanceTimersByTime(collectionStartDelay); @@ -54,28 +60,39 @@ describe('startTrackingEventLoopDelaysUsage', () => { expect(mockInternalRepository.create).toBeCalledTimes(3); }); - it('resets histogram every histogramReset duration', () => { + it('resets eventLoopDelaysMonitor every histogramReset duration', () => { const collectionStartDelay = 0; const collectionInterval = 1000; const histogramReset = 5000; - startTrackingEventLoopDelaysUsage(mockInternalRepository, stopMonitoringEventLoop$, { - collectionStartDelay, - collectionInterval, - histogramReset, - }); - expect(mockMonitorReset).toBeCalledTimes(0); + startTrackingEventLoopDelaysUsage( + mockInternalRepository, + instanceUuid, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + collectionStartDelay, + collectionInterval, + histogramReset, + } + ); + + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(0); jest.advanceTimersByTime(collectionInterval * 5); - expect(mockMonitorReset).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(1); jest.advanceTimersByTime(collectionInterval * 5); - expect(mockMonitorReset).toBeCalledTimes(2); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(2); }); it('stops monitoring event loop delays once stopMonitoringEventLoop$.next is called', () => { - startTrackingEventLoopDelaysUsage(mockInternalRepository, stopMonitoringEventLoop$); - - expect(mockMonitorDisable).toBeCalledTimes(0); + startTrackingEventLoopDelaysUsage( + mockInternalRepository, + instanceUuid, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor + ); + expect(eventLoopDelaysMonitor.stop).toBeCalledTimes(0); stopMonitoringEventLoop$.next(); - expect(mockMonitorDisable).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor.stop).toBeCalledTimes(1); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_delays.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_delays.ts index 70638d3b07cbc..facdb549d0df7 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_delays.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_delays.ts @@ -9,13 +9,13 @@ import { takeUntil, finalize, map } from 'rxjs/operators'; import { Observable, timer } from 'rxjs'; import type { ISavedObjectsRepository } from 'kibana/server'; +import type { EventLoopDelaysMonitor } from '../../../../../core/server'; import { MONITOR_EVENT_LOOP_DELAYS_START, MONITOR_EVENT_LOOP_DELAYS_INTERVAL, MONITOR_EVENT_LOOP_DELAYS_RESET, } from './constants'; import { storeHistogram } from './saved_objects'; -import { EventLoopDelaysCollector } from './event_loop_delays'; /** * The monitoring of the event loop starts immediately. @@ -24,7 +24,9 @@ import { EventLoopDelaysCollector } from './event_loop_delays'; */ export function startTrackingEventLoopDelaysUsage( internalRepository: ISavedObjectsRepository, + instanceUuid: string, stopMonitoringEventLoop$: Observable, + eventLoopDelaysMonitor: EventLoopDelaysMonitor, configs: { collectionStartDelay?: number; collectionInterval?: number; @@ -37,20 +39,19 @@ export function startTrackingEventLoopDelaysUsage( histogramReset = MONITOR_EVENT_LOOP_DELAYS_RESET, } = configs; - const eventLoopDelaysCollector = new EventLoopDelaysCollector(); const resetOnCount = Math.ceil(histogramReset / collectionInterval); timer(collectionStartDelay, collectionInterval) .pipe( map((i) => (i + 1) % resetOnCount === 0), takeUntil(stopMonitoringEventLoop$), - finalize(() => eventLoopDelaysCollector.stop()) + finalize(() => eventLoopDelaysMonitor.stop()) ) .subscribe(async (shouldReset) => { - const histogram = eventLoopDelaysCollector.collect(); + const histogram = eventLoopDelaysMonitor.collect(); if (shouldReset) { - eventLoopDelaysCollector.reset(); + eventLoopDelaysMonitor.reset(); } - await storeHistogram(histogram, internalRepository); + await storeHistogram(histogram, internalRepository, instanceUuid); }); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.test.ts index 1ff49a735a775..49b1943033719 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.test.ts @@ -7,13 +7,8 @@ */ import { Subject } from 'rxjs'; -import { - mockMonitorPercentile, - monitorEventLoopDelay, - mockMonitorReset, -} from './event_loop_delays.mocks'; +import { loggingSystemMock, metricsServiceMock } from '../../../../../core/server/mocks'; import { startTrackingEventLoopDelaysThreshold } from './track_threshold'; -import { loggingSystemMock } from '../../../../../core/server/mocks'; import { usageCountersServiceMock } from '../../../../usage_collection/server/usage_counters/usage_counters_service.mock'; describe('startTrackingEventLoopDelaysThreshold', () => { @@ -21,6 +16,7 @@ describe('startTrackingEventLoopDelaysThreshold', () => { const stopMonitoringEventLoop$ = new Subject(); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockEventLoopCounter = mockUsageCountersSetup.createUsageCounter('testCounter'); + const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); beforeAll(() => jest.useFakeTimers('modern')); beforeEach(() => jest.clearAllMocks()); @@ -29,15 +25,20 @@ describe('startTrackingEventLoopDelaysThreshold', () => { it('initializes EventLoopDelaysCollector and starts timer', () => { const collectionStartDelay = 1000; const warnThreshold = 1000; - startTrackingEventLoopDelaysThreshold(mockEventLoopCounter, logger, stopMonitoringEventLoop$, { - warnThreshold, - collectionStartDelay, - }); + startTrackingEventLoopDelaysThreshold( + mockEventLoopCounter, + logger, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + warnThreshold, + collectionStartDelay, + } + ); - expect(monitorEventLoopDelay).toBeCalledTimes(1); - expect(mockMonitorPercentile).toBeCalledTimes(0); + expect(eventLoopDelaysMonitor.collect).toBeCalledTimes(0); jest.advanceTimersByTime(collectionStartDelay); - expect(mockMonitorPercentile).toBeCalled(); + expect(eventLoopDelaysMonitor.collect).toBeCalledTimes(1); }); it('logs a warning and increments usage counter when the mean delay exceeds the threshold', () => { @@ -45,48 +46,60 @@ describe('startTrackingEventLoopDelaysThreshold', () => { const collectionInterval = 1000; const warnThreshold = 10; - startTrackingEventLoopDelaysThreshold(mockEventLoopCounter, logger, stopMonitoringEventLoop$, { - warnThreshold, - collectionStartDelay, - collectionInterval, - }); + startTrackingEventLoopDelaysThreshold( + mockEventLoopCounter, + logger, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + warnThreshold, + collectionStartDelay, + collectionInterval, + } + ); expect(logger.warn).toBeCalledTimes(0); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(0); - expect(mockMonitorReset).toBeCalledTimes(0); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(0); jest.advanceTimersByTime(collectionStartDelay); expect(logger.warn).toBeCalledTimes(1); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(1); - expect(mockMonitorReset).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(1); jest.advanceTimersByTime(collectionInterval); expect(logger.warn).toBeCalledTimes(2); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(2); - expect(mockMonitorReset).toBeCalledTimes(2); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(2); jest.advanceTimersByTime(collectionInterval); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(3); expect(logger.warn).toBeCalledTimes(3); - expect(mockMonitorReset).toBeCalledTimes(3); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(3); }); it('does not log warning or increment usage if threshold did not exceed mean delay', () => { const collectionStartDelay = 100; const warnThreshold = 15; - startTrackingEventLoopDelaysThreshold(mockEventLoopCounter, logger, stopMonitoringEventLoop$, { - warnThreshold, - collectionStartDelay, - }); + startTrackingEventLoopDelaysThreshold( + mockEventLoopCounter, + logger, + stopMonitoringEventLoop$, + eventLoopDelaysMonitor, + { + warnThreshold, + collectionStartDelay, + } + ); expect(logger.warn).toBeCalledTimes(0); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(0); - expect(mockMonitorReset).toBeCalledTimes(0); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(0); jest.advanceTimersByTime(collectionStartDelay); expect(logger.warn).toBeCalledTimes(0); expect(mockEventLoopCounter.incrementCounter).toBeCalledTimes(0); - expect(mockMonitorReset).toBeCalledTimes(1); + expect(eventLoopDelaysMonitor.reset).toBeCalledTimes(1); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.ts index 246d88496a158..ba4e12a7bfced 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/track_threshold.ts @@ -17,7 +17,7 @@ import { MONITOR_EVENT_LOOP_WARN_THRESHOLD, ONE_MILLISECOND_AS_NANOSECONDS, } from './constants'; -import { EventLoopDelaysCollector } from './event_loop_delays'; +import type { EventLoopDelaysMonitor } from '../../../../../core/server'; /** * The monitoring of the event loop starts immediately. @@ -29,6 +29,7 @@ export function startTrackingEventLoopDelaysThreshold( eventLoopCounter: UsageCounter, logger: Logger, stopMonitoringEventLoop$: Observable, + eventLoopDelaysMonitor: EventLoopDelaysMonitor, configs: { warnThreshold?: number; collectionStartDelay?: number; @@ -41,14 +42,13 @@ export function startTrackingEventLoopDelaysThreshold( collectionInterval = MONITOR_EVENT_LOOP_THRESHOLD_INTERVAL, } = configs; - const eventLoopDelaysCollector = new EventLoopDelaysCollector(); timer(collectionStartDelay, collectionInterval) .pipe( takeUntil(stopMonitoringEventLoop$), - finalize(() => eventLoopDelaysCollector.stop()) + finalize(() => eventLoopDelaysMonitor.stop()) ) .subscribe(async () => { - const { mean } = eventLoopDelaysCollector.collect(); + const { mean } = eventLoopDelaysMonitor.collect(); const meanDurationMs = moment .duration(mean / ONE_MILLISECOND_AS_NANOSECONDS) .asMilliseconds(); @@ -64,6 +64,6 @@ export function startTrackingEventLoopDelaysThreshold( }); } - eventLoopDelaysCollector.reset(); + eventLoopDelaysMonitor.reset(); }); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/index.test.ts.snap deleted file mode 100644 index 678237ffb6ea2..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`telemetry_ops_stats should return something when there is a metric 1`] = ` -Object { - "concurrent_connections": 20, - "os": Object { - "load": Object { - "15m": 3, - "1m": 0.5, - "5m": 1, - }, - "memory": Object { - "free_in_bytes": 10, - "total_in_bytes": 10, - "used_in_bytes": 10, - }, - "platform": "darwin", - "platformRelease": "test", - "uptime_in_millis": 1000, - }, - "process": Object { - "event_loop_delay": 10, - "memory": Object { - "heap": Object { - "size_limit": 0, - "total_in_bytes": 0, - "used_in_bytes": 0, - }, - "resident_set_size_in_bytes": 0, - }, - "uptime_in_millis": 1000, - }, - "requests": Object { - "disconnects": 10, - "total": 100, - }, - "response_times": Object { - "average": 100, - "max": 200, - }, - "timestamp": Any, -} -`; diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap new file mode 100644 index 0000000000000..f962eca858199 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`telemetry_ops_stats should return something when there is a metric 1`] = ` +Object { + "concurrent_connections": 1, + "os": Object { + "load": Object { + "15m": 1, + "1m": 1, + "5m": 1, + }, + "memory": Object { + "free_in_bytes": 1, + "total_in_bytes": 1, + "used_in_bytes": 1, + }, + "platform": "darwin", + "platformRelease": "test", + "uptime_in_millis": 1, + }, + "process": Object { + "event_loop_delay": 1, + "event_loop_delay_histogram": Any, + "memory": Object { + "heap": Object { + "size_limit": 1, + "total_in_bytes": 1, + "used_in_bytes": 1, + }, + "resident_set_size_in_bytes": 1, + }, + "uptime_in_millis": 1, + }, + "processes": Array [ + Object { + "event_loop_delay": 1, + "event_loop_delay_histogram": Any, + "memory": Object { + "heap": Object { + "size_limit": 1, + "total_in_bytes": 1, + "used_in_bytes": 1, + }, + "resident_set_size_in_bytes": 1, + }, + "uptime_in_millis": 1, + }, + ], + "requests": Object { + "disconnects": 1, + "total": 1, + }, + "response_times": Object { + "average": 1, + "max": 1, + }, + "timestamp": Any, +} +`; diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/index.test.ts b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.test.ts similarity index 50% rename from src/plugins/kibana_usage_collection/server/collectors/ops_stats/index.test.ts rename to src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.test.ts index dfd6a93b7ea18..54a7d3e220242 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/index.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.test.ts @@ -7,15 +7,16 @@ */ import { Subject } from 'rxjs'; +import { take } from 'rxjs/operators'; import { Collector, createUsageCollectionSetupMock, createCollectorFetchContextMock, } from '../../../../usage_collection/server/mocks'; -import { registerOpsStatsCollector } from './'; +import { registerOpsStatsCollector } from '.'; import { OpsMetrics } from '../../../../../core/server'; -import { loggingSystemMock } from '../../../../../core/server/mocks'; +import { loggingSystemMock, metricsServiceMock } from '../../../../../core/server/mocks'; const logger = loggingSystemMock.createLogger(); @@ -23,6 +24,8 @@ describe('telemetry_ops_stats', () => { let collector: Collector; const usageCollectionMock = createUsageCollectionSetupMock(); + const metricsServiceSetupMock = metricsServiceMock.createInternalSetupContract(); + usageCollectionMock.makeStatsCollector.mockImplementation((config) => { collector = new Collector(logger, config); return createUsageCollectionSetupMock().makeStatsCollector(config); @@ -31,45 +34,6 @@ describe('telemetry_ops_stats', () => { const metrics$ = new Subject(); const mockedFetchContext = createCollectorFetchContextMock(); - const metric: OpsMetrics = { - collected_at: new Date('2020-01-01 01:00:00'), - process: { - memory: { - heap: { - total_in_bytes: 0, - used_in_bytes: 0, - size_limit: 0, - }, - resident_set_size_in_bytes: 0, - }, - event_loop_delay: 10, - pid: 10, - uptime_in_millis: 1000, - }, - os: { - platform: 'darwin', - platformRelease: 'test', - load: { - '1m': 0.5, - '5m': 1, - '15m': 3, - }, - memory: { - total_in_bytes: 10, - free_in_bytes: 10, - used_in_bytes: 10, - }, - uptime_in_millis: 1000, - }, - response_times: { avg_in_millis: 100, max_in_millis: 200 }, - requests: { - disconnects: 10, - total: 100, - statusCodes: { 200: 100 }, - }, - concurrent_connections: 20, - }; - beforeAll(() => registerOpsStatsCollector(usageCollectionMock, metrics$)); afterAll(() => jest.clearAllTimers()); @@ -83,45 +47,18 @@ describe('telemetry_ops_stats', () => { }); test('should return something when there is a metric', async () => { - metrics$.next(metric); + const opsMetrics = await metricsServiceSetupMock.getOpsMetrics$().pipe(take(1)).toPromise(); + metrics$.next(opsMetrics); expect(collector.isReady()).toBe(true); expect(await collector.fetch(mockedFetchContext)).toMatchSnapshot({ - concurrent_connections: 20, - os: { - load: { - '15m': 3, - '1m': 0.5, - '5m': 1, - }, - memory: { - free_in_bytes: 10, - total_in_bytes: 10, - used_in_bytes: 10, - }, - platform: 'darwin', - platformRelease: 'test', - uptime_in_millis: 1000, - }, process: { - event_loop_delay: 10, - memory: { - heap: { - size_limit: 0, - total_in_bytes: 0, - used_in_bytes: 0, - }, - resident_set_size_in_bytes: 0, - }, - uptime_in_millis: 1000, - }, - requests: { - disconnects: 10, - total: 100, - }, - response_times: { - average: 100, - max: 200, + event_loop_delay_histogram: expect.any(Object), }, + processes: [ + { + event_loop_delay_histogram: expect.any(Object), + }, + ], timestamp: expect.any(String), }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.ts index 7959b67b4f468..ca7cca2a9de8c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/ops_stats_collector.ts @@ -33,6 +33,11 @@ export function getOpsStatsCollector( // Ensure we only include the same data that Metricbeat collection would get // @ts-expect-error delete metrics.process.pid; + for (const process of metrics.processes) { + // @ts-expect-error + delete process.pid; + } + const responseTimes = { average: metrics.response_times.avg_in_millis, max: metrics.response_times.max_in_millis, diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index dadb4283e84a7..f6c4bb01ca518 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -21,7 +21,7 @@ import type { Logger, CoreUsageDataStart, } from 'src/core/server'; -import { SavedObjectsClient } from '../../../core/server'; +import { SavedObjectsClient, EventLoopDelaysMonitor } from '../../../core/server'; import { startTrackingEventLoopDelaysUsage, startTrackingEventLoopDelaysThreshold, @@ -56,6 +56,7 @@ type SavedObjectsRegisterType = SavedObjectsServiceSetup['registerType']; export class KibanaUsageCollectionPlugin implements Plugin { private readonly logger: Logger; private readonly legacyConfig$: Observable; + private readonly instanceUuid: string; private savedObjectsClient?: ISavedObjectsRepository; private uiSettingsClient?: IUiSettingsClient; private metric$: Subject; @@ -68,6 +69,7 @@ export class KibanaUsageCollectionPlugin implements Plugin { this.legacyConfig$ = initializerContext.config.legacy.globalConfig$; this.metric$ = new Subject(); this.pluginStop$ = new Subject(); + this.instanceUuid = initializerContext.env.instanceUuid; } public setup(coreSetup: CoreSetup, { usageCollection }: KibanaUsageCollectionPluginsDepsSetup) { @@ -92,11 +94,17 @@ export class KibanaUsageCollectionPlugin implements Plugin { this.uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); core.metrics.getOpsMetrics$().subscribe(this.metric$); this.coreUsageData = core.coreUsageData; - startTrackingEventLoopDelaysUsage(this.savedObjectsClient, this.pluginStop$.asObservable()); + startTrackingEventLoopDelaysUsage( + this.savedObjectsClient, + this.instanceUuid, + this.pluginStop$.asObservable(), + new EventLoopDelaysMonitor() + ); startTrackingEventLoopDelaysThreshold( this.eventLoopUsageCounter, this.logger, - this.pluginStop$.asObservable() + this.pluginStop$.asObservable(), + new EventLoopDelaysMonitor() ); } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index b6cbc7abca17d..ab98b2521b2ac 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7012,6 +7012,12 @@ "description": "The process id of the monitored kibana instance." } }, + "instanceUuid": { + "type": "keyword", + "_meta": { + "description": "The uuid of the kibana instance." + } + }, "fromTimestamp": { "type": "date", "_meta": {