You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 6.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import KFIcon from '@/components/KFIcon';
  2. import { ExperimentStatus } from '@/enums';
  3. import { getRayInsReq } from '@/services/hyperParameter';
  4. import { NodeStatus } from '@/types';
  5. import { parseJsonText } from '@/utils';
  6. import { safeInvoke } from '@/utils/functional';
  7. import { to } from '@/utils/promise';
  8. import { useParams } from '@umijs/max';
  9. import { Tabs } from 'antd';
  10. import { useEffect, useRef, useState } from 'react';
  11. import ExperimentHistory from '../components/ExperimentHistory';
  12. import ExperimentLog from '../components/ExperimentLog';
  13. import ExperimentResult from '../components/ExperimentResult';
  14. import HyperParameterBasic from '../components/HyperParameterBasic';
  15. import { HyperParameterData, HyperParameterInstanceData } from '../types';
  16. import styles from './index.less';
  17. enum TabKeys {
  18. Params = 'params',
  19. Log = 'log',
  20. Result = 'result',
  21. History = 'history',
  22. }
  23. const NodePrefix = 'workflow';
  24. function HyperParameterInstance() {
  25. const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined);
  26. const [instanceInfo, setInstanceInfo] = useState<HyperParameterInstanceData | undefined>(
  27. undefined,
  28. );
  29. // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态
  30. const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined);
  31. const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined);
  32. const params = useParams();
  33. const instanceId = safeInvoke(Number)(params.id);
  34. const evtSourceRef = useRef<EventSource | null>(null);
  35. useEffect(() => {
  36. if (instanceId) {
  37. getExperimentInsInfo(false);
  38. }
  39. return () => {
  40. closeSSE();
  41. };
  42. // eslint-disable-next-line react-hooks/exhaustive-deps
  43. }, [instanceId]);
  44. // 获取实验实例详情
  45. const getExperimentInsInfo = async (isStatusDetermined: boolean) => {
  46. const [res] = await to(getRayInsReq(instanceId));
  47. if (res && res.data) {
  48. const info = res.data as HyperParameterInstanceData;
  49. const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
  50. // 解析配置参数
  51. const paramJson = parseJsonText(param).data;
  52. if (paramJson) {
  53. // 实例详情返回的参数是字符串,需要转换
  54. if (typeof paramJson.parameters === 'string') {
  55. paramJson.parameters = parseJsonText(paramJson.parameters);
  56. }
  57. if (!Array.isArray(paramJson.parameters)) {
  58. paramJson.parameters = [];
  59. }
  60. // 实例详情返回的运行参数是字符串,需要转换
  61. if (typeof paramJson.points_to_evaluate === 'string') {
  62. paramJson.points_to_evaluate = parseJsonText(paramJson.points_to_evaluate);
  63. }
  64. if (!Array.isArray(paramJson.points_to_evaluate)) {
  65. paramJson.points_to_evaluate = [];
  66. }
  67. setExperimentInfo({
  68. ...paramJson,
  69. });
  70. }
  71. setInstanceInfo(info);
  72. // 这个接口返回的状态有延时,SSE 返回的状态是最新的
  73. // SSE 调用时,不需要解析 node_status,也不要重新建立 SSE
  74. if (isStatusDetermined) {
  75. return;
  76. }
  77. // 进行节点状态
  78. const nodeStatusJson = parseJsonText(node_status);
  79. if (nodeStatusJson) {
  80. setNodes(nodeStatusJson);
  81. Object.keys(nodeStatusJson).some((key) => {
  82. if (key.startsWith(NodePrefix)) {
  83. const workflowStatus = nodeStatusJson[key];
  84. setWorkflowStatus(workflowStatus);
  85. return true;
  86. }
  87. return false;
  88. });
  89. }
  90. // 运行中或者等待中,开启 SSE
  91. if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
  92. setupSSE(argo_ins_name, argo_ins_ns);
  93. }
  94. }
  95. };
  96. const setupSSE = (name: string, namespace: string) => {
  97. const { origin } = location;
  98. const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
  99. const evtSource = new EventSource(
  100. `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
  101. { withCredentials: false },
  102. );
  103. evtSource.onmessage = (event) => {
  104. const data = event?.data;
  105. if (!data) {
  106. return;
  107. }
  108. const dataJson = parseJsonText(data);
  109. if (dataJson) {
  110. const nodes = dataJson?.result?.object?.status?.nodes;
  111. if (nodes) {
  112. // 设置节点
  113. setNodes(nodes);
  114. // 设置总 workflow 状态
  115. const workflowStatus = Object.values(nodes).find((node: any) =>
  116. node.displayName.startsWith(NodePrefix),
  117. ) as NodeStatus;
  118. if (workflowStatus) {
  119. setWorkflowStatus(workflowStatus);
  120. // 实验结束,关闭 SSE,获取实验实例结果
  121. if (
  122. workflowStatus.phase !== ExperimentStatus.Pending &&
  123. workflowStatus.phase !== ExperimentStatus.Running
  124. ) {
  125. closeSSE();
  126. getExperimentInsInfo(true);
  127. }
  128. }
  129. }
  130. }
  131. };
  132. evtSource.onerror = (error) => {
  133. console.error('SSE error: ', error);
  134. };
  135. evtSourceRef.current = evtSource;
  136. };
  137. const closeSSE = () => {
  138. if (evtSourceRef.current) {
  139. evtSourceRef.current.close();
  140. evtSourceRef.current = null;
  141. }
  142. };
  143. const basicTabItems = [
  144. {
  145. key: TabKeys.Params,
  146. label: '基本信息',
  147. icon: <KFIcon type="icon-jibenxinxi" />,
  148. children: (
  149. <HyperParameterBasic
  150. className={styles['hyper-parameter-instance__basic']}
  151. info={experimentInfo}
  152. workflowStatus={workflowStatus}
  153. instanceStatus={instanceInfo?.status as ExperimentStatus}
  154. isInstance
  155. />
  156. ),
  157. },
  158. {
  159. key: TabKeys.Log,
  160. label: '日志',
  161. icon: <KFIcon type="icon-rizhi1" />,
  162. children: (
  163. <div className={styles['hyper-parameter-instance__log']}>
  164. {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />}
  165. </div>
  166. ),
  167. },
  168. ];
  169. const resultTabItems = [
  170. {
  171. key: TabKeys.Result,
  172. label: '实验结果',
  173. icon: <KFIcon type="icon-shiyanjieguo1" />,
  174. children: <ExperimentResult fileUrl={instanceInfo?.result_txt} />,
  175. },
  176. {
  177. key: TabKeys.History,
  178. label: '寻优列表',
  179. icon: <KFIcon type="icon-Trialliebiao" />,
  180. children: <ExperimentHistory trialList={instanceInfo?.trial_list ?? []} />,
  181. },
  182. ];
  183. const tabItems =
  184. instanceInfo?.status === ExperimentStatus.Succeeded
  185. ? [...basicTabItems, ...resultTabItems]
  186. : basicTabItems;
  187. return (
  188. <div className={styles['hyper-parameter-instance']}>
  189. <Tabs className={styles['hyper-parameter-instance__tabs']} items={tabItems} />
  190. </div>
  191. );
  192. }
  193. export default HyperParameterInstance;