mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
(cherry picked from commit c05190e516
)
Co-authored-by: Dario Gieselaar <dario.gieselaar@elastic.co>
This commit is contained in:
parent
195c2622ce
commit
237173dfbb
11 changed files with 204 additions and 134 deletions
|
@ -43,35 +43,40 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
services: { fetchElasticFlamechart },
|
||||
} = useProfilingDependencies();
|
||||
|
||||
const state = useTimeRangeAsync(() => {
|
||||
return Promise.all([
|
||||
fetchElasticFlamechart({
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
kuery,
|
||||
}),
|
||||
comparisonTimeRange.start && comparisonTimeRange.end
|
||||
? fetchElasticFlamechart({
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
kuery: comparisonKuery,
|
||||
})
|
||||
: Promise.resolve(undefined),
|
||||
]).then(([primaryFlamegraph, comparisonFlamegraph]) => {
|
||||
return {
|
||||
primaryFlamegraph,
|
||||
comparisonFlamegraph,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
timeRange.start,
|
||||
timeRange.end,
|
||||
kuery,
|
||||
comparisonTimeRange.start,
|
||||
comparisonTimeRange.end,
|
||||
comparisonKuery,
|
||||
fetchElasticFlamechart,
|
||||
]);
|
||||
const state = useTimeRangeAsync(
|
||||
({ http }) => {
|
||||
return Promise.all([
|
||||
fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
kuery,
|
||||
}),
|
||||
comparisonTimeRange.start && comparisonTimeRange.end
|
||||
? fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
kuery: comparisonKuery,
|
||||
})
|
||||
: Promise.resolve(undefined),
|
||||
]).then(([primaryFlamegraph, comparisonFlamegraph]) => {
|
||||
return {
|
||||
primaryFlamegraph,
|
||||
comparisonFlamegraph,
|
||||
};
|
||||
});
|
||||
},
|
||||
[
|
||||
timeRange.start,
|
||||
timeRange.end,
|
||||
kuery,
|
||||
comparisonTimeRange.start,
|
||||
comparisonTimeRange.end,
|
||||
comparisonKuery,
|
||||
fetchElasticFlamechart,
|
||||
]
|
||||
);
|
||||
|
||||
const { data } = state;
|
||||
|
||||
|
@ -173,7 +178,6 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
<AsyncComponent {...state} style={{ height: '100%' }} size="xl">
|
||||
<FlameGraph
|
||||
id="flamechart"
|
||||
height={'100%'}
|
||||
primaryFlamegraph={data?.primaryFlamegraph}
|
||||
comparisonFlamegraph={data?.comparisonFlamegraph}
|
||||
comparisonMode={comparisonMode}
|
||||
|
|
|
@ -145,7 +145,6 @@ function FlameGraphTooltip({
|
|||
|
||||
export interface FlameGraphProps {
|
||||
id: string;
|
||||
height: number | string;
|
||||
comparisonMode: FlameGraphComparisonMode;
|
||||
primaryFlamegraph?: ElasticFlameGraph;
|
||||
comparisonFlamegraph?: ElasticFlameGraph;
|
||||
|
@ -153,7 +152,6 @@ export interface FlameGraphProps {
|
|||
|
||||
export const FlameGraph: React.FC<FlameGraphProps> = ({
|
||||
id,
|
||||
height,
|
||||
comparisonMode,
|
||||
primaryFlamegraph,
|
||||
comparisonFlamegraph,
|
||||
|
|
|
@ -42,28 +42,36 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
|
|||
services: { fetchTopNFunctions },
|
||||
} = useProfilingDependencies();
|
||||
|
||||
const state = useTimeRangeAsync(() => {
|
||||
return fetchTopNFunctions({
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
startIndex: 0,
|
||||
endIndex: 1000,
|
||||
kuery,
|
||||
});
|
||||
}, [timeRange.start, timeRange.end, kuery, fetchTopNFunctions]);
|
||||
const state = useTimeRangeAsync(
|
||||
({ http }) => {
|
||||
return fetchTopNFunctions({
|
||||
http,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
startIndex: 0,
|
||||
endIndex: 1000,
|
||||
kuery,
|
||||
});
|
||||
},
|
||||
[timeRange.start, timeRange.end, kuery, fetchTopNFunctions]
|
||||
);
|
||||
|
||||
const comparisonState = useTimeRangeAsync(() => {
|
||||
if (!comparisonTimeRange.start || !comparisonTimeRange.end) {
|
||||
return undefined;
|
||||
}
|
||||
return fetchTopNFunctions({
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
startIndex: 0,
|
||||
endIndex: 1000,
|
||||
kuery: comparisonKuery,
|
||||
});
|
||||
}, [comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions]);
|
||||
const comparisonState = useTimeRangeAsync(
|
||||
({ http }) => {
|
||||
if (!comparisonTimeRange.start || !comparisonTimeRange.end) {
|
||||
return undefined;
|
||||
}
|
||||
return fetchTopNFunctions({
|
||||
http,
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
startIndex: 0,
|
||||
endIndex: 1000,
|
||||
kuery: comparisonKuery,
|
||||
});
|
||||
},
|
||||
[comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions]
|
||||
);
|
||||
|
||||
const routePath = useProfilingRoutePath() as
|
||||
| '/functions'
|
||||
|
|
|
@ -50,29 +50,33 @@ export function StackTracesView() {
|
|||
rangeTo,
|
||||
});
|
||||
|
||||
const state = useTimeRangeAsync(() => {
|
||||
if (!topNType) {
|
||||
return Promise.resolve({ charts: [], metadata: {} });
|
||||
}
|
||||
return fetchTopN({
|
||||
type: topNType,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
kuery,
|
||||
}).then((response: TopNResponse) => {
|
||||
const totalCount = response.TotalCount;
|
||||
const samples = response.TopN;
|
||||
const charts = groupSamplesByCategory({
|
||||
samples,
|
||||
totalCount,
|
||||
metadata: response.Metadata,
|
||||
labels: response.Labels,
|
||||
const state = useTimeRangeAsync(
|
||||
({ http }) => {
|
||||
if (!topNType) {
|
||||
return Promise.resolve({ charts: [], metadata: {} });
|
||||
}
|
||||
return fetchTopN({
|
||||
http,
|
||||
type: topNType,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
kuery,
|
||||
}).then((response: TopNResponse) => {
|
||||
const totalCount = response.TotalCount;
|
||||
const samples = response.TopN;
|
||||
const charts = groupSamplesByCategory({
|
||||
samples,
|
||||
totalCount,
|
||||
metadata: response.Metadata,
|
||||
labels: response.Labels,
|
||||
});
|
||||
return {
|
||||
charts,
|
||||
};
|
||||
});
|
||||
return {
|
||||
charts,
|
||||
};
|
||||
});
|
||||
}, [topNType, timeRange.start, timeRange.end, fetchTopN, kuery]);
|
||||
},
|
||||
[topNType, timeRange.start, timeRange.end, fetchTopN, kuery]
|
||||
);
|
||||
|
||||
const [highlightedSubchart, setHighlightedSubchart] = useState<TopNSubchart | undefined>(
|
||||
undefined
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { HttpStart } from '@kbn/core-http-browser';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { HttpFetchOptions, HttpHandler, HttpStart } from '@kbn/core-http-browser';
|
||||
import { AbortError } from '@kbn/kibana-utils-plugin/common';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Overwrite, ValuesType } from 'utility-types';
|
||||
import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
|
||||
export enum AsyncStatus {
|
||||
|
@ -20,8 +22,22 @@ export interface AsyncState<T> {
|
|||
status: AsyncStatus;
|
||||
}
|
||||
|
||||
const HTTP_METHODS = ['fetch', 'get', 'post', 'put', 'delete', 'patch'] as const;
|
||||
|
||||
type HttpMethod = ValuesType<typeof HTTP_METHODS>;
|
||||
|
||||
type AutoAbortedHttpMethod = (
|
||||
path: string,
|
||||
options: Omit<HttpFetchOptions, 'signal'>
|
||||
) => ReturnType<HttpHandler>;
|
||||
|
||||
export type AutoAbortedHttpService = Overwrite<
|
||||
HttpStart,
|
||||
Record<HttpMethod, AutoAbortedHttpMethod>
|
||||
>;
|
||||
|
||||
export type UseAsync = <T>(
|
||||
fn: ({ http }: { http: HttpStart }) => Promise<T> | undefined,
|
||||
fn: ({ http }: { http: AutoAbortedHttpService }) => Promise<T> | undefined,
|
||||
dependencies: any[]
|
||||
) => AsyncState<T>;
|
||||
|
||||
|
@ -37,8 +53,30 @@ export const useAsync: UseAsync = (fn, dependencies) => {
|
|||
|
||||
const { data, error } = asyncState;
|
||||
|
||||
const controllerRef = useRef(new AbortController());
|
||||
|
||||
useEffect(() => {
|
||||
const returnValue = fn({ http });
|
||||
controllerRef.current.abort();
|
||||
|
||||
controllerRef.current = new AbortController();
|
||||
|
||||
const autoAbortedMethods = {} as Record<HttpMethod, AutoAbortedHttpMethod>;
|
||||
|
||||
for (const key of HTTP_METHODS) {
|
||||
autoAbortedMethods[key] = (path, options) => {
|
||||
return http[key](path, { ...options, signal: controllerRef.current.signal }).catch(
|
||||
(err) => {
|
||||
if (err.name === 'AbortError') {
|
||||
// return never-resolving promise
|
||||
return new Promise(() => {});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const returnValue = fn({ http: { ...http, ...autoAbortedMethods } });
|
||||
|
||||
if (returnValue === undefined) {
|
||||
setAsyncState({
|
||||
|
@ -63,13 +101,23 @@ export const useAsync: UseAsync = (fn, dependencies) => {
|
|||
});
|
||||
|
||||
returnValue.catch((nextError) => {
|
||||
if (nextError instanceof AbortError) {
|
||||
return;
|
||||
}
|
||||
setAsyncState({
|
||||
status: AsyncStatus.Settled,
|
||||
error: nextError,
|
||||
});
|
||||
throw nextError;
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [http, ...dependencies]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
controllerRef.current.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return asyncState;
|
||||
};
|
||||
|
|
|
@ -90,7 +90,7 @@ export class ProfilingPlugin implements Plugin {
|
|||
unknown
|
||||
];
|
||||
|
||||
const profilingFetchServices = getServices(coreStart);
|
||||
const profilingFetchServices = getServices();
|
||||
const { renderApp } = await import('./app');
|
||||
|
||||
function pushKueryToSubject(location: Location) {
|
||||
|
|
|
@ -4,21 +4,23 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CoreStart, HttpFetchQuery } from '@kbn/core/public';
|
||||
import { HttpFetchQuery } from '@kbn/core/public';
|
||||
import { getRoutePaths } from '../common';
|
||||
import { BaseFlameGraph, createFlameGraph, ElasticFlameGraph } from '../common/flamegraph';
|
||||
import { TopNFunctions } from '../common/functions';
|
||||
import { TopNResponse } from '../common/topn';
|
||||
import { AutoAbortedHttpService } from './hooks/use_async';
|
||||
|
||||
export interface Services {
|
||||
fetchTopN: (params: {
|
||||
http: AutoAbortedHttpService;
|
||||
type: string;
|
||||
timeFrom: number;
|
||||
timeTo: number;
|
||||
kuery: string;
|
||||
}) => Promise<TopNResponse>;
|
||||
fetchTopNFunctions: (params: {
|
||||
http: AutoAbortedHttpService;
|
||||
timeFrom: number;
|
||||
timeTo: number;
|
||||
startIndex: number;
|
||||
|
@ -26,42 +28,31 @@ export interface Services {
|
|||
kuery: string;
|
||||
}) => Promise<TopNFunctions>;
|
||||
fetchElasticFlamechart: (params: {
|
||||
http: AutoAbortedHttpService;
|
||||
timeFrom: number;
|
||||
timeTo: number;
|
||||
kuery: string;
|
||||
}) => Promise<ElasticFlameGraph>;
|
||||
}
|
||||
|
||||
export function getServices(core: CoreStart): Services {
|
||||
export function getServices(): Services {
|
||||
const paths = getRoutePaths();
|
||||
|
||||
return {
|
||||
fetchTopN: async ({ type, timeFrom, timeTo, kuery }) => {
|
||||
fetchTopN: async ({ http, type, timeFrom, timeTo, kuery }) => {
|
||||
try {
|
||||
const query: HttpFetchQuery = {
|
||||
timeFrom,
|
||||
timeTo,
|
||||
kuery,
|
||||
};
|
||||
return await core.http.get(`${paths.TopN}/${type}`, { query });
|
||||
return await http.get(`${paths.TopN}/${type}`, { query });
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
|
||||
fetchTopNFunctions: async ({
|
||||
timeFrom,
|
||||
timeTo,
|
||||
startIndex,
|
||||
endIndex,
|
||||
kuery,
|
||||
}: {
|
||||
timeFrom: number;
|
||||
timeTo: number;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
kuery: string;
|
||||
}) => {
|
||||
fetchTopNFunctions: async ({ http, timeFrom, timeTo, startIndex, endIndex, kuery }) => {
|
||||
try {
|
||||
const query: HttpFetchQuery = {
|
||||
timeFrom,
|
||||
|
@ -70,28 +61,20 @@ export function getServices(core: CoreStart): Services {
|
|||
endIndex,
|
||||
kuery,
|
||||
};
|
||||
return await core.http.get(paths.TopNFunctions, { query });
|
||||
return await http.get(paths.TopNFunctions, { query });
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
|
||||
fetchElasticFlamechart: async ({
|
||||
timeFrom,
|
||||
timeTo,
|
||||
kuery,
|
||||
}: {
|
||||
timeFrom: number;
|
||||
timeTo: number;
|
||||
kuery: string;
|
||||
}) => {
|
||||
fetchElasticFlamechart: async ({ http, timeFrom, timeTo, kuery }) => {
|
||||
try {
|
||||
const query: HttpFetchQuery = {
|
||||
timeFrom,
|
||||
timeTo,
|
||||
kuery,
|
||||
};
|
||||
const baseFlamegraph: BaseFlameGraph = await core.http.get(paths.Flamechart, { query });
|
||||
const baseFlamegraph = (await http.get(paths.Flamechart, { query })) as BaseFlameGraph;
|
||||
return createFlameGraph(baseFlamegraph);
|
||||
} catch (e) {
|
||||
return e;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths } from '../../common';
|
||||
import { createCalleeTree } from '../../common/callee';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
import { createBaseFlameGraph } from '../../common/flamegraph';
|
||||
import { createProfilingEsClient } from '../utils/create_profiling_es_client';
|
||||
import { withProfilingSpan } from '../utils/with_profiling_span';
|
||||
|
@ -71,14 +72,8 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP
|
|||
logger.info('returning payload response to client');
|
||||
|
||||
return response.ok({ body: flamegraph });
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return response.customError({
|
||||
statusCode: e.statusCode ?? 500,
|
||||
body: {
|
||||
message: e.message,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return handleRouteHandlerError({ error, logger, response });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
|
|||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths } from '../../common';
|
||||
import { createTopNFunctions } from '../../common/functions';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
import { createProfilingEsClient } from '../utils/create_profiling_es_client';
|
||||
import { withProfilingSpan } from '../utils/with_profiling_span';
|
||||
import { getClient } from './compat';
|
||||
|
@ -72,14 +73,8 @@ export function registerTopNFunctionsSearchRoute({ router, logger }: RouteRegist
|
|||
return response.ok({
|
||||
body: topNFunctions,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return response.customError({
|
||||
statusCode: e.statusCode ?? 500,
|
||||
body: {
|
||||
message: e.message,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return handleRouteHandlerError({ error, logger, response });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/hist
|
|||
import { groupStackFrameMetadataByStackTrace, StackTraceID } from '../../common/profiling';
|
||||
import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces';
|
||||
import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
import { ProfilingRequestHandlerContext } from '../types';
|
||||
import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client';
|
||||
import { withProfilingSpan } from '../utils/with_profiling_span';
|
||||
|
@ -189,15 +190,8 @@ export function queryTopNCommon(
|
|||
kuery,
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
|
||||
return response.customError({
|
||||
statusCode: e.statusCode ?? 500,
|
||||
body: {
|
||||
message: 'Profiling TopN request failed: ' + e.message + '; full error ' + e.toString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return handleRouteHandlerError({ error, logger, response });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { KibanaResponseFactory } from '@kbn/core-http-server';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { WrappedElasticsearchClientError } from '@kbn/observability-plugin/server';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
|
||||
export function handleRouteHandlerError({
|
||||
error,
|
||||
logger,
|
||||
response,
|
||||
}: {
|
||||
error: any;
|
||||
response: KibanaResponseFactory;
|
||||
logger: Logger;
|
||||
}) {
|
||||
if (
|
||||
error instanceof WrappedElasticsearchClientError &&
|
||||
error.originalError instanceof errors.RequestAbortedError
|
||||
) {
|
||||
return response.custom({
|
||||
statusCode: 499,
|
||||
body: {
|
||||
message: 'Client closed request',
|
||||
},
|
||||
});
|
||||
}
|
||||
logger.error(error);
|
||||
|
||||
return response.customError({
|
||||
statusCode: error.statusCode ?? 500,
|
||||
body: {
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue