You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ne...@apache.org on 2022/11/14 18:53:21 UTC

[pinot] branch master updated: UI: show segment debug details when segment is in bad state (#9700)

This is an automated email from the ASF dual-hosted git repository.

nehapawar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new daab972af5 UI: show segment debug details when segment is in bad state (#9700)
daab972af5 is described below

commit daab972af526bf1d6d4ee47c9010b0cab5310b37
Author: Jayesh Choudhary <ja...@gmail.com>
AuthorDate: Tue Nov 15 00:23:14 2022 +0530

    UI: show segment debug details when segment is in bad state (#9700)
    
    What does this PR do?
    show segment debug details when segment is in bad state
    Issue:
    One of the recurring issue is that if a segments show as BAD status, users have no idea why.
    To get more info about error state then we need to check the debug API or check the server log for the segment.
    We need to have some way in UI so that users can get immediate feedback on why the segment is in BAD state
    Description:
    Show a help icon besides Bad state. This also helps grab user attention when Bad state is visible
    On click of help icon a dialog will open with segment level debug details.
---
 .../main/resources/app/components/CustomDialog.tsx |  9 ++-
 .../app/components/SegmentStatusRenderer.tsx       | 87 +++++++++++++++++++++-
 .../src/main/resources/app/interfaces/types.d.ts   | 15 ++++
 .../src/main/resources/app/pages/TenantDetails.tsx |  2 +
 .../src/main/resources/app/requests/index.ts       |  9 ++-
 .../src/main/resources/app/utils/axios-config.ts   | 10 ++-
 6 files changed, 124 insertions(+), 8 deletions(-)

diff --git a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
index 912a0b107a..60815d8e03 100644
--- a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
@@ -18,13 +18,16 @@
  */
 
 import React from 'react';
-import { Button, Dialog, DialogActions, DialogTitle, makeStyles, withStyles } from '@material-ui/core';
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles, withStyles } from '@material-ui/core';
 import { red } from '@material-ui/core/colors';
 
 const useStyles = makeStyles((theme) => ({
   root: {
     '& .MuiDialog-container > .MuiPaper-root':{
       minWidth: '600px'
+    },
+    "& .MuiDialogContent-root": {
+      padding: 8
     }
   },
   dialogTitle: {
@@ -86,7 +89,9 @@ export default function CustomDialog({
       disableEscapeKeyDown={disableEscapeKeyDown}
     >
       <DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
-      {children}
+      <DialogContent>
+        {children}
+      </DialogContent>
       <DialogActions>
         {showCancelBtn &&
         <CancelButton onClick={handleClose} variant="outlined">
diff --git a/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx b/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx
index 9b4b18bd8a..34a4f53a2b 100644
--- a/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx
+++ b/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx
@@ -18,13 +18,21 @@
  */
 
 import {
+  Box,
   Chip,
+  CircularProgress,
+  IconButton,
   makeStyles,
   Tooltip,
 } from "@material-ui/core";
+import { HelpOutlineOutlined } from "@material-ui/icons";
+import { getSegmentLevelDebugDetails } from "../requests";
+import CustomCodemirror from "./CustomCodemirror";
+import CustomDialog from "./CustomDialog";
+import { NotificationContext } from "./Notification/NotificationContext";
 import clsx from "clsx";
-import { DISPLAY_SEGMENT_STATUS } from "Models";
-import React, { useEffect, useState } from "react";
+import { DISPLAY_SEGMENT_STATUS, SegmentDebugDetails } from "Models";
+import React, { useContext, useEffect, useState } from "react";
 
 const useStyles = makeStyles((theme) => ({
   error: {
@@ -38,11 +46,17 @@ const useStyles = makeStyles((theme) => ({
   warning: {
     color: theme.palette.warning.main,
     border: `1px solid ${theme.palette.warning.main}`,
-  }
+  },
+  segmentDebugDetails: {
+    "& .CodeMirror": { fontSize: 14, height: "100%" },
+    maxHeight: 500,
+  },
 }));
 
 interface SegmentStatusRendererProps {
   status: DISPLAY_SEGMENT_STATUS;
+  segmentName: string;
+  tableName: string;
 }
 
 export enum StatusColor {
@@ -53,11 +67,18 @@ export enum StatusColor {
 
 export const SegmentStatusRenderer = ({
   status,
+  segmentName,
+  tableName,
 }: SegmentStatusRendererProps) => {
+  const { dispatch: notify } = useContext(NotificationContext);
   const [statusTooltipTitle, setStatusTooltipTitle] = useState<string>("");
   const [statusColor, setStatusColor] = useState<StatusColor | null>(
     null
   );
+  const [errorDetailsVisible, setErrorDetailsVisible] =
+    useState<boolean>(false);
+  const [segmentDebugDetails, setSegmentDebugDetails] =
+    useState<SegmentDebugDetails | null>(null);
   const segmentStatusRendererClasses = useStyles();
 
   useEffect(() => {
@@ -87,6 +108,33 @@ export const SegmentStatusRenderer = ({
     }
   };
 
+  const fetchSegmentErrorDetails = async () => {
+    setSegmentDebugDetails(null);
+    try {
+      const segmentDebugDetails = await getSegmentLevelDebugDetails(
+        tableName,
+        segmentName
+      );
+      setSegmentDebugDetails(segmentDebugDetails);
+    } catch (error) {
+      notify({
+        type: "error",
+        message: "Error occurred while fetching segment debug details.",
+        show: true,
+      });
+      setSegmentDebugDetails({} as SegmentDebugDetails);
+    }
+  };
+
+  const handleShowErrorDetailsClick = () => {
+    setErrorDetailsVisible(true);
+    fetchSegmentErrorDetails();
+  };
+
+  const handleHideErrorDetails = () => {
+    setErrorDetailsVisible(false);
+  };
+
   return (
     <>
       <Tooltip arrow title={statusTooltipTitle} placement="top">
@@ -96,6 +144,39 @@ export const SegmentStatusRenderer = ({
           variant="outlined"
         />
       </Tooltip>
+
+      {/* Only show when segment status is bad */}
+      {status === DISPLAY_SEGMENT_STATUS.BAD && (
+        <>
+          <Tooltip
+            title="Click to show segment error details"
+            arrow
+            placement="top"
+          >
+            <IconButton onClick={handleShowErrorDetailsClick}>
+              <HelpOutlineOutlined fontSize="small" />
+            </IconButton>
+          </Tooltip>
+          <CustomDialog
+            title="Segment Debug Details"
+            open={errorDetailsVisible}
+            handleClose={handleHideErrorDetails}
+            showOkBtn={false}
+          >
+            {/* Loading */}
+            {!segmentDebugDetails && (
+              <Box display="flex" justifyContent="center"><CircularProgress /></Box>
+            )}
+            {segmentDebugDetails && (
+              <CustomCodemirror
+                showLineWrapToggle
+                customClass={segmentStatusRendererClasses.segmentDebugDetails}
+                data={segmentDebugDetails}
+              />
+            )}
+          </CustomDialog>
+        </>
+      )}
     </>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/interfaces/types.d.ts b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
index d3f9b9a3d2..172ed843f9 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -206,6 +206,21 @@ declare module 'Models' {
     MinionWorkerGroupTag: string
   }
 
+  export interface SegmentDebugDetails {
+    segmentName: string;
+    serverState: {
+      [key: string]: {
+        idealState: SEGMENT_STATUS,
+        externalView: SEGMENT_STATUS,
+        errorInfo?: {
+          timeStamp: string,
+          errorMessage: string,
+          stackTrace: string
+        }
+      }
+    }
+  }
+
   export const enum SEGMENT_STATUS {
     ONLINE = "ONLINE",
     OFFLINE = "OFFLINE",
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index 0a1fb8b365..f456dbb7e0 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -184,6 +184,8 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
         {
           customRenderer: (
             <SegmentStatusRenderer
+              segmentName={name}
+              tableName={tableName}
               status={status as DISPLAY_SEGMENT_STATUS}
             />
           ),
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts
index 8258e200ed..9e14df149a 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -20,7 +20,7 @@
 import { AxiosResponse } from 'axios';
 import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, TableSize,
   IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, ZKConfig, OperationResponse,
-  BrokerList, ServerList, UserList, TableList, UserObject, TaskProgressResponse, TableSegmentJobs, TaskRuntimeConfig
+  BrokerList, ServerList, UserList, TableList, UserObject, TaskProgressResponse, TableSegmentJobs, TaskRuntimeConfig, SegmentDebugDetails
 } from 'Models';
 
 const headers = {
@@ -28,7 +28,7 @@ const headers = {
   'Accept': 'text/plain, */*; q=0.01'
 };
 
-import { baseApi, transformApi } from '../utils/axios-config';
+import { baseApi, baseApiWithErrors, transformApi } from '../utils/axios-config';
 
 export const getTenants = (): Promise<AxiosResponse<Tenants>> =>
   baseApi.get('/tenants');
@@ -237,6 +237,11 @@ export const authenticateUser = (authToken): Promise<AxiosResponse<OperationResp
 export const getSegmentDebugInfo = (tableName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.get(`debug/tables/${tableName}?type=${tableType}&verbosity=10`);
 
+export const getSegmentLevelDebugDetails = async (tableName: string, segmentName: string): Promise<SegmentDebugDetails> => {
+  const response = await baseApiWithErrors.get(`debug/segments/${tableName}/${segmentName}`);
+  return response.data;
+}
+
 export const requestTable = (): Promise<AxiosResponse<TableList>> =>
     baseApi.get(`/tables`);
 
diff --git a/pinot-controller/src/main/resources/app/utils/axios-config.ts b/pinot-controller/src/main/resources/app/utils/axios-config.ts
index be713ee6d9..595628c4fe 100644
--- a/pinot-controller/src/main/resources/app/utils/axios-config.ts
+++ b/pinot-controller/src/main/resources/app/utils/axios-config.ts
@@ -65,4 +65,12 @@ baseApi.interceptors.response.use(handleResponse, handleError);
 
 export const transformApi = axios.create({baseURL: '/', transformResponse: [data => data]});
 transformApi.interceptors.request.use(handleConfig, handleError);
-transformApi.interceptors.response.use(handleResponse, handleError);
\ No newline at end of file
+transformApi.interceptors.response.use(handleResponse, handleError);
+
+// baseApi axios instance does not throw an error when API fails hence the control will never go to catch block
+// changing the handleError method of baseApi will cause current UI to break (as UI might have not handle error properly)
+// creating a new axios instance baseApiWithErrors which can be used when adding new API's
+// NOTE: It is an add-on utility and can be used in case you want to handle/show UI when API fails.
+export const baseApiWithErrors = axios.create({ baseURL: '/' });
+baseApiWithErrors.interceptors.request.use(handleConfig);
+baseApiWithErrors.interceptors.response.use(handleResponse);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org