Merge pull request #64 from SigNoz/adds-external-calls

Adds external API monitoring calls
This commit is contained in:
Ankit Nayan 2021-05-02 17:08:09 +05:30 committed by GitHub
commit c110a71fff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 468 additions and 18 deletions

View File

@ -1,3 +1,3 @@
export const ENVIRONMENT = { export const ENVIRONMENT = {
baseURL: "", baseURL: "",
}; };

View File

@ -0,0 +1,114 @@
import React from "react";
import { Line as ChartJSLine } from "react-chartjs-2";
import { ChartOptions } from "chart.js";
import { withRouter } from "react-router";
import { RouteComponentProps } from "react-router-dom";
import styled from "styled-components";
import { getOptions, borderColors } from "./graphConfig";
import { externalMetricsItem } from "../../store/actions/metrics";
import { uniqBy, filter } from "lodash";
const theme = "dark";
interface ExternalApiGraphProps extends RouteComponentProps<any> {
data: externalMetricsItem[];
keyIdentifier?: string;
label?: string;
title: string;
dataIdentifier: string;
fnDataIdentifier?: (s: number | string) => number | string;
}
interface ExternalApiGraph {
chartRef: any;
}
class ExternalApiGraph extends React.Component<ExternalApiGraphProps> {
constructor(props: ExternalApiGraphProps) {
super(props);
this.chartRef = React.createRef();
}
state = {
xcoordinate: 0,
ycoordinate: 0,
showpopUp: false,
firstpoint_ts: 0,
// graphInfo:{}
};
render() {
const {
title,
label,
data,
dataIdentifier,
keyIdentifier,
fnDataIdentifier,
} = this.props;
const getDataSets = () => {
if (!keyIdentifier) {
return [
{
label: label || "",
data: data.map((s: externalMetricsItem) =>
fnDataIdentifier
? fnDataIdentifier(s[dataIdentifier])
: s[dataIdentifier],
),
pointRadius: 0.5,
borderColor: borderColors[0],
borderWidth: 2,
},
];
}
const uniq = uniqBy(data, keyIdentifier);
return uniq.map((obj: externalMetricsItem, i: number) => {
const _data = filter(
data,
(s: externalMetricsItem) => s[keyIdentifier] === obj[keyIdentifier],
);
return {
label: obj[keyIdentifier],
data: _data.map((s: externalMetricsItem) =>
fnDataIdentifier
? fnDataIdentifier(s[dataIdentifier])
: s[dataIdentifier],
),
pointRadius: 0.5,
borderColor: borderColors[i] || borderColors[0], // Can also add transparency in border color
borderWidth: 2,
};
});
};
const data_chartJS = (canvas: any) => {
const ctx = canvas.getContext("2d");
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
gradient.addColorStop(0, "rgba(250,174,50,1)");
gradient.addColorStop(1, "rgba(250,174,50,1)");
const uniqTimestamp = uniqBy(data, "timestamp");
return {
labels: uniqTimestamp.map(
(s: externalMetricsItem) => new Date(s.timestamp / 1000000),
),
datasets: getDataSets(),
};
};
return (
<div>
<div>
<div style={{ textAlign: "center" }}>{title}</div>
<ChartJSLine
ref={this.chartRef}
data={data_chartJS}
options={getOptions(theme)}
/>
</div>
</div>
);
}
}
export default withRouter(ExternalApiGraph);

View File

@ -0,0 +1,97 @@
import { ChartOptions } from "chart.js";
export const getOptions = (theme: string): ChartOptions => {
return {
maintainAspectRatio: true,
responsive: true,
title: {
display: true,
text: "",
fontSize: 20,
position: "top",
padding: 8,
fontFamily: "Arial",
fontStyle: "regular",
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
},
legend: {
display: true,
position: "bottom",
align: "center",
labels: {
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
fontSize: 10,
boxWidth: 10,
usePointStyle: true,
},
},
tooltips: {
mode: "label",
bodyFontSize: 12,
titleFontSize: 12,
callbacks: {
label: function (tooltipItem, data) {
if (typeof tooltipItem.yLabel === "number") {
return (
data.datasets![tooltipItem.datasetIndex!].label +
" : " +
tooltipItem.yLabel.toFixed(2)
);
} else {
return "";
}
},
},
},
scales: {
yAxes: [
{
stacked: false,
ticks: {
beginAtZero: false,
fontSize: 10,
autoSkip: true,
maxTicksLimit: 6,
},
gridLines: {
// You can change the color, the dash effect, the main axe color, etc.
borderDash: [1, 4],
color: "#D3D3D3",
lineWidth: 0.25,
},
},
],
xAxes: [
{
type: "time",
// time: {
// unit: 'second'
// },
distribution: "linear",
//'linear': data are spread according to their time (distances can vary)
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
ticks: {
beginAtZero: false,
fontSize: 10,
autoSkip: true,
maxTicksLimit: 10,
},
// gridLines: false, --> not a valid option
},
],
},
};
};
export const borderColors = [
"rgba(250,174,50,1)",
"rgba(227, 74, 51, 1.0)",
"rgba(57, 255, 20, 1.0)",
];

View File

@ -0,0 +1 @@
export { default } from "./ExternalApiGraph";

View File

@ -9,23 +9,35 @@ import {
getServicesMetrics, getServicesMetrics,
metricItem, metricItem,
getTopEndpoints, getTopEndpoints,
getExternalMetrics,
externalMetricsAvgDurationItem,
externalErrCodeMetricsItem,
externalMetricsItem,
getExternalAvgDurationMetrics,
getExternalErrCodeMetrics,
topEndpointListItem, topEndpointListItem,
GlobalTime, GlobalTime,
updateTimeInterval, updateTimeInterval,
} from "../../store/actions"; } from "Src/store/actions";
import { StoreState } from "../../store/reducers"; import { StoreState } from "../../store/reducers";
import LatencyLineChart from "./LatencyLineChart"; import LatencyLineChart from "./LatencyLineChart";
import RequestRateChart from "./RequestRateChart"; import RequestRateChart from "./RequestRateChart";
import ErrorRateChart from "./ErrorRateChart"; import ErrorRateChart from "./ErrorRateChart";
import TopEndpointsTable from "./TopEndpointsTable"; import TopEndpointsTable from "./TopEndpointsTable";
import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query"; import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query";
import ExternalApiGraph from "./ExternalApi";
const { TabPane } = Tabs; const { TabPane } = Tabs;
interface ServicesMetricsProps extends RouteComponentProps<any> { interface ServicesMetricsProps extends RouteComponentProps<any> {
serviceMetrics: metricItem[]; serviceMetrics: metricItem[];
getServicesMetrics: Function; getServicesMetrics: Function;
getExternalMetrics: Function;
getExternalErrCodeMetrics: Function;
getExternalAvgDurationMetrics: Function;
externalMetrics: externalMetricsItem[];
topEndpointsList: topEndpointListItem[]; topEndpointsList: topEndpointListItem[];
externalAvgDurationMetrics: externalMetricsAvgDurationItem[];
externalErrCodeMetrics: externalErrCodeMetricsItem[];
getTopEndpoints: Function; getTopEndpoints: Function;
globalTime: GlobalTime; globalTime: GlobalTime;
updateTimeInterval: Function; updateTimeInterval: Function;
@ -36,6 +48,9 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
useEffect(() => { useEffect(() => {
props.getServicesMetrics(servicename, props.globalTime); props.getServicesMetrics(servicename, props.globalTime);
props.getTopEndpoints(servicename, props.globalTime); props.getTopEndpoints(servicename, props.globalTime);
props.getExternalMetrics(servicename, props.globalTime);
props.getExternalErrCodeMetrics(servicename, props.globalTime);
props.getExternalAvgDurationMetrics(servicename, props.globalTime);
}, [props.globalTime, servicename]); }, [props.globalTime, servicename]);
const onTracePopupClick = (timestamp: number) => { const onTracePopupClick = (timestamp: number) => {
@ -90,20 +105,55 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
</TabPane> </TabPane>
<TabPane tab="External Calls" key="2"> <TabPane tab="External Calls" key="2">
<div style={{ margin: 20 }}> Coming Soon.. </div> <Row gutter={32} style={{ margin: 20 }}>
<div <Col span={12}>
className="container" <Card bodyStyle={{ padding: 10 }}>
style={{ display: "flex", flexFlow: "column wrap" }} <ExternalApiGraph
> title=" External Call Error Rate"
<div className="row"> keyIdentifier="externalHttpUrl"
<div className="col-md-6 col-sm-12 col-12"> dataIdentifier="numCalls"
{/* <ChartJSLineChart data={this.state.graphData} /> */} data={props.externalErrCodeMetrics}
</div> />
<div className="col-md-6 col-sm-12 col-12"> </Card>
{/* <ChartJSLineChart data={this.state.graphData} /> */} </Col>
</div>
</div> <Col span={12}>
</div> <Card bodyStyle={{ padding: 10 }}>
<ExternalApiGraph
label="Average Duration"
title="External Call duration"
dataIdentifier="avgDuration"
fnDataIdentifier={(s) => Number(s) / 1000000}
data={props.externalAvgDurationMetrics}
/>
</Card>
</Col>
</Row>
<Row gutter={32} style={{ margin: 20 }}>
<Col span={12}>
<Card bodyStyle={{ padding: 10 }}>
<ExternalApiGraph
title="External Call RPS(by Address)"
keyIdentifier="externalHttpUrl"
dataIdentifier="callRate"
data={props.externalMetrics}
/>
</Card>
</Col>
<Col span={12}>
<Card bodyStyle={{ padding: 10 }}>
<ExternalApiGraph
title="External Call duration(by Address)"
keyIdentifier="externalHttpUrl"
dataIdentifier="avgDuration"
fnDataIdentifier={(s) => Number(s) / 1000000}
data={props.externalMetrics}
/>
</Card>
</Col>
</Row>
</TabPane> </TabPane>
</Tabs> </Tabs>
); );
@ -114,18 +164,27 @@ const mapStateToProps = (
): { ): {
serviceMetrics: metricItem[]; serviceMetrics: metricItem[];
topEndpointsList: topEndpointListItem[]; topEndpointsList: topEndpointListItem[];
externalAvgDurationMetrics: externalMetricsAvgDurationItem[];
externalErrCodeMetrics: externalErrCodeMetricsItem[];
externalMetrics: externalMetricsItem[];
globalTime: GlobalTime; globalTime: GlobalTime;
} => { } => {
return { return {
externalErrCodeMetrics: state.externalErrCodeMetrics,
serviceMetrics: state.serviceMetrics, serviceMetrics: state.serviceMetrics,
topEndpointsList: state.topEndpointsList, topEndpointsList: state.topEndpointsList,
externalMetrics: state.externalMetrics,
globalTime: state.globalTime, globalTime: state.globalTime,
externalAvgDurationMetrics: state.externalAvgDurationMetrics,
}; };
}; };
export const ServiceMetrics = withRouter( export const ServiceMetrics = withRouter(
connect(mapStateToProps, { connect(mapStateToProps, {
getServicesMetrics: getServicesMetrics, getServicesMetrics: getServicesMetrics,
getExternalMetrics: getExternalMetrics,
getExternalErrCodeMetrics: getExternalErrCodeMetrics,
getExternalAvgDurationMetrics: getExternalAvgDurationMetrics,
getTopEndpoints: getTopEndpoints, getTopEndpoints: getTopEndpoints,
updateTimeInterval: updateTimeInterval, updateTimeInterval: updateTimeInterval,
})(_ServiceMetrics), })(_ServiceMetrics),

View File

@ -27,6 +27,17 @@ export interface metricItem {
errorRate: number; errorRate: number;
} }
export interface externalMetricsAvgDurationItem {
avgDuration: number;
timestamp: number;
}
export interface externalErrCodeMetricsItem {
callRate: number;
externalHttpUrl: string;
numCalls: number;
timestamp: number;
}
export interface topEndpointListItem { export interface topEndpointListItem {
p50: number; p50: number;
p90: number; p90: number;
@ -35,6 +46,14 @@ export interface topEndpointListItem {
name: string; name: string;
} }
export interface externalMetricsItem {
avgDuration: number;
callRate: number;
externalHttpUrl: string;
numCalls: number;
timestamp: number;
}
export interface customMetricsItem { export interface customMetricsItem {
timestamp: number; timestamp: number;
value: number; value: number;
@ -45,10 +64,22 @@ export interface getServicesListAction {
payload: servicesListItem[]; payload: servicesListItem[];
} }
export interface externalErrCodeMetricsActions {
type: ActionTypes.getErrCodeMetrics;
payload: externalErrCodeMetricsItem[];
}
export interface externalMetricsAvgDurationAction {
type: ActionTypes.getAvgDurationMetrics;
payload: externalMetricsAvgDurationItem[];
}
export interface getServiceMetricsAction { export interface getServiceMetricsAction {
type: ActionTypes.getServiceMetrics; type: ActionTypes.getServiceMetrics;
payload: metricItem[]; payload: metricItem[];
} }
export interface getExternalMetricsAction {
type: ActionTypes.getExternalMetrics;
payload: externalMetricsItem[];
}
export interface getTopEndpointsAction { export interface getTopEndpointsAction {
type: ActionTypes.getTopEndpoints; type: ActionTypes.getTopEndpoints;
@ -75,6 +106,74 @@ export const getServicesList = (globalTime: GlobalTime) => {
}; };
}; };
export const getExternalMetrics = (
serviceName: string,
globalTime: GlobalTime,
) => {
return async (dispatch: Dispatch) => {
let request_string =
"/service/external?service=" +
serviceName +
"&start=" +
globalTime.minTime +
"&end=" +
globalTime.maxTime +
"&step=60";
const response = await api.get<externalMetricsItem[]>(apiV1 + request_string);
dispatch<getExternalMetricsAction>({
type: ActionTypes.getExternalMetrics,
payload: response.data,
});
};
};
export const getExternalAvgDurationMetrics = (
serviceName: string,
globalTime: GlobalTime,
) => {
return async (dispatch: Dispatch) => {
let request_string =
"/service/externalAvgDuration?service=" +
serviceName +
"&start=" +
globalTime.minTime +
"&end=" +
globalTime.maxTime +
"&step=60";
const response = await api.get<externalMetricsAvgDurationItem[]>(
apiV1 + request_string,
);
dispatch<externalMetricsAvgDurationAction>({
type: ActionTypes.getAvgDurationMetrics,
payload: response.data,
});
};
};
export const getExternalErrCodeMetrics = (
serviceName: string,
globalTime: GlobalTime,
) => {
return async (dispatch: Dispatch) => {
let request_string =
"/service/externalErrors?service=" +
serviceName +
"&start=" +
globalTime.minTime +
"&end=" +
globalTime.maxTime +
"&step=60";
const response = await api.get<externalErrCodeMetricsItem[]>(
apiV1 + request_string,
);
dispatch<externalErrCodeMetricsActions>({
type: ActionTypes.getErrCodeMetrics,
payload: response.data,
});
};
};
export const getServicesMetrics = ( export const getServicesMetrics = (
serviceName: string, serviceName: string,
globalTime: GlobalTime, globalTime: GlobalTime,

View File

@ -3,6 +3,9 @@ import { updateTraceFiltersAction, updateInputTagAction } from "./traceFilters";
import { import {
getServicesListAction, getServicesListAction,
getServiceMetricsAction, getServiceMetricsAction,
externalErrCodeMetricsActions,
externalMetricsAvgDurationAction,
getExternalMetricsAction,
getTopEndpointsAction, getTopEndpointsAction,
getFilteredTraceMetricsAction, getFilteredTraceMetricsAction,
} from "./metrics"; } from "./metrics";
@ -16,6 +19,9 @@ export enum ActionTypes {
fetchTraceItem = "FETCH_TRACE_ITEM", fetchTraceItem = "FETCH_TRACE_ITEM",
getServicesList = "GET_SERVICE_LIST", getServicesList = "GET_SERVICE_LIST",
getServiceMetrics = "GET_SERVICE_METRICS", getServiceMetrics = "GET_SERVICE_METRICS",
getAvgDurationMetrics = "GET_AVG_DURATION_METRICS",
getErrCodeMetrics = "GET_ERR_CODE_METRICS",
getExternalMetrics = "GET_EXTERNAL_METRICS",
getTopEndpoints = "GET_TOP_ENDPOINTS", getTopEndpoints = "GET_TOP_ENDPOINTS",
getUsageData = "GET_USAGE_DATE", getUsageData = "GET_USAGE_DATE",
updateTimeInterval = "UPDATE_TIME_INTERVAL", updateTimeInterval = "UPDATE_TIME_INTERVAL",
@ -32,4 +38,7 @@ export type Action =
| getTopEndpointsAction | getTopEndpointsAction
| getUsageDataAction | getUsageDataAction
| updateTimeIntervalAction | updateTimeIntervalAction
| getFilteredTraceMetricsAction; | getFilteredTraceMetricsAction
| getExternalMetricsAction
| externalErrCodeMetricsActions
| externalMetricsAvgDurationAction;

View File

@ -5,8 +5,11 @@ import {
servicesListItem, servicesListItem,
metricItem, metricItem,
topEndpointListItem, topEndpointListItem,
externalMetricsItem,
externalMetricsAvgDurationItem,
usageDataItem, usageDataItem,
GlobalTime, GlobalTime,
externalErrCodeMetricsItem,
customMetricsItem, customMetricsItem,
TraceFilters, TraceFilters,
} from "../actions"; } from "../actions";
@ -14,8 +17,11 @@ import { updateGlobalTimeReducer } from "./global";
import { import {
filteredTraceMetricsReducer, filteredTraceMetricsReducer,
serviceMetricsReducer, serviceMetricsReducer,
externalErrCodeMetricsReducer,
serviceTableReducer, serviceTableReducer,
topEndpointsReducer, topEndpointsReducer,
externalMetricsReducer,
externalAvgDurationMetricsReducer,
} from "./metrics"; } from "./metrics";
import { traceFiltersReducer, inputsReducer } from "./traceFilters"; import { traceFiltersReducer, inputsReducer } from "./traceFilters";
import { traceItemReducer, tracesReducer } from "./traces"; import { traceItemReducer, tracesReducer } from "./traces";
@ -29,6 +35,9 @@ export interface StoreState {
servicesList: servicesListItem[]; servicesList: servicesListItem[];
serviceMetrics: metricItem[]; serviceMetrics: metricItem[];
topEndpointsList: topEndpointListItem[]; topEndpointsList: topEndpointListItem[];
externalMetrics: externalMetricsItem[];
externalAvgDurationMetrics: externalMetricsAvgDurationItem[];
externalErrCodeMetrics: externalErrCodeMetricsItem[];
usageDate: usageDataItem[]; usageDate: usageDataItem[];
globalTime: GlobalTime; globalTime: GlobalTime;
filteredTraceMetrics: customMetricsItem[]; filteredTraceMetrics: customMetricsItem[];
@ -42,6 +51,9 @@ const reducers = combineReducers<StoreState>({
servicesList: serviceTableReducer, servicesList: serviceTableReducer,
serviceMetrics: serviceMetricsReducer, serviceMetrics: serviceMetricsReducer,
topEndpointsList: topEndpointsReducer, topEndpointsList: topEndpointsReducer,
externalAvgDurationMetrics: externalAvgDurationMetricsReducer,
externalMetrics: externalMetricsReducer,
externalErrCodeMetrics: externalErrCodeMetricsReducer,
usageDate: usageDataReducer, usageDate: usageDataReducer,
globalTime: updateGlobalTimeReducer, globalTime: updateGlobalTimeReducer,
filteredTraceMetrics: filteredTraceMetricsReducer, filteredTraceMetrics: filteredTraceMetricsReducer,

View File

@ -4,7 +4,10 @@ import {
servicesListItem, servicesListItem,
metricItem, metricItem,
topEndpointListItem, topEndpointListItem,
externalErrCodeMetricsItem,
customMetricsItem, customMetricsItem,
externalMetricsItem,
externalMetricsAvgDurationItem,
} from "../actions"; } from "../actions";
export const serviceTableReducer = ( export const serviceTableReducer = (
@ -66,6 +69,62 @@ export const topEndpointsReducer = (
} }
}; };
export const externalAvgDurationMetricsReducer = (
state: externalMetricsAvgDurationItem[] = [
{
avgDuration: 0,
timestamp: 0,
},
],
action: Action,
) => {
switch (action.type) {
case ActionTypes.getAvgDurationMetrics:
return action.payload;
default:
return state;
}
};
export const externalErrCodeMetricsReducer = (
state: externalErrCodeMetricsItem[] = [
{
callRate: 0,
externalHttpUrl: "",
numCalls: 0,
timestamp: 0,
},
],
action: Action,
) => {
switch (action.type) {
case ActionTypes.getErrCodeMetrics:
return action.payload;
default:
return state;
}
};
export const externalMetricsReducer = (
state: externalMetricsItem[] = [
{
avgDuration: 0,
callRate: 0,
externalHttpUrl: "",
numCalls: 0,
timestamp: 0,
},
],
action: Action,
) => {
switch (action.type) {
case ActionTypes.getExternalMetrics:
return action.payload;
default:
return state;
}
};
export const filteredTraceMetricsReducer = ( export const filteredTraceMetricsReducer = (
state: customMetricsItem[] = [{ timestamp: 0, value: 0 }], state: customMetricsItem[] = [{ timestamp: 0, value: 0 }],
action: Action, action: Action,