* create base service and collectors * wire the service into server, add mock * add collector tests * add main collector test * export metric types from server * add service and server tests * updates generated doc * improve doc * nits and comments * add disconnected requests test
This commit is contained in:
parent
1fdad2e2cc
commit
321952f998
|
@ -88,11 +88,16 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. |
|
||||
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
|
||||
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
|
||||
| [MetricsServiceSetup](./kibana-plugin-server.metricsservicesetup.md) | APIs to retrieves metrics gathered and exposed by the core platform. |
|
||||
| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. |
|
||||
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
|
||||
| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. |
|
||||
| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. |
|
||||
| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
|
||||
| [OpsMetrics](./kibana-plugin-server.opsmetrics.md) | Regroups metrics gathered by all the collectors. This contains metrics about the os/runtime, the kibana process and the http server. |
|
||||
| [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) | OS related metrics |
|
||||
| [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md) | Process related metrics |
|
||||
| [OpsServerMetrics](./kibana-plugin-server.opsservermetrics.md) | server related metrics |
|
||||
| [PackageInfo](./kibana-plugin-server.packageinfo.md) | |
|
||||
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. |
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [MetricsServiceSetup](./kibana-plugin-server.metricsservicesetup.md) > [getOpsMetrics$](./kibana-plugin-server.metricsservicesetup.getopsmetrics_.md)
|
||||
|
||||
## MetricsServiceSetup.getOpsMetrics$ property
|
||||
|
||||
Retrieve an observable emitting the [OpsMetrics](./kibana-plugin-server.opsmetrics.md) gathered. The observable will emit an initial value during core's `start` phase, and a new value every fixed interval of time, based on the `opts.interval` configuration property.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getOpsMetrics$: () => Observable<OpsMetrics>;
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```ts
|
||||
core.metrics.getOpsMetrics$().subscribe(metrics => {
|
||||
// do something with the metrics
|
||||
})
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [MetricsServiceSetup](./kibana-plugin-server.metricsservicesetup.md)
|
||||
|
||||
## MetricsServiceSetup interface
|
||||
|
||||
APIs to retrieves metrics gathered and exposed by the core platform.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface MetricsServiceSetup
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [getOpsMetrics$](./kibana-plugin-server.metricsservicesetup.getopsmetrics_.md) | <code>() => Observable<OpsMetrics></code> | Retrieve an observable emitting the [OpsMetrics](./kibana-plugin-server.opsmetrics.md) gathered. The observable will emit an initial value during core's <code>start</code> phase, and a new value every fixed interval of time, based on the <code>opts.interval</code> configuration property. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md) > [concurrent\_connections](./kibana-plugin-server.opsmetrics.concurrent_connections.md)
|
||||
|
||||
## OpsMetrics.concurrent\_connections property
|
||||
|
||||
number of current concurrent connections to the server
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
concurrent_connections: OpsServerMetrics['concurrent_connections'];
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md)
|
||||
|
||||
## OpsMetrics interface
|
||||
|
||||
Regroups metrics gathered by all the collectors. This contains metrics about the os/runtime, the kibana process and the http server.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface OpsMetrics
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [concurrent\_connections](./kibana-plugin-server.opsmetrics.concurrent_connections.md) | <code>OpsServerMetrics['concurrent_connections']</code> | number of current concurrent connections to the server |
|
||||
| [os](./kibana-plugin-server.opsmetrics.os.md) | <code>OpsOsMetrics</code> | OS related metrics |
|
||||
| [process](./kibana-plugin-server.opsmetrics.process.md) | <code>OpsProcessMetrics</code> | Process related metrics |
|
||||
| [requests](./kibana-plugin-server.opsmetrics.requests.md) | <code>OpsServerMetrics['requests']</code> | server requests stats |
|
||||
| [response\_times](./kibana-plugin-server.opsmetrics.response_times.md) | <code>OpsServerMetrics['response_times']</code> | server response time stats |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md) > [os](./kibana-plugin-server.opsmetrics.os.md)
|
||||
|
||||
## OpsMetrics.os property
|
||||
|
||||
OS related metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
os: OpsOsMetrics;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md) > [process](./kibana-plugin-server.opsmetrics.process.md)
|
||||
|
||||
## OpsMetrics.process property
|
||||
|
||||
Process related metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
process: OpsProcessMetrics;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md) > [requests](./kibana-plugin-server.opsmetrics.requests.md)
|
||||
|
||||
## OpsMetrics.requests property
|
||||
|
||||
server requests stats
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
requests: OpsServerMetrics['requests'];
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsMetrics](./kibana-plugin-server.opsmetrics.md) > [response\_times](./kibana-plugin-server.opsmetrics.response_times.md)
|
||||
|
||||
## OpsMetrics.response\_times property
|
||||
|
||||
server response time stats
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
response_times: OpsServerMetrics['response_times'];
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [distro](./kibana-plugin-server.opsosmetrics.distro.md)
|
||||
|
||||
## OpsOsMetrics.distro property
|
||||
|
||||
The os distrib. Only present for linux platforms
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
distro?: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [distroRelease](./kibana-plugin-server.opsosmetrics.distrorelease.md)
|
||||
|
||||
## OpsOsMetrics.distroRelease property
|
||||
|
||||
The os distrib release, prefixed by the os distrib. Only present for linux platforms
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
distroRelease?: string;
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [load](./kibana-plugin-server.opsosmetrics.load.md)
|
||||
|
||||
## OpsOsMetrics.load property
|
||||
|
||||
cpu load metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
load: {
|
||||
'1m': number;
|
||||
'5m': number;
|
||||
'15m': number;
|
||||
};
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md)
|
||||
|
||||
## OpsOsMetrics interface
|
||||
|
||||
OS related metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface OpsOsMetrics
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [distro](./kibana-plugin-server.opsosmetrics.distro.md) | <code>string</code> | The os distrib. Only present for linux platforms |
|
||||
| [distroRelease](./kibana-plugin-server.opsosmetrics.distrorelease.md) | <code>string</code> | The os distrib release, prefixed by the os distrib. Only present for linux platforms |
|
||||
| [load](./kibana-plugin-server.opsosmetrics.load.md) | <code>{</code><br/><code> '1m': number;</code><br/><code> '5m': number;</code><br/><code> '15m': number;</code><br/><code> }</code> | cpu load metrics |
|
||||
| [memory](./kibana-plugin-server.opsosmetrics.memory.md) | <code>{</code><br/><code> total_in_bytes: number;</code><br/><code> free_in_bytes: number;</code><br/><code> used_in_bytes: number;</code><br/><code> }</code> | system memory usage metrics |
|
||||
| [platform](./kibana-plugin-server.opsosmetrics.platform.md) | <code>NodeJS.Platform</code> | The os platform |
|
||||
| [platformRelease](./kibana-plugin-server.opsosmetrics.platformrelease.md) | <code>string</code> | The os platform release, prefixed by the platform name |
|
||||
| [uptime\_in\_millis](./kibana-plugin-server.opsosmetrics.uptime_in_millis.md) | <code>number</code> | the OS uptime |
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [memory](./kibana-plugin-server.opsosmetrics.memory.md)
|
||||
|
||||
## OpsOsMetrics.memory property
|
||||
|
||||
system memory usage metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
memory: {
|
||||
total_in_bytes: number;
|
||||
free_in_bytes: number;
|
||||
used_in_bytes: number;
|
||||
};
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [platform](./kibana-plugin-server.opsosmetrics.platform.md)
|
||||
|
||||
## OpsOsMetrics.platform property
|
||||
|
||||
The os platform
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
platform: NodeJS.Platform;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [platformRelease](./kibana-plugin-server.opsosmetrics.platformrelease.md)
|
||||
|
||||
## OpsOsMetrics.platformRelease property
|
||||
|
||||
The os platform release, prefixed by the platform name
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
platformRelease: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsOsMetrics](./kibana-plugin-server.opsosmetrics.md) > [uptime\_in\_millis](./kibana-plugin-server.opsosmetrics.uptime_in_millis.md)
|
||||
|
||||
## OpsOsMetrics.uptime\_in\_millis property
|
||||
|
||||
the OS uptime
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uptime_in_millis: number;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md) > [event\_loop\_delay](./kibana-plugin-server.opsprocessmetrics.event_loop_delay.md)
|
||||
|
||||
## OpsProcessMetrics.event\_loop\_delay property
|
||||
|
||||
node event loop delay
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
event_loop_delay: number;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md)
|
||||
|
||||
## OpsProcessMetrics interface
|
||||
|
||||
Process related metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface OpsProcessMetrics
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [event\_loop\_delay](./kibana-plugin-server.opsprocessmetrics.event_loop_delay.md) | <code>number</code> | node event loop delay |
|
||||
| [memory](./kibana-plugin-server.opsprocessmetrics.memory.md) | <code>{</code><br/><code> heap: {</code><br/><code> total_in_bytes: number;</code><br/><code> used_in_bytes: number;</code><br/><code> size_limit: number;</code><br/><code> };</code><br/><code> resident_set_size_in_bytes: number;</code><br/><code> }</code> | process memory usage |
|
||||
| [pid](./kibana-plugin-server.opsprocessmetrics.pid.md) | <code>number</code> | pid of the kibana process |
|
||||
| [uptime\_in\_millis](./kibana-plugin-server.opsprocessmetrics.uptime_in_millis.md) | <code>number</code> | uptime of the kibana process |
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md) > [memory](./kibana-plugin-server.opsprocessmetrics.memory.md)
|
||||
|
||||
## OpsProcessMetrics.memory property
|
||||
|
||||
process memory usage
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
memory: {
|
||||
heap: {
|
||||
total_in_bytes: number;
|
||||
used_in_bytes: number;
|
||||
size_limit: number;
|
||||
};
|
||||
resident_set_size_in_bytes: number;
|
||||
};
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md) > [pid](./kibana-plugin-server.opsprocessmetrics.pid.md)
|
||||
|
||||
## OpsProcessMetrics.pid property
|
||||
|
||||
pid of the kibana process
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
pid: number;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsProcessMetrics](./kibana-plugin-server.opsprocessmetrics.md) > [uptime\_in\_millis](./kibana-plugin-server.opsprocessmetrics.uptime_in_millis.md)
|
||||
|
||||
## OpsProcessMetrics.uptime\_in\_millis property
|
||||
|
||||
uptime of the kibana process
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uptime_in_millis: number;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsServerMetrics](./kibana-plugin-server.opsservermetrics.md) > [concurrent\_connections](./kibana-plugin-server.opsservermetrics.concurrent_connections.md)
|
||||
|
||||
## OpsServerMetrics.concurrent\_connections property
|
||||
|
||||
number of current concurrent connections to the server
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
concurrent_connections: number;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsServerMetrics](./kibana-plugin-server.opsservermetrics.md)
|
||||
|
||||
## OpsServerMetrics interface
|
||||
|
||||
server related metrics
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface OpsServerMetrics
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [concurrent\_connections](./kibana-plugin-server.opsservermetrics.concurrent_connections.md) | <code>number</code> | number of current concurrent connections to the server |
|
||||
| [requests](./kibana-plugin-server.opsservermetrics.requests.md) | <code>{</code><br/><code> disconnects: number;</code><br/><code> total: number;</code><br/><code> statusCodes: Record<number, number>;</code><br/><code> }</code> | server requests stats |
|
||||
| [response\_times](./kibana-plugin-server.opsservermetrics.response_times.md) | <code>{</code><br/><code> avg_in_millis: number;</code><br/><code> max_in_millis: number;</code><br/><code> }</code> | server response time stats |
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsServerMetrics](./kibana-plugin-server.opsservermetrics.md) > [requests](./kibana-plugin-server.opsservermetrics.requests.md)
|
||||
|
||||
## OpsServerMetrics.requests property
|
||||
|
||||
server requests stats
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
requests: {
|
||||
disconnects: number;
|
||||
total: number;
|
||||
statusCodes: Record<number, number>;
|
||||
};
|
||||
```
|
|
@ -0,0 +1,16 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OpsServerMetrics](./kibana-plugin-server.opsservermetrics.md) > [response\_times](./kibana-plugin-server.opsservermetrics.response_times.md)
|
||||
|
||||
## OpsServerMetrics.response\_times property
|
||||
|
||||
server response time stats
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
response_times: {
|
||||
avg_in_millis: number;
|
||||
max_in_millis: number;
|
||||
};
|
||||
```
|
|
@ -326,6 +326,7 @@
|
|||
"@types/fetch-mock": "^7.3.1",
|
||||
"@types/flot": "^0.0.31",
|
||||
"@types/getopts": "^2.0.1",
|
||||
"@types/getos": "^3.0.0",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/globby": "^8.0.0",
|
||||
"@types/hapi": "^17.0.18",
|
||||
|
|
|
@ -245,6 +245,14 @@ export {
|
|||
StringValidationRegexString,
|
||||
} from './ui_settings';
|
||||
|
||||
export {
|
||||
OpsMetrics,
|
||||
OpsOsMetrics,
|
||||
OpsServerMetrics,
|
||||
OpsProcessMetrics,
|
||||
MetricsServiceSetup,
|
||||
} from './metrics';
|
||||
|
||||
export { RecursiveReadonly } from '../utils';
|
||||
|
||||
export {
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from './saved_objects';
|
||||
import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings';
|
||||
import { UuidServiceSetup } from './uuid';
|
||||
import { InternalMetricsServiceSetup } from './metrics';
|
||||
|
||||
/** @internal */
|
||||
export interface InternalCoreSetup {
|
||||
|
@ -40,6 +41,7 @@ export interface InternalCoreSetup {
|
|||
uiSettings: InternalUiSettingsServiceSetup;
|
||||
savedObjects: InternalSavedObjectsServiceSetup;
|
||||
uuid: UuidServiceSetup;
|
||||
metrics: InternalMetricsServiceSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,6 +43,7 @@ import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.
|
|||
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
|
||||
import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
|
||||
import { uuidServiceMock } from '../uuid/uuid_service.mock';
|
||||
import { metricsServiceMock } from '../metrics/metrics_service.mock';
|
||||
import { findLegacyPluginSpecs } from './plugins';
|
||||
import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
|
||||
import { LegacyService } from './legacy_service';
|
||||
|
@ -93,6 +94,7 @@ beforeEach(() => {
|
|||
},
|
||||
},
|
||||
rendering: renderingServiceMock,
|
||||
metrics: metricsServiceMock.createInternalSetupContract(),
|
||||
uuid: uuidSetup,
|
||||
},
|
||||
plugins: { 'plugin-id': 'plugin-value' },
|
||||
|
|
23
src/core/server/metrics/collectors/index.ts
Normal file
23
src/core/server/metrics/collectors/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics, MetricsCollector } from './types';
|
||||
export { OsMetricsCollector } from './os';
|
||||
export { ProcessMetricsCollector } from './process';
|
||||
export { ServerMetricsCollector } from './server';
|
99
src/core/server/metrics/collectors/os.test.ts
Normal file
99
src/core/server/metrics/collectors/os.test.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
jest.mock('getos', () => (cb: Function) => cb(null, { dist: 'distrib', release: 'release' }));
|
||||
|
||||
import os from 'os';
|
||||
import { OsMetricsCollector } from './os';
|
||||
|
||||
describe('OsMetricsCollector', () => {
|
||||
let collector: OsMetricsCollector;
|
||||
|
||||
beforeEach(() => {
|
||||
collector = new OsMetricsCollector();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('collects platform info from the os package', async () => {
|
||||
const platform = 'darwin';
|
||||
const release = '10.14.1';
|
||||
|
||||
jest.spyOn(os, 'platform').mockImplementation(() => platform);
|
||||
jest.spyOn(os, 'release').mockImplementation(() => release);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.platform).toBe(platform);
|
||||
expect(metrics.platformRelease).toBe(`${platform}-${release}`);
|
||||
});
|
||||
|
||||
it('collects distribution info when platform is linux', async () => {
|
||||
const platform = 'linux';
|
||||
|
||||
jest.spyOn(os, 'platform').mockImplementation(() => platform);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.distro).toBe('distrib');
|
||||
expect(metrics.distroRelease).toBe('distrib-release');
|
||||
});
|
||||
|
||||
it('collects memory info from the os package', async () => {
|
||||
const totalMemory = 1457886;
|
||||
const freeMemory = 456786;
|
||||
|
||||
jest.spyOn(os, 'totalmem').mockImplementation(() => totalMemory);
|
||||
jest.spyOn(os, 'freemem').mockImplementation(() => freeMemory);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.memory.total_in_bytes).toBe(totalMemory);
|
||||
expect(metrics.memory.free_in_bytes).toBe(freeMemory);
|
||||
expect(metrics.memory.used_in_bytes).toBe(totalMemory - freeMemory);
|
||||
});
|
||||
|
||||
it('collects uptime info from the os package', async () => {
|
||||
const uptime = 325;
|
||||
|
||||
jest.spyOn(os, 'uptime').mockImplementation(() => uptime);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.uptime_in_millis).toBe(uptime * 1000);
|
||||
});
|
||||
|
||||
it('collects load info from the os package', async () => {
|
||||
const oneMinLoad = 1;
|
||||
const fiveMinLoad = 2;
|
||||
const fifteenMinLoad = 3;
|
||||
|
||||
jest.spyOn(os, 'loadavg').mockImplementation(() => [oneMinLoad, fiveMinLoad, fifteenMinLoad]);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.load).toEqual({
|
||||
'1m': oneMinLoad,
|
||||
'5m': fiveMinLoad,
|
||||
'15m': fifteenMinLoad,
|
||||
});
|
||||
});
|
||||
});
|
60
src/core/server/metrics/collectors/os.ts
Normal file
60
src/core/server/metrics/collectors/os.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import os from 'os';
|
||||
import getosAsync, { LinuxOs } from 'getos';
|
||||
import { promisify } from 'util';
|
||||
import { OpsOsMetrics, MetricsCollector } from './types';
|
||||
|
||||
const getos = promisify(getosAsync);
|
||||
|
||||
export class OsMetricsCollector implements MetricsCollector<OpsOsMetrics> {
|
||||
public async collect(): Promise<OpsOsMetrics> {
|
||||
const platform = os.platform();
|
||||
const load = os.loadavg();
|
||||
|
||||
const metrics: OpsOsMetrics = {
|
||||
platform,
|
||||
platformRelease: `${platform}-${os.release()}`,
|
||||
load: {
|
||||
'1m': load[0],
|
||||
'5m': load[1],
|
||||
'15m': load[2],
|
||||
},
|
||||
memory: {
|
||||
total_in_bytes: os.totalmem(),
|
||||
free_in_bytes: os.freemem(),
|
||||
used_in_bytes: os.totalmem() - os.freemem(),
|
||||
},
|
||||
uptime_in_millis: os.uptime() * 1000,
|
||||
};
|
||||
|
||||
if (platform === 'linux') {
|
||||
try {
|
||||
const distro = (await getos()) as LinuxOs;
|
||||
metrics.distro = distro.dist;
|
||||
metrics.distroRelease = `${distro.dist}-${distro.release}`;
|
||||
} catch (e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
}
|
81
src/core/server/metrics/collectors/process.test.ts
Normal file
81
src/core/server/metrics/collectors/process.test.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import v8, { HeapInfo } from 'v8';
|
||||
import { ProcessMetricsCollector } from './process';
|
||||
|
||||
describe('ProcessMetricsCollector', () => {
|
||||
let collector: ProcessMetricsCollector;
|
||||
|
||||
beforeEach(() => {
|
||||
collector = new ProcessMetricsCollector();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('collects pid from the process', async () => {
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.pid).toEqual(process.pid);
|
||||
});
|
||||
|
||||
it('collects event loop delay', async () => {
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.event_loop_delay).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('collects uptime info from the process', async () => {
|
||||
const uptime = 58986;
|
||||
jest.spyOn(process, 'uptime').mockImplementation(() => uptime);
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(metrics.uptime_in_millis).toEqual(uptime * 1000);
|
||||
});
|
||||
|
||||
it('collects memory info from the process', async () => {
|
||||
const heapTotal = 58986;
|
||||
const heapUsed = 4688;
|
||||
const heapSizeLimit = 5788;
|
||||
const rss = 5865;
|
||||
jest.spyOn(process, 'memoryUsage').mockImplementation(() => ({
|
||||
rss,
|
||||
heapTotal,
|
||||
heapUsed,
|
||||
external: 0,
|
||||
}));
|
||||
|
||||
jest.spyOn(v8, 'getHeapStatistics').mockImplementation(
|
||||
() =>
|
||||
({
|
||||
heap_size_limit: heapSizeLimit,
|
||||
} as HeapInfo)
|
||||
);
|
||||
|
||||
const metrics = await 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);
|
||||
});
|
||||
});
|
52
src/core/server/metrics/collectors/process.ts
Normal file
52
src/core/server/metrics/collectors/process.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import v8 from 'v8';
|
||||
import { Bench } from 'hoek';
|
||||
import { OpsProcessMetrics, MetricsCollector } from './types';
|
||||
|
||||
export class ProcessMetricsCollector implements MetricsCollector<OpsProcessMetrics> {
|
||||
public async collect(): Promise<OpsProcessMetrics> {
|
||||
const heapStats = v8.getHeapStatistics();
|
||||
const memoryUsage = process.memoryUsage();
|
||||
const [eventLoopDelay] = await Promise.all([getEventLoopDelay()]);
|
||||
return {
|
||||
memory: {
|
||||
heap: {
|
||||
total_in_bytes: memoryUsage.heapTotal,
|
||||
used_in_bytes: memoryUsage.heapUsed,
|
||||
size_limit: heapStats.heap_size_limit,
|
||||
},
|
||||
resident_set_size_in_bytes: memoryUsage.rss,
|
||||
},
|
||||
pid: process.pid,
|
||||
event_loop_delay: eventLoopDelay,
|
||||
uptime_in_millis: process.uptime() * 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getEventLoopDelay = (): Promise<number> => {
|
||||
const bench = new Bench();
|
||||
return new Promise(resolve => {
|
||||
setImmediate(() => {
|
||||
return resolve(bench.elapsed());
|
||||
});
|
||||
});
|
||||
};
|
80
src/core/server/metrics/collectors/server.ts
Normal file
80
src/core/server/metrics/collectors/server.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ResponseObject, Server as HapiServer } from 'hapi';
|
||||
import { OpsServerMetrics, MetricsCollector } from './types';
|
||||
|
||||
interface ServerResponseTime {
|
||||
count: number;
|
||||
total: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export class ServerMetricsCollector implements MetricsCollector<OpsServerMetrics> {
|
||||
private readonly requests: OpsServerMetrics['requests'] = {
|
||||
disconnects: 0,
|
||||
total: 0,
|
||||
statusCodes: {},
|
||||
};
|
||||
private readonly responseTimes: ServerResponseTime = {
|
||||
count: 0,
|
||||
total: 0,
|
||||
max: 0,
|
||||
};
|
||||
|
||||
constructor(private readonly server: HapiServer) {
|
||||
this.server.ext('onRequest', (request, h) => {
|
||||
this.requests.total++;
|
||||
request.events.once('disconnect', () => {
|
||||
this.requests.disconnects++;
|
||||
});
|
||||
return h.continue;
|
||||
});
|
||||
this.server.events.on('response', request => {
|
||||
const statusCode = (request.response as ResponseObject)?.statusCode;
|
||||
if (statusCode) {
|
||||
if (!this.requests.statusCodes[statusCode]) {
|
||||
this.requests.statusCodes[statusCode] = 0;
|
||||
}
|
||||
this.requests.statusCodes[statusCode]++;
|
||||
}
|
||||
|
||||
const duration = Date.now() - request.info.received;
|
||||
this.responseTimes.count++;
|
||||
this.responseTimes.total += duration;
|
||||
this.responseTimes.max = Math.max(this.responseTimes.max, duration);
|
||||
});
|
||||
}
|
||||
|
||||
public async collect(): Promise<OpsServerMetrics> {
|
||||
const connections = await new Promise<number>(resolve => {
|
||||
this.server.listener.getConnections((_, count) => {
|
||||
resolve(count);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
requests: this.requests,
|
||||
response_times: {
|
||||
avg_in_millis: this.responseTimes.total / Math.max(this.responseTimes.count, 1),
|
||||
max_in_millis: this.responseTimes.max,
|
||||
},
|
||||
concurrent_connections: connections,
|
||||
};
|
||||
}
|
||||
}
|
110
src/core/server/metrics/collectors/types.ts
Normal file
110
src/core/server/metrics/collectors/types.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/** Base interface for all metrics gatherers */
|
||||
export interface MetricsCollector<T> {
|
||||
collect(): Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process related metrics
|
||||
* @public
|
||||
*/
|
||||
export interface OpsProcessMetrics {
|
||||
/** process memory usage */
|
||||
memory: {
|
||||
/** heap memory usage */
|
||||
heap: {
|
||||
/** total heap available */
|
||||
total_in_bytes: number;
|
||||
/** used heap */
|
||||
used_in_bytes: number;
|
||||
/** v8 heap size limit */
|
||||
size_limit: number;
|
||||
};
|
||||
/** node rss */
|
||||
resident_set_size_in_bytes: number;
|
||||
};
|
||||
/** node event loop delay */
|
||||
event_loop_delay: number;
|
||||
/** pid of the kibana process */
|
||||
pid: number;
|
||||
/** uptime of the kibana process */
|
||||
uptime_in_millis: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* OS related metrics
|
||||
* @public
|
||||
*/
|
||||
export interface OpsOsMetrics {
|
||||
/** The os platform */
|
||||
platform: NodeJS.Platform;
|
||||
/** The os platform release, prefixed by the platform name */
|
||||
platformRelease: string;
|
||||
/** The os distrib. Only present for linux platforms */
|
||||
distro?: string;
|
||||
/** The os distrib release, prefixed by the os distrib. Only present for linux platforms */
|
||||
distroRelease?: string;
|
||||
/** cpu load metrics */
|
||||
load: {
|
||||
/** load for last minute */
|
||||
'1m': number;
|
||||
/** load for last 5 minutes */
|
||||
'5m': number;
|
||||
/** load for last 15 minutes */
|
||||
'15m': number;
|
||||
};
|
||||
/** system memory usage metrics */
|
||||
memory: {
|
||||
/** total memory available */
|
||||
total_in_bytes: number;
|
||||
/** current free memory */
|
||||
free_in_bytes: number;
|
||||
/** current used memory */
|
||||
used_in_bytes: number;
|
||||
};
|
||||
/** the OS uptime */
|
||||
uptime_in_millis: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* server related metrics
|
||||
* @public
|
||||
*/
|
||||
export interface OpsServerMetrics {
|
||||
/** server response time stats */
|
||||
response_times: {
|
||||
/** average response time */
|
||||
avg_in_millis: number;
|
||||
/** maximum response time */
|
||||
max_in_millis: number;
|
||||
};
|
||||
/** server requests stats */
|
||||
requests: {
|
||||
/** number of disconnected requests since startup */
|
||||
disconnects: number;
|
||||
/** total number of requests handled since startup */
|
||||
total: number;
|
||||
/** number of request handled per response status code */
|
||||
statusCodes: Record<number, number>;
|
||||
};
|
||||
/** number of current concurrent connections to the server */
|
||||
concurrent_connections: number;
|
||||
}
|
29
src/core/server/metrics/index.ts
Normal file
29
src/core/server/metrics/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
InternalMetricsServiceStart,
|
||||
InternalMetricsServiceSetup,
|
||||
MetricsServiceSetup,
|
||||
MetricsServiceStart,
|
||||
OpsMetrics,
|
||||
} from './types';
|
||||
export { OpsProcessMetrics, OpsServerMetrics, OpsOsMetrics } from './collectors';
|
||||
export { MetricsService } from './metrics_service';
|
||||
export { opsConfig } from './ops_config';
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import supertest from 'supertest';
|
||||
import { Server as HapiServer } from 'hapi';
|
||||
import { createHttpServer } from '../../http/test_utils';
|
||||
import { HttpService, IRouter } from '../../http';
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { ServerMetricsCollector } from '../collectors/server';
|
||||
|
||||
describe('ServerMetricsCollector', () => {
|
||||
let server: HttpService;
|
||||
let collector: ServerMetricsCollector;
|
||||
let hapiServer: HapiServer;
|
||||
let router: IRouter;
|
||||
|
||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const sendGet = (path: string) => supertest(hapiServer.listener).get(path);
|
||||
|
||||
beforeEach(async () => {
|
||||
server = createHttpServer();
|
||||
const contextSetup = contextServiceMock.createSetupContract();
|
||||
const httpSetup = await server.setup({ context: contextSetup });
|
||||
hapiServer = httpSetup.server;
|
||||
router = httpSetup.createRouter('/');
|
||||
collector = new ServerMetricsCollector(hapiServer);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('collect requests infos', async () => {
|
||||
router.get({ path: '/', validate: false }, async (ctx, req, res) => {
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
await server.start();
|
||||
|
||||
let metrics = await collector.collect();
|
||||
|
||||
expect(metrics.requests).toEqual({
|
||||
total: 0,
|
||||
disconnects: 0,
|
||||
statusCodes: {},
|
||||
});
|
||||
|
||||
await sendGet('/');
|
||||
await sendGet('/');
|
||||
await sendGet('/not-found');
|
||||
|
||||
metrics = await collector.collect();
|
||||
|
||||
expect(metrics.requests).toEqual({
|
||||
total: 3,
|
||||
disconnects: 0,
|
||||
statusCodes: {
|
||||
'200': 2,
|
||||
'404': 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('collect disconnects requests infos', async () => {
|
||||
const never = new Promise(resolve => undefined);
|
||||
|
||||
router.get({ path: '/', validate: false }, async (ctx, req, res) => {
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
router.get({ path: '/disconnect', validate: false }, async (ctx, req, res) => {
|
||||
await never;
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
await server.start();
|
||||
|
||||
await sendGet('/');
|
||||
const discoReq1 = sendGet('/disconnect').end();
|
||||
const discoReq2 = sendGet('/disconnect').end();
|
||||
await delay(20);
|
||||
|
||||
let metrics = await collector.collect();
|
||||
expect(metrics.requests).toEqual(
|
||||
expect.objectContaining({
|
||||
total: 3,
|
||||
disconnects: 0,
|
||||
})
|
||||
);
|
||||
|
||||
discoReq1.abort();
|
||||
await delay(20);
|
||||
|
||||
metrics = await collector.collect();
|
||||
expect(metrics.requests).toEqual(
|
||||
expect.objectContaining({
|
||||
total: 3,
|
||||
disconnects: 1,
|
||||
})
|
||||
);
|
||||
|
||||
discoReq2.abort();
|
||||
await delay(20);
|
||||
|
||||
metrics = await collector.collect();
|
||||
expect(metrics.requests).toEqual(
|
||||
expect.objectContaining({
|
||||
total: 3,
|
||||
disconnects: 2,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('collect response times', async () => {
|
||||
router.get({ path: '/no-delay', validate: false }, async (ctx, req, res) => {
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
router.get({ path: '/500-ms', validate: false }, async (ctx, req, res) => {
|
||||
await delay(500);
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
router.get({ path: '/250-ms', validate: false }, async (ctx, req, res) => {
|
||||
await delay(250);
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
await server.start();
|
||||
|
||||
await Promise.all([sendGet('/no-delay'), sendGet('/250-ms')]);
|
||||
let metrics = await collector.collect();
|
||||
|
||||
expect(metrics.response_times.avg_in_millis).toBeGreaterThanOrEqual(125);
|
||||
expect(metrics.response_times.max_in_millis).toBeGreaterThanOrEqual(250);
|
||||
|
||||
await Promise.all([sendGet('/500-ms'), sendGet('/500-ms')]);
|
||||
metrics = await collector.collect();
|
||||
|
||||
expect(metrics.response_times.avg_in_millis).toBeGreaterThanOrEqual(250);
|
||||
expect(metrics.response_times.max_in_millis).toBeGreaterThanOrEqual(500);
|
||||
});
|
||||
|
||||
it('collect connection count', async () => {
|
||||
const waitSubject = new Subject();
|
||||
|
||||
router.get({ path: '/', validate: false }, async (ctx, req, res) => {
|
||||
await waitSubject.pipe(take(1)).toPromise();
|
||||
return res.ok({ body: '' });
|
||||
});
|
||||
await server.start();
|
||||
|
||||
let metrics = await collector.collect();
|
||||
expect(metrics.concurrent_connections).toEqual(0);
|
||||
|
||||
sendGet('/').end(() => null);
|
||||
await delay(20);
|
||||
metrics = await collector.collect();
|
||||
expect(metrics.concurrent_connections).toEqual(1);
|
||||
|
||||
sendGet('/').end(() => null);
|
||||
await delay(20);
|
||||
metrics = await collector.collect();
|
||||
expect(metrics.concurrent_connections).toEqual(2);
|
||||
|
||||
waitSubject.next('go');
|
||||
await delay(20);
|
||||
metrics = await collector.collect();
|
||||
expect(metrics.concurrent_connections).toEqual(0);
|
||||
});
|
||||
});
|
67
src/core/server/metrics/metrics_service.mock.ts
Normal file
67
src/core/server/metrics/metrics_service.mock.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { MetricsService } from './metrics_service';
|
||||
import {
|
||||
InternalMetricsServiceSetup,
|
||||
InternalMetricsServiceStart,
|
||||
MetricsServiceSetup,
|
||||
MetricsServiceStart,
|
||||
} from './types';
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<MetricsServiceSetup> = {
|
||||
getOpsMetrics$: jest.fn(),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createInternalSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<InternalMetricsServiceSetup> = createSetupContractMock();
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<MetricsServiceStart> = {};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createInternalStartContractMock = () => {
|
||||
const startContract: jest.Mocked<InternalMetricsServiceStart> = createStartContractMock();
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type MetricsServiceContract = PublicMethodsOf<MetricsService>;
|
||||
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<MetricsServiceContract> = {
|
||||
setup: jest.fn().mockReturnValue(createInternalSetupContractMock()),
|
||||
start: jest.fn().mockReturnValue(createInternalStartContractMock()),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const metricsServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
createInternalSetupContract: createInternalSetupContractMock,
|
||||
createInternalStartContract: createInternalStartContractMock,
|
||||
};
|
25
src/core/server/metrics/metrics_service.test.mocks.ts
Normal file
25
src/core/server/metrics/metrics_service.test.mocks.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export const mockOpsCollector = {
|
||||
collect: jest.fn(),
|
||||
};
|
||||
jest.doMock('./ops_metrics_collector', () => ({
|
||||
OpsMetricsCollector: jest.fn().mockImplementation(() => mockOpsCollector),
|
||||
}));
|
134
src/core/server/metrics/metrics_service.test.ts
Normal file
134
src/core/server/metrics/metrics_service.test.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { mockOpsCollector } from './metrics_service.test.mocks';
|
||||
import { MetricsService } from './metrics_service';
|
||||
import { mockCoreContext } from '../core_context.mock';
|
||||
import { configServiceMock } from '../config/config_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
const testInterval = 100;
|
||||
|
||||
const dummyMetrics = { metricA: 'value', metricB: 'otherValue' };
|
||||
|
||||
describe('MetricsService', () => {
|
||||
const httpMock = httpServiceMock.createSetupContract();
|
||||
let metricsService: MetricsService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const configService = configServiceMock.create({
|
||||
atPath: { interval: moment.duration(testInterval) },
|
||||
});
|
||||
const coreContext = mockCoreContext.create({ configService });
|
||||
metricsService = new MetricsService(coreContext);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
describe('#start', () => {
|
||||
it('invokes setInterval with the configured interval', async () => {
|
||||
await metricsService.setup({ http: httpMock });
|
||||
await metricsService.start();
|
||||
|
||||
expect(setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(setInterval).toHaveBeenCalledWith(expect.any(Function), testInterval);
|
||||
});
|
||||
|
||||
it('emits the metrics at start', async () => {
|
||||
mockOpsCollector.collect.mockResolvedValue(dummyMetrics);
|
||||
|
||||
const { getOpsMetrics$ } = await metricsService.setup({
|
||||
http: httpMock,
|
||||
});
|
||||
|
||||
await metricsService.start();
|
||||
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
await getOpsMetrics$()
|
||||
.pipe(take(1))
|
||||
.toPromise()
|
||||
).toEqual(dummyMetrics);
|
||||
});
|
||||
|
||||
it('collects the metrics at every interval', async () => {
|
||||
mockOpsCollector.collect.mockResolvedValue(dummyMetrics);
|
||||
|
||||
await metricsService.setup({ http: httpMock });
|
||||
|
||||
await metricsService.start();
|
||||
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(testInterval);
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(2);
|
||||
|
||||
jest.advanceTimersByTime(testInterval);
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('throws when called before setup', async () => {
|
||||
await expect(metricsService.start()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"#setup() needs to be run first"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop', () => {
|
||||
it('stops the metrics interval', async () => {
|
||||
const { getOpsMetrics$ } = await metricsService.setup({ http: httpMock });
|
||||
await metricsService.start();
|
||||
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(testInterval);
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(2);
|
||||
|
||||
await metricsService.stop();
|
||||
jest.advanceTimersByTime(10 * testInterval);
|
||||
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(2);
|
||||
|
||||
getOpsMetrics$().subscribe({ complete: () => {} });
|
||||
});
|
||||
|
||||
it('completes the metrics observable', async () => {
|
||||
const { getOpsMetrics$ } = await metricsService.setup({ http: httpMock });
|
||||
await metricsService.start();
|
||||
|
||||
let completed = false;
|
||||
|
||||
getOpsMetrics$().subscribe({
|
||||
complete: () => {
|
||||
completed = true;
|
||||
},
|
||||
});
|
||||
|
||||
await metricsService.stop();
|
||||
|
||||
expect(completed).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
86
src/core/server/metrics/metrics_service.ts
Normal file
86
src/core/server/metrics/metrics_service.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { first, shareReplay } from 'rxjs/operators';
|
||||
import { CoreService } from '../../types';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { Logger } from '../logging';
|
||||
import { InternalHttpServiceSetup } from '../http';
|
||||
import { InternalMetricsServiceSetup, InternalMetricsServiceStart, OpsMetrics } from './types';
|
||||
import { OpsMetricsCollector } from './ops_metrics_collector';
|
||||
import { opsConfig, OpsConfigType } from './ops_config';
|
||||
|
||||
interface MetricsServiceSetupDeps {
|
||||
http: InternalHttpServiceSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class MetricsService
|
||||
implements CoreService<InternalMetricsServiceSetup, InternalMetricsServiceStart> {
|
||||
private readonly logger: Logger;
|
||||
private metricsCollector?: OpsMetricsCollector;
|
||||
private collectInterval?: NodeJS.Timeout;
|
||||
private metrics$ = new ReplaySubject<OpsMetrics>(1);
|
||||
|
||||
constructor(private readonly coreContext: CoreContext) {
|
||||
this.logger = coreContext.logger.get('metrics');
|
||||
}
|
||||
|
||||
public async setup({ http }: MetricsServiceSetupDeps): Promise<InternalMetricsServiceSetup> {
|
||||
this.metricsCollector = new OpsMetricsCollector(http.server);
|
||||
|
||||
const metricsObservable = this.metrics$.pipe(shareReplay(1));
|
||||
|
||||
return {
|
||||
getOpsMetrics$: () => metricsObservable,
|
||||
};
|
||||
}
|
||||
|
||||
public async start(): Promise<InternalMetricsServiceStart> {
|
||||
if (!this.metricsCollector) {
|
||||
throw new Error('#setup() needs to be run first');
|
||||
}
|
||||
const config = await this.coreContext.configService
|
||||
.atPath<OpsConfigType>(opsConfig.path)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
await this.refreshMetrics();
|
||||
|
||||
this.collectInterval = setInterval(() => {
|
||||
this.refreshMetrics();
|
||||
}, config.interval.asMilliseconds());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private async refreshMetrics() {
|
||||
this.logger.debug('Refreshing metrics');
|
||||
const metrics = await this.metricsCollector!.collect();
|
||||
this.metrics$.next(metrics);
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
if (this.collectInterval) {
|
||||
clearInterval(this.collectInterval);
|
||||
}
|
||||
this.metrics$.complete();
|
||||
}
|
||||
}
|
29
src/core/server/metrics/ops_config.ts
Normal file
29
src/core/server/metrics/ops_config.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const opsConfig = {
|
||||
path: 'ops',
|
||||
schema: schema.object({
|
||||
interval: schema.duration({ defaultValue: '5s' }),
|
||||
}),
|
||||
};
|
||||
|
||||
export type OpsConfigType = TypeOf<typeof opsConfig.schema>;
|
39
src/core/server/metrics/ops_metrics_collector.test.mocks.ts
Normal file
39
src/core/server/metrics/ops_metrics_collector.test.mocks.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export const mockOsCollector = {
|
||||
collect: jest.fn(),
|
||||
};
|
||||
jest.doMock('./collectors/os', () => ({
|
||||
OsMetricsCollector: jest.fn().mockImplementation(() => mockOsCollector),
|
||||
}));
|
||||
|
||||
export const mockProcessCollector = {
|
||||
collect: jest.fn(),
|
||||
};
|
||||
jest.doMock('./collectors/process', () => ({
|
||||
ProcessMetricsCollector: jest.fn().mockImplementation(() => mockProcessCollector),
|
||||
}));
|
||||
|
||||
export const mockServerCollector = {
|
||||
collect: jest.fn(),
|
||||
};
|
||||
jest.doMock('./collectors/server', () => ({
|
||||
ServerMetricsCollector: jest.fn().mockImplementation(() => mockServerCollector),
|
||||
}));
|
59
src/core/server/metrics/ops_metrics_collector.test.ts
Normal file
59
src/core/server/metrics/ops_metrics_collector.test.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
mockOsCollector,
|
||||
mockProcessCollector,
|
||||
mockServerCollector,
|
||||
} from './ops_metrics_collector.test.mocks';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { OpsMetricsCollector } from './ops_metrics_collector';
|
||||
|
||||
describe('OpsMetricsCollector', () => {
|
||||
let collector: OpsMetricsCollector;
|
||||
|
||||
beforeEach(() => {
|
||||
const hapiServer = httpServiceMock.createSetupContract().server;
|
||||
collector = new OpsMetricsCollector(hapiServer);
|
||||
|
||||
mockOsCollector.collect.mockResolvedValue('osMetrics');
|
||||
});
|
||||
|
||||
it('gathers metrics from the underlying collectors', async () => {
|
||||
mockOsCollector.collect.mockResolvedValue('osMetrics');
|
||||
mockProcessCollector.collect.mockResolvedValue('processMetrics');
|
||||
mockServerCollector.collect.mockResolvedValue({
|
||||
requests: 'serverRequestsMetrics',
|
||||
response_times: 'serverTimingMetrics',
|
||||
});
|
||||
|
||||
const metrics = await collector.collect();
|
||||
|
||||
expect(mockOsCollector.collect).toHaveBeenCalledTimes(1);
|
||||
expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1);
|
||||
expect(mockServerCollector.collect).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(metrics).toEqual({
|
||||
process: 'processMetrics',
|
||||
os: 'osMetrics',
|
||||
requests: 'serverRequestsMetrics',
|
||||
response_times: 'serverTimingMetrics',
|
||||
});
|
||||
});
|
||||
});
|
52
src/core/server/metrics/ops_metrics_collector.ts
Normal file
52
src/core/server/metrics/ops_metrics_collector.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Server as HapiServer } from 'hapi';
|
||||
import {
|
||||
ProcessMetricsCollector,
|
||||
OsMetricsCollector,
|
||||
ServerMetricsCollector,
|
||||
MetricsCollector,
|
||||
} from './collectors';
|
||||
import { OpsMetrics } from './types';
|
||||
|
||||
export class OpsMetricsCollector implements MetricsCollector<OpsMetrics> {
|
||||
private readonly processCollector: ProcessMetricsCollector;
|
||||
private readonly osCollector: OsMetricsCollector;
|
||||
private readonly serverCollector: ServerMetricsCollector;
|
||||
|
||||
constructor(server: HapiServer) {
|
||||
this.processCollector = new ProcessMetricsCollector();
|
||||
this.osCollector = new OsMetricsCollector();
|
||||
this.serverCollector = new ServerMetricsCollector(server);
|
||||
}
|
||||
|
||||
public async collect(): Promise<OpsMetrics> {
|
||||
const [process, os, server] = await Promise.all([
|
||||
this.processCollector.collect(),
|
||||
this.osCollector.collect(),
|
||||
this.serverCollector.collect(),
|
||||
]);
|
||||
return {
|
||||
process,
|
||||
os,
|
||||
...server,
|
||||
};
|
||||
}
|
||||
}
|
66
src/core/server/metrics/types.ts
Normal file
66
src/core/server/metrics/types.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors';
|
||||
|
||||
/**
|
||||
* APIs to retrieves metrics gathered and exposed by the core platform.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface MetricsServiceSetup {
|
||||
/**
|
||||
* Retrieve an observable emitting the {@link OpsMetrics} gathered.
|
||||
* The observable will emit an initial value during core's `start` phase, and a new value every fixed interval of time,
|
||||
* based on the `opts.interval` configuration property.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* core.metrics.getOpsMetrics$().subscribe(metrics => {
|
||||
* // do something with the metrics
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
getOpsMetrics$: () => Observable<OpsMetrics>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface MetricsServiceStart {}
|
||||
|
||||
export type InternalMetricsServiceSetup = MetricsServiceSetup;
|
||||
export type InternalMetricsServiceStart = MetricsServiceStart;
|
||||
|
||||
/**
|
||||
* Regroups metrics gathered by all the collectors.
|
||||
* This contains metrics about the os/runtime, the kibana process and the http server.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface OpsMetrics {
|
||||
/** Process related metrics */
|
||||
process: OpsProcessMetrics;
|
||||
/** OS related metrics */
|
||||
os: OpsOsMetrics;
|
||||
/** server response time stats */
|
||||
response_times: OpsServerMetrics['response_times'];
|
||||
/** server requests stats */
|
||||
requests: OpsServerMetrics['requests'];
|
||||
/** number of current concurrent connections to the server */
|
||||
concurrent_connections: OpsServerMetrics['concurrent_connections'];
|
||||
}
|
|
@ -30,6 +30,8 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
|||
import { SharedGlobalConfig } from './plugins';
|
||||
import { InternalCoreSetup, InternalCoreStart } from './internal_types';
|
||||
import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
|
||||
import { metricsServiceMock } from './metrics/metrics_service.mock';
|
||||
import { uuidServiceMock } from './uuid/uuid_service.mock';
|
||||
|
||||
export { httpServerMock } from './http/http_server.mocks';
|
||||
export { sessionStorageMock } from './http/cookie_session_storage.mocks';
|
||||
|
@ -40,7 +42,7 @@ export { loggingServiceMock } from './logging/logging_service.mock';
|
|||
export { savedObjectsRepositoryMock } from './saved_objects/service/lib/repository.mock';
|
||||
export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_objects/saved_objects_type_registry.mock';
|
||||
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
import { uuidServiceMock } from './uuid/uuid_service.mock';
|
||||
export { metricsServiceMock } from './metrics/metrics_service.mock';
|
||||
|
||||
export function pluginInitializerContextConfigMock<T>(config: T) {
|
||||
const globalConfig: SharedGlobalConfig = {
|
||||
|
@ -153,6 +155,7 @@ function createInternalCoreSetupMock() {
|
|||
uiSettings: uiSettingsServiceMock.createSetupContract(),
|
||||
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
|
||||
uuid: uuidServiceMock.createSetupContract(),
|
||||
metrics: metricsServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
return setupDeps;
|
||||
}
|
||||
|
|
|
@ -1176,6 +1176,11 @@ export interface LogRecord {
|
|||
timestamp: Date;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface MetricsServiceSetup {
|
||||
getOpsMetrics$: () => Observable<OpsMetrics>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex';
|
||||
|
||||
|
@ -1227,6 +1232,63 @@ export interface OnPreResponseToolkit {
|
|||
next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface OpsMetrics {
|
||||
concurrent_connections: OpsServerMetrics['concurrent_connections'];
|
||||
os: OpsOsMetrics;
|
||||
process: OpsProcessMetrics;
|
||||
requests: OpsServerMetrics['requests'];
|
||||
response_times: OpsServerMetrics['response_times'];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface OpsOsMetrics {
|
||||
distro?: string;
|
||||
distroRelease?: string;
|
||||
load: {
|
||||
'1m': number;
|
||||
'5m': number;
|
||||
'15m': number;
|
||||
};
|
||||
memory: {
|
||||
total_in_bytes: number;
|
||||
free_in_bytes: number;
|
||||
used_in_bytes: number;
|
||||
};
|
||||
platform: NodeJS.Platform;
|
||||
platformRelease: string;
|
||||
uptime_in_millis: number;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface OpsProcessMetrics {
|
||||
event_loop_delay: number;
|
||||
memory: {
|
||||
heap: {
|
||||
total_in_bytes: number;
|
||||
used_in_bytes: number;
|
||||
size_limit: number;
|
||||
};
|
||||
resident_set_size_in_bytes: number;
|
||||
};
|
||||
pid: number;
|
||||
uptime_in_millis: number;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface OpsServerMetrics {
|
||||
concurrent_connections: number;
|
||||
requests: {
|
||||
disconnects: number;
|
||||
total: number;
|
||||
statusCodes: Record<number, number>;
|
||||
};
|
||||
response_times: {
|
||||
avg_in_millis: number;
|
||||
max_in_millis: number;
|
||||
};
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PackageInfo {
|
||||
// (undocumented)
|
||||
|
|
|
@ -79,3 +79,9 @@ export const mockUuidService = uuidServiceMock.create();
|
|||
jest.doMock('./uuid/uuid_service', () => ({
|
||||
UuidService: jest.fn(() => mockUuidService),
|
||||
}));
|
||||
|
||||
import { metricsServiceMock } from './metrics/metrics_service.mock';
|
||||
export const mockMetricsService = metricsServiceMock.create();
|
||||
jest.doMock('./metrics/metrics_service', () => ({
|
||||
MetricsService: jest.fn(() => mockMetricsService),
|
||||
}));
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
mockEnsureValidConfiguration,
|
||||
mockUiSettingsService,
|
||||
mockRenderingService,
|
||||
mockMetricsService,
|
||||
} from './server.test.mocks';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
@ -61,6 +62,7 @@ test('sets up services on "setup"', async () => {
|
|||
expect(mockSavedObjectsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockUiSettingsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockRenderingService.setup).not.toHaveBeenCalled();
|
||||
expect(mockMetricsService.setup).not.toHaveBeenCalled();
|
||||
|
||||
await server.setup();
|
||||
|
||||
|
@ -71,6 +73,7 @@ test('sets up services on "setup"', async () => {
|
|||
expect(mockSavedObjectsService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockUiSettingsService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockRenderingService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockMetricsService.setup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('injects legacy dependency to context#setup()', async () => {
|
||||
|
@ -107,6 +110,7 @@ test('runs services on "start"', async () => {
|
|||
expect(mockLegacyService.start).not.toHaveBeenCalled();
|
||||
expect(mockSavedObjectsService.start).not.toHaveBeenCalled();
|
||||
expect(mockUiSettingsService.start).not.toHaveBeenCalled();
|
||||
expect(mockMetricsService.start).not.toHaveBeenCalled();
|
||||
|
||||
await server.start();
|
||||
|
||||
|
@ -114,6 +118,7 @@ test('runs services on "start"', async () => {
|
|||
expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockSavedObjectsService.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockUiSettingsService.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockMetricsService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('does not fail on "setup" if there are unused paths detected', async () => {
|
||||
|
@ -135,6 +140,7 @@ test('stops services on "stop"', async () => {
|
|||
expect(mockLegacyService.stop).not.toHaveBeenCalled();
|
||||
expect(mockSavedObjectsService.stop).not.toHaveBeenCalled();
|
||||
expect(mockUiSettingsService.stop).not.toHaveBeenCalled();
|
||||
expect(mockMetricsService.stop).not.toHaveBeenCalled();
|
||||
|
||||
await server.stop();
|
||||
|
||||
|
@ -144,6 +150,7 @@ test('stops services on "stop"', async () => {
|
|||
expect(mockLegacyService.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockSavedObjectsService.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockUiSettingsService.stop).toHaveBeenCalledTimes(1);
|
||||
expect(mockMetricsService.stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test(`doesn't setup core services if config validation fails`, async () => {
|
||||
|
@ -159,6 +166,7 @@ test(`doesn't setup core services if config validation fails`, async () => {
|
|||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
expect(mockUiSettingsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockRenderingService.setup).not.toHaveBeenCalled();
|
||||
expect(mockMetricsService.setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test(`doesn't setup core services if legacy config validation fails`, async () => {
|
||||
|
@ -178,4 +186,5 @@ test(`doesn't setup core services if legacy config validation fails`, async () =
|
|||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
expect(mockSavedObjectsService.stop).not.toHaveBeenCalled();
|
||||
expect(mockUiSettingsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockMetricsService.setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ import { Logger, LoggerFactory } from './logging';
|
|||
import { UiSettingsService } from './ui_settings';
|
||||
import { PluginsService, config as pluginsConfig } from './plugins';
|
||||
import { SavedObjectsService } from '../server/saved_objects';
|
||||
import { MetricsService, opsConfig } from './metrics';
|
||||
|
||||
import { config as cspConfig } from './csp';
|
||||
import { config as elasticsearchConfig } from './elasticsearch';
|
||||
|
@ -67,6 +68,7 @@ export class Server {
|
|||
private readonly savedObjects: SavedObjectsService;
|
||||
private readonly uiSettings: UiSettingsService;
|
||||
private readonly uuid: UuidService;
|
||||
private readonly metrics: MetricsService;
|
||||
|
||||
private coreStart?: InternalCoreStart;
|
||||
|
||||
|
@ -89,6 +91,7 @@ export class Server {
|
|||
this.uiSettings = new UiSettingsService(core);
|
||||
this.capabilities = new CapabilitiesService(core);
|
||||
this.uuid = new UuidService(core);
|
||||
this.metrics = new MetricsService(core);
|
||||
}
|
||||
|
||||
public async setup() {
|
||||
|
@ -137,6 +140,8 @@ export class Server {
|
|||
legacyPlugins,
|
||||
});
|
||||
|
||||
const metricsSetup = await this.metrics.setup({ http: httpSetup });
|
||||
|
||||
const coreSetup: InternalCoreSetup = {
|
||||
capabilities: capabilitiesSetup,
|
||||
context: contextServiceSetup,
|
||||
|
@ -145,6 +150,7 @@ export class Server {
|
|||
uiSettings: uiSettingsSetup,
|
||||
savedObjects: savedObjectsSetup,
|
||||
uuid: uuidSetup,
|
||||
metrics: metricsSetup,
|
||||
};
|
||||
|
||||
const pluginsSetup = await this.plugins.setup(coreSetup);
|
||||
|
@ -193,6 +199,7 @@ export class Server {
|
|||
|
||||
await this.http.start();
|
||||
await this.rendering.start();
|
||||
await this.metrics.start();
|
||||
|
||||
return this.coreStart;
|
||||
}
|
||||
|
@ -207,6 +214,7 @@ export class Server {
|
|||
await this.http.stop();
|
||||
await this.uiSettings.stop();
|
||||
await this.rendering.stop();
|
||||
await this.metrics.stop();
|
||||
}
|
||||
|
||||
private registerDefaultRoute(httpSetup: InternalHttpServiceSetup) {
|
||||
|
@ -260,6 +268,7 @@ export class Server {
|
|||
[savedObjectsConfig.path, savedObjectsConfig.schema],
|
||||
[savedObjectsMigrationConfig.path, savedObjectsMigrationConfig.schema],
|
||||
[uiSettingsConfig.path, uiSettingsConfig.schema],
|
||||
[opsConfig.path, opsConfig.schema],
|
||||
];
|
||||
|
||||
this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider);
|
||||
|
|
Loading…
Reference in a new issue