Refactor log components(remove log collection) (#4794)

This commit is contained in:
Lijiaoa 2022-05-06 16:53:06 +08:00 коммит произвёл GitHub
Родитель cbb63c5bec
Коммит cadf3a5564
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 269 добавлений и 418 удалений

Просмотреть файл

@ -1,22 +0,0 @@
import * as React from 'react';
import { DetailsRow, IDetailsRowBaseProps } from '@fluentui/react';
import OpenRow from './OpenRow';
interface ExpandableDetailsProps {
detailsProps: IDetailsRowBaseProps;
isExpand: boolean;
}
class ExpandableDetails extends React.Component<ExpandableDetailsProps, {}> {
render(): React.ReactNode {
const { detailsProps, isExpand } = this.props;
return (
<div>
<DetailsRow {...detailsProps} />
{isExpand && <OpenRow trialId={detailsProps.item.id} />}
</div>
);
}
}
export default ExpandableDetails;

Просмотреть файл

@ -0,0 +1,22 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { DetailsRow } from '@fluentui/react';
import OpenRow from './OpenRow';
import '@style/table.scss';
const ExpandableDetails = (props): any => {
const { detailsProps, isExpand } = props;
return (
<div>
<DetailsRow {...detailsProps} />
{isExpand && <OpenRow trialId={detailsProps.item.id} />}
</div>
);
};
ExpandableDetails.propTypes = {
detailsProps: PropTypes.object,
isExpand: PropTypes.bool
};
export default ExpandableDetails;

Просмотреть файл

@ -0,0 +1,168 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Stack, PrimaryButton, Pivot, PivotItem, DefaultButton } from '@fluentui/react';
import * as copy from 'copy-to-clipboard';
import JSONTree from 'react-json-tree';
import { Trial } from '@model/trial';
import { MANAGER_IP, RETIARIIPARAMETERS } from '@static/const';
import { EXPERIMENT, TRIALS } from '@static/datamodel';
import { reformatRetiariiParameter } from '@static/function';
import PaiTrialLog from './PaiTrialLog';
import TrialLog from './TrialLog';
import MessageInfo from '../MessageInfo';
import PanelMonacoEditor from '../PanelMonacoEditor';
import '@style/experiment/overview/overview.scss';
/**
* netron URL must be synchronized with ts/nni_manager/rest_server/index.ts`.
* Remember to update it if the value is changed or this file is moved.
**/
const OpenRow = (props): any => {
const [typeInfo, setTypeInfo] = useState('');
const [info, setInfo] = useState('');
const [isHidenInfo, setHideninfo] = useState(true);
const [showRetiaParamPanel, setShowRetiaparamPanel] = useState(false);
const trialId = props.trialId;
const trial = TRIALS.getTrial(trialId);
const logPathRow = trial.info.logPath || "This trial's log path is not available.";
const originParameters = trial.description.parameters;
const hasVisualHyperParams = RETIARIIPARAMETERS in originParameters;
const hideMessageInfo = (): void => {
setHideninfo(true);
};
const hideRetiaParam = (): void => {
setShowRetiaparamPanel(false);
};
const isshowRetiaParamPanel = (): void => {
setShowRetiaparamPanel(true);
};
/**
* info: message content
* typeInfo: message type: success | error...
* continuousTime: show time, 2000ms
*/
const getCopyStatus = (info: string, typeInfo: string): void => {
setTypeInfo(typeInfo);
setInfo(info);
setHideninfo(false);
setTimeout(hideMessageInfo, 2000);
};
const copyParams = (trial: Trial): void => {
// get copy parameters
const params = JSON.stringify(reformatRetiariiParameter(trial.description.parameters as any), null, 4);
if (copy.default(params)) {
getCopyStatus('Successfully copy parameters to clipboard in form of python dict !', 'success');
} else {
getCopyStatus('Failed !', 'error');
}
};
const openTrialLog = (filename: string): void => {
window.open(`${MANAGER_IP}/trial-file/${props.trialId}/${filename}`);
};
const openModelOnnx = (): void => {
// TODO: netron might need prefix.
window.open(`/netron/index.html?url=${MANAGER_IP}/trial-file/${props.trialId}/model.onnx`);
};
return (
<Stack className='openRow'>
<Stack className='openRowContent'>
<Pivot>
<PivotItem headerText='Parameters' key='1' itemIcon='TestParameter'>
{trial.info.hyperParameters !== undefined ? (
<Stack className='description'>
<Stack className='bgHyper'>
<JSONTree
hideRoot={true}
shouldExpandNode={() => true} // default expandNode
getItemString={() => null} // remove the {} items
data={reformatRetiariiParameter(originParameters as any)}
/>
</Stack>
<Stack horizontal className='copy'>
<PrimaryButton
onClick={copyParams.bind(this, trial)}
text='Copy as json'
styles={{ root: { width: 128, marginRight: 10 } }}
/>
{hasVisualHyperParams && (
<DefaultButton onClick={isshowRetiaParamPanel} text='Original parameters' />
)}
{/* copy success | failed message info */}
{!isHidenInfo && <MessageInfo typeInfo={typeInfo} info={info} />}
{showRetiaParamPanel && (
<PanelMonacoEditor
hideConfigPanel={hideRetiaParam}
panelName='Retiarii parameters'
panelContent={JSON.stringify(originParameters, null, 2)}
/>
)}
</Stack>
</Stack>
) : (
<Stack className='logpath'>
<span className='logName'>Error: </span>
<span className='error'>{`This trial's parameters are not available.'`}</span>
</Stack>
)}
</PivotItem>
<PivotItem headerText='Log' key='2' itemIcon='M365InvoicingLogo'>
{
// FIXME: this should not be handled in web UI side
EXPERIMENT.trainingServicePlatform !== 'local' ? (
<PaiTrialLog logStr={logPathRow} />
) : (
<div>
<TrialLog logStr={logPathRow} logName='LogPath:' />
{/* view trial log */}
<div className='copy' style={{ marginTop: 4 }}>
<PrimaryButton
onClick={openTrialLog.bind(this, 'trial.log')}
text='View trial log'
/>
<PrimaryButton
onClick={openTrialLog.bind(this, 'stderr')}
text='View trial error'
styles={{ root: { marginLeft: 15 } }}
/>
<PrimaryButton
onClick={openTrialLog.bind(this, 'stdout')}
text='View trial stdout'
styles={{ root: { marginLeft: 15 } }}
/>
</div>
</div>
)
}
</PivotItem>
{EXPERIMENT.metadata.tag.includes('retiarii') ? (
<PivotItem headerText='Visualization' key='3' itemIcon='FlowChart'>
<div id='visualization'>
<div id='visualizationText'>Visualize models with 3rd-party tools.</div>
<PrimaryButton
onClick={openModelOnnx.bind(this)}
text='Netron'
styles={{ root: { marginLeft: 15 } }}
/>
</div>
</PivotItem>
) : null}
</Pivot>
</Stack>
</Stack>
);
};
OpenRow.propTypes = {
trialId: PropTypes.string
};
export default OpenRow;

Просмотреть файл

@ -0,0 +1,26 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import TrialLog from './TrialLog';
const PaitrialLog = (props): any => {
const { logStr } = props;
const isHasNFSLog = logStr.indexOf(',') !== -1 ? true : false;
return (
<div>
{isHasNFSLog ? (
<div>
<TrialLog logStr={logStr.split(',')[0]} logName='Trial stdout:' />
<TrialLog logStr={logStr.split(',')[1]} logName='Log on NFS:' />
</div>
) : (
<TrialLog logStr={logStr} logName='Trial stdout:' />
)}
</div>
);
};
PaitrialLog.propTypes = {
logStr: PropTypes.string
};
export default PaitrialLog;

Просмотреть файл

@ -0,0 +1,27 @@
import * as React from 'react';
import PropTypes from 'prop-types';
const TrialLog = (props): any => {
const { logStr, logName } = props;
const isHyperlink = logStr.toLowerCase().startsWith('http');
return (
<div className='logpath'>
<span className='logName'>{logName}</span>
{isHyperlink ? (
<a className='link' rel='noopener noreferrer' href={logStr} target='_blank'>
{logStr}
</a>
) : (
<span className='fontColor333'>{logStr}</span>
)}
</div>
);
};
TrialLog.propTypes = {
logStr: PropTypes.string,
logName: PropTypes.string
};
export default TrialLog;

Просмотреть файл

@ -1,32 +0,0 @@
import * as React from 'react';
interface LogpathChildProps {
eachLogpath: string;
logName: string;
}
class LogPathChild extends React.Component<LogpathChildProps, {}> {
constructor(props: LogpathChildProps) {
super(props);
}
render(): React.ReactNode {
const { eachLogpath, logName } = this.props;
const isLink = /^http/gi.test(eachLogpath);
return (
<div className='logpath'>
<span className='logName'>{logName}</span>
{isLink ? (
<a className='fontColor333 logHref' rel='noopener noreferrer' href={eachLogpath} target='_blank'>
{eachLogpath}
</a>
) : (
<span className='fontColor333'>{eachLogpath}</span>
)}
</div>
);
}
}
export default LogPathChild;

Просмотреть файл

@ -1,190 +0,0 @@
import * as React from 'react';
import * as copy from 'copy-to-clipboard';
import { Stack, PrimaryButton, Pivot, PivotItem, DefaultButton } from '@fluentui/react';
import JSONTree from 'react-json-tree';
import { Trial } from '@model/trial';
import { MANAGER_IP, RETIARIIPARAMETERS } from '@static/const';
import { EXPERIMENT, TRIALS } from '@static/datamodel';
import { reformatRetiariiParameter } from '@static/function';
import PaiTrialLog from './PaiTrialLog';
import TrialLog from './TrialLog';
import MessageInfo from './MessageInfo';
import PanelMonacoEditor from './PanelMonacoEditor';
import '@style/experiment/overview/overview.scss';
import '@style/openRow.scss';
/**
* netron URL must be synchronized with ts/nni_manager/rest_server/index.ts`.
* Remember to update it if the value is changed or this file is moved.
**/
interface OpenRowProps {
trialId: string;
}
interface OpenRowState {
typeInfo: string;
info: string;
isHidenInfo: boolean;
showRetiaParamPanel: boolean;
}
class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
constructor(props: OpenRowProps) {
super(props);
this.state = {
typeInfo: '',
info: '',
isHidenInfo: true,
showRetiaParamPanel: false
};
}
hideMessageInfo = (): void => {
this.setState(() => ({ isHidenInfo: true }));
};
hideRetiaParam = (): void => {
this.setState(() => ({ showRetiaParamPanel: false }));
};
isshowRetiaParamPanel = (): void => {
this.setState(() => ({ showRetiaParamPanel: true }));
};
/**
* info: message content
* typeInfo: message type: success | error...
* continuousTime: show time, 2000ms
*/
getCopyStatus = (info: string, typeInfo: string): void => {
this.setState(() => ({ info, typeInfo, isHidenInfo: false }));
setTimeout(this.hideMessageInfo, 2000);
};
copyParams = (trial: Trial): void => {
// get copy parameters
const params = JSON.stringify(reformatRetiariiParameter(trial.description.parameters as any), null, 4);
if (copy.default(params)) {
this.getCopyStatus('Success copy parameters to clipboard in form of python dict !', 'success');
} else {
this.getCopyStatus('Failed !', 'error');
}
};
openTrialLog = (filename: string): void => {
window.open(`${MANAGER_IP}/trial-file/${this.props.trialId}/${filename}`);
};
openModelOnnx = (): void => {
// TODO: netron might need prefix.
window.open(`/netron/index.html?url=${MANAGER_IP}/trial-file/${this.props.trialId}/model.onnx`);
};
render(): React.ReactNode {
const { isHidenInfo, typeInfo, info, showRetiaParamPanel } = this.state;
const trialId = this.props.trialId;
const trial = TRIALS.getTrial(trialId);
const logPathRow = trial.info.logPath || "This trial's log path is not available.";
const originParameters = trial.description.parameters;
const hasVisualHyperParams = RETIARIIPARAMETERS in originParameters;
return (
<Stack className='openRow'>
<Stack className='openRowContent'>
<Pivot>
<PivotItem headerText='Parameters' key='1' itemIcon='TestParameter'>
{trial.info.hyperParameters !== undefined ? (
<Stack id='description'>
<Stack className='bgHyper'>
<JSONTree
hideRoot={true}
shouldExpandNode={(): boolean => true} // default expandNode
getItemString={(): null => null} // remove the {} items
data={reformatRetiariiParameter(originParameters as any)}
/>
</Stack>
<Stack horizontal className='copy'>
<PrimaryButton
onClick={this.copyParams.bind(this, trial)}
text='Copy as json'
styles={{ root: { width: 128, marginRight: 10 } }}
/>
{hasVisualHyperParams && (
<DefaultButton
onClick={this.isshowRetiaParamPanel}
text='Original parameters'
/>
)}
{/* copy success | failed message info */}
{!isHidenInfo && <MessageInfo typeInfo={typeInfo} info={info} />}
{showRetiaParamPanel && (
<PanelMonacoEditor
hideConfigPanel={this.hideRetiaParam}
panelName='Retiarii parameters'
panelContent={JSON.stringify(originParameters, null, 2)}
/>
)}
</Stack>
</Stack>
) : (
<Stack className='logpath'>
<span className='logName'>Error: </span>
<span className='error'>{`This trial's parameters are not available.'`}</span>
</Stack>
)}
</PivotItem>
<PivotItem headerText='Log' key='2' itemIcon='M365InvoicingLogo'>
{
// FIXME: this should not be handled in web UI side
EXPERIMENT.trainingServicePlatform !== 'local' ? (
<PaiTrialLog
logStr={logPathRow}
id={trialId}
logCollection={EXPERIMENT.logCollectionEnabled}
/>
) : (
<div>
<TrialLog logStr={logPathRow} id={trialId} />
{/* view each trial log in drawer*/}
<div id='trialog'>
<div className='copy' style={{ marginTop: 15 }}>
<PrimaryButton
onClick={this.openTrialLog.bind(this, 'trial.log')}
text='View trial log'
/>
<PrimaryButton
onClick={this.openTrialLog.bind(this, 'stderr')}
text='View trial error'
styles={{ root: { marginLeft: 15 } }}
/>
<PrimaryButton
onClick={this.openTrialLog.bind(this, 'stdout')}
text='View trial stdout'
styles={{ root: { marginLeft: 15 } }}
/>
</div>
</div>
</div>
)
}
</PivotItem>
{EXPERIMENT.metadata.tag.includes('retiarii') ? (
<PivotItem headerText='Visualization' key='3' itemIcon='FlowChart'>
<div id='visualization'>
<div id='visualizationText'>Visualize models with 3rd-party tools.</div>
<PrimaryButton
onClick={this.openModelOnnx.bind(this)}
text='Netron'
styles={{ root: { marginLeft: 15 } }}
/>
</div>
</PivotItem>
) : null}
</Pivot>
</Stack>
</Stack>
);
}
}
export default OpenRow;

Просмотреть файл

@ -1,41 +0,0 @@
import * as React from 'react';
import { DOWNLOAD_IP } from '@static/const';
import LogPathChild from './LogPathChild';
interface PaiTrialChildProps {
logString: string;
id: string;
logCollect: boolean;
}
class PaiTrialChild extends React.Component<PaiTrialChildProps, {}> {
constructor(props: PaiTrialChildProps) {
super(props);
}
render(): React.ReactNode {
const { logString, id, logCollect } = this.props;
return (
<div>
{logString === '' ? null : (
<div>
{logCollect ? (
<a
target='_blank'
rel='noopener noreferrer'
href={`${DOWNLOAD_IP}/trial_${id}.log`}
style={{ marginRight: 10 }}
>
Trial stdout
</a>
) : (
<LogPathChild eachLogpath={logString} logName='Trial stdout:' />
)}
</div>
)}
</div>
);
}
}
export default PaiTrialChild;

Просмотреть файл

@ -1,55 +0,0 @@
import * as React from 'react';
import { DOWNLOAD_IP } from '@static/const';
import PaiTrialChild from './PaiTrialChild';
import LogPathChild from './LogPathChild';
interface PaitrialLogProps {
logStr: string;
id: string;
logCollection: boolean;
}
class PaitrialLog extends React.Component<PaitrialLogProps, {}> {
constructor(props: PaitrialLogProps) {
super(props);
}
render(): React.ReactNode {
const { logStr, id, logCollection } = this.props;
const isTwopath = logStr.indexOf(',') !== -1 ? true : false;
return (
<div>
<div>
{isTwopath ? (
<div>
{logCollection ? (
<div>
<a
target='_blank'
rel='noopener noreferrer'
href={`${DOWNLOAD_IP}/trial_${id}.log`}
style={{ marginRight: 10 }}
>
Trial stdout
</a>
<a target='_blank' rel='noopener noreferrer' href={logStr.split(',')[1]}>
NFS log
</a>
</div>
) : (
<div>
<LogPathChild eachLogpath={logStr.split(',')[0]} logName='Trial stdout:' />
<LogPathChild eachLogpath={logStr.split(',')[1]} logName='Log on NFS:' />
</div>
)}
</div>
) : (
<PaiTrialChild logString={logStr} id={id} logCollect={logCollection} />
)}
</div>
</div>
);
}
}
export default PaitrialLog;

Просмотреть файл

@ -1,25 +0,0 @@
import * as React from 'react';
import LogPathChild from './LogPathChild';
interface TrialLogProps {
logStr: string;
id: string;
}
class TrialLog extends React.Component<TrialLogProps, {}> {
constructor(props: TrialLogProps) {
super(props);
}
render(): React.ReactNode {
const { logStr } = this.props;
return (
<div>
<LogPathChild eachLogpath={logStr} logName='Log path:' />
</div>
);
}
}
export default TrialLog;

Просмотреть файл

@ -16,7 +16,7 @@ import { TitleContext } from './TitleContext';
import { itemStyleSucceed, entriesOption } from './overviewConst';
import '@style/experiment/overview/overview.scss';
import '@style/experiment/overview/topTrial.scss';
import '@style/logPath.scss';
import '@style/table.scss';
/**
* single experiment

Просмотреть файл

@ -14,7 +14,7 @@ import {
ScrollbarVisibility
} from '@fluentui/react';
import DefaultMetric from './DefaultMetric';
import OpenRow from '@components/common/OpenRow';
import OpenRow from '@/components/common/ExpandableDetails/OpenRow';
import CopyButton from '@components/common/CopyButton';
import { convertDuration, copyAndSort } from '@static/function';
import { TRIALS } from '@static/datamodel';

Просмотреть файл

@ -8,7 +8,6 @@ import Para from './chart/Para';
import Intermediate from './chart/Intermediate';
import TableList from './table/TableList';
import '@style/button.scss';
import '@style/logPath.scss';
import '@style/openRow.scss';
import '@style/pagination.scss';
import '@style/experiment/overview/overviewTitle.scss';

Просмотреть файл

@ -23,14 +23,14 @@ import {
} from '@static/function';
import { TableObj, SortInfo, SearchItems } from '@static/interface';
import { blocked, copy, LineChart, tableListIcon } from '@components/fluent/Icon';
import Search from './tableFunction/search/Search';
import Customize from './tableFunction/CustomizedTrial';
import TensorboardUI from './tableFunction/tensorboard/TensorboardUI';
import Search from './tableFunction/search/Search';
import ExpandableDetails from '@components/common/ExpandableDetails/ExpandableIndex';
import ChangeColumnComponent from '../ChangeColumnComponent';
import Compare from './tableFunction/Compare';
import KillJobIndex from './tableFunction/killJob/KillJobIndex';
import { getTrialsBySearchFilters } from './tableFunction/search/searchFunction';
import ExpandableDetails from '@components/common/ExpandableDetails';
import PaginationTable from '@components/common/PaginationTable';
import CopyButton from '@components/common/CopyButton';

Просмотреть файл

@ -147,13 +147,8 @@ interface TrialJobInfo {
hyperParameters?: string[];
logPath?: string;
finalMetricData?: MetricDataRecord[];
stderrPath?: string;
}
//interface ClusterItem {
// command?: string;
//}
interface ExperimentProfile {
params: ExperimentConfig;
id: string;

Просмотреть файл

@ -182,10 +182,6 @@ class Experiment {
return new SearchSpace('', '', this.searchSpace);
}
get logCollectionEnabled(): boolean {
return false;
}
get status(): string {
if (!this.statusField) {
// throw Error('Experiment status not initialized');

Просмотреть файл

@ -7,17 +7,18 @@ $themeBlue: #0071bc;
}
.link {
color: $themeBlue;
outline: none;
text-decoration: none;
&:hover {
color: #0071bc !important;
color: $themeBlue;
text-decoration: underline;
}
&:active,
&:visited {
color: #0071bc;
color: $themeBlue;
}
}

Просмотреть файл

@ -1,18 +0,0 @@
.logpath {
margin-left: 10px;
font-size: 14px;
.logName {
color: #268bd2;
margin-right: 5px;
}
.error {
color: #cb4b16;
}
}
.logHref:hover {
color: blue;
text-decoration: underline;
}

Просмотреть файл

@ -17,18 +17,7 @@ $bgColor: #f2f2f2;
}
}
.trialLog {
white-space: normal;
color: #212121;
}
#trialLogContent {
.logcontent {
height: 100%;
}
}
#description {
.description {
padding: 0 10px;
.bgHyper ul {

Просмотреть файл

@ -1,7 +1,7 @@
/* react-json-tree background */
#description ul,
.description ul,
#allList ul {
background: none !important;
background: none;
}
.tabScroll {
@ -46,11 +46,6 @@
}
}
/* office-fabric-ui */
.ms-DetailsRow {
height: 30px;
}
.detail-table {
padding-top: 5px;
}
@ -78,3 +73,19 @@ $checkboxwidth: 17px;
}
}
}
/* trial table: open row style */
.logpath {
margin: 10px 0 10px 10px;
font-size: 14px;
.logName {
color: #268bd2;
margin-right: 5px;
}
.error {
color: #cb4b16;
}
}