Skip to content

Commit 42e11e6

Browse files
lukasolsonLiza K
andauthored
[data.search.session] Server telemetry on search sessions (#91256)
* [data.search.session] Server telemetry on search sessions * Update telemetry mappings * Added tests and logger Co-authored-by: Liza K <liza.katz@elastic.co>
1 parent 2db193b commit 42e11e6

6 files changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import {
9+
SharedGlobalConfig,
10+
ElasticsearchClient,
11+
SavedObjectsErrorHelpers,
12+
Logger,
13+
} from '../../../../../src/core/server';
14+
import { BehaviorSubject } from 'rxjs';
15+
import { fetchProvider } from './fetch';
16+
import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
17+
18+
describe('fetchProvider', () => {
19+
let fetchFn: any;
20+
let esClient: jest.Mocked<ElasticsearchClient>;
21+
let mockLogger: Logger;
22+
23+
beforeEach(async () => {
24+
const config$ = new BehaviorSubject<SharedGlobalConfig>({
25+
kibana: {
26+
index: '123',
27+
},
28+
} as any);
29+
mockLogger = {
30+
warn: jest.fn(),
31+
debug: jest.fn(),
32+
} as any;
33+
esClient = elasticsearchServiceMock.createElasticsearchClient();
34+
fetchFn = fetchProvider(config$, mockLogger);
35+
});
36+
37+
test('returns when ES returns no results', async () => {
38+
esClient.search.mockResolvedValue({
39+
statusCode: 200,
40+
body: {
41+
aggregations: {
42+
persisted: {
43+
buckets: [],
44+
},
45+
},
46+
},
47+
} as any);
48+
49+
const collRes = await fetchFn({ esClient });
50+
expect(collRes.transientCount).toBe(0);
51+
expect(collRes.persistedCount).toBe(0);
52+
expect(collRes.totalCount).toBe(0);
53+
expect(mockLogger.warn).not.toBeCalled();
54+
});
55+
56+
test('returns when ES throws an error', async () => {
57+
esClient.search.mockRejectedValue(
58+
SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')
59+
);
60+
61+
const collRes = await fetchFn({ esClient });
62+
expect(collRes.transientCount).toBe(0);
63+
expect(collRes.persistedCount).toBe(0);
64+
expect(collRes.totalCount).toBe(0);
65+
expect(mockLogger.warn).toBeCalledTimes(1);
66+
});
67+
68+
test('returns when ES returns full buckets', async () => {
69+
esClient.search.mockResolvedValue({
70+
statusCode: 200,
71+
body: {
72+
aggregations: {
73+
persisted: {
74+
buckets: [
75+
{
76+
key_as_string: 'true',
77+
doc_count: 10,
78+
},
79+
{
80+
key_as_string: 'false',
81+
doc_count: 7,
82+
},
83+
],
84+
},
85+
},
86+
},
87+
} as any);
88+
89+
const collRes = await fetchFn({ esClient });
90+
expect(collRes.transientCount).toBe(7);
91+
expect(collRes.persistedCount).toBe(10);
92+
expect(collRes.totalCount).toBe(17);
93+
});
94+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { Observable } from 'rxjs';
9+
import { first } from 'rxjs/operators';
10+
import { SearchResponse } from 'elasticsearch';
11+
import { SharedGlobalConfig, Logger } from 'kibana/server';
12+
import { CollectorFetchContext } from '../../../../../src/plugins/usage_collection/server';
13+
import { SEARCH_SESSION_TYPE } from '../../common';
14+
import { ReportedUsage } from './register';
15+
16+
interface SessionPersistedTermsBucket {
17+
key_as_string: 'false' | 'true';
18+
doc_count: number;
19+
}
20+
21+
export function fetchProvider(config$: Observable<SharedGlobalConfig>, logger: Logger) {
22+
return async ({ esClient }: CollectorFetchContext): Promise<ReportedUsage> => {
23+
try {
24+
const config = await config$.pipe(first()).toPromise();
25+
const { body: esResponse } = await esClient.search<SearchResponse<unknown>>({
26+
index: config.kibana.index,
27+
body: {
28+
size: 0,
29+
aggs: {
30+
persisted: {
31+
terms: {
32+
field: `${SEARCH_SESSION_TYPE}.persisted`,
33+
},
34+
},
35+
},
36+
},
37+
});
38+
39+
const { buckets } = esResponse.aggregations.persisted;
40+
if (!buckets.length) {
41+
return { transientCount: 0, persistedCount: 0, totalCount: 0 };
42+
}
43+
44+
const { transientCount = 0, persistedCount = 0 } = buckets.reduce(
45+
(usage: Partial<ReportedUsage>, bucket: SessionPersistedTermsBucket) => {
46+
const key = bucket.key_as_string === 'false' ? 'transientCount' : 'persistedCount';
47+
return { ...usage, [key]: bucket.doc_count };
48+
},
49+
{}
50+
);
51+
const totalCount = transientCount + persistedCount;
52+
logger.debug(`fetchProvider | ${persistedCount} persisted | ${transientCount} transient`);
53+
return { transientCount, persistedCount, totalCount };
54+
} catch (e) {
55+
logger.warn(`fetchProvider | error | ${e.message}`);
56+
return { transientCount: 0, persistedCount: 0, totalCount: 0 };
57+
}
58+
};
59+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { registerUsageCollector } from './register';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { PluginInitializerContext, Logger } from 'kibana/server';
9+
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
10+
import { fetchProvider } from './fetch';
11+
12+
export interface ReportedUsage {
13+
transientCount: number;
14+
persistedCount: number;
15+
totalCount: number;
16+
}
17+
18+
export async function registerUsageCollector(
19+
usageCollection: UsageCollectionSetup,
20+
context: PluginInitializerContext,
21+
logger: Logger
22+
) {
23+
try {
24+
const collector = usageCollection.makeUsageCollector<ReportedUsage>({
25+
type: 'search-session',
26+
isReady: () => true,
27+
fetch: fetchProvider(context.config.legacy.globalConfig$, logger),
28+
schema: {
29+
transientCount: { type: 'long' },
30+
persistedCount: { type: 'long' },
31+
totalCount: { type: 'long' },
32+
},
33+
});
34+
usageCollection.registerCollector(collector);
35+
} catch (err) {
36+
return; // kibana plugin is not enabled (test environment)
37+
}
38+
}

x-pack/plugins/data_enhanced/server/plugin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { getUiSettings } from './ui_settings';
2525
import type { DataEnhancedRequestHandlerContext } from './type';
2626
import { ConfigSchema } from '../config';
27+
import { registerUsageCollector } from './collectors';
2728
import { SecurityPluginSetup } from '../../security/server';
2829

2930
interface SetupDependencies {
@@ -85,6 +86,10 @@ export class EnhancedDataServerPlugin
8586
this.sessionService.setup(core, {
8687
taskManager: deps.taskManager,
8788
});
89+
90+
if (deps.usageCollection) {
91+
registerUsageCollector(deps.usageCollection, this.initializerContext, this.logger);
92+
}
8893
}
8994

9095
public start(core: CoreStart, { taskManager }: StartDependencies) {

x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,6 +3183,19 @@
31833183
}
31843184
}
31853185
},
3186+
"search-session": {
3187+
"properties": {
3188+
"transientCount": {
3189+
"type": "long"
3190+
},
3191+
"persistedCount": {
3192+
"type": "long"
3193+
},
3194+
"totalCount": {
3195+
"type": "long"
3196+
}
3197+
}
3198+
},
31863199
"security_solution": {
31873200
"properties": {
31883201
"detections": {

0 commit comments

Comments
 (0)