Skip to content

Commit

Permalink
[APM] Progressive fetching (experimental) (elastic#127598)
Browse files Browse the repository at this point in the history
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
2 people authored and kertal committed May 24, 2022
1 parent a02109d commit b61f596
Show file tree
Hide file tree
Showing 33 changed files with 662 additions and 237 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,10 @@ module.exports = {
},
],
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useFetcher$' }],
'react-hooks/exhaustive-deps': [
'error',
{ additionalHooks: '^(useFetcher|useProgressiveFetcher)$' },
],
},
},
{
Expand Down
5 changes: 5 additions & 0 deletions src/core/types/elasticsearch/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ export type AggregateOf<
reverse_nested: {
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
random_sampler: {
seed: number;
probability: number;
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
sampler: {
doc_count: number;
} & SubAggregateOf<TAggregationContainer, TDocument>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useTimeRange } from '../../../hooks/use_time_range';
import { SearchBar } from '../../shared/search_bar';
import { ServiceList } from './service_list';
import { MLCallout, shouldDisplayMlCallout } from '../../shared/ml_callout';
import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher';
import { joinByKey } from '../../../../common/utils/join_by_key';
import { ServiceInventoryFieldName } from '../../../../common/service_inventory';
import { orderServiceItems } from './service_list/order_service_items';
Expand Down Expand Up @@ -62,7 +63,7 @@ function useServicesFetcher() {
[start, end, environment, kuery, serviceGroup]
);

const mainStatisticsFetch = useFetcher(
const mainStatisticsFetch = useProgressiveFetcher(
(callApmApi) => {
if (start && end) {
return callApmApi('GET /internal/apm/services', {
Expand All @@ -88,9 +89,14 @@ function useServicesFetcher() {

const { data: mainStatisticsData = initialData } = mainStatisticsFetch;

const comparisonFetch = useFetcher(
const comparisonFetch = useProgressiveFetcher(
(callApmApi) => {
if (start && end && mainStatisticsData.items.length) {
if (
start &&
end &&
mainStatisticsData.items.length &&
mainStatisticsFetch.status === FETCH_STATUS.SUCCESS
) {
return callApmApi('GET /internal/apm/services/detailed_statistics', {
params: {
query: {
Expand Down Expand Up @@ -141,14 +147,16 @@ export function ServiceInventory() {
!userHasDismissedCallout &&
shouldDisplayMlCallout(anomalyDetectionSetupState);

const useOptimizedSorting = useKibana().services.uiSettings?.get<boolean>(
apmServiceInventoryOptimizedSorting
);
const useOptimizedSorting =
useKibana().services.uiSettings?.get<boolean>(
apmServiceInventoryOptimizedSorting
) || false;

let isLoading: boolean;

if (useOptimizedSorting) {
isLoading =
// ensures table is usable when sorted and filtered services have loaded
sortedAndFilteredServicesFetch.status === FETCH_STATUS.LOADING ||
(sortedAndFilteredServicesFetch.status === FETCH_STATUS.SUCCESS &&
sortedAndFilteredServicesFetch.data?.services.length === 0 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { useApmParams } from '../../../hooks/use_apm_params';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
import { SearchBar } from '../../shared/search_bar';
import { TraceList } from './trace_list';
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge';
import { useTimeRange } from '../../../hooks/use_time_range';
import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher';

type TracesAPIResponse = APIReturnType<'GET /internal/apm/traces'>;
const DEFAULT_RESPONSE: TracesAPIResponse = {
Expand All @@ -31,7 +32,7 @@ export function TraceOverview() {

const { start, end } = useTimeRange({ rangeFrom, rangeTo });

const { status, data = DEFAULT_RESPONSE } = useFetcher(
const { status, data = DEFAULT_RESPONSE } = useProgressiveFetcher(
(callApmApi) => {
if (start && end) {
return callApmApi('GET /internal/apm/traces', {
Expand Down
155 changes: 155 additions & 0 deletions x-pack/plugins/apm/public/hooks/use_progressive_fetcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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 type { OmitByValue, Assign } from 'utility-types';
import type {
ClientRequestParamsOf,
EndpointOf,
ReturnOf,
} from '@kbn/server-route-repository';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
apmProgressiveLoading,
getProbabilityFromProgressiveLoadingQuality,
ProgressiveLoadingQuality,
} from '@kbn/observability-plugin/common';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { APMServerRouteRepository } from '../../server';

import type {
APMClient,
APMClientOptions,
} from '../services/rest/create_call_apm_api';
import { FetcherResult, FETCH_STATUS, useFetcher } from './use_fetcher';

type APMProgressivelyLoadingServerRouteRepository = OmitByValue<
{
[key in keyof APMServerRouteRepository]: ClientRequestParamsOf<
APMServerRouteRepository,
key
> extends {
params: { query: { probability: any } };
}
? APMServerRouteRepository[key]
: undefined;
},
undefined
>;

type WithoutProbabilityParameter<T extends Record<string, any>> = {
params: { query: {} };
} & Assign<
T,
{
params: Omit<T['params'], 'query'> & {
query: Omit<T['params']['query'], 'probability'>;
};
}
>;

type APMProgressiveAPIClient = <
TEndpoint extends EndpointOf<APMProgressivelyLoadingServerRouteRepository>
>(
endpoint: TEndpoint,
options: Omit<APMClientOptions, 'signal'> &
WithoutProbabilityParameter<
ClientRequestParamsOf<
APMProgressivelyLoadingServerRouteRepository,
TEndpoint
>
>
) => Promise<ReturnOf<APMProgressivelyLoadingServerRouteRepository, TEndpoint>>;

function clientWithProbability(
regularCallApmApi: APMClient,
probability: number
) {
return <
TEndpoint extends EndpointOf<APMProgressivelyLoadingServerRouteRepository>
>(
endpoint: TEndpoint,
options: Omit<APMClientOptions, 'signal'> &
WithoutProbabilityParameter<
ClientRequestParamsOf<
APMProgressivelyLoadingServerRouteRepository,
TEndpoint
>
>
) => {
return regularCallApmApi(endpoint, {
...options,
params: {
...options.params,
query: {
...options.params.query,
probability,
},
},
} as any);
};
}

export function useProgressiveFetcher<TReturn>(
callback: (
callApmApi: APMProgressiveAPIClient
) => Promise<TReturn> | undefined,
dependencies: any[],
options?: Parameters<typeof useFetcher>[2]
): FetcherResult<TReturn> {
const {
services: { uiSettings },
} = useKibana();

const progressiveLoadingQuality =
uiSettings?.get<ProgressiveLoadingQuality>(apmProgressiveLoading) ??
ProgressiveLoadingQuality.off;

const sampledProbability = getProbabilityFromProgressiveLoadingQuality(
progressiveLoadingQuality
);

const sampledFetch = useFetcher(
(regularCallApmApi) => {
if (progressiveLoadingQuality === ProgressiveLoadingQuality.off) {
return;
}
return callback(
clientWithProbability(regularCallApmApi, sampledProbability)
);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
dependencies,
options
);

const unsampledFetch = useFetcher(
(regularCallApmApi) => {
return callback(clientWithProbability(regularCallApmApi, 1));
},
// eslint-disable-next-line react-hooks/exhaustive-deps
dependencies
);

const fetches = [unsampledFetch, sampledFetch];

const isError = unsampledFetch.status === FETCH_STATUS.FAILURE;

const usedFetch =
(!isError &&
fetches.find((fetch) => fetch.status === FETCH_STATUS.SUCCESS)) ||
unsampledFetch;

const status =
unsampledFetch.status === FETCH_STATUS.LOADING &&
usedFetch.status === FETCH_STATUS.SUCCESS
? FETCH_STATUS.LOADING
: usedFetch.status;

return {
...usedFetch,
status,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { eventMetadataRouteRepository } from '../event_metadata/route';
import { suggestionsRouteRepository } from '../suggestions/route';
import { agentKeysRouteRepository } from '../agent_keys/route';

const getTypedGlobalApmServerRouteRepository = () => {
function getTypedGlobalApmServerRouteRepository() {
const repository = {
...dataViewRouteRepository,
...environmentsRouteRepository,
Expand Down Expand Up @@ -70,7 +70,7 @@ const getTypedGlobalApmServerRouteRepository = () => {
};

return repository;
};
}

const getGlobalApmServerRouteRepository = (): ServerRouteRepository => {
return getTypedGlobalApmServerRouteRepository();
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/apm/server/routes/default_api_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import * as t from 'io-ts';
import { isoToEpochRt } from '@kbn/io-ts-utils';
import { isoToEpochRt, toNumberRt } from '@kbn/io-ts-utils';

export { environmentRt } from '../../common/environment_rt';

Expand All @@ -15,4 +15,7 @@ export const rangeRt = t.type({
end: isoToEpochRt,
});

export const probabilityRt = t.type({
probability: toNumberRt,
});
export const kueryRt = t.type({ kuery: t.string });
Loading

0 comments on commit b61f596

Please sign in to comment.