mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-22 18:44:27 +08:00
262 lines
8.1 KiB
TypeScript
262 lines
8.1 KiB
TypeScript
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
|
import Graph from 'components/Graph';
|
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
|
import ROUTES from 'constants/routes';
|
|
import FullView from 'container/GridGraphLayout/Graph/FullView';
|
|
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
|
import { colors } from 'lib/getRandomColor';
|
|
import history from 'lib/history';
|
|
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
|
import { escapeRegExp } from 'lodash-es';
|
|
import React, { useMemo, useRef } from 'react';
|
|
import { useSelector } from 'react-redux';
|
|
import { useParams } from 'react-router-dom';
|
|
import { AppState } from 'store/reducers';
|
|
import { PromQLWidgets } from 'types/api/dashboard/getAll';
|
|
import MetricReducer from 'types/reducer/metrics';
|
|
|
|
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
|
import TopOperationsTable from '../TopOperationsTable';
|
|
import { Button } from './styles';
|
|
|
|
function Application({ getWidget }: DashboardProps): JSX.Element {
|
|
const { servicename } = useParams<{ servicename?: string }>();
|
|
const selectedTimeStamp = useRef(0);
|
|
|
|
const {
|
|
topOperations,
|
|
serviceOverview,
|
|
resourceAttributePromQLQuery,
|
|
resourceAttributeQueries,
|
|
topLevelOperations,
|
|
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
|
const operationsRegex = useMemo(() => {
|
|
return encodeURIComponent(
|
|
topLevelOperations.map((e) => escapeRegExp(e)).join('|'),
|
|
);
|
|
}, [topLevelOperations]);
|
|
|
|
const selectedTraceTags: string = JSON.stringify(
|
|
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries, 'array') || [],
|
|
);
|
|
|
|
const onTracePopupClick = (timestamp: number): void => {
|
|
const currentTime = timestamp;
|
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
|
|
|
const urlParams = new URLSearchParams();
|
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
|
|
|
history.replace(
|
|
`${
|
|
ROUTES.TRACE
|
|
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
|
|
);
|
|
};
|
|
|
|
const onClickHandler = async (
|
|
event: ChartEvent,
|
|
elements: ActiveElement[],
|
|
chart: Chart,
|
|
data: ChartData,
|
|
from: string,
|
|
): Promise<void> => {
|
|
if (event.native) {
|
|
const points = chart.getElementsAtEventForMode(
|
|
event.native,
|
|
'nearest',
|
|
{ intersect: true },
|
|
true,
|
|
);
|
|
|
|
const id = `${from}_button`;
|
|
const buttonElement = document.getElementById(id);
|
|
|
|
if (points.length !== 0) {
|
|
const firstPoint = points[0];
|
|
|
|
if (data.labels) {
|
|
const time = data?.labels[firstPoint.index] as Date;
|
|
|
|
if (buttonElement) {
|
|
buttonElement.style.display = 'block';
|
|
buttonElement.style.left = `${firstPoint.element.x}px`;
|
|
buttonElement.style.top = `${firstPoint.element.y}px`;
|
|
selectedTimeStamp.current = time.getTime();
|
|
}
|
|
}
|
|
} else if (buttonElement && buttonElement.style.display === 'block') {
|
|
buttonElement.style.display = 'none';
|
|
}
|
|
}
|
|
};
|
|
|
|
const onErrorTrackHandler = (timestamp: number): void => {
|
|
const currentTime = timestamp;
|
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
|
|
|
const urlParams = new URLSearchParams();
|
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
|
|
|
history.replace(
|
|
`${
|
|
ROUTES.TRACE
|
|
}?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Row gutter={24}>
|
|
<Col span={12}>
|
|
<Button
|
|
type="default"
|
|
size="small"
|
|
id="Service_button"
|
|
onClick={(): void => {
|
|
onTracePopupClick(selectedTimeStamp.current);
|
|
}}
|
|
>
|
|
View Traces
|
|
</Button>
|
|
<Card>
|
|
<GraphTitle>Latency</GraphTitle>
|
|
<GraphContainer>
|
|
<Graph
|
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
|
onClickHandler(ChartEvent, activeElements, chart, data, 'Service');
|
|
}}
|
|
name="service_latency"
|
|
type="line"
|
|
data={{
|
|
datasets: [
|
|
{
|
|
data: serviceOverview.map((e) =>
|
|
parseFloat(convertToNanoSecondsToSecond(e.p99)),
|
|
),
|
|
borderColor: colors[0],
|
|
label: 'p99 Latency',
|
|
showLine: true,
|
|
borderWidth: 1.5,
|
|
spanGaps: true,
|
|
pointRadius: 1.5,
|
|
},
|
|
{
|
|
data: serviceOverview.map((e) =>
|
|
parseFloat(convertToNanoSecondsToSecond(e.p95)),
|
|
),
|
|
borderColor: colors[1],
|
|
label: 'p95 Latency',
|
|
showLine: true,
|
|
borderWidth: 1.5,
|
|
spanGaps: true,
|
|
pointRadius: 1.5,
|
|
},
|
|
{
|
|
data: serviceOverview.map((e) =>
|
|
parseFloat(convertToNanoSecondsToSecond(e.p50)),
|
|
),
|
|
borderColor: colors[2],
|
|
label: 'p50 Latency',
|
|
showLine: true,
|
|
borderWidth: 1.5,
|
|
spanGaps: true,
|
|
pointRadius: 1.5,
|
|
},
|
|
],
|
|
labels: serviceOverview.map((e) => {
|
|
return new Date(
|
|
parseFloat(convertToNanoSecondsToSecond(e.timestamp)),
|
|
);
|
|
}),
|
|
}}
|
|
yAxisUnit="ms"
|
|
/>
|
|
</GraphContainer>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col span={12}>
|
|
<Button
|
|
type="default"
|
|
size="small"
|
|
id="Rate_button"
|
|
onClick={(): void => {
|
|
onTracePopupClick(selectedTimeStamp.current);
|
|
}}
|
|
>
|
|
View Traces
|
|
</Button>
|
|
<Card>
|
|
<GraphTitle>Rate (ops/s)</GraphTitle>
|
|
<GraphContainer>
|
|
<FullView
|
|
name="operations_per_sec"
|
|
fullViewOptions={false}
|
|
onClickHandler={(event, element, chart, data): void => {
|
|
onClickHandler(event, element, chart, data, 'Rate');
|
|
}}
|
|
widget={getWidget([
|
|
{
|
|
query: `sum(rate(signoz_latency_count{service_name="${servicename}", operation=~\`${operationsRegex}\`${resourceAttributePromQLQuery}}[5m]))`,
|
|
legend: 'Operations',
|
|
},
|
|
])}
|
|
yAxisUnit="ops"
|
|
/>
|
|
</GraphContainer>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={24}>
|
|
<Col span={12}>
|
|
<Button
|
|
type="default"
|
|
size="small"
|
|
id="Error_button"
|
|
onClick={(): void => {
|
|
onErrorTrackHandler(selectedTimeStamp.current);
|
|
}}
|
|
>
|
|
View Traces
|
|
</Button>
|
|
|
|
<Card>
|
|
<GraphTitle>Error Percentage</GraphTitle>
|
|
<GraphContainer>
|
|
<FullView
|
|
name="error_percentage_%"
|
|
fullViewOptions={false}
|
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
|
onClickHandler(ChartEvent, activeElements, chart, data, 'Error');
|
|
}}
|
|
widget={getWidget([
|
|
{
|
|
query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`, status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[5m]) OR rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`, http_status_code=~"5.."${resourceAttributePromQLQuery}}[5m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`${resourceAttributePromQLQuery}}[5m]))) < 1000 OR vector(0)`,
|
|
legend: 'Error Percentage',
|
|
},
|
|
])}
|
|
yAxisUnit="%"
|
|
/>
|
|
</GraphContainer>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col span={12}>
|
|
<Card>
|
|
<TopOperationsTable data={topOperations} />
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
}
|
|
|
|
interface DashboardProps {
|
|
getWidget: (query: PromQLWidgets['query']) => PromQLWidgets;
|
|
}
|
|
|
|
export default Application;
|