Core usage data (#79101)
* Core Telemetry service * CoreTelemetryService mock * Add missing config values back, cleanup * Core usage collector * HttpConfig path is 'server' * Fix tests * CoreTelemetry -> CoreUsageData * Improve tests / docs * Fix telemetry_check * Don't catch fetch function exceptions, let usage collector handle it * Code review * Collect saved object index usage data * Fix tests and telemetry_check * explicitly import/export usage data types for telemetry_check * Remove OS data for now, test for SO usage data * Fix tests * Polish core docs * This shouldn't be here
This commit is contained in:
parent
7eeb4eed83
commit
cd383800e3
153
src/core/server/core_usage_data/core_usage_data_service.mock.ts
Normal file
153
src/core/server/core_usage_data/core_usage_data_service.mock.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { CoreUsageDataService } from './core_usage_data_service';
|
||||
import { CoreUsageData, CoreUsageDataStart } from './types';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<CoreUsageDataStart> = {
|
||||
getCoreUsageData: jest.fn().mockResolvedValue(
|
||||
new BehaviorSubject<CoreUsageData>({
|
||||
config: {
|
||||
elasticsearch: {
|
||||
apiVersion: 'master',
|
||||
customHeadersConfigured: false,
|
||||
healthCheckDelayMs: 2500,
|
||||
logQueries: false,
|
||||
numberOfHostsConfigured: 1,
|
||||
pingTimeoutMs: 30000,
|
||||
requestHeadersWhitelistConfigured: false,
|
||||
requestTimeoutMs: 30000,
|
||||
shardTimeoutMs: 30000,
|
||||
sniffIntervalMs: -1,
|
||||
sniffOnConnectionFault: false,
|
||||
sniffOnStart: false,
|
||||
ssl: {
|
||||
alwaysPresentCertificate: false,
|
||||
certificateAuthoritiesConfigured: false,
|
||||
certificateConfigured: false,
|
||||
keyConfigured: false,
|
||||
verificationMode: 'full',
|
||||
keystoreConfigured: false,
|
||||
truststoreConfigured: false,
|
||||
},
|
||||
},
|
||||
http: {
|
||||
basePathConfigured: false,
|
||||
compression: {
|
||||
enabled: true,
|
||||
referrerWhitelistConfigured: false,
|
||||
},
|
||||
keepaliveTimeout: 120000,
|
||||
maxPayloadInBytes: 1048576,
|
||||
requestId: {
|
||||
allowFromAnyIp: false,
|
||||
ipAllowlistConfigured: false,
|
||||
},
|
||||
rewriteBasePath: false,
|
||||
socketTimeout: 120000,
|
||||
ssl: {
|
||||
certificateAuthoritiesConfigured: false,
|
||||
certificateConfigured: false,
|
||||
cipherSuites: [
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'DHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES128-SHA256',
|
||||
'DHE-RSA-AES128-SHA256',
|
||||
'ECDHE-RSA-AES256-SHA384',
|
||||
'DHE-RSA-AES256-SHA384',
|
||||
'ECDHE-RSA-AES256-SHA256',
|
||||
'DHE-RSA-AES256-SHA256',
|
||||
'HIGH',
|
||||
'!aNULL',
|
||||
'!eNULL',
|
||||
'!EXPORT',
|
||||
'!DES',
|
||||
'!RC4',
|
||||
'!MD5',
|
||||
'!PSK',
|
||||
'!SRP',
|
||||
'!CAMELLIA',
|
||||
],
|
||||
clientAuthentication: 'none',
|
||||
keyConfigured: false,
|
||||
keystoreConfigured: false,
|
||||
redirectHttpFromPortConfigured: false,
|
||||
supportedProtocols: ['TLSv1.1', 'TLSv1.2'],
|
||||
truststoreConfigured: false,
|
||||
},
|
||||
xsrf: {
|
||||
disableProtection: false,
|
||||
whitelistConfigured: false,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
appendersTypesUsed: [],
|
||||
loggersConfiguredCount: 0,
|
||||
},
|
||||
savedObjects: {
|
||||
maxImportExportSizeBytes: 10000,
|
||||
maxImportPayloadBytes: 10485760,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
memory: {
|
||||
heapSizeLimit: 1,
|
||||
heapTotalBytes: 1,
|
||||
heapUsedBytes: 1,
|
||||
},
|
||||
},
|
||||
services: {
|
||||
savedObjects: {
|
||||
indices: [
|
||||
{
|
||||
docsCount: 1,
|
||||
docsDeleted: 1,
|
||||
alias: 'test_index',
|
||||
primaryStoreSizeBytes: 1,
|
||||
storeSizeBytes: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<PublicMethodsOf<CoreUsageDataService>> = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn().mockReturnValue(createStartContractMock()),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const coreUsageDataServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
259
src/core/server/core_usage_data/core_usage_data_service.test.ts
Normal file
259
src/core/server/core_usage_data/core_usage_data_service.test.ts
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* 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 { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { HotObservable } from 'rxjs/internal/testing/HotObservable';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
import { configServiceMock } from '../config/mocks';
|
||||
|
||||
import { mockCoreContext } from '../core_context.mock';
|
||||
import { config as RawElasticsearchConfig } from '../elasticsearch/elasticsearch_config';
|
||||
import { config as RawHttpConfig } from '../http/http_config';
|
||||
import { config as RawLoggingConfig } from '../logging/logging_config';
|
||||
import { config as RawKibanaConfig } from '../kibana_config';
|
||||
import { savedObjectsConfig as RawSavedObjectsConfig } from '../saved_objects/saved_objects_config';
|
||||
import { metricsServiceMock } from '../metrics/metrics_service.mock';
|
||||
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
|
||||
|
||||
import { CoreUsageDataService } from './core_usage_data_service';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
|
||||
describe('CoreUsageDataService', () => {
|
||||
const getTestScheduler = () =>
|
||||
new TestScheduler((actual, expected) => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
let service: CoreUsageDataService;
|
||||
const configService = configServiceMock.create();
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'elasticsearch') {
|
||||
return new BehaviorSubject(RawElasticsearchConfig.schema.validate({}));
|
||||
} else if (path === 'server') {
|
||||
return new BehaviorSubject(RawHttpConfig.schema.validate({}));
|
||||
} else if (path === 'logging') {
|
||||
return new BehaviorSubject(RawLoggingConfig.schema.validate({}));
|
||||
} else if (path === 'savedObjects') {
|
||||
return new BehaviorSubject(RawSavedObjectsConfig.schema.validate({}));
|
||||
} else if (path === 'kibana') {
|
||||
return new BehaviorSubject(RawKibanaConfig.schema.validate({}));
|
||||
}
|
||||
return new BehaviorSubject({});
|
||||
});
|
||||
const coreContext = mockCoreContext.create({ configService });
|
||||
|
||||
beforeEach(() => {
|
||||
service = new CoreUsageDataService(coreContext);
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
describe('getCoreUsageData', () => {
|
||||
it('returns core metrics for default config', () => {
|
||||
const metrics = metricsServiceMock.createInternalSetupContract();
|
||||
service.setup({ metrics });
|
||||
const elasticsearch = elasticsearchServiceMock.createStart();
|
||||
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
|
||||
body: [
|
||||
{
|
||||
name: '.kibana_task_manager_1',
|
||||
'docs.count': 10,
|
||||
'docs.deleted': 10,
|
||||
'store.size': 1000,
|
||||
'pri.store.size': 2000,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
|
||||
body: [
|
||||
{
|
||||
name: '.kibana_1',
|
||||
'docs.count': 20,
|
||||
'docs.deleted': 20,
|
||||
'store.size': 2000,
|
||||
'pri.store.size': 4000,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
const typeRegistry = savedObjectsServiceMock.createTypeRegistryMock();
|
||||
typeRegistry.getAllTypes.mockReturnValue([
|
||||
{ name: 'type 1', indexPattern: '.kibana' },
|
||||
{ name: 'type 2', indexPattern: '.kibana_task_manager' },
|
||||
] as any);
|
||||
|
||||
const { getCoreUsageData } = service.start({
|
||||
savedObjects: savedObjectsServiceMock.createInternalStartContract(typeRegistry),
|
||||
elasticsearch,
|
||||
});
|
||||
expect(getCoreUsageData()).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"config": Object {
|
||||
"elasticsearch": Object {
|
||||
"apiVersion": "master",
|
||||
"customHeadersConfigured": false,
|
||||
"healthCheckDelayMs": 2500,
|
||||
"logQueries": false,
|
||||
"numberOfHostsConfigured": 1,
|
||||
"pingTimeoutMs": 30000,
|
||||
"requestHeadersWhitelistConfigured": false,
|
||||
"requestTimeoutMs": 30000,
|
||||
"shardTimeoutMs": 30000,
|
||||
"sniffIntervalMs": -1,
|
||||
"sniffOnConnectionFault": false,
|
||||
"sniffOnStart": false,
|
||||
"ssl": Object {
|
||||
"alwaysPresentCertificate": false,
|
||||
"certificateAuthoritiesConfigured": false,
|
||||
"certificateConfigured": false,
|
||||
"keyConfigured": false,
|
||||
"keystoreConfigured": false,
|
||||
"truststoreConfigured": false,
|
||||
"verificationMode": "full",
|
||||
},
|
||||
},
|
||||
"http": Object {
|
||||
"basePathConfigured": false,
|
||||
"compression": Object {
|
||||
"enabled": true,
|
||||
"referrerWhitelistConfigured": false,
|
||||
},
|
||||
"keepaliveTimeout": 120000,
|
||||
"maxPayloadInBytes": 1048576,
|
||||
"requestId": Object {
|
||||
"allowFromAnyIp": false,
|
||||
"ipAllowlistConfigured": false,
|
||||
},
|
||||
"rewriteBasePath": false,
|
||||
"socketTimeout": 120000,
|
||||
"ssl": Object {
|
||||
"certificateAuthoritiesConfigured": false,
|
||||
"certificateConfigured": false,
|
||||
"cipherSuites": Array [
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES256-GCM-SHA384",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
"DHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-SHA256",
|
||||
"DHE-RSA-AES128-SHA256",
|
||||
"ECDHE-RSA-AES256-SHA384",
|
||||
"DHE-RSA-AES256-SHA384",
|
||||
"ECDHE-RSA-AES256-SHA256",
|
||||
"DHE-RSA-AES256-SHA256",
|
||||
"HIGH",
|
||||
"!aNULL",
|
||||
"!eNULL",
|
||||
"!EXPORT",
|
||||
"!DES",
|
||||
"!RC4",
|
||||
"!MD5",
|
||||
"!PSK",
|
||||
"!SRP",
|
||||
"!CAMELLIA",
|
||||
],
|
||||
"clientAuthentication": "none",
|
||||
"keyConfigured": false,
|
||||
"keystoreConfigured": false,
|
||||
"redirectHttpFromPortConfigured": false,
|
||||
"supportedProtocols": Array [
|
||||
"TLSv1.1",
|
||||
"TLSv1.2",
|
||||
],
|
||||
"truststoreConfigured": false,
|
||||
},
|
||||
"xsrf": Object {
|
||||
"disableProtection": false,
|
||||
"whitelistConfigured": false,
|
||||
},
|
||||
},
|
||||
"logging": Object {
|
||||
"appendersTypesUsed": Array [],
|
||||
"loggersConfiguredCount": 0,
|
||||
},
|
||||
"savedObjects": Object {
|
||||
"maxImportExportSizeBytes": 10000,
|
||||
"maxImportPayloadBytes": 10485760,
|
||||
},
|
||||
},
|
||||
"environment": Object {
|
||||
"memory": Object {
|
||||
"heapSizeLimit": 1,
|
||||
"heapTotalBytes": 1,
|
||||
"heapUsedBytes": 1,
|
||||
},
|
||||
},
|
||||
"services": Object {
|
||||
"savedObjects": Object {
|
||||
"indices": Array [
|
||||
Object {
|
||||
"alias": ".kibana",
|
||||
"docsCount": 10,
|
||||
"docsDeleted": 10,
|
||||
"primaryStoreSizeBytes": 2000,
|
||||
"storeSizeBytes": 1000,
|
||||
},
|
||||
Object {
|
||||
"alias": ".kibana_task_manager",
|
||||
"docsCount": 20,
|
||||
"docsDeleted": 20,
|
||||
"primaryStoreSizeBytes": 4000,
|
||||
"storeSizeBytes": 2000,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup and stop', () => {
|
||||
it('subscribes and unsubscribes from all config paths and metrics', () => {
|
||||
getTestScheduler().run(({ cold, hot, expectSubscriptions }) => {
|
||||
const observables: Array<HotObservable<string>> = [];
|
||||
configService.atPath.mockImplementation(() => {
|
||||
const newObservable = hot('-a-------');
|
||||
observables.push(newObservable);
|
||||
return newObservable;
|
||||
});
|
||||
const metrics = metricsServiceMock.createInternalSetupContract();
|
||||
metrics.getOpsMetrics$.mockImplementation(() => {
|
||||
const newObservable = hot('-a-------');
|
||||
observables.push(newObservable);
|
||||
return newObservable as Observable<any>;
|
||||
});
|
||||
|
||||
service.setup({ metrics });
|
||||
|
||||
// Use the stopTimer$ to delay calling stop() until the third frame
|
||||
const stopTimer$ = cold('---a|');
|
||||
stopTimer$.subscribe(() => {
|
||||
service.stop();
|
||||
});
|
||||
|
||||
const subs = '^--!';
|
||||
|
||||
observables.forEach((o) => {
|
||||
expectSubscriptions(o.subscriptions).toBe(subs);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
285
src/core/server/core_usage_data/core_usage_data_service.ts
Normal file
285
src/core/server/core_usage_data/core_usage_data_service.ts
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* 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 { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { CoreService } from 'src/core/types';
|
||||
import { SavedObjectsServiceStart } from 'src/core/server';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config';
|
||||
import { HttpConfigType } from '../http';
|
||||
import { LoggingConfigType } from '../logging';
|
||||
import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config';
|
||||
import { CoreServicesUsageData, CoreUsageData, CoreUsageDataStart } from './types';
|
||||
import { isConfigured } from './is_configured';
|
||||
import { ElasticsearchServiceStart } from '../elasticsearch';
|
||||
import { KibanaConfigType } from '../kibana_config';
|
||||
import { MetricsServiceSetup, OpsMetrics } from '..';
|
||||
|
||||
export interface SetupDeps {
|
||||
metrics: MetricsServiceSetup;
|
||||
}
|
||||
|
||||
export interface StartDeps {
|
||||
savedObjects: SavedObjectsServiceStart;
|
||||
elasticsearch: ElasticsearchServiceStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because users can configure their Saved Object to any arbitrary index name,
|
||||
* we need to map customized index names back to a "standard" index name.
|
||||
*
|
||||
* e.g. If a user configures `kibana.index: .my_saved_objects` we want to the
|
||||
* collected data to be grouped under `.kibana` not ".my_saved_objects".
|
||||
*
|
||||
* This is rather brittle, but the option to configure index names might go
|
||||
* away completely anyway (see #60053).
|
||||
*
|
||||
* @param index The index name configured for this SO type
|
||||
* @param kibanaConfigIndex The default kibana index as configured by the user
|
||||
* with `kibana.index`
|
||||
*/
|
||||
const kibanaOrTaskManagerIndex = (index: string, kibanaConfigIndex: string) => {
|
||||
return index === kibanaConfigIndex ? '.kibana' : '.kibana_task_manager';
|
||||
};
|
||||
|
||||
export class CoreUsageDataService implements CoreService<void, CoreUsageDataStart> {
|
||||
private elasticsearchConfig?: ElasticsearchConfigType;
|
||||
private configService: CoreContext['configService'];
|
||||
private httpConfig?: HttpConfigType;
|
||||
private loggingConfig?: LoggingConfigType;
|
||||
private soConfig?: SavedObjectsConfigType;
|
||||
private stop$: Subject<void>;
|
||||
private opsMetrics?: OpsMetrics;
|
||||
private kibanaConfig?: KibanaConfigType;
|
||||
|
||||
constructor(core: CoreContext) {
|
||||
this.configService = core.configService;
|
||||
this.stop$ = new Subject();
|
||||
}
|
||||
|
||||
private async getSavedObjectIndicesUsageData(
|
||||
savedObjects: SavedObjectsServiceStart,
|
||||
elasticsearch: ElasticsearchServiceStart
|
||||
): Promise<CoreServicesUsageData['savedObjects']> {
|
||||
const indices = await Promise.all(
|
||||
Array.from(
|
||||
savedObjects
|
||||
.getTypeRegistry()
|
||||
.getAllTypes()
|
||||
.reduce((acc, type) => {
|
||||
const index = type.indexPattern ?? this.kibanaConfig!.index;
|
||||
return index != null ? acc.add(index) : acc;
|
||||
}, new Set<string>())
|
||||
.values()
|
||||
).map((index) => {
|
||||
// The _cat/indices API returns the _index_ and doesn't return a way
|
||||
// to map back from the index to the alias. So we have to make an API
|
||||
// call for every alias
|
||||
return elasticsearch.client.asInternalUser.cat
|
||||
.indices<any[]>({
|
||||
index,
|
||||
format: 'JSON',
|
||||
bytes: 'b',
|
||||
})
|
||||
.then(({ body }) => {
|
||||
const stats = body[0];
|
||||
return {
|
||||
alias: kibanaOrTaskManagerIndex(index, this.kibanaConfig!.index),
|
||||
docsCount: stats['docs.count'],
|
||||
docsDeleted: stats['docs.deleted'],
|
||||
storeSizeBytes: stats['store.size'],
|
||||
primaryStoreSizeBytes: stats['pri.store.size'],
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
indices,
|
||||
};
|
||||
}
|
||||
|
||||
private async getCoreUsageData(
|
||||
savedObjects: SavedObjectsServiceStart,
|
||||
elasticsearch: ElasticsearchServiceStart
|
||||
): Promise<CoreUsageData> {
|
||||
if (
|
||||
this.elasticsearchConfig == null ||
|
||||
this.httpConfig == null ||
|
||||
this.soConfig == null ||
|
||||
this.opsMetrics == null
|
||||
) {
|
||||
throw new Error('Unable to read config values. Ensure that setup() has completed.');
|
||||
}
|
||||
|
||||
const es = this.elasticsearchConfig;
|
||||
const soUsageData = await this.getSavedObjectIndicesUsageData(savedObjects, elasticsearch);
|
||||
|
||||
const http = this.httpConfig;
|
||||
return {
|
||||
config: {
|
||||
elasticsearch: {
|
||||
apiVersion: es.apiVersion,
|
||||
sniffOnStart: es.sniffOnStart,
|
||||
sniffIntervalMs: es.sniffInterval !== false ? es.sniffInterval.asMilliseconds() : -1,
|
||||
sniffOnConnectionFault: es.sniffOnConnectionFault,
|
||||
numberOfHostsConfigured: Array.isArray(es.hosts)
|
||||
? es.hosts.length
|
||||
: isConfigured.string(es.hosts)
|
||||
? 1
|
||||
: 0,
|
||||
customHeadersConfigured: isConfigured.record(es.customHeaders),
|
||||
healthCheckDelayMs: es.healthCheck.delay.asMilliseconds(),
|
||||
logQueries: es.logQueries,
|
||||
pingTimeoutMs: es.pingTimeout.asMilliseconds(),
|
||||
requestHeadersWhitelistConfigured: isConfigured.stringOrArray(
|
||||
es.requestHeadersWhitelist,
|
||||
['authorization']
|
||||
),
|
||||
requestTimeoutMs: es.requestTimeout.asMilliseconds(),
|
||||
shardTimeoutMs: es.shardTimeout.asMilliseconds(),
|
||||
ssl: {
|
||||
alwaysPresentCertificate: es.ssl.alwaysPresentCertificate,
|
||||
certificateAuthoritiesConfigured: isConfigured.stringOrArray(
|
||||
es.ssl.certificateAuthorities
|
||||
),
|
||||
certificateConfigured: isConfigured.string(es.ssl.certificate),
|
||||
keyConfigured: isConfigured.string(es.ssl.key),
|
||||
verificationMode: es.ssl.verificationMode,
|
||||
truststoreConfigured: isConfigured.record(es.ssl.truststore),
|
||||
keystoreConfigured: isConfigured.record(es.ssl.keystore),
|
||||
},
|
||||
},
|
||||
http: {
|
||||
basePathConfigured: isConfigured.string(http.basePath),
|
||||
maxPayloadInBytes: http.maxPayload.getValueInBytes(),
|
||||
rewriteBasePath: http.rewriteBasePath,
|
||||
keepaliveTimeout: http.keepaliveTimeout,
|
||||
socketTimeout: http.socketTimeout,
|
||||
compression: {
|
||||
enabled: http.compression.enabled,
|
||||
referrerWhitelistConfigured: isConfigured.array(http.compression.referrerWhitelist),
|
||||
},
|
||||
xsrf: {
|
||||
disableProtection: http.xsrf.disableProtection,
|
||||
whitelistConfigured: isConfigured.array(http.xsrf.whitelist),
|
||||
},
|
||||
requestId: {
|
||||
allowFromAnyIp: http.requestId.allowFromAnyIp,
|
||||
ipAllowlistConfigured: isConfigured.array(http.requestId.ipAllowlist),
|
||||
},
|
||||
ssl: {
|
||||
certificateAuthoritiesConfigured: isConfigured.stringOrArray(
|
||||
http.ssl.certificateAuthorities
|
||||
),
|
||||
certificateConfigured: isConfigured.string(http.ssl.certificate),
|
||||
cipherSuites: http.ssl.cipherSuites,
|
||||
keyConfigured: isConfigured.string(http.ssl.key),
|
||||
redirectHttpFromPortConfigured: isConfigured.number(http.ssl.redirectHttpFromPort),
|
||||
supportedProtocols: http.ssl.supportedProtocols,
|
||||
clientAuthentication: http.ssl.clientAuthentication,
|
||||
keystoreConfigured: isConfigured.record(http.ssl.keystore),
|
||||
truststoreConfigured: isConfigured.record(http.ssl.truststore),
|
||||
},
|
||||
},
|
||||
|
||||
logging: {
|
||||
appendersTypesUsed: Array.from(
|
||||
Array.from(this.loggingConfig?.appenders.values() ?? [])
|
||||
.reduce((acc, a) => acc.add(a.kind), new Set<string>())
|
||||
.values()
|
||||
),
|
||||
loggersConfiguredCount: this.loggingConfig?.loggers.length ?? 0,
|
||||
},
|
||||
|
||||
savedObjects: {
|
||||
maxImportPayloadBytes: this.soConfig.maxImportPayloadBytes.getValueInBytes(),
|
||||
maxImportExportSizeBytes: this.soConfig.maxImportExportSize.getValueInBytes(),
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
memory: {
|
||||
heapSizeLimit: this.opsMetrics.process.memory.heap.size_limit,
|
||||
heapTotalBytes: this.opsMetrics.process.memory.heap.total_in_bytes,
|
||||
heapUsedBytes: this.opsMetrics.process.memory.heap.used_in_bytes,
|
||||
},
|
||||
},
|
||||
services: {
|
||||
savedObjects: soUsageData,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setup({ metrics }: SetupDeps) {
|
||||
metrics
|
||||
.getOpsMetrics$()
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((opsMetrics) => (this.opsMetrics = opsMetrics));
|
||||
|
||||
this.configService
|
||||
.atPath<ElasticsearchConfigType>('elasticsearch')
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((config) => {
|
||||
this.elasticsearchConfig = config;
|
||||
});
|
||||
|
||||
this.configService
|
||||
.atPath<HttpConfigType>('server')
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((config) => {
|
||||
this.httpConfig = config;
|
||||
});
|
||||
|
||||
this.configService
|
||||
.atPath<LoggingConfigType>('logging')
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((config) => {
|
||||
this.loggingConfig = config;
|
||||
});
|
||||
|
||||
this.configService
|
||||
.atPath<SavedObjectsConfigType>('savedObjects')
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((config) => {
|
||||
this.soConfig = config;
|
||||
});
|
||||
|
||||
this.configService
|
||||
.atPath<KibanaConfigType>('kibana')
|
||||
.pipe(takeUntil(this.stop$))
|
||||
.subscribe((config) => {
|
||||
this.kibanaConfig = config;
|
||||
});
|
||||
}
|
||||
|
||||
start({ savedObjects, elasticsearch }: StartDeps) {
|
||||
return {
|
||||
getCoreUsageData: () => {
|
||||
return this.getCoreUsageData(savedObjects, elasticsearch);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stop$.next();
|
||||
this.stop$.complete();
|
||||
}
|
||||
}
|
31
src/core/server/core_usage_data/index.ts
Normal file
31
src/core/server/core_usage_data/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { CoreUsageDataStart } from './types';
|
||||
export { CoreUsageDataService } from './core_usage_data_service';
|
||||
|
||||
// Because of #79265 we need to explicity import, then export these types for
|
||||
// scripts/telemetry_check.js to work as expected
|
||||
import {
|
||||
CoreUsageData,
|
||||
CoreConfigUsageData,
|
||||
CoreEnvironmentUsageData,
|
||||
CoreServicesUsageData,
|
||||
} from './types';
|
||||
|
||||
export { CoreUsageData, CoreConfigUsageData, CoreEnvironmentUsageData, CoreServicesUsageData };
|
137
src/core/server/core_usage_data/is_configured.test.ts
Normal file
137
src/core/server/core_usage_data/is_configured.test.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 { isConfigured } from './is_configured';
|
||||
|
||||
describe('isConfigured', () => {
|
||||
describe('#string', () => {
|
||||
it('returns true for a non-empty string', () => {
|
||||
expect(isConfigured.string('I am configured')).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false for an empty string', () => {
|
||||
expect(isConfigured.string(' ')).toEqual(false);
|
||||
expect(isConfigured.string(' ')).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for undefined', () => {
|
||||
expect(isConfigured.string(undefined)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for null', () => {
|
||||
expect(isConfigured.string(null as any)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for a record', () => {
|
||||
expect(isConfigured.string({} as any)).toEqual(false);
|
||||
expect(isConfigured.string({ key: 'hello' } as any)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for an array', () => {
|
||||
expect(isConfigured.string([] as any)).toEqual(false);
|
||||
expect(isConfigured.string(['hello'] as any)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('array', () => {
|
||||
it('returns true for a non-empty array', () => {
|
||||
expect(isConfigured.array(['a'])).toEqual(true);
|
||||
expect(isConfigured.array([{}])).toEqual(true);
|
||||
expect(isConfigured.array([{ key: 'hello' }])).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false for an empty array', () => {
|
||||
expect(isConfigured.array([])).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for undefined', () => {
|
||||
expect(isConfigured.array(undefined)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for null', () => {
|
||||
expect(isConfigured.array(null as any)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for a string', () => {
|
||||
expect(isConfigured.array('string')).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for a record', () => {
|
||||
expect(isConfigured.array({} as any)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringOrArray', () => {
|
||||
const arraySpy = jest.spyOn(isConfigured, 'array');
|
||||
const stringSpy = jest.spyOn(isConfigured, 'string');
|
||||
|
||||
it('calls #array for an array', () => {
|
||||
isConfigured.stringOrArray([]);
|
||||
expect(arraySpy).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it('calls #string for non-array values', () => {
|
||||
isConfigured.stringOrArray('string');
|
||||
expect(stringSpy).toHaveBeenCalledWith('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('record', () => {
|
||||
it('returns true for a non-empty record', () => {
|
||||
expect(isConfigured.record({ key: 'hello' })).toEqual(true);
|
||||
expect(isConfigured.record({ key: undefined })).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false for an empty record', () => {
|
||||
expect(isConfigured.record({})).toEqual(false);
|
||||
});
|
||||
it('returns false for undefined', () => {
|
||||
expect(isConfigured.record(undefined)).toEqual(false);
|
||||
});
|
||||
it('returns false for null', () => {
|
||||
expect(isConfigured.record(null as any)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('number', () => {
|
||||
it('returns true for a valid number', () => {
|
||||
expect(isConfigured.number(0)).toEqual(true);
|
||||
expect(isConfigured.number(-0)).toEqual(true);
|
||||
expect(isConfigured.number(1)).toEqual(true);
|
||||
expect(isConfigured.number(-0)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false for NaN', () => {
|
||||
expect(isConfigured.number(Number.NaN)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for a string', () => {
|
||||
expect(isConfigured.number('1' as any)).toEqual(false);
|
||||
expect(isConfigured.number('' as any)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for undefined', () => {
|
||||
expect(isConfigured.number(undefined)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for null', () => {
|
||||
expect(isConfigured.number(null as any)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
65
src/core/server/core_usage_data/is_configured.ts
Normal file
65
src/core/server/core_usage_data/is_configured.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { isEqual } from 'lodash';
|
||||
|
||||
/**
|
||||
* Test whether a given config value is configured based on it's schema type.
|
||||
* Our configuration schema and code often accept and ignore empty values like
|
||||
* `elasticsearch.customHeaders: {}`. However, for telemetry purposes, we're
|
||||
* only interested when these values have been set to something that will
|
||||
* change the behaviour of Kibana.
|
||||
*/
|
||||
export const isConfigured = {
|
||||
/**
|
||||
* config is a string with non-zero length
|
||||
*/
|
||||
string: (config?: string): boolean => {
|
||||
return (config?.trim?.()?.length ?? 0) > 0;
|
||||
},
|
||||
/**
|
||||
* config is an array with non-zero length
|
||||
*/
|
||||
array: (config?: unknown[] | string, defaultValue?: any): boolean => {
|
||||
return Array.isArray(config)
|
||||
? (config?.length ?? 0) > 0 && !isEqual(config, defaultValue)
|
||||
: false;
|
||||
},
|
||||
/**
|
||||
* config is a string or array of strings where each element has non-zero length
|
||||
*/
|
||||
stringOrArray: (config?: string[] | string, defaultValue?: any): boolean => {
|
||||
return Array.isArray(config)
|
||||
? isConfigured.array(config, defaultValue)
|
||||
: isConfigured.string(config);
|
||||
},
|
||||
/**
|
||||
* config is a record with at least one key
|
||||
*/
|
||||
record: (config?: Record<string, unknown>): boolean => {
|
||||
return config != null && typeof config === 'object' && Object.keys(config).length > 0;
|
||||
},
|
||||
/**
|
||||
* config is a number
|
||||
*/
|
||||
number: (config?: number): boolean => {
|
||||
// kbn-config-schema already does NaN validation, but doesn't hurt to be sure
|
||||
return typeof config === 'number' && !isNaN(config);
|
||||
},
|
||||
};
|
162
src/core/server/core_usage_data/types.ts
Normal file
162
src/core/server/core_usage_data/types.ts
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type describing Core's usage data payload
|
||||
* @internal
|
||||
*/
|
||||
export interface CoreUsageData {
|
||||
config: CoreConfigUsageData;
|
||||
services: CoreServicesUsageData;
|
||||
environment: CoreEnvironmentUsageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage data from Core services
|
||||
* @internal
|
||||
*/
|
||||
export interface CoreServicesUsageData {
|
||||
savedObjects: {
|
||||
// scripts/telemetry_check.js does not support parsing Array<{...}> types
|
||||
// so we have to disable eslint here and use {...}[]
|
||||
// eslint-disable-next-line @typescript-eslint/array-type
|
||||
indices: {
|
||||
alias: string;
|
||||
docsCount: number;
|
||||
docsDeleted: number;
|
||||
storeSizeBytes: number;
|
||||
primaryStoreSizeBytes: number;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage data on this Kibana node's runtime environment.
|
||||
* @internal
|
||||
*/
|
||||
export interface CoreEnvironmentUsageData {
|
||||
memory: {
|
||||
heapTotalBytes: number;
|
||||
heapUsedBytes: number;
|
||||
/** V8 heap size limit */
|
||||
heapSizeLimit: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage data on this cluster's configuration of Core features
|
||||
* @internal
|
||||
*/
|
||||
export interface CoreConfigUsageData {
|
||||
elasticsearch: {
|
||||
sniffOnStart: boolean;
|
||||
sniffIntervalMs?: number;
|
||||
sniffOnConnectionFault: boolean;
|
||||
numberOfHostsConfigured: number;
|
||||
requestHeadersWhitelistConfigured: boolean;
|
||||
customHeadersConfigured: boolean;
|
||||
shardTimeoutMs: number;
|
||||
requestTimeoutMs: number;
|
||||
pingTimeoutMs: number;
|
||||
logQueries: boolean;
|
||||
ssl: {
|
||||
verificationMode: 'none' | 'certificate' | 'full';
|
||||
certificateAuthoritiesConfigured: boolean;
|
||||
certificateConfigured: boolean;
|
||||
keyConfigured: boolean;
|
||||
keystoreConfigured: boolean;
|
||||
truststoreConfigured: boolean;
|
||||
alwaysPresentCertificate: boolean;
|
||||
};
|
||||
apiVersion: string;
|
||||
healthCheckDelayMs: number;
|
||||
};
|
||||
|
||||
http: {
|
||||
basePathConfigured: boolean;
|
||||
maxPayloadInBytes: number;
|
||||
rewriteBasePath: boolean;
|
||||
keepaliveTimeout: number;
|
||||
socketTimeout: number;
|
||||
compression: {
|
||||
enabled: boolean;
|
||||
referrerWhitelistConfigured: boolean;
|
||||
};
|
||||
xsrf: {
|
||||
disableProtection: boolean;
|
||||
whitelistConfigured: boolean;
|
||||
};
|
||||
requestId: {
|
||||
allowFromAnyIp: boolean;
|
||||
ipAllowlistConfigured: boolean;
|
||||
};
|
||||
ssl: {
|
||||
certificateAuthoritiesConfigured: boolean;
|
||||
certificateConfigured: boolean;
|
||||
cipherSuites: string[];
|
||||
keyConfigured: boolean;
|
||||
keystoreConfigured: boolean;
|
||||
truststoreConfigured: boolean;
|
||||
redirectHttpFromPortConfigured: boolean;
|
||||
supportedProtocols: string[];
|
||||
clientAuthentication: 'none' | 'optional' | 'required';
|
||||
};
|
||||
};
|
||||
|
||||
logging: {
|
||||
appendersTypesUsed: string[];
|
||||
loggersConfiguredCount: number;
|
||||
};
|
||||
|
||||
// plugins: {
|
||||
// /** list of built-in plugins that are disabled */
|
||||
// firstPartyDisabled: string[];
|
||||
// /** list of third-party plugins that are installed and enabled */
|
||||
// thirdParty: string[];
|
||||
// };
|
||||
|
||||
savedObjects: {
|
||||
maxImportPayloadBytes: number;
|
||||
maxImportExportSizeBytes: number;
|
||||
};
|
||||
|
||||
// uiSettings: {
|
||||
// overridesCount: number;
|
||||
// };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API for getting Core's usage data payload.
|
||||
*
|
||||
* @note This API should never be used to drive application logic and is only
|
||||
* intended for telemetry purposes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface CoreUsageDataStart {
|
||||
/**
|
||||
* Internal API for getting Core's usage data payload.
|
||||
*
|
||||
* @note This API should never be used to drive application logic and is only
|
||||
* intended for telemetry purposes.
|
||||
*
|
||||
* @internal
|
||||
* */
|
||||
getCoreUsageData(): Promise<CoreUsageData>;
|
||||
}
|
|
@ -64,6 +64,18 @@ import { MetricsServiceSetup, MetricsServiceStart } from './metrics';
|
|||
import { StatusServiceSetup } from './status';
|
||||
import { Auditor, AuditTrailSetup, AuditTrailStart } from './audit_trail';
|
||||
import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging';
|
||||
import { CoreUsageDataStart } from './core_usage_data';
|
||||
|
||||
// Because of #79265 we need to explicity import, then export these types for
|
||||
// scripts/telemetry_check.js to work as expected
|
||||
import {
|
||||
CoreUsageData,
|
||||
CoreConfigUsageData,
|
||||
CoreEnvironmentUsageData,
|
||||
CoreServicesUsageData,
|
||||
} from './core_usage_data';
|
||||
|
||||
export { CoreUsageData, CoreConfigUsageData, CoreEnvironmentUsageData, CoreServicesUsageData };
|
||||
|
||||
export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup } from './audit_trail';
|
||||
export { bootstrap } from './bootstrap';
|
||||
|
@ -349,6 +361,8 @@ export {
|
|||
StatusServiceSetup,
|
||||
} from './status';
|
||||
|
||||
export { CoreUsageDataStart } from './core_usage_data';
|
||||
|
||||
/**
|
||||
* Plugin specific context passed to a route handler.
|
||||
*
|
||||
|
@ -456,6 +470,8 @@ export interface CoreStart {
|
|||
uiSettings: UiSettingsServiceStart;
|
||||
/** {@link AuditTrailSetup} */
|
||||
auditTrail: AuditTrailStart;
|
||||
/** @internal {@link CoreUsageDataStart} */
|
||||
coreUsageData: CoreUsageDataStart;
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -39,6 +39,7 @@ import { InternalHttpResourcesSetup } from './http_resources';
|
|||
import { InternalStatusServiceSetup } from './status';
|
||||
import { AuditTrailSetup, AuditTrailStart } from './audit_trail';
|
||||
import { InternalLoggingServiceSetup } from './logging';
|
||||
import { CoreUsageDataStart } from './core_usage_data';
|
||||
|
||||
/** @internal */
|
||||
export interface InternalCoreSetup {
|
||||
|
@ -68,6 +69,7 @@ export interface InternalCoreStart {
|
|||
savedObjects: InternalSavedObjectsServiceStart;
|
||||
uiSettings: InternalUiSettingsServiceStart;
|
||||
auditTrail: AuditTrailStart;
|
||||
coreUsageData: CoreUsageDataStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -217,6 +217,11 @@ export class LegacyService implements CoreService {
|
|||
},
|
||||
uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient },
|
||||
auditTrail: startDeps.core.auditTrail,
|
||||
coreUsageData: {
|
||||
getCoreUsageData: () => {
|
||||
throw new Error('core.start.coreUsageData.getCoreUsageData is unsupported in legacy');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const router = setupDeps.core.http.createRouter('', this.legacyId);
|
||||
|
|
|
@ -37,6 +37,7 @@ import { metricsServiceMock } from './metrics/metrics_service.mock';
|
|||
import { environmentServiceMock } from './environment/environment_service.mock';
|
||||
import { statusServiceMock } from './status/status_service.mock';
|
||||
import { auditTrailServiceMock } from './audit_trail/audit_trail_service.mock';
|
||||
import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock';
|
||||
|
||||
export { configServiceMock } from './config/mocks';
|
||||
export { httpServerMock } from './http/http_server.mocks';
|
||||
|
@ -55,6 +56,7 @@ export { renderingMock } from './rendering/rendering_service.mock';
|
|||
export { statusServiceMock } from './status/status_service.mock';
|
||||
export { contextServiceMock } from './context/context_service.mock';
|
||||
export { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
|
||||
export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock';
|
||||
|
||||
export function pluginInitializerContextConfigMock<T>(config: T) {
|
||||
const globalConfig: SharedGlobalConfig = {
|
||||
|
@ -157,6 +159,7 @@ function createCoreStartMock() {
|
|||
metrics: metricsServiceMock.createStartContract(),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
@ -190,6 +193,7 @@ function createInternalCoreStartMock() {
|
|||
savedObjects: savedObjectsServiceMock.createInternalStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
auditTrail: auditTrailServiceMock.createStartContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
};
|
||||
return startDeps;
|
||||
}
|
||||
|
|
|
@ -251,5 +251,6 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>(
|
|||
asScopedToClient: deps.uiSettings.asScopedToClient,
|
||||
},
|
||||
auditTrail: deps.auditTrail,
|
||||
coreUsageData: deps.coreUsageData,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,10 +33,11 @@ import { savedObjectsClientMock } from './service/saved_objects_client.mock';
|
|||
import { typeRegistryMock } from './saved_objects_type_registry.mock';
|
||||
import { migrationMocks } from './migrations/mocks';
|
||||
import { ServiceStatusLevels } from '../status';
|
||||
import { ISavedObjectTypeRegistry } from './saved_objects_type_registry';
|
||||
|
||||
type SavedObjectsServiceContract = PublicMethodsOf<SavedObjectsService>;
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const createStartContractMock = (typeRegistry?: jest.Mocked<ISavedObjectTypeRegistry>) => {
|
||||
const startContrat: jest.Mocked<SavedObjectsServiceStart> = {
|
||||
getScopedClient: jest.fn(),
|
||||
createInternalRepository: jest.fn(),
|
||||
|
@ -48,13 +49,15 @@ const createStartContractMock = () => {
|
|||
startContrat.getScopedClient.mockReturnValue(savedObjectsClientMock.create());
|
||||
startContrat.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
startContrat.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
startContrat.getTypeRegistry.mockReturnValue(typeRegistryMock.create());
|
||||
startContrat.getTypeRegistry.mockReturnValue(typeRegistry ?? typeRegistryMock.create());
|
||||
|
||||
return startContrat;
|
||||
};
|
||||
|
||||
const createInternalStartContractMock = () => {
|
||||
const internalStartContract: jest.Mocked<InternalSavedObjectsServiceStart> = createStartContractMock();
|
||||
const createInternalStartContractMock = (typeRegistry?: jest.Mocked<ISavedObjectTypeRegistry>) => {
|
||||
const internalStartContract: jest.Mocked<InternalSavedObjectsServiceStart> = createStartContractMock(
|
||||
typeRegistry
|
||||
);
|
||||
|
||||
return internalStartContract;
|
||||
};
|
||||
|
|
|
@ -401,9 +401,102 @@ export interface ContextSetup {
|
|||
createContextContainer<THandler extends HandlerFunction<any>>(): IContextContainer<THandler>;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface CoreConfigUsageData {
|
||||
// (undocumented)
|
||||
elasticsearch: {
|
||||
sniffOnStart: boolean;
|
||||
sniffIntervalMs?: number;
|
||||
sniffOnConnectionFault: boolean;
|
||||
numberOfHostsConfigured: number;
|
||||
requestHeadersWhitelistConfigured: boolean;
|
||||
customHeadersConfigured: boolean;
|
||||
shardTimeoutMs: number;
|
||||
requestTimeoutMs: number;
|
||||
pingTimeoutMs: number;
|
||||
logQueries: boolean;
|
||||
ssl: {
|
||||
verificationMode: 'none' | 'certificate' | 'full';
|
||||
certificateAuthoritiesConfigured: boolean;
|
||||
certificateConfigured: boolean;
|
||||
keyConfigured: boolean;
|
||||
keystoreConfigured: boolean;
|
||||
truststoreConfigured: boolean;
|
||||
alwaysPresentCertificate: boolean;
|
||||
};
|
||||
apiVersion: string;
|
||||
healthCheckDelayMs: number;
|
||||
};
|
||||
// (undocumented)
|
||||
http: {
|
||||
basePathConfigured: boolean;
|
||||
maxPayloadInBytes: number;
|
||||
rewriteBasePath: boolean;
|
||||
keepaliveTimeout: number;
|
||||
socketTimeout: number;
|
||||
compression: {
|
||||
enabled: boolean;
|
||||
referrerWhitelistConfigured: boolean;
|
||||
};
|
||||
xsrf: {
|
||||
disableProtection: boolean;
|
||||
whitelistConfigured: boolean;
|
||||
};
|
||||
requestId: {
|
||||
allowFromAnyIp: boolean;
|
||||
ipAllowlistConfigured: boolean;
|
||||
};
|
||||
ssl: {
|
||||
certificateAuthoritiesConfigured: boolean;
|
||||
certificateConfigured: boolean;
|
||||
cipherSuites: string[];
|
||||
keyConfigured: boolean;
|
||||
keystoreConfigured: boolean;
|
||||
truststoreConfigured: boolean;
|
||||
redirectHttpFromPortConfigured: boolean;
|
||||
supportedProtocols: string[];
|
||||
clientAuthentication: 'none' | 'optional' | 'required';
|
||||
};
|
||||
};
|
||||
// (undocumented)
|
||||
logging: {
|
||||
appendersTypesUsed: string[];
|
||||
loggersConfiguredCount: number;
|
||||
};
|
||||
// (undocumented)
|
||||
savedObjects: {
|
||||
maxImportPayloadBytes: number;
|
||||
maxImportExportSizeBytes: number;
|
||||
};
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface CoreEnvironmentUsageData {
|
||||
// (undocumented)
|
||||
memory: {
|
||||
heapTotalBytes: number;
|
||||
heapUsedBytes: number;
|
||||
heapSizeLimit: number;
|
||||
};
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export type CoreId = symbol;
|
||||
|
||||
// @internal
|
||||
export interface CoreServicesUsageData {
|
||||
// (undocumented)
|
||||
savedObjects: {
|
||||
indices: {
|
||||
alias: string;
|
||||
docsCount: number;
|
||||
docsDeleted: number;
|
||||
storeSizeBytes: number;
|
||||
primaryStoreSizeBytes: number;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface CoreSetup<TPluginsStart extends object = object, TStart = unknown> {
|
||||
// (undocumented)
|
||||
|
@ -438,6 +531,8 @@ export interface CoreStart {
|
|||
auditTrail: AuditTrailStart;
|
||||
// (undocumented)
|
||||
capabilities: CapabilitiesStart;
|
||||
// @internal (undocumented)
|
||||
coreUsageData: CoreUsageDataStart;
|
||||
// (undocumented)
|
||||
elasticsearch: ElasticsearchServiceStart;
|
||||
// (undocumented)
|
||||
|
@ -458,6 +553,21 @@ export interface CoreStatus {
|
|||
savedObjects: ServiceStatus;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface CoreUsageData {
|
||||
// (undocumented)
|
||||
config: CoreConfigUsageData;
|
||||
// (undocumented)
|
||||
environment: CoreEnvironmentUsageData;
|
||||
// (undocumented)
|
||||
services: CoreServicesUsageData;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface CoreUsageDataStart {
|
||||
getCoreUsageData(): Promise<CoreUsageData>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CountResponse {
|
||||
// (undocumented)
|
||||
|
|
|
@ -48,6 +48,7 @@ import { config as statusConfig } from './status';
|
|||
import { ContextService } from './context';
|
||||
import { RequestHandlerContext } from '.';
|
||||
import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from './internal_types';
|
||||
import { CoreUsageDataService } from './core_usage_data';
|
||||
import { CoreRouteHandlerContext } from './core_route_handler_context';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
|
@ -72,6 +73,7 @@ export class Server {
|
|||
private readonly logging: LoggingService;
|
||||
private readonly coreApp: CoreApp;
|
||||
private readonly auditTrail: AuditTrailService;
|
||||
private readonly coreUsageData: CoreUsageDataService;
|
||||
|
||||
#pluginsInitialized?: boolean;
|
||||
private coreStart?: InternalCoreStart;
|
||||
|
@ -103,6 +105,7 @@ export class Server {
|
|||
this.httpResources = new HttpResourcesService(core);
|
||||
this.auditTrail = new AuditTrailService(core);
|
||||
this.logging = new LoggingService(core);
|
||||
this.coreUsageData = new CoreUsageDataService(core);
|
||||
}
|
||||
|
||||
public async setup() {
|
||||
|
@ -184,6 +187,8 @@ export class Server {
|
|||
loggingSystem: this.loggingSystem,
|
||||
});
|
||||
|
||||
this.coreUsageData.setup({ metrics: metricsSetup });
|
||||
|
||||
const coreSetup: InternalCoreSetup = {
|
||||
capabilities: capabilitiesSetup,
|
||||
context: contextServiceSetup,
|
||||
|
@ -235,6 +240,10 @@ export class Server {
|
|||
const uiSettingsStart = await this.uiSettings.start();
|
||||
const metricsStart = await this.metrics.start();
|
||||
const httpStart = this.http.getStartContract();
|
||||
const coreUsageDataStart = this.coreUsageData.start({
|
||||
elasticsearch: elasticsearchStart,
|
||||
savedObjects: savedObjectsStart,
|
||||
});
|
||||
|
||||
this.coreStart = {
|
||||
capabilities: capabilitiesStart,
|
||||
|
@ -244,6 +253,7 @@ export class Server {
|
|||
savedObjects: savedObjectsStart,
|
||||
uiSettings: uiSettingsStart,
|
||||
auditTrail: auditTrailStart,
|
||||
coreUsageData: coreUsageDataStart,
|
||||
};
|
||||
|
||||
const pluginsStart = await this.plugins.start(this.coreStart);
|
||||
|
|
|
@ -8,3 +8,4 @@ This plugin registers the basic usage collectors from Kibana:
|
|||
- Number of Saved Objects per type
|
||||
- Non-default UI Settings
|
||||
- CSP configuration
|
||||
- Core Metrics
|
||||
|
|
|
@ -11,3 +11,5 @@ exports[`kibana_usage_collection Runs the setup method without issues 4`] = `fal
|
|||
exports[`kibana_usage_collection Runs the setup method without issues 5`] = `false`;
|
||||
|
||||
exports[`kibana_usage_collection Runs the setup method without issues 6`] = `true`;
|
||||
|
||||
exports[`kibana_usage_collection Runs the setup method without issues 7`] = `false`;
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { CoreUsageData, CoreUsageDataStart } from '../../../../../core/server';
|
||||
|
||||
export function getCoreUsageCollector(
|
||||
usageCollection: UsageCollectionSetup,
|
||||
getCoreUsageDataService: () => CoreUsageDataStart
|
||||
) {
|
||||
return usageCollection.makeUsageCollector<CoreUsageData, { core: CoreUsageData }>({
|
||||
type: 'core',
|
||||
isReady: () => typeof getCoreUsageDataService() !== 'undefined',
|
||||
schema: {
|
||||
config: {
|
||||
elasticsearch: {
|
||||
sniffOnStart: { type: 'boolean' },
|
||||
sniffIntervalMs: { type: 'long' },
|
||||
sniffOnConnectionFault: { type: 'boolean' },
|
||||
numberOfHostsConfigured: { type: 'long' },
|
||||
requestHeadersWhitelistConfigured: { type: 'boolean' },
|
||||
customHeadersConfigured: { type: 'boolean' },
|
||||
shardTimeoutMs: { type: 'long' },
|
||||
requestTimeoutMs: { type: 'long' },
|
||||
pingTimeoutMs: { type: 'long' },
|
||||
logQueries: { type: 'boolean' },
|
||||
ssl: {
|
||||
verificationMode: { type: 'keyword' },
|
||||
certificateAuthoritiesConfigured: { type: 'boolean' },
|
||||
certificateConfigured: { type: 'boolean' },
|
||||
keyConfigured: { type: 'boolean' },
|
||||
keystoreConfigured: { type: 'boolean' },
|
||||
truststoreConfigured: { type: 'boolean' },
|
||||
alwaysPresentCertificate: { type: 'boolean' },
|
||||
},
|
||||
apiVersion: { type: 'keyword' },
|
||||
healthCheckDelayMs: { type: 'long' },
|
||||
},
|
||||
|
||||
http: {
|
||||
basePathConfigured: { type: 'boolean' },
|
||||
maxPayloadInBytes: { type: 'long' },
|
||||
rewriteBasePath: { type: 'boolean' },
|
||||
keepaliveTimeout: { type: 'long' },
|
||||
socketTimeout: { type: 'long' },
|
||||
compression: {
|
||||
enabled: { type: 'boolean' },
|
||||
referrerWhitelistConfigured: { type: 'boolean' },
|
||||
},
|
||||
xsrf: {
|
||||
disableProtection: { type: 'boolean' },
|
||||
whitelistConfigured: { type: 'boolean' },
|
||||
},
|
||||
requestId: {
|
||||
allowFromAnyIp: { type: 'boolean' },
|
||||
ipAllowlistConfigured: { type: 'boolean' },
|
||||
},
|
||||
ssl: {
|
||||
certificateAuthoritiesConfigured: { type: 'boolean' },
|
||||
certificateConfigured: { type: 'boolean' },
|
||||
cipherSuites: { type: 'array', items: { type: 'keyword' } },
|
||||
keyConfigured: { type: 'boolean' },
|
||||
keystoreConfigured: { type: 'boolean' },
|
||||
truststoreConfigured: { type: 'boolean' },
|
||||
redirectHttpFromPortConfigured: { type: 'boolean' },
|
||||
supportedProtocols: { type: 'array', items: { type: 'keyword' } },
|
||||
clientAuthentication: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
|
||||
logging: {
|
||||
appendersTypesUsed: { type: 'array', items: { type: 'keyword' } },
|
||||
loggersConfiguredCount: { type: 'long' },
|
||||
},
|
||||
|
||||
savedObjects: {
|
||||
maxImportPayloadBytes: { type: 'long' },
|
||||
maxImportExportSizeBytes: { type: 'long' },
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
memory: {
|
||||
heapSizeLimit: { type: 'long' },
|
||||
heapTotalBytes: { type: 'long' },
|
||||
heapUsedBytes: { type: 'long' },
|
||||
},
|
||||
},
|
||||
services: {
|
||||
savedObjects: {
|
||||
indices: {
|
||||
type: 'array',
|
||||
items: {
|
||||
docsCount: { type: 'long' },
|
||||
docsDeleted: { type: 'long' },
|
||||
alias: { type: 'text' },
|
||||
primaryStoreSizeBytes: { type: 'long' },
|
||||
storeSizeBytes: { type: 'long' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fetch() {
|
||||
return getCoreUsageDataService().getCoreUsageData();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function registerCoreUsageCollector(
|
||||
usageCollection: UsageCollectionSetup,
|
||||
getCoreUsageDataService: () => CoreUsageDataStart
|
||||
) {
|
||||
usageCollection.registerCollector(
|
||||
getCoreUsageCollector(usageCollection, getCoreUsageDataService)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 {
|
||||
CollectorOptions,
|
||||
createUsageCollectionSetupMock,
|
||||
} from '../../../../usage_collection/server/usage_collection.mock';
|
||||
|
||||
import { registerCoreUsageCollector } from '.';
|
||||
import { coreUsageDataServiceMock } from '../../../../../core/server/mocks';
|
||||
import { CoreUsageData } from 'src/core/server/';
|
||||
|
||||
describe('telemetry_core', () => {
|
||||
let collector: CollectorOptions;
|
||||
|
||||
const usageCollectionMock = createUsageCollectionSetupMock();
|
||||
usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
|
||||
collector = config;
|
||||
return createUsageCollectionSetupMock().makeUsageCollector(config);
|
||||
});
|
||||
|
||||
const callCluster = jest.fn().mockImplementation(() => ({}));
|
||||
const coreUsageDataStart = coreUsageDataServiceMock.createStartContract();
|
||||
const getCoreUsageDataReturnValue = (Symbol('core telemetry') as any) as CoreUsageData;
|
||||
coreUsageDataStart.getCoreUsageData.mockResolvedValue(getCoreUsageDataReturnValue);
|
||||
|
||||
beforeAll(() => registerCoreUsageCollector(usageCollectionMock, () => coreUsageDataStart));
|
||||
|
||||
test('registered collector is set', () => {
|
||||
expect(collector).not.toBeUndefined();
|
||||
expect(collector.type).toBe('core');
|
||||
});
|
||||
|
||||
test('fetch', async () => {
|
||||
expect(await collector.fetch(callCluster)).toEqual(getCoreUsageDataReturnValue);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { registerCoreUsageCollector } from './core_usage_collector';
|
|
@ -23,3 +23,4 @@ export { registerApplicationUsageCollector } from './application_usage';
|
|||
export { registerKibanaUsageCollector } from './kibana';
|
||||
export { registerOpsStatsCollector } from './ops_stats';
|
||||
export { registerCspCollector } from './csp';
|
||||
export { registerCoreUsageCollector } from './core';
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
SavedObjectsServiceSetup,
|
||||
OpsMetrics,
|
||||
Logger,
|
||||
CoreUsageDataStart,
|
||||
} from '../../../core/server';
|
||||
import {
|
||||
registerApplicationUsageCollector,
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
registerOpsStatsCollector,
|
||||
registerUiMetricUsageCollector,
|
||||
registerCspCollector,
|
||||
registerCoreUsageCollector,
|
||||
} from './collectors';
|
||||
|
||||
interface KibanaUsageCollectionPluginsDepsSetup {
|
||||
|
@ -53,6 +55,7 @@ export class KibanaUsageCollectionPlugin implements Plugin {
|
|||
private savedObjectsClient?: ISavedObjectsRepository;
|
||||
private uiSettingsClient?: IUiSettingsClient;
|
||||
private metric$: Subject<OpsMetrics>;
|
||||
private coreUsageData?: CoreUsageDataStart;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
|
@ -72,6 +75,7 @@ export class KibanaUsageCollectionPlugin implements Plugin {
|
|||
const savedObjectsClient = new SavedObjectsClient(this.savedObjectsClient);
|
||||
this.uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient);
|
||||
core.metrics.getOpsMetrics$().subscribe(this.metric$);
|
||||
this.coreUsageData = core.coreUsageData;
|
||||
}
|
||||
|
||||
public stop() {
|
||||
|
@ -86,6 +90,7 @@ export class KibanaUsageCollectionPlugin implements Plugin {
|
|||
) {
|
||||
const getSavedObjectsClient = () => this.savedObjectsClient;
|
||||
const getUiSettingsClient = () => this.uiSettingsClient;
|
||||
const getCoreUsageDataService = () => this.coreUsageData!;
|
||||
|
||||
registerOpsStatsCollector(usageCollection, metric$);
|
||||
registerKibanaUsageCollector(usageCollection, this.legacyConfig$);
|
||||
|
@ -98,5 +103,6 @@ export class KibanaUsageCollectionPlugin implements Plugin {
|
|||
getSavedObjectsClient
|
||||
);
|
||||
registerCspCollector(usageCollection, coreSetup.http);
|
||||
registerCoreUsageCollector(usageCollection, getCoreUsageDataService);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue