diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b6d74ec820..5e90b19456 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -46,16 +46,15 @@ } }, "@ant-design/icons": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.5.0.tgz", - "integrity": "sha512-ZAKJcmr4DBV3NWr8wm2dCxNKN4eFrX+qCaPsuFejP6FRsf+m5OKxvCVi9bSp1lmKWeOI5yECAx5s0uFm4QHuPw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.6.2.tgz", + "integrity": "sha512-QsBG2BxBYU/rxr2eb8b2cZ4rPKAPBpzAR+0v6rrZLp/lnyvflLH3tw1vregK+M7aJauGWjIGNdFmUfpAOtw25A==", "requires": { "@ant-design/colors": "^6.0.0", "@ant-design/icons-svg": "^4.0.0", "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", - "insert-css": "^2.0.0", - "rc-util": "^5.0.1" + "rc-util": "^5.9.4" } }, "@ant-design/icons-svg": { @@ -11076,11 +11075,6 @@ "css-in-js-utils": "^2.0.0" } }, - "insert-css": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz", - "integrity": "sha1-610Ql7dUL0x56jBg067gfQU4gPQ=" - }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e14b4a03a0..58962d35bc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "dependencies": { + "@ant-design/icons": "^4.6.2", "@auth0/auth0-react": "^1.2.0", "@babel/core": "7.12.3", "@material-ui/core": "^4.0.0", diff --git a/frontend/src/modules/Servicemap/SelectService.tsx b/frontend/src/modules/Servicemap/SelectService.tsx index 7247c984f5..8317e87212 100644 --- a/frontend/src/modules/Servicemap/SelectService.tsx +++ b/frontend/src/modules/Servicemap/SelectService.tsx @@ -1,8 +1,27 @@ import React, { useState } from "react"; import { servicesItem } from "Src/store/actions"; +import { InfoCircleOutlined } from "@ant-design/icons"; import { Select } from "antd"; +import styled from "styled-components"; const { Option } = Select; +const Container = styled.div` + margin-top: 12px; + display: flex; + .info { + display:flex; + font-family: Roboto; + margin-left: auto; + margin-right: 12px; + color: #4F4F4F; + font-size: 14px; + .anticon-info-circle { + margin-top: 22px; + margin-right: 18px; + } + } +`; + interface SelectServiceProps { services: servicesItem[]; zoomToService: (arg0: string) => void; @@ -16,18 +35,28 @@ const SelectService = (props: SelectServiceProps) => { zoomToService(value); }; return ( - + + +
+ +
+ +
-> Size of circles is proportial to the number of requests served by each node
+
-> Click on node name to reposition the node
+
+
+
); }; diff --git a/frontend/src/modules/Servicemap/ServiceMap.tsx b/frontend/src/modules/Servicemap/ServiceMap.tsx index e4bd73f0fa..f7ce9debda 100644 --- a/frontend/src/modules/Servicemap/ServiceMap.tsx +++ b/frontend/src/modules/Servicemap/ServiceMap.tsx @@ -8,11 +8,28 @@ import { getDetailedServiceMapItems, } from "Src/store/actions"; import { Spin } from "antd"; - +import styled from "styled-components"; import { StoreState } from "../../store/reducers"; import { getGraphData } from "./utils"; import SelectService from "./SelectService"; import { ForceGraph2D } from "react-force-graph"; + +const Container = styled.div` + .force-graph-container .graph-tooltip { + background: black; + padding: 1px; + .keyval { + display: flex; + .key { + margin-right: 4px; + } + .val { + margin-left: auto; + } + } + } +`; + interface ServiceMapProps extends RouteComponentProps { serviceMap: serviceMapStore; globalTime: GlobalTime; @@ -52,13 +69,13 @@ const ServiceMap = (props: ServiceMapProps) => { } const zoomToService = (value: string) => { - fgRef && fgRef.current.zoomToFit(700, 480, (e) => e.id === value); + fgRef && fgRef.current.zoomToFit(700, 280, (e) => e.id === value); }; const { nodes, links } = getGraphData(serviceMap); const graphData = { nodes, links }; return ( -
+ { fgRef.current.zoomToFit(100, 200)} + onEngineStop={() => fgRef.current.zoomToFit(100, 120)} graphData={graphData} nodeLabel="id" linkAutoColorBy={(d) => d.target} @@ -75,46 +92,46 @@ const ServiceMap = (props: ServiceMapProps) => { nodeCanvasObject={(node, ctx, globalScale) => { const label = node.id; const fontSize = node.fontSize; - ctx.font = `${fontSize}px Sans-Serif`; - const textWidth = ctx.measureText(label).width; - const width = textWidth > node.width ? textWidth : node.width; - const bckgDimensions = [width, node.height].map((n) => n + fontSize); // some padding + ctx.font = `${fontSize}px Roboto`; + const width = node.width; + ctx.fillStyle = node.color; - ctx.fillRect( - node.x - bckgDimensions[0] / 2, - node.y - bckgDimensions[1] / 2, - ...bckgDimensions, - ); + ctx.beginPath(); + ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false); + ctx.fill(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; - ctx.fillStyle = "black"; + ctx.fillStyle = "#333333"; ctx.fillText(label, node.x, node.y); - - node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint }} onNodeClick={(node) => { const tooltip = document.querySelector(".graph-tooltip"); if (tooltip && node) { - tooltip.innerHTML = `
-
${node.id}
-
P99 latency: ${node.p99 / 1000000}
-
Error Rate: ${node.errorRate}%
-
Request Per Sec: ${node.callRate}
+ tooltip.innerHTML = `
+
${node.id}
+
+
P99 latency:
+
${node.p99 / 1000000}
+
+
+
Request:
+
${node.callRate}/sec
+
+
+
Error Rate:
+
${node.errorRate}%
+
`; } }} nodePointerAreaPaint={(node, color, ctx) => { ctx.fillStyle = color; - const bckgDimensions = node.__bckgDimensions; - bckgDimensions && - ctx.fillRect( - node.x - bckgDimensions[0] / 2, - node.y - bckgDimensions[1] / 2, - ...bckgDimensions, - ); + ctx.beginPath(); + ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false); + ctx.fill(); }} /> -
+
); }; diff --git a/frontend/src/modules/Servicemap/utils.ts b/frontend/src/modules/Servicemap/utils.ts index 28eceeb336..72fb61782a 100644 --- a/frontend/src/modules/Servicemap/utils.ts +++ b/frontend/src/modules/Servicemap/utils.ts @@ -2,20 +2,16 @@ import { uniqBy, uniq, maxBy, cloneDeep, find } from "lodash"; import { serviceMapStore } from "Src/store/actions"; import { graphDataType } from "./ServiceMap"; -const MIN_WIDTH = 25; -const MAX_WIDTH = 50; -const MIN_HEIGHT = 10; -const MAX_HEIGHT = 15; -const DEFAULT_FONT_SIZE = 4; +const MIN_WIDTH = 12; +const MAX_WIDTH = 20; +const DEFAULT_FONT_SIZE = 6; export const getDimensions = (num, highest) => { const percentage = (num / highest) * 100; const width = (percentage * (MAX_WIDTH - MIN_WIDTH)) / 100 + MIN_WIDTH; - const height = (percentage * (MAX_HEIGHT - MIN_HEIGHT)) / 100 + MIN_HEIGHT; const fontSize = DEFAULT_FONT_SIZE; return { fontSize, width, - height, }; }; @@ -40,14 +36,13 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => { const uniqNodes = uniq([...uniqParent, ...uniqChild]); const nodes = uniqNodes.map((node, i) => { const service = find(services, (service) => service.serviceName === node); - let color = "#84ff00"; + let color = "#88CEA5"; if (!service) { return { id: node, group: i + 1, fontSize: DEFAULT_FONT_SIZE, width: MIN_WIDTH, - height: MIN_HEIGHT, color, nodeVal: MIN_WIDTH, callRate: 0, @@ -56,17 +51,16 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => { }; } if (service.errorRate > 0) { - color = "#f00a0a"; + color = "#F98989"; } else if (service.fourXXRate > 0) { - color = "#ebeb15"; + color = "#F9DA7B"; } - const { fontSize, width, height } = getDimensions(service.callRate, highestCallRate); + const { fontSize, width } = getDimensions(service.callRate, highestCallRate); return { id: node, group: i + 1, fontSize, width, - height, color, nodeVal: width, callRate: service.callRate, diff --git a/frontend/src/store/actions/serviceMap.ts b/frontend/src/store/actions/serviceMap.ts index 883e274a71..d8706fecda 100644 --- a/frontend/src/store/actions/serviceMap.ts +++ b/frontend/src/store/actions/serviceMap.ts @@ -44,36 +44,36 @@ export const getServiceMapItems = (globalTime: GlobalTime) => { "&end=" + globalTime.maxTime; - const response = await api.get(apiV1 + request_string); - // const response = { - // data: [ - // { - // parent: "driver", - // child: "redis", - // callCount: 17050, - // }, - // { - // parent: "frontend", - // child: "driver", - // callCount: 1263, - // }, - // { - // parent: "customer", - // child: "mysql", - // callCount: 1262, - // }, - // { - // parent: "frontend", - // child: "customer", - // callCount: 1262, - // }, - // { - // parent: "frontend", - // child: "route", - // callCount: 12636, - // }, - // ], - // }; + // const response = await api.get(apiV1 + request_string); + const response = { + data: [ + { + parent: "driver", + child: "redisredisredisredisredisredisredisredisre", + callCount: 17050, + }, + { + parent: "frontend", + child: "driver", + callCount: 1263, + }, + { + parent: "customer", + child: "mysql", + callCount: 1262, + }, + { + parent: "frontend", + child: "customer", + callCount: 1262, + }, + { + parent: "frontend", + child: "route", + callCount: 12636, + }, + ], + }; dispatch({ type: ActionTypes.getServiceMapItems, payload: response.data, @@ -86,77 +86,77 @@ export const getDetailedServiceMapItems = (globalTime: GlobalTime) => { let request_string = "/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime; - const response = await api.get(apiV1 + request_string); - // const response = { - // data: [ - // { - // serviceName: "redis", - // p99: 1179518000, - // avgDuration: 742158850, - // numCalls: 1898, - // callRate: 0.019097411, - // numErrors: 0, - // errorRate: 3, - // num4XX: 0, - // fourXXRate: 0, - // }, - // { - // serviceName: "mysql", - // p99: 1179518000, - // avgDuration: 742158850, - // numCalls: 1898, - // callRate: 0.019097411, - // numErrors: 0, - // errorRate: 0, - // num4XX: 0, - // fourXXRate: 0, - // }, - // { - // serviceName: "frontend", - // p99: 1179518000, - // avgDuration: 742158850, - // numCalls: 1898, - // callRate: 0.000019097411, - // numErrors: 0, - // errorRate: 0, - // num4XX: 0, - // fourXXRate: 0, - // }, - // { - // serviceName: "customer", - // p99: 728385000, - // avgDuration: 342475680, - // numCalls: 1897, - // callRate: 0.000019087349, - // numErrors: 0, - // errorRate: 0, - // num4XX: 0, - // fourXXRate: 0.6325778, - // }, - // { - // serviceName: "driver", - // p99: 243808000, - // avgDuration: 204640670, - // numCalls: 1898, - // callRate: 0.000019097411, - // numErrors: 0, - // errorRate: 0, - // num4XX: 0, - // fourXXRate: 0.63224447, - // }, - // { - // serviceName: "route", - // p99: 79419000, - // avgDuration: 50748804, - // numCalls: 18979, - // callRate: 0.00019096404, - // numErrors: 0, - // errorRate: 3, - // num4XX: 0, - // fourXXRate: 0, - // }, - // ], - // }; + // const response = await api.get(apiV1 + request_string); + const response = { + data: [ + { + serviceName: "redisredisredisredisredisredisredisredisre", + p99: 1179518000, + avgDuration: 742158850, + numCalls: 1898, + callRate: 0.019097411, + numErrors: 0, + errorRate: 3, + num4XX: 0, + fourXXRate: 0, + }, + { + serviceName: "mysql", + p99: 1179518000, + avgDuration: 742158850, + numCalls: 1898, + callRate: 0.019097411, + numErrors: 0, + errorRate: 0, + num4XX: 0, + fourXXRate: 0, + }, + { + serviceName: "frontend", + p99: 1179518000, + avgDuration: 742158850, + numCalls: 1898, + callRate: 0.000019097411, + numErrors: 0, + errorRate: 0, + num4XX: 0, + fourXXRate: 0, + }, + { + serviceName: "customer", + p99: 728385000, + avgDuration: 342475680, + numCalls: 1897, + callRate: 0.000019087349, + numErrors: 0, + errorRate: 0, + num4XX: 0, + fourXXRate: 0.6325778, + }, + { + serviceName: "driver", + p99: 243808000, + avgDuration: 204640670, + numCalls: 1898, + callRate: 0.000019097411, + numErrors: 0, + errorRate: 0, + num4XX: 0, + fourXXRate: 0.63224447, + }, + { + serviceName: "route", + p99: 79419000, + avgDuration: 50748804, + numCalls: 18979, + callRate: 0.00019096404, + numErrors: 0, + errorRate: 3, + num4XX: 0, + fourXXRate: 0, + }, + ], + }; dispatch({ type: ActionTypes.getServices, payload: response.data,