feat: show warning when the top level operations count is more than 2500 (#5193)

This commit is contained in:
Yunus M 2024-06-14 17:06:40 +05:30 committed by GitHub
parent dbfa4e80bb
commit 34750aba84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 291 additions and 19 deletions

View File

@ -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'),
);

View File

@ -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,

View File

@ -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',

View File

@ -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 => (
<div className="popover-description">
The service `{metrics}` has too many top level operations. It makes the
dashboard slow to load.
</div>
);
export const getColumnSearchProps = (
dataIndex: keyof ServicesList,
@ -15,24 +28,61 @@ export const getColumnSearchProps = (
): ColumnType<ServicesList> => ({
filterDropdown,
filterIcon: <SearchOutlined />,
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 (
<Link
to={`${ROUTES.APPLICATION}/${encodeURIComponent(
metrics,
)}?${queryString.join('')}`}
>
<Name>{metrics}</Name>
</Link>
<div className={`serviceName ${hasHighTopLevelOperations ? 'error' : ''} `}>
{hasHighTopLevelOperations && (
<Popconfirm
title="Too Many Top Level Operations"
description={highTopLevelOperationsPopoverDesc(metrics)}
placement="right"
overlayClassName="service-high-top-level-operations"
onConfirm={handleShowTopLevelOperations}
trigger={['hover']}
showCancel={false}
okText="Show Top Level Operations"
>
<Info size={14} />
</Popconfirm>
)}
<Link
to={`${ROUTES.APPLICATION}/${encodeURIComponent(
metrics,
)}?${queryString.join('')}`}
>
{metrics}
</Link>
</div>
);
},
});

View File

@ -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;
}
}

View File

@ -15,11 +15,19 @@ export const getColumnSearchProps = (
): ColumnType<ServicesList> => ({
filterDropdown,
filterIcon: <SearchOutlined />,
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];

View File

@ -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];

View File

@ -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;
}

View File

@ -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<IServiceName>();
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<string[]>([]);
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 => (
<div className="">
SigNoz calculates the RED metrics for a service using the entry-point spans.
For more details, you can check out our
<a
href="https://signoz.io/docs/userguide/metrics/#open-the-services-section"
target="_blank"
rel="noreferrer"
>
{' '}
docs
</a>
. 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{' '}
<a
href="https://opentelemetry.io/docs/specs/otel/trace/api/#span"
target="_blank"
rel="noreferrer"
>
{' '}
here
</a>{' '}
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.
</div>
);
const columns = [
{
title: 'Top Level Operation',
key: 'top-level-operation',
render: (operation: string): JSX.Element => (
<div className="top-level-operations-list-item" key={operation}>
<Typography.Text> {operation} </Typography.Text>
</div>
),
},
];
return (
<div className="container">
<Typography.Title level={5} className="top-level-operations-header">
<Link to={ROUTES.APPLICATION}>
<span className="breadcrumb">
{' '}
<BarChart2 size={12} /> services{' '}
</span>
</Link>
<div className="divider">/</div>
<Link to={`${ROUTES.APPLICATION}/${servicename}`}>
<span className="breadcrumb">{servicename} </span>
</Link>
</Typography.Title>
<div className="info-alert">
<Alert message={alertDesc()} type="info" showIcon />
</div>
{isLoading && (
<div className="loading-top-level-operations">
<Typography.Title level={5}>
<SyncOutlined spin /> Loading ...
</Typography.Title>
</div>
)}
{!isLoading && (
<div className="top-level-operations-list">
<Table
columns={columns}
bordered
title={(): string => '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}`,
}}
/>
</div>
)}
</div>
);
}

View File

@ -15,6 +15,9 @@ export interface ServicesList {
callRate: number;
numErrors: number;
errorRate: number;
dataWarning?: {
topLevelOps?: string[];
};
}
export type PayloadProps = ServicesList[];

View File

@ -97,4 +97,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
OLD_LOGS_EXPLORER: [],
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
SERVICE_TOP_LEVEL_OPERATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
};