Fix search telemetry to only update SO periodically (#93130)

* Fix search telemetry to only update SO periodically

* Handle case when searches completed mid flight

* Fix error in resetting counters

* Update docs

* update docs

* Don't track restored searches

* Update docs

* Update docs

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Anton Dosov <anton.dosov@elastic.co>
This commit is contained in:
Lukas Olson 2021-03-11 09:17:43 -07:00 committed by GitHub
parent 633b53ec8d
commit 813e16f6ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 130 additions and 63 deletions

View file

@ -1,18 +0,0 @@
---
id: kibCasePluginApi
slug: /kibana-dev-docs/casePluginApi
title: case
image: https://source.unsplash.com/400x175/?github
summary: API docs for the case plugin
date: 2020-11-16
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'case']
warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info.
---
import caseObj from './case.json';
## Server
### Interfaces
<DocDefinitionList data={caseObj.server.interfaces}/>

View file

@ -1,5 +1,5 @@
{
"id": "case",
"id": "cases",
"client": {
"classes": [],
"functions": [],
@ -34,7 +34,7 @@
{
"pluginId": "cases",
"scope": "server",
"docId": "kibCasePluginApi",
"docId": "kibCasesPluginApi",
"section": "def-server.CasesClient",
"text": "CasesClient"
}
@ -60,4 +60,4 @@
"misc": [],
"objects": []
}
}
}

18
api_docs/cases.mdx Normal file
View file

@ -0,0 +1,18 @@
---
id: kibCasesPluginApi
slug: /kibana-dev-docs/casesPluginApi
title: cases
image: https://source.unsplash.com/400x175/?github
summary: API docs for the cases plugin
date: 2020-11-16
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases']
warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info.
---
import casesObj from './cases.json';
## Server
### Interfaces
<DocDefinitionList data={casesObj.server.interfaces}/>

View file

@ -1435,7 +1435,15 @@
"section": "def-server.SearchUsage",
"text": "SearchUsage"
},
" | undefined) => { next(response: ",
" | undefined, { isRestore }: ",
{
"pluginId": "data",
"scope": "common",
"docId": "kibDataSearchPluginApi",
"section": "def-common.ISearchOptions",
"text": "ISearchOptions"
},
") => { next(response: ",
{
"pluginId": "data",
"scope": "common",
@ -1459,7 +1467,7 @@
"description": [],
"source": {
"path": "src/plugins/data/server/search/collectors/usage.ts",
"lineNumber": 64
"lineNumber": 83
}
},
{
@ -1479,7 +1487,26 @@
"description": [],
"source": {
"path": "src/plugins/data/server/search/collectors/usage.ts",
"lineNumber": 64
"lineNumber": 84
}
},
{
"type": "Object",
"label": "{ isRestore }",
"isRequired": true,
"signature": [
{
"pluginId": "data",
"scope": "common",
"docId": "kibDataSearchPluginApi",
"section": "def-common.ISearchOptions",
"text": "ISearchOptions"
}
],
"description": [],
"source": {
"path": "src/plugins/data/server/search/collectors/usage.ts",
"lineNumber": 85
}
}
],
@ -1487,7 +1514,7 @@
"returnComment": [],
"source": {
"path": "src/plugins/data/server/search/collectors/usage.ts",
"lineNumber": 64
"lineNumber": 82
},
"initialIsOpen": false
},

View file

@ -4489,6 +4489,22 @@
],
"initialIsOpen": false
},
{
"tags": [],
"id": "def-common.osType",
"type": "Object",
"label": "osType",
"description": [],
"source": {
"path": "x-pack/plugins/lists/common/schemas/common/schemas.ts",
"lineNumber": 317
},
"signature": [
"KeyofC",
"<{ linux: null; macos: null; windows: null; }>"
],
"initialIsOpen": false
},
{
"tags": [],
"id": "def-common.osTypeArray",

View file

@ -607,7 +607,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/security_solution/server/plugin.ts",
"lineNumber": 336
"lineNumber": 337
}
},
{
@ -626,7 +626,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/security_solution/server/plugin.ts",
"lineNumber": 336
"lineNumber": 337
}
}
],
@ -634,7 +634,7 @@
"returnComment": [],
"source": {
"path": "x-pack/plugins/security_solution/server/plugin.ts",
"lineNumber": 336
"lineNumber": 337
}
},
{
@ -650,7 +650,7 @@
"returnComment": [],
"source": {
"path": "x-pack/plugins/security_solution/server/plugin.ts",
"lineNumber": 397
"lineNumber": 398
}
}
],

View file

@ -35,7 +35,7 @@
| [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | |
| [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | |
| [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally |
| [searchUsageObserver(logger, usage)](./kibana-plugin-plugins-data-server.searchusageobserver.md) | Rxjs observer for easily doing <code>tap(searchUsageObserver(logger, usage))</code> in an rxjs chain. |
| [searchUsageObserver(logger, usage, { isRestore })](./kibana-plugin-plugins-data-server.searchusageobserver.md) | Rxjs observer for easily doing <code>tap(searchUsageObserver(logger, usage))</code> in an rxjs chain. |
| [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | |
| [usageProvider(core)](./kibana-plugin-plugins-data-server.usageprovider.md) | |

View file

@ -9,7 +9,7 @@ Rxjs observer for easily doing `tap(searchUsageObserver(logger, usage))` in an r
<b>Signature:</b>
```typescript
export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage): {
export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage, { isRestore }?: ISearchOptions): {
next(response: IEsSearchResponse): void;
error(): void;
};
@ -21,6 +21,7 @@ export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage)
| --- | --- | --- |
| logger | <code>Logger</code> | |
| usage | <code>SearchUsage</code> | |
| { isRestore } | <code>ISearchOptions</code> | |
<b>Returns:</b>

View file

@ -6,13 +6,13 @@
* Side Public License, v 1.
*/
import { once } from 'lodash';
import { once, debounce } from 'lodash';
import type { CoreSetup, Logger } from 'kibana/server';
import { SavedObjectsErrorHelpers } from '../../../../../core/server';
import type { IEsSearchResponse } from '../../../common';
import type { IEsSearchResponse, ISearchOptions } from '../../../common';
import { isCompleteResponse } from '../../../common';
import { CollectedUsage } from './register';
const SAVED_OBJECT_ID = 'search-telemetry';
const MAX_RETRY_COUNT = 3;
export interface SearchUsage {
trackError(): Promise<void>;
@ -25,34 +25,52 @@ export function usageProvider(core: CoreSetup): SearchUsage {
return coreStart.savedObjects.createInternalRepository();
});
const trackSuccess = async (duration: number, retryCount = 0) => {
const repository = await getRepository();
try {
await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [
{ fieldName: 'successCount' },
{
fieldName: 'totalDuration',
incrementBy: duration,
},
]);
} catch (e) {
if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) {
setTimeout(() => trackSuccess(duration, retryCount + 1), 1000);
}
}
const collectedUsage: CollectedUsage = {
successCount: 0,
errorCount: 0,
totalDuration: 0,
};
const trackError = async (retryCount = 0) => {
const repository = await getRepository();
try {
await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [
{ fieldName: 'errorCount' },
]);
} catch (e) {
if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) {
setTimeout(() => trackError(retryCount + 1), 1000);
// Instead of updating the search count every time a search completes, we update some in-memory
// counts and only update the saved object every ~5 seconds
const updateSearchUsage = debounce(
async () => {
const repository = await getRepository();
const { successCount, errorCount, totalDuration } = collectedUsage;
const counterFields = Object.entries(collectedUsage)
.map(([fieldName, incrementBy]) => ({ fieldName, incrementBy }))
// Filter out any zero values because `incrementCounter` will still increment them
.filter(({ incrementBy }) => incrementBy > 0);
try {
await repository.incrementCounter<CollectedUsage>(
SAVED_OBJECT_ID,
SAVED_OBJECT_ID,
counterFields
);
// Since search requests may have completed while the saved object was being updated, we minus
// what was just updated in the saved object rather than resetting the values to 0
collectedUsage.successCount -= successCount;
collectedUsage.errorCount -= errorCount;
collectedUsage.totalDuration -= totalDuration;
} catch (e) {
// We didn't reset the counters so we'll retry when the next search request completes
}
}
},
5000,
{ maxWait: 5000 }
);
const trackSuccess = (duration: number) => {
collectedUsage.successCount++;
collectedUsage.totalDuration += duration;
return updateSearchUsage();
};
const trackError = () => {
collectedUsage.errorCount++;
return updateSearchUsage();
};
return { trackSuccess, trackError };
@ -61,9 +79,14 @@ export function usageProvider(core: CoreSetup): SearchUsage {
/**
* Rxjs observer for easily doing `tap(searchUsageObserver(logger, usage))` in an rxjs chain.
*/
export function searchUsageObserver(logger: Logger, usage?: SearchUsage) {
export function searchUsageObserver(
logger: Logger,
usage?: SearchUsage,
{ isRestore }: ISearchOptions = {}
) {
return {
next(response: IEsSearchResponse) {
if (isRestore || !isCompleteResponse(response)) return;
logger.debug(`trackSearchStatus:next ${response.rawResponse.took}`);
usage?.trackSuccess(response.rawResponse.took);
},

View file

@ -53,6 +53,6 @@ export const esSearchStrategyProvider = (
}
};
return from(search()).pipe(tap(searchUsageObserver(logger, usage)));
return from(search()).pipe(tap(searchUsageObserver(logger, usage, options)));
},
});

View file

@ -1350,7 +1350,7 @@ export interface SearchUsage {
// Warning: (ae-missing-release-tag) "searchUsageObserver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage): {
export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage, { isRestore }?: ISearchOptions): {
next(response: IEsSearchResponse): void;
error(): void;
};