-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Asset Management] Osquery telemetry updates #100754
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
adb5276
first pass of basic osquery usage stats collection
lykkin 821eee4
updates, linting
lykkin cab8d8d
updated exported metrics
lykkin 838ae1d
clean up comments, add description fields to metric fields
lykkin d01db68
reworked types
lykkin d6b904d
actually use the updated types
lykkin eb20cf8
added tests around the route usage recoder functions
lykkin be2e6f0
review comments
lykkin bcb6138
update aggregate types
lykkin 91d1b19
Merge branch 'master' into osquery-telemetry
kibanamachine c7746d2
Merge branch 'master' into osquery-telemetry
kibanamachine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
export * from './recorder'; |
135 changes: 135 additions & 0 deletions
135
x-pack/plugins/osquery/server/routes/usage/recorder.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; | ||
|
||
import { usageMetricSavedObjectType } from '../../../common/types'; | ||
|
||
import { | ||
CounterValue, | ||
createMetricObjects, | ||
getRouteMetric, | ||
incrementCount, | ||
RouteString, | ||
routeStrings, | ||
} from './recorder'; | ||
|
||
const savedObjectsClient = savedObjectsClientMock.create(); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function checkGetCalls(calls: any[]) { | ||
expect(calls.length).toEqual(routeStrings.length); | ||
for (let i = 0; i < routeStrings.length; ++i) { | ||
expect(calls[i]).toEqual([usageMetricSavedObjectType, routeStrings[i]]); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function checkCreateCalls(calls: any[], expectedCallRoutes: string[] = routeStrings) { | ||
expect(calls.length).toEqual(expectedCallRoutes.length); | ||
for (let i = 0; i < expectedCallRoutes.length; ++i) { | ||
expect(calls[i][0]).toEqual(usageMetricSavedObjectType); | ||
expect(calls[i][2].id).toEqual(expectedCallRoutes[i]); | ||
} | ||
} | ||
|
||
describe('Usage metric recorder', () => { | ||
describe('Metric initalizer', () => { | ||
const get = savedObjectsClient.get as jest.Mock; | ||
const create = savedObjectsClient.create as jest.Mock; | ||
afterEach(() => { | ||
get.mockClear(); | ||
create.mockClear(); | ||
}); | ||
it('should seed route metrics objects', async () => { | ||
get.mockRejectedValueOnce('stub value'); | ||
create.mockReturnValueOnce('stub value'); | ||
const result = await createMetricObjects(savedObjectsClient); | ||
checkGetCalls(get.mock.calls); | ||
checkCreateCalls(create.mock.calls); | ||
expect(result).toBe(true); | ||
}); | ||
|
||
it('should handle previously seeded objects properly', async () => { | ||
get.mockReturnValueOnce('stub value'); | ||
create.mockRejectedValueOnce('stub value'); | ||
const result = await createMetricObjects(savedObjectsClient); | ||
checkGetCalls(get.mock.calls); | ||
checkCreateCalls(create.mock.calls, []); | ||
expect(result).toBe(true); | ||
}); | ||
|
||
it('should report failure to create the metrics object', async () => { | ||
get.mockRejectedValueOnce('stub value'); | ||
create.mockRejectedValueOnce('stub value'); | ||
const result = await createMetricObjects(savedObjectsClient); | ||
checkGetCalls(get.mock.calls); | ||
checkCreateCalls(create.mock.calls); | ||
expect(result).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('Incrementation', () => { | ||
let counterMap: { [key: string]: CounterValue }; | ||
const get = savedObjectsClient.get as jest.Mock; | ||
const update = savedObjectsClient.update as jest.Mock; | ||
update.mockImplementation( | ||
async (objectType: string, route: RouteString, newVal: CounterValue) => { | ||
counterMap[`${objectType}-${route}`] = newVal; | ||
} | ||
); | ||
get.mockImplementation(async (objectType: string, route: RouteString) => ({ | ||
attributes: counterMap[`${objectType}-${route}`], | ||
})); | ||
beforeEach(() => { | ||
counterMap = routeStrings.reduce((acc, route) => { | ||
acc[`${usageMetricSavedObjectType}-${route}`] = { | ||
count: 0, | ||
errors: 0, | ||
}; | ||
return acc; | ||
}, {} as { [key: string]: CounterValue }); | ||
get.mockClear(); | ||
update.mockClear(); | ||
}); | ||
it('should increment the route counter', async () => { | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 0, | ||
errors: 0, | ||
}); | ||
await incrementCount(savedObjectsClient, 'live_query'); | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 1, | ||
errors: 0, | ||
}); | ||
}); | ||
|
||
it('should allow incrementing the error counter', async () => { | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 0, | ||
errors: 0, | ||
}); | ||
await incrementCount(savedObjectsClient, 'live_query', 'errors'); | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 0, | ||
errors: 1, | ||
}); | ||
}); | ||
|
||
it('should allow adjustment of the increment', async () => { | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 0, | ||
errors: 0, | ||
}); | ||
await incrementCount(savedObjectsClient, 'live_query', 'count', 2); | ||
expect(await getRouteMetric(savedObjectsClient, 'live_query')).toEqual({ | ||
count: 2, | ||
errors: 0, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { SavedObjectsClientContract } from 'kibana/server'; | ||
import { usageMetricSavedObjectType } from '../../../common/types'; | ||
import { LiveQuerySessionUsage } from '../../usage/types'; | ||
|
||
export interface RouteUsageMetric { | ||
queries: number; | ||
errors: number; | ||
} | ||
|
||
export type RouteString = 'live_query'; | ||
|
||
export const routeStrings: RouteString[] = ['live_query']; | ||
|
||
export async function createMetricObjects(soClient: SavedObjectsClientContract) { | ||
const res = await Promise.allSettled( | ||
routeStrings.map(async (route) => { | ||
try { | ||
await soClient.get(usageMetricSavedObjectType, route); | ||
} catch (e) { | ||
await soClient.create( | ||
usageMetricSavedObjectType, | ||
{ | ||
errors: 0, | ||
count: 0, | ||
}, | ||
{ | ||
id: route, | ||
} | ||
); | ||
} | ||
}) | ||
); | ||
return !res.some((e) => e.status === 'rejected'); | ||
} | ||
|
||
export async function getCount(soClient: SavedObjectsClientContract, route: RouteString) { | ||
return await soClient.get<LiveQuerySessionUsage>(usageMetricSavedObjectType, route); | ||
} | ||
|
||
export interface CounterValue { | ||
count: number; | ||
errors: number; | ||
} | ||
|
||
export async function incrementCount( | ||
soClient: SavedObjectsClientContract, | ||
route: RouteString, | ||
key: keyof CounterValue = 'count', | ||
increment = 1 | ||
) { | ||
const metric = await soClient.get<CounterValue>(usageMetricSavedObjectType, route); | ||
metric.attributes[key] += increment; | ||
await soClient.update(usageMetricSavedObjectType, route, metric.attributes); | ||
} | ||
|
||
export async function getRouteMetric(soClient: SavedObjectsClientContract, route: RouteString) { | ||
return (await getCount(soClient, route)).attributes; | ||
} |
28 changes: 28 additions & 0 deletions
28
x-pack/plugins/osquery/server/routes/usage/saved_object_mappings.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { SavedObjectsType } from '../../../../../../src/core/server'; | ||
|
||
import { usageMetricSavedObjectType } from '../../../common/types'; | ||
|
||
export const usageMetricSavedObjectMappings: SavedObjectsType['mappings'] = { | ||
properties: { | ||
count: { | ||
type: 'long', | ||
}, | ||
errors: { | ||
type: 'long', | ||
}, | ||
}, | ||
}; | ||
|
||
export const usageMetricType: SavedObjectsType = { | ||
name: usageMetricSavedObjectType, | ||
hidden: false, | ||
namespaceType: 'single', | ||
mappings: usageMetricSavedObjectMappings, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this ever flush the state, or does it just increment forever?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a cumulative count. i'm not entirely certain about the life times of saved objects, what events do they get flushed in outside of clearing them manually? i might be able to do something like clearing the count on fetch, but it felt like a wash to me.
are there any situations where delta metrics would be better for this kind of thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bearing with me. I have been OOO for a couple of days :-)
They don't - unless a space is deleted AFAIK.
Delta is maybe not a good word to describe it, but from what I have seen the usage collector runs every 24 hr and developers typically try to get a snapshot of the last 24 hours. That makes me think that clearing the count on read is what is needed. But I guess it only matters to people / systems consuming the telemetry. It's your call :- )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the context! i feel better about changing it knowing there's prior art, and knowing about the collection interval.