From 34750aba84433b74936a6afc65afdc79d71e2b05 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Fri, 14 Jun 2024 17:06:40 +0530 Subject: [PATCH] feat: show warning when the top level operations count is more than 2500 (#5193) --- frontend/src/AppRoutes/pageComponents.ts | 7 + frontend/src/AppRoutes/routes.ts | 8 + frontend/src/constants/routes.ts | 1 + .../Columns/GetColumnSearchProps.tsx | 78 ++++++++-- .../ServiceApplication.styles.scss | 25 +++ .../Columns/GetColumnSearchProps.tsx | 18 ++- .../TopNav/DateTimeSelectionV2/config.ts | 1 + .../ServiceTopLevelOperations.styles.scss | 26 ++++ .../pages/ServiceTopLevelOperations/index.tsx | 142 ++++++++++++++++++ frontend/src/types/api/metrics/getService.ts | 3 + frontend/src/utils/permission/index.ts | 1 + 11 files changed, 291 insertions(+), 19 deletions(-) create mode 100644 frontend/src/container/ServiceApplication/ServiceApplication.styles.scss create mode 100644 frontend/src/pages/ServiceTopLevelOperations/ServiceTopLevelOperations.styles.scss create mode 100644 frontend/src/pages/ServiceTopLevelOperations/index.tsx diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index bda390afbf..9275e7d6f6 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -11,6 +11,13 @@ export const ServiceMetricsPage = Loadable( ), ); +export const ServiceTopLevelOperationsPage = Loadable( + () => + import( + /* webpackChunkName: "ServiceMetricsPage" */ 'pages/ServiceTopLevelOperations' + ), +); + export const ServiceMapPage = Loadable( () => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'), ); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index fed77f186e..4fd421ffba 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -33,6 +33,7 @@ import { ServiceMapPage, ServiceMetricsPage, ServicesTablePage, + ServiceTopLevelOperationsPage, SettingsPage, ShortcutsPage, SignupPage, @@ -84,6 +85,13 @@ const routes: AppRoutes[] = [ isPrivate: true, key: 'SERVICE_METRICS', }, + { + path: ROUTES.SERVICE_TOP_LEVEL_OPERATIONS, + exact: true, + component: ServiceTopLevelOperationsPage, + isPrivate: true, + key: 'SERVICE_TOP_LEVEL_OPERATIONS', + }, { path: ROUTES.SERVICE_MAP, component: ServiceMapPage, diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index cbeb672a5c..243bdd0bba 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -2,6 +2,7 @@ const ROUTES = { SIGN_UP: '/signup', LOGIN: '/login', SERVICE_METRICS: '/services/:servicename', + SERVICE_TOP_LEVEL_OPERATIONS: '/services/:servicename/top-level-operations', SERVICE_MAP: '/service-map', TRACE: '/trace', TRACE_DETAIL: '/trace/:id', diff --git a/frontend/src/container/ServiceApplication/Columns/GetColumnSearchProps.tsx b/frontend/src/container/ServiceApplication/Columns/GetColumnSearchProps.tsx index b272a39475..372f301008 100644 --- a/frontend/src/container/ServiceApplication/Columns/GetColumnSearchProps.tsx +++ b/frontend/src/container/ServiceApplication/Columns/GetColumnSearchProps.tsx @@ -1,13 +1,26 @@ +import '../ServiceApplication.styles.scss'; + import { SearchOutlined } from '@ant-design/icons'; +import { Popconfirm, PopconfirmProps } from 'antd'; import type { ColumnType } from 'antd/es/table'; import ROUTES from 'constants/routes'; import { routeConfig } from 'container/SideNav/config'; import { getQueryString } from 'container/SideNav/helper'; +import history from 'lib/history'; +import { Info } from 'lucide-react'; import { Link } from 'react-router-dom'; import { ServicesList } from 'types/api/metrics/getService'; import { filterDropdown } from '../Filter/FilterDropdown'; -import { Name } from '../styles'; + +const MAX_TOP_LEVEL_OPERATIONS = 2500; + +const highTopLevelOperationsPopoverDesc = (metrics: string): JSX.Element => ( +
+ The service `{metrics}` has too many top level operations. It makes the + dashboard slow to load. +
+); export const getColumnSearchProps = ( dataIndex: keyof ServicesList, @@ -15,24 +28,61 @@ export const getColumnSearchProps = ( ): ColumnType => ({ filterDropdown, filterIcon: , - onFilter: (value: string | number | boolean, record: ServicesList): boolean => - record[dataIndex] - .toString() - .toLowerCase() - .includes(value.toString().toLowerCase()), - render: (metrics: string): JSX.Element => { + onFilter: ( + value: string | number | boolean, + record: ServicesList, + ): boolean => { + if (record[dataIndex]) { + record[dataIndex] + ?.toString() + .toLowerCase() + .includes(value.toString().toLowerCase()); + } + + return false; + }, + render: (metrics: string, record: ServicesList): JSX.Element => { const urlParams = new URLSearchParams(search); const avialableParams = routeConfig[ROUTES.SERVICE_METRICS]; const queryString = getQueryString(avialableParams, urlParams); + const topLevelOperations = record?.dataWarning?.topLevelOps || []; + + const handleShowTopLevelOperations: PopconfirmProps['onConfirm'] = () => { + history.push( + `${ROUTES.APPLICATION}/${encodeURIComponent(metrics)}/top-level-operations`, + ); + }; + + const hasHighTopLevelOperations = + topLevelOperations && + Array.isArray(topLevelOperations) && + topLevelOperations.length > MAX_TOP_LEVEL_OPERATIONS; return ( - - {metrics} - +
+ {hasHighTopLevelOperations && ( + + + + )} + + + {metrics} + +
); }, }); diff --git a/frontend/src/container/ServiceApplication/ServiceApplication.styles.scss b/frontend/src/container/ServiceApplication/ServiceApplication.styles.scss new file mode 100644 index 0000000000..9cdbd2329c --- /dev/null +++ b/frontend/src/container/ServiceApplication/ServiceApplication.styles.scss @@ -0,0 +1,25 @@ +.serviceName { + color: #4e74f8; + font-weight: 600; + cursor: pointer; + + display: flex; + align-items: center; + gap: 8px; +} + +.error { + color: var(--bg-cherry-500); + + a { + color: var(--bg-cherry-500); + } +} + +.service-high-top-level-operations { + width: 300px; + + .popover-description { + padding: 16px 0; + } +} diff --git a/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx b/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx index 4257dc57ec..059063e5d9 100644 --- a/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx +++ b/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx @@ -15,11 +15,19 @@ export const getColumnSearchProps = ( ): ColumnType => ({ filterDropdown, filterIcon: , - onFilter: (value: string | number | boolean, record: ServicesList): boolean => - record[dataIndex] - .toString() - .toLowerCase() - .includes(value.toString().toLowerCase()), + onFilter: ( + value: string | number | boolean, + record: ServicesList, + ): boolean => { + if (record[dataIndex]) { + record[dataIndex] + ?.toString() + .toLowerCase() + .includes(value.toString().toLowerCase()); + } + + return false; + }, render: (metrics: string): JSX.Element => { const urlParams = new URLSearchParams(search); const avialableParams = routeConfig[ROUTES.SERVICE_METRICS]; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index 7909984592..7543e02a47 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -202,6 +202,7 @@ export const routesToSkip = [ ROUTES.INTEGRATIONS, ROUTES.DASHBOARD, ROUTES.DASHBOARD_WIDGET, + ROUTES.SERVICE_TOP_LEVEL_OPERATIONS, ]; export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS]; diff --git a/frontend/src/pages/ServiceTopLevelOperations/ServiceTopLevelOperations.styles.scss b/frontend/src/pages/ServiceTopLevelOperations/ServiceTopLevelOperations.styles.scss new file mode 100644 index 0000000000..e7c5dfcda4 --- /dev/null +++ b/frontend/src/pages/ServiceTopLevelOperations/ServiceTopLevelOperations.styles.scss @@ -0,0 +1,26 @@ +.title { + font-weight: 500; + font-size: 14px; + + margin: 16px 0; +} + +.info-alert { + margin: 16px 0; +} + +.top-level-operations-header { + display: flex; + align-items: center; + gap: 8px; +} + +.breadcrumb { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.top-level-operations-list { + margin-top: 16px; +} diff --git a/frontend/src/pages/ServiceTopLevelOperations/index.tsx b/frontend/src/pages/ServiceTopLevelOperations/index.tsx new file mode 100644 index 0000000000..38a2ae2807 --- /dev/null +++ b/frontend/src/pages/ServiceTopLevelOperations/index.tsx @@ -0,0 +1,142 @@ +import './ServiceTopLevelOperations.styles.scss'; + +import { SyncOutlined } from '@ant-design/icons'; +import { Alert, Table, Typography } from 'antd'; +import ROUTES from 'constants/routes'; +import { IServiceName } from 'container/MetricsApplication/Tabs/types'; +import useErrorNotification from 'hooks/useErrorNotification'; +import { useQueryService } from 'hooks/useQueryService'; +import useResourceAttribute from 'hooks/useResourceAttribute'; +import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; +import { BarChart2 } from 'lucide-react'; +import { ReactNode, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { Link, useParams } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { Tags } from 'types/reducer/trace'; + +export default function ServiceTopLevelOperations(): JSX.Element { + const { servicename: encodedServiceName } = useParams(); + const { maxTime, minTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + const servicename = decodeURIComponent(encodedServiceName); + const { queries } = useResourceAttribute(); + const selectedTags = useMemo( + () => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [], + [queries], + ); + + const [topLevelOperations, setTopLevelOperations] = useState([]); + + const { data, error, isLoading } = useQueryService({ + minTime, + maxTime, + selectedTime, + selectedTags, + }); + + useErrorNotification(error); + + useEffect(() => { + const selectedService = data?.find( + (service) => service.serviceName === servicename, + ); + + setTopLevelOperations(selectedService?.dataWarning?.topLevelOps || []); + }, [servicename, data]); + + const alertDesc = (): ReactNode => ( +
+ SigNoz calculates the RED metrics for a service using the entry-point spans. + For more details, you can check out our + + {' '} + docs + + . We expect the number of unique entry-point operations to be no more than + 2500. The high number of top level operations might be due to an + instrumentation issue in your service. Below table shows the sample top level + operations. Please refer to official docs for span name guidelines{' '} + + {' '} + here + {' '} + and update the instrumentation to to follow the guidelines. If there are any + dynamic IDs in the span name, make sure to use the span attributes instead. + If you have more questions, please reach out to us via support. +
+ ); + + const columns = [ + { + title: 'Top Level Operation', + key: 'top-level-operation', + render: (operation: string): JSX.Element => ( +
+ {operation} +
+ ), + }, + ]; + + return ( +
+ + + + {' '} + services{' '} + + +
/
+ + {servicename} + +
+ +
+ +
+ + {isLoading && ( +
+ + Loading ... + +
+ )} + + {!isLoading && ( +
+ 'Top Level Operations'} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + dataSource={topLevelOperations} + loading={isLoading} + showHeader={false} + pagination={{ + pageSize: 100, + hideOnSinglePage: true, + showTotal: (total: number, range: number[]): string => + `${range[0]}-${range[1]} of ${total}`, + }} + /> + + )} + + ); +} diff --git a/frontend/src/types/api/metrics/getService.ts b/frontend/src/types/api/metrics/getService.ts index bb9d6db941..d931197433 100644 --- a/frontend/src/types/api/metrics/getService.ts +++ b/frontend/src/types/api/metrics/getService.ts @@ -15,6 +15,9 @@ export interface ServicesList { callRate: number; numErrors: number; errorRate: number; + dataWarning?: { + topLevelOps?: string[]; + }; } export type PayloadProps = ServicesList[]; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 1c992965d4..44757e3508 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -97,4 +97,5 @@ export const routePermission: Record = { OLD_LOGS_EXPLORER: [], SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'], INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'], + SERVICE_TOP_LEVEL_OPERATIONS: ['ADMIN', 'EDITOR', 'VIEWER'], };