github.com/minio/console@v1.4.1/web-app/src/screens/Console/Support/Profile.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2022 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { Fragment, useEffect, useState } from "react";
    18  import { Button, PageLayout, FormLayout, Box, Checkbox, InputLabel } from "mds";
    19  import { wsProtocol } from "../../../utils/wsUtils";
    20  import { useNavigate } from "react-router-dom";
    21  import { registeredCluster } from "../../../config";
    22  import { useAppDispatch } from "../../../store";
    23  import { setHelpName } from "../../../systemSlice";
    24  import RegisterCluster from "./RegisterCluster";
    25  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    26  import HelpMenu from "../HelpMenu";
    27  
    28  var socket: any = null;
    29  
    30  const Profile = () => {
    31    const navigate = useNavigate();
    32  
    33    const [profilingStarted, setProfilingStarted] = useState<boolean>(false);
    34    const [types, setTypes] = useState<string[]>([
    35      "cpu",
    36      "mem",
    37      "block",
    38      "mutex",
    39      "goroutines",
    40    ]);
    41    const clusterRegistered = registeredCluster();
    42    const typesList = [
    43      { label: "cpu", value: "cpu" },
    44      { label: "mem", value: "mem" },
    45      { label: "block", value: "block" },
    46      { label: "mutex", value: "mutex" },
    47      { label: "goroutines", value: "goroutines" },
    48    ];
    49  
    50    const onCheckboxClick = (e: React.ChangeEvent<HTMLInputElement>) => {
    51      let newArr: string[] = [];
    52      if (types.indexOf(e.target.value) > -1) {
    53        newArr = types.filter((type) => type !== e.target.value);
    54      } else {
    55        newArr = [...types, e.target.value];
    56      }
    57      setTypes(newArr);
    58    };
    59  
    60    const startProfiling = () => {
    61      const typeString = types.join(",");
    62  
    63      const url = new URL(window.location.toString());
    64      const isDev = process.env.NODE_ENV === "development";
    65      const port = isDev ? "9090" : url.port;
    66  
    67      // check if we are using base path, if not this always is `/`
    68      const baseLocation = new URL(document.baseURI);
    69      const baseUrl = baseLocation.pathname;
    70  
    71      const wsProt = wsProtocol(url.protocol);
    72      socket = new WebSocket(
    73        `${wsProt}://${url.hostname}:${port}${baseUrl}ws/profile?types=${typeString}`,
    74      );
    75  
    76      if (socket !== null) {
    77        socket.onopen = () => {
    78          setProfilingStarted(true);
    79          socket.send("ok");
    80        };
    81        socket.onmessage = (message: MessageEvent) => {
    82          // process received message
    83          let response = new Blob([message.data], { type: "application/zip" });
    84          let filename = "profile.zip";
    85          setProfilingStarted(false);
    86          var link = document.createElement("a");
    87          link.href = window.URL.createObjectURL(response);
    88          link.download = filename;
    89          document.body.appendChild(link);
    90          link.click();
    91          document.body.removeChild(link);
    92        };
    93        socket.onclose = () => {
    94          console.log("connection closed by server");
    95          setProfilingStarted(false);
    96        };
    97        return () => {
    98          socket.close(1000);
    99          console.log("closing websockets");
   100          setProfilingStarted(false);
   101        };
   102      }
   103    };
   104  
   105    const stopProfiling = () => {
   106      socket.close(1000);
   107      setProfilingStarted(false);
   108    };
   109  
   110    const dispatch = useAppDispatch();
   111    useEffect(() => {
   112      dispatch(setHelpName("profile"));
   113      // eslint-disable-next-line react-hooks/exhaustive-deps
   114    }, []);
   115  
   116    return (
   117      <Fragment>
   118        <PageHeaderWrapper label="Profile" actions={<HelpMenu />} />
   119        <PageLayout>
   120          {!clusterRegistered && <RegisterCluster compactMode />}
   121          <FormLayout>
   122            <Box
   123              sx={{
   124                display: "flex",
   125                gap: 10,
   126                "& div": { width: "initial" },
   127                "& .inputItem:not(:last-of-type)": { marginBottom: 0 },
   128              }}
   129            >
   130              <InputLabel noMinWidth>Types to profile:</InputLabel>
   131              {typesList.map((t) => (
   132                <Checkbox
   133                  checked={types.indexOf(t.value) > -1}
   134                  disabled={profilingStarted || !clusterRegistered}
   135                  key={`checkbox-${t.label}`}
   136                  id={`checkbox-${t.label}`}
   137                  label={t.label}
   138                  name={`checkbox-${t.label}`}
   139                  onChange={onCheckboxClick}
   140                  value={t.value}
   141                />
   142              ))}
   143            </Box>
   144            <Box
   145              sx={{
   146                display: "flex",
   147                justifyContent: "flex-end",
   148                marginTop: 24,
   149                gap: 10,
   150              }}
   151            >
   152              <Button
   153                id={"start-profiling"}
   154                type="submit"
   155                variant={clusterRegistered ? "callAction" : "regular"}
   156                disabled={
   157                  profilingStarted || types.length < 1 || !clusterRegistered
   158                }
   159                onClick={() => {
   160                  if (!clusterRegistered) {
   161                    navigate("/support/register");
   162                    return;
   163                  }
   164                  startProfiling();
   165                }}
   166                label={"Start Profiling"}
   167              />
   168              <Button
   169                id={"stop-profiling"}
   170                type="submit"
   171                variant="callAction"
   172                color="primary"
   173                disabled={!profilingStarted || !clusterRegistered}
   174                onClick={() => {
   175                  stopProfiling();
   176                }}
   177                label={"Stop Profiling"}
   178              />
   179            </Box>
   180          </FormLayout>
   181        </PageLayout>
   182      </Fragment>
   183    );
   184  };
   185  
   186  export default Profile;