ソースを参照

[attu metric view] healthy index row

Signed-off-by: min.tian <min.tian.cn@gmail.com>
min.tian 2 年 前
コミット
de58eb3cc9

+ 122 - 3
client/src/pages/systemHealthy/HealthyIndexOverview.tsx

@@ -1,5 +1,124 @@
-const ThresholdPanel = () => {
-  return <div></div>;
+import { makeStyles, Theme } from '@material-ui/core';
+import { CHART_WIDTH, MAIN_VIEW_WIDTH } from './consts';
+import HealthyIndexRow from './HealthyIndexRow';
+import { INodeTreeStructure } from './Types';
+
+// export const CHART_LABEL_WIDTH = 70;
+
+const getStyles = makeStyles((theme: Theme) => ({
+  root: {
+    width: `${MAIN_VIEW_WIDTH}px`,
+    height: '100%',
+    // boxShadow: '0 0 5px #ccc',
+    fontSize: '14px',
+  },
+  headerContent: {
+    display: 'grid',
+    gridTemplateColumns: '1fr auto',
+  },
+  titleContainer: {},
+  title: {
+    fontSize: '18px',
+    fontWeight: 500,
+  },
+  timeRangeTabs: {
+    fontSize: '12px'
+  },
+  legendContainer: {
+    display: 'flex',
+    alignItems: 'flex-end',
+  },
+  legendItem: { display: 'flex', marginLeft: '12px' },
+  legendIcon: {},
+  legendText: { marginLeft: '8px' },
+  settingIcon: { marginLeft: '16px' },
+  mainView: {
+    width: '100%',
+    marginTop: '12px',
+  },
+  healthyIndexItem: {
+    display: 'flex',
+    marginTop: '6px',
+    justifyContent: 'space-between',
+  },
+  healthyIndexLabel: {
+    // width: `${CHART_LABEL_WIDTH}px`,
+  },
+  healthyIndexRow: {
+    width: `${CHART_WIDTH}px`,
+    // border: '1px solid brown',
+  },
+  chartView: { width: '100%', marginTop: '30px' },
+  chartItem: {
+    margin: '10px 0',
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'flex-end',
+  },
+  chartLabel: {
+    // width: `${CHART_LABEL_WIDTH}px`
+  },
+  chart: {
+    height: '100px',
+    width: `${CHART_WIDTH}px`,
+
+    border: '1px solid brown',
+  },
+}));
+
+const HealthyIndexOverview = ({ nodes }: { nodes: INodeTreeStructure[] }) => {
+  const classes = getStyles();
+  console.log('nodes', nodes);
+  return (
+    <div className={classes.root}>
+      <div className={classes.headerContent}>
+        <div className={classes.titleContainer}>
+          <div className={classes.title}>Healthy Status</div>
+          <div className={classes.timeRangeTabs}>7day / 24h / 1h</div>
+        </div>
+        <div className={classes.legendContainer}>
+          <div className={classes.legendItem}>
+            <div className={classes.legendIcon}>icon</div>
+            <div className={classes.legendText}>healthy</div>
+          </div>
+          <div className={classes.legendItem}>
+            <div className={classes.legendIcon}>icon</div>
+            <div className={classes.legendText}>healthy</div>
+          </div>
+          <div className={classes.legendItem}>
+            <div className={classes.legendIcon}>icon</div>
+            <div className={classes.legendText}>healthy</div>
+          </div>
+          <div className={classes.settingIcon}>setting</div>
+        </div>
+      </div>
+      <div className={classes.mainView}>
+        {nodes.map(node => (
+          <div className={classes.healthyIndexItem}>
+            <div className={classes.healthyIndexLabel}>{node.label}</div>
+            <div className={classes.healthyIndexRow}>
+              <HealthyIndexRow statusList={node.healthyStatus} />
+            </div>
+          </div>
+        ))}
+      </div>
+      <div className={classes.chartView}>
+        <div className={classes.title}>Search Query History</div>
+        <div className={classes.chartItem}>
+          <div className={classes.chartLabel}>Search Count</div>
+          <div className={classes.chart}></div>
+        </div>
+        <div className={classes.chartItem}>
+          <div className={classes.chartLabel}>Search Count</div>
+          <div className={classes.chart}></div>
+        </div>
+        <div className={classes.chartItem}>
+          <div className={classes.chartLabel}>Search Count</div>
+          <div className={classes.chart}></div>
+        </div>
+      </div>
+    </div>
+  );
 };
 
-export default ThresholdPanel;
+export default HealthyIndexOverview;

+ 26 - 10
client/src/pages/systemHealthy/HealthyIndexRow.tsx

@@ -1,15 +1,31 @@
+import {
+  CHART_WIDTH,
+  HEALTHY_INDEX_ROW_GAP_RATIO,
+  HEALTHY_INDEX_ROW_HEIGHT,
+  HEALTHY_STATUS_COLORS,
+} from './consts';
 import { EHealthyStatus } from './Types';
 
-const HealthyIndexRow = ({
-  statusList,
-  width,
-  height,
-}: {
-  statusList: EHealthyStatus[];
-  width: number;
-  height: number;
-}) => {
-  return <div></div>;
+const HealthyIndexRow = ({ statusList }: { statusList: EHealthyStatus[] }) => {
+  const length = statusList.length;
+  const stautsItemWidth = length === 0 ? 0 : CHART_WIDTH / length;
+  const statusBlockGap = stautsItemWidth * HEALTHY_INDEX_ROW_GAP_RATIO;
+  const statusBlockWidth = stautsItemWidth * (1 - HEALTHY_INDEX_ROW_GAP_RATIO);
+  return (
+    <svg width={CHART_WIDTH} height={HEALTHY_INDEX_ROW_HEIGHT}>
+      {statusList.map((status, i) => (
+        <rect
+          x={i * stautsItemWidth + statusBlockGap / 2}
+          y={0}
+          rx={1}
+          ry={1}
+          width={statusBlockWidth}
+          height={HEALTHY_INDEX_ROW_HEIGHT}
+          fill={HEALTHY_STATUS_COLORS[status]}
+        />
+      ))}
+    </svg>
+  );
 };
 
 export default HealthyIndexRow;

+ 36 - 23
client/src/pages/systemHealthy/SystemHealthyView.tsx

@@ -19,6 +19,8 @@ import clsx from 'clsx';
 import Topology from './Topology';
 import * as d3 from 'd3';
 import { reconNodeTree } from './dataHandler';
+import HealthyIndexOverview from './HealthyIndexOverview';
+import HealthyIndexDetailView from './HealthyIndexDetailView';
 // import data from "./data.json";
 
 const getStyles = makeStyles((theme: Theme) => ({
@@ -58,8 +60,6 @@ const getStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-
-
 const SystemHealthyView = () => {
   useNavigationHook(ALL_ROUTER_TYPES.SYSTEM);
 
@@ -88,7 +88,7 @@ const SystemHealthyView = () => {
   );
   const [nodes, setNodes] = useState<INodeTreeStructure[]>([]);
   const [lineChartsData, setLineChartsData] = useState<ILineChartData[]>([]);
-  const [selectedNode, setSelectedNode] = useState<string>('');
+  const [selectedNode, setSelectedNode] = useState<INodeTreeStructure>();
   const defaultThreshold = {
     cpu: 1,
     memory: 8 * 1024 * 1024 * 1024,
@@ -97,23 +97,27 @@ const SystemHealthyView = () => {
 
   const updateData = async () => {
     const curT = new Date().getTime();
-    const result = await PrometheusHttp.getHealthyData({
+    const result = (await PrometheusHttp.getHealthyData({
       start: curT - timeRange.value,
       end: curT,
       step: timeRange.step,
-    }) as IPrometheusAllData;
-    console.log(result);
+    })) as IPrometheusAllData;
+    console.log('prometheus data', result);
     setNodes(reconNodeTree(result, threshold));
-    setLineChartsData([{
-      label: 'TotalCount',
-      data: result.totalVectorsCount
-    },{
-      label: 'SearchCount',
-      data: result.searchVectorsCount
-    },{
-      label: 'SearchLatency',
-      data: result.sqLatency
-    },])
+    setLineChartsData([
+      {
+        label: 'TotalCount',
+        data: result.totalVectorsCount,
+      },
+      {
+        label: 'SearchCount',
+        data: result.searchVectorsCount,
+      },
+      {
+        label: 'SearchLatency',
+        data: result.sqLatency,
+      },
+    ]);
   };
 
   useEffect(() => {
@@ -125,24 +129,33 @@ const SystemHealthyView = () => {
     updateData();
   }, INTERVAL);
 
-  console.log('nodes', nodes, lineChartsData);
+  const topoNodes = !!selectedNode ? selectedNode.children : nodes;
 
   return (
     <div className={classes.root}>
       <div className={classes.mainView}>
-        {/* <Topology
-          tree={tree}
-          selectedNode={selectedNode}
+        <Topology
+          nodes={nodes}
+          selectedNode={selectedNode as INodeTreeStructure}
           setSelectedNode={setSelectedNode}
-        ></Topology> */}
-        <div style={{ height: '200px', width: '300px' }}></div>
+        />
+        <HealthyIndexOverview nodes={nodes} />
       </div>
       <div
         className={clsx(
           classes.detailView,
           selectedNode ? classes.showDetailView : classes.hideDetailView
         )}
-      ></div>
+      >
+        {selectedNode && (
+          <Topology
+            nodes={selectedNode.children}
+            selectedNode={selectedNode}
+            setSelectedNode={setSelectedNode}
+          />
+        )}
+        <HealthyIndexDetailView />
+      </div>
     </div>
   );
 };

+ 5 - 5
client/src/pages/systemHealthy/Topology.tsx

@@ -2,15 +2,15 @@ import { Dispatch } from 'react';
 import { INodeTreeStructure } from './Types';
 
 const Topology = ({
-  tree,
+  nodes,
   selectedNode,
   setSelectedNode,
 }: {
-  tree: INodeTreeStructure;
-  selectedNode: string;
-  setSelectedNode: Dispatch<string>;
+  nodes: INodeTreeStructure[];
+  selectedNode: INodeTreeStructure;
+  setSelectedNode: Dispatch<INodeTreeStructure>;
 }) => {
-  return <div></div>;
+  return <div>Topology</div>;
 };
 
 export default Topology;

+ 12 - 0
client/src/pages/systemHealthy/consts.ts

@@ -0,0 +1,12 @@
+import { EHealthyStatus } from './Types';
+
+export const MAIN_VIEW_WIDTH = 600;
+export const CHART_WIDTH = 500;
+export const HEALTHY_INDEX_ROW_HEIGHT = 20;
+export const HEALTHY_INDEX_ROW_GAP_RATIO = 0.3;
+export const HEALTHY_STATUS_COLORS = {
+  [EHealthyStatus.noData]: '#ccc',
+  [EHealthyStatus.healthy]: '#6CD676',
+  [EHealthyStatus.warning]: '#F4DD0E',
+  [EHealthyStatus.failed]: '#F16415',
+};

+ 1 - 18
server/src/prometheus/prometheus.service.ts

@@ -139,7 +139,7 @@ export class PrometheusService {
   async getSQLatency(start: number, end: number, step: number) {
     const expr =
       `histogram_quantile(0.99, sum by (le, query_type, pod, node_id)` +
-      `(rate(milvus_proxy_sq_latency_bucket${PrometheusService.selector}[${
+      `(rate(${sqLatencyMetric}${PrometheusService.selector}[${
         step / 1000
       }s])))`;
     const result = await this.queryRange(expr, start, end, step);
@@ -302,23 +302,6 @@ export class PrometheusService {
       step
     );
     const sqLatency = await this.getSQLatency(start, end, step);
-
-    const cpuNodes = await this.getInternalNodesCPUData(start, end, step);
-    const memoryNodes = await this.getInternalNodesMemoryData(start, end, step);
-
-    // const rootNodes: IPrometheusNode[] = [
-    //   {
-    //     type: 'coord',
-    //     pod: cpuNodes.find((node: any) => node.metric.container === 'rootcoord')
-    //       .metric.pod,
-    //     cpu: cpuNodes
-    //       .find((node: any) => node.metric.container === 'rootcoord')
-    //       .values.map((d: any) => +d[1]),
-    //     memory: memoryNodes
-    //       .find((node: any) => node.metric.container === 'rootcoord')
-    //       .values.map((d: any) => +d[1]),
-    //   },
-    // ];
     const { rootNodes, queryNodes, indexNodes, dataNodes } =
       await this.getInternalNodesData(start, end, step);