-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[APM] Metrics powered UI POC #66871
[APM] Metrics powered UI POC #66871
Changes from 1 commit
cb45b3a
10cc497
0ebef5d
f2714bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,37 +7,46 @@ | |
import { | ||
Setup, | ||
SetupUIFilters, | ||
SetupTimeRange | ||
SetupTimeRange, | ||
SetupHasTransactionDurationMetrics | ||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths | ||
} from '../../server/lib/helpers/setup_request'; | ||
import { SERVICE_NAME, PROCESSOR_EVENT } from '../elasticsearch_fieldnames'; | ||
import { SERVICE_NAME } from '../elasticsearch_fieldnames'; | ||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths | ||
import { rangeFilter } from '../../server/lib/helpers/range_filter'; | ||
|
||
export function getServicesProjection({ | ||
setup | ||
}: { | ||
setup: Setup & SetupTimeRange & SetupUIFilters; | ||
setup: Setup & | ||
SetupTimeRange & | ||
SetupUIFilters & | ||
SetupHasTransactionDurationMetrics; | ||
}) { | ||
const { start, end, uiFiltersES, indices } = setup; | ||
const { | ||
start, | ||
end, | ||
uiFiltersES, | ||
indices, | ||
hasTransactionDurationMetrics | ||
} = setup; | ||
|
||
const index = [ | ||
indices['apm_oss.metricsIndices'], | ||
indices['apm_oss.errorIndices'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any plans to pre-aggregate errors in the future? |
||
]; | ||
|
||
if (!hasTransactionDurationMetrics) { | ||
index.push(indices['apm_oss.transactionIndices']); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of having this for every query, what about letting the APM elasticsearch client handle this? So the consumer side specifies the indicies that should be queried as strings (typescript can enforce correctness): {
apmIndices: ['transactions', 'errors']
body: {...}
} Then the client will determine which indices should be queried according to const indices = apmIndices.map(apmIndex => {
switch(apmIndex) {
case 'span':
return indices['apm_oss.spanIndices'];
case 'error':
return indices['apm_oss.errorIndices'];
case 'metric':
return indices['apm_oss.metricsIndices'];
case 'transaction':
return hasTransactionDurationMetrics
? indices['apm_oss.metricsIndices']
: indices['apm_oss.transactionIndices']
}
}) We need this client anyway to make it easier when teams outside APM needs want to query apm data (siem for instance) |
||
|
||
return { | ||
index: [ | ||
indices['apm_oss.metricsIndices'], | ||
indices['apm_oss.errorIndices'], | ||
indices['apm_oss.transactionIndices'] | ||
], | ||
index, | ||
body: { | ||
size: 0, | ||
query: { | ||
bool: { | ||
filter: [ | ||
{ | ||
terms: { [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'] } | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing the processor.event filter could cause problems because by default we query I think the queried indicies and processor.event should always be aligned. I was hoping we could add the const { index, processorEvent } = hasTransactionDurationMetrics
? // transactions are stored as pre-aggregated (histogram) docs in metric index
getIndexAndProcessorEvent('metric', 'error')
: // transactions are not pre-aggregated and stored in transaction index
getIndexAndProcessorEvent('metric', 'error', transaction); |
||
{ range: rangeFilter(start, end) }, | ||
...uiFiltersES | ||
] | ||
filter: [{ range: rangeFilter(start, end) }, ...uiFiltersES] | ||
} | ||
}, | ||
aggs: { | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,10 @@ | |||||||||||
*/ | ||||||||||||
|
||||||||||||
import moment from 'moment'; | ||||||||||||
import { | ||||||||||||
PROCESSOR_EVENT, | ||||||||||||
TRANSACTION_DURATION_HISTOGRAM | ||||||||||||
} from '../../../common/elasticsearch_fieldnames'; | ||||||||||||
import { KibanaRequest } from '../../../../../../src/core/server'; | ||||||||||||
import { IIndexPattern } from '../../../../../../src/plugins/data/common'; | ||||||||||||
import { APMConfig } from '../..'; | ||||||||||||
|
@@ -19,6 +23,7 @@ import { APMRequestHandlerContext } from '../../routes/typings'; | |||||||||||
import { getESClient } from './es_client'; | ||||||||||||
import { ProcessorEvent } from '../../../common/processor_event'; | ||||||||||||
import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern'; | ||||||||||||
import { rangeFilter } from './range_filter'; | ||||||||||||
|
||||||||||||
function decodeUiFilters( | ||||||||||||
indexPattern: IIndexPattern | undefined, | ||||||||||||
|
@@ -50,6 +55,10 @@ export interface SetupUIFilters { | |||||||||||
uiFiltersES: ESFilter[]; | ||||||||||||
} | ||||||||||||
|
||||||||||||
export interface SetupHasTransactionDurationMetrics { | ||||||||||||
hasTransactionDurationMetrics: boolean; | ||||||||||||
} | ||||||||||||
|
||||||||||||
interface SetupRequestParams { | ||||||||||||
query?: { | ||||||||||||
_debug?: boolean; | ||||||||||||
|
@@ -60,17 +69,31 @@ interface SetupRequestParams { | |||||||||||
}; | ||||||||||||
} | ||||||||||||
|
||||||||||||
type InferSetup<TParams extends SetupRequestParams> = Setup & | ||||||||||||
interface SetupRequestOptions { | ||||||||||||
checkForTransactionDurationMetrics: boolean; | ||||||||||||
} | ||||||||||||
|
||||||||||||
type InferSetup< | ||||||||||||
TParams extends SetupRequestParams, | ||||||||||||
TOptions extends SetupRequestOptions | ||||||||||||
> = Setup & | ||||||||||||
(TParams extends { query: { start: string } } ? { start: number } : {}) & | ||||||||||||
(TParams extends { query: { end: string } } ? { end: number } : {}) & | ||||||||||||
(TParams extends { query: { uiFilters: string } } | ||||||||||||
? { uiFiltersES: ESFilter[] } | ||||||||||||
: {}) & | ||||||||||||
(TOptions extends { checkForTransactionDurationMetrics: true } | ||||||||||||
? { hasTransactionDurationMetrics: boolean } | ||||||||||||
: {}); | ||||||||||||
|
||||||||||||
export async function setupRequest<TParams extends SetupRequestParams>( | ||||||||||||
export async function setupRequest< | ||||||||||||
TParams extends SetupRequestParams, | ||||||||||||
TOptions extends SetupRequestOptions | ||||||||||||
>( | ||||||||||||
context: APMRequestHandlerContext<TParams>, | ||||||||||||
request: KibanaRequest | ||||||||||||
): Promise<InferSetup<TParams>> { | ||||||||||||
request: KibanaRequest, | ||||||||||||
options?: TOptions | ||||||||||||
): Promise<InferSetup<TParams, TOptions>> { | ||||||||||||
const { config } = context; | ||||||||||||
const { query } = context.params; | ||||||||||||
|
||||||||||||
|
@@ -79,17 +102,56 @@ export async function setupRequest<TParams extends SetupRequestParams>( | |||||||||||
config | ||||||||||||
}); | ||||||||||||
|
||||||||||||
const dynamicIndexPattern = await getDynamicIndexPattern({ | ||||||||||||
context, | ||||||||||||
indices, | ||||||||||||
processorEvent: query.processorEvent | ||||||||||||
}); | ||||||||||||
const client = getESClient(context, request, { clientAsInternalUser: false }); | ||||||||||||
|
||||||||||||
const start = | ||||||||||||
'start' in query ? { start: moment.utc(query.start).valueOf() } : {}; | ||||||||||||
const end = 'end' in query ? { end: moment.utc(query.end).valueOf() } : {}; | ||||||||||||
Comment on lines
+107
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about:
Suggested change
Then you don't have to do |
||||||||||||
|
||||||||||||
const checkTransactionDurationMetrics = async () => { | ||||||||||||
const response = await client.search({ | ||||||||||||
index: indices['apm_oss.metricsIndices'], | ||||||||||||
body: { | ||||||||||||
query: { | ||||||||||||
bool: { | ||||||||||||
filter: [ | ||||||||||||
{ term: { [PROCESSOR_EVENT]: 'metric' } }, | ||||||||||||
...(start.start && end.end | ||||||||||||
? [{ range: rangeFilter(start.start, end.end) }] | ||||||||||||
: []), | ||||||||||||
{ exists: { field: TRANSACTION_DURATION_HISTOGRAM } } | ||||||||||||
] | ||||||||||||
} | ||||||||||||
}, | ||||||||||||
size: 0 | ||||||||||||
}, | ||||||||||||
terminateAfter: 1 | ||||||||||||
}); | ||||||||||||
|
||||||||||||
return { | ||||||||||||
hasTransactionDurationMetrics: response.hits.total.value > 0 | ||||||||||||
}; | ||||||||||||
}; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps extract this to separate file to avoid making setup_request too overwhelming |
||||||||||||
|
||||||||||||
const [ | ||||||||||||
dynamicIndexPattern, | ||||||||||||
hasTransactionDurationMetrics | ||||||||||||
] = await Promise.all([ | ||||||||||||
getDynamicIndexPattern({ | ||||||||||||
context, | ||||||||||||
indices, | ||||||||||||
processorEvent: query.processorEvent | ||||||||||||
}), | ||||||||||||
options?.checkForTransactionDurationMetrics | ||||||||||||
? checkTransactionDurationMetrics() | ||||||||||||
: Promise.resolve({}) | ||||||||||||
]); | ||||||||||||
|
||||||||||||
const uiFiltersES = decodeUiFilters(dynamicIndexPattern, query.uiFilters); | ||||||||||||
|
||||||||||||
const coreSetupRequest = { | ||||||||||||
indices, | ||||||||||||
client: getESClient(context, request, { clientAsInternalUser: false }), | ||||||||||||
client, | ||||||||||||
internalClient: getESClient(context, request, { | ||||||||||||
clientAsInternalUser: true | ||||||||||||
}), | ||||||||||||
|
@@ -98,9 +160,10 @@ export async function setupRequest<TParams extends SetupRequestParams>( | |||||||||||
}; | ||||||||||||
|
||||||||||||
return { | ||||||||||||
...('start' in query ? { start: moment.utc(query.start).valueOf() } : {}), | ||||||||||||
...('end' in query ? { end: moment.utc(query.end).valueOf() } : {}), | ||||||||||||
...start, | ||||||||||||
...end, | ||||||||||||
...hasTransactionDurationMetrics, | ||||||||||||
...('uiFilters' in query ? { uiFiltersES } : {}), | ||||||||||||
...coreSetupRequest | ||||||||||||
} as InferSetup<TParams>; | ||||||||||||
} as InferSetup<TParams, TOptions>; | ||||||||||||
} |
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.
Nit about the naming: Is it important to note that it's
TransactionDuration
docs, and not justTransaction
docs?What about
hasPreAggregatedTransactions
orisPreAggregationEnabled
(if we in the future want to pre-aggregate other data types, eg. errors ?