Unverified Commit d90433da authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #246 from microsoft/master

merge master
parents 1e511829 bf7daa8f
...@@ -77,7 +77,7 @@ class KillJob extends React.Component<KillJobProps, KillJobState> { ...@@ -77,7 +77,7 @@ class KillJob extends React.Component<KillJobProps, KillJobState> {
onKill = (): void => { onKill = (): void => {
this.setState({ isCalloutVisible: false }, () => { this.setState({ isCalloutVisible: false }, () => {
const { trial } = this.props; const { trial } = this.props;
killJob(trial.key, trial.id, trial.status); killJob(trial.key, trial.jobId, trial.status);
}); });
} }
...@@ -127,4 +127,4 @@ class KillJob extends React.Component<KillJobProps, KillJobState> { ...@@ -127,4 +127,4 @@ class KillJob extends React.Component<KillJobProps, KillJobState> {
} }
} }
export default KillJob; export default KillJob;
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { Stack, IStackTokens } from 'office-ui-fabric-react'; import { Stack, IStackTokens, Dropdown } from 'office-ui-fabric-react';
import { EXPERIMENT, TRIALS } from '../static/datamodel'; import { EXPERIMENT, TRIALS } from '../static/datamodel';
import { Trial } from '../static/model/trial'; import { Trial } from '../static/model/trial';
import Title1 from './overview/Title1'; import Title1 from './overview/Title1';
...@@ -16,7 +16,9 @@ interface OverviewProps { ...@@ -16,7 +16,9 @@ interface OverviewProps {
experimentUpdateBroadcast: number; experimentUpdateBroadcast: number;
trialsUpdateBroadcast: number; trialsUpdateBroadcast: number;
metricGraphMode: 'max' | 'min'; metricGraphMode: 'max' | 'min';
bestTrialEntries: string;
changeMetricGraphMode: (val: 'max' | 'min') => void; changeMetricGraphMode: (val: 'max' | 'min') => void;
changeEntries: (entries: string) => void;
} }
interface OverviewState { interface OverviewState {
...@@ -38,6 +40,7 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -38,6 +40,7 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
changeMetricGraphMode('max'); changeMetricGraphMode('max');
} }
clickMinTop = (event: React.SyntheticEvent<EventTarget>): void => { clickMinTop = (event: React.SyntheticEvent<EventTarget>): void => {
event.stopPropagation(); event.stopPropagation();
const { changeMetricGraphMode } = this.props; const { changeMetricGraphMode } = this.props;
...@@ -48,9 +51,16 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -48,9 +51,16 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
this.setState({ trialConcurrency: val }); this.setState({ trialConcurrency: val });
} }
// updateEntries = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption | undefined): void => {
updateEntries = (event: React.FormEvent<HTMLDivElement>, item: any): void => {
if (item !== undefined) {
this.props.changeEntries(item.key);
}
}
render(): React.ReactNode { render(): React.ReactNode {
const { trialConcurrency } = this.state; const { trialConcurrency } = this.state;
const { experimentUpdateBroadcast, metricGraphMode } = this.props; const { experimentUpdateBroadcast, metricGraphMode, bestTrialEntries } = this.props;
const searchSpace = this.convertSearchSpace(); const searchSpace = this.convertSearchSpace();
const bestTrials = this.findBestTrials(); const bestTrials = this.findBestTrials();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...@@ -58,23 +68,31 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -58,23 +68,31 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
const accuracyGraphData = this.generateAccuracyGraph(bestTrials); const accuracyGraphData = this.generateAccuracyGraph(bestTrials);
const noDataMessage = bestTrials.length > 0 ? '' : 'No data'; const noDataMessage = bestTrials.length > 0 ? '' : 'No data';
const titleMaxbgcolor = (metricGraphMode === 'max' ? '#999' : '#b3b3b3'); const titleMaxbgcolor = (metricGraphMode === 'max' ? '#333' : '#b3b3b3');
const titleMinbgcolor = (metricGraphMode === 'min' ? '#999' : '#b3b3b3'); const titleMinbgcolor = (metricGraphMode === 'min' ? '#333' : '#b3b3b3');
const stackTokens: IStackTokens = { const stackTokens: IStackTokens = {
childrenGap: 30, childrenGap: 30,
}; };
const entriesOption = [
{ key: '10', text: 'Display top 10 trials' },
{ key: '20', text: 'Display top 20 trials' },
{ key: '30', text: 'Display top 30 trials' },
{ key: '50', text: 'Display top 50 trials' },
{ key: '100', text: 'Display top 100 trials' }
];
return ( return (
<div className="overview"> <div className="overview">
{/* status and experiment block */} {/* status and experiment block */}
<Stack> <Stack className="bottomDiv bgNNI">
<Title1 text="Experiment" icon="11.png" /> <Title1 text="Experiment" icon="11.png" />
<BasicInfo experimentUpdateBroadcast={experimentUpdateBroadcast} /> <BasicInfo experimentUpdateBroadcast={experimentUpdateBroadcast} />
</Stack> </Stack>
<Stack horizontal className="overMessage"> <Stack horizontal className="overMessage bottomDiv">
{/* status block */} {/* status block */}
<Stack.Item grow className="prograph overviewBoder cc"> <Stack.Item grow className="prograph bgNNI borderRight">
<Title1 text="Status" icon="5.png" /> <Title1 text="Status" icon="5.png" />
<Progressed <Progressed
bestAccuracy={bestAccuracy} bestAccuracy={bestAccuracy}
...@@ -84,13 +102,14 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -84,13 +102,14 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
/> />
</Stack.Item> </Stack.Item>
{/* experiment parameters search space tuner assessor... */} {/* experiment parameters search space tuner assessor... */}
<Stack.Item grow styles={{root: {width: 450}}} className="overviewBoder"> <Stack.Item grow styles={{root: {width: 450}}} className="overviewBoder borderRight bgNNI">
<Title1 text="Search space" icon="10.png" /> <Title1 text="Search space" icon="10.png" />
<Stack className="experiment"> <Stack className="experiment">
<SearchSpace searchSpace={searchSpace} /> <SearchSpace searchSpace={searchSpace} />
</Stack> </Stack>
</Stack.Item> </Stack.Item>
<Stack.Item grow styles={{root: {width: 450}}}> {/* <Stack.Item grow styles={{root: {width: 450}}} className="bgNNI"> */}
<Stack.Item grow styles={{root: {width: 450}}} className="bgNNI">
<Title1 text="Config" icon="4.png" /> <Title1 text="Config" icon="4.png" />
<Stack className="experiment"> <Stack className="experiment">
{/* the scroll bar all the trial profile in the searchSpace div*/} {/* the scroll bar all the trial profile in the searchSpace div*/}
...@@ -104,19 +123,27 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -104,19 +123,27 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
</Stack.Item> </Stack.Item>
</Stack> </Stack>
<Stack> <Stack style={{backgroundColor: '#fff'}}>
<Stack horizontal className="top10bg"> <Stack horizontal className="top10bg" style={{position: 'relative'}}>
<div <div
className="title" className="title"
onClick={this.clickMaxTop} onClick={this.clickMaxTop}
> >
<Title1 text="Top10 Maximal trials" icon="max.png" bgcolor={titleMaxbgcolor} /> <Title1 text="Top Maximal trials" icon="max.png" fontColor={titleMaxbgcolor} />
</div> </div>
<div <div
className="title minTitle" className="title minTitle"
onClick={this.clickMinTop} onClick={this.clickMinTop}
> >
<Title1 text="Top10 Minimal trials" icon="min.png" bgcolor={titleMinbgcolor} /> <Title1 text="Top Minimal trials" icon="min.png" fontColor={titleMinbgcolor} />
</div>
<div style={{position: 'absolute', right: 52, top: 6}}>
<Dropdown
selectedKey={bestTrialEntries}
options={entriesOption}
onChange={this.updateEntries}
styles={{ root: { width: 170 } }}
/>
</div> </div>
</Stack> </Stack>
<Stack horizontal tokens={stackTokens}> <Stack horizontal tokens={stackTokens}>
...@@ -128,7 +155,7 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -128,7 +155,7 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
/> />
</div> </div>
<div style={{ width: '60%'}}> <div style={{ width: '60%'}}>
<SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} /> <SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} />
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
...@@ -155,10 +182,11 @@ class Overview extends React.Component<OverviewProps, OverviewState> { ...@@ -155,10 +182,11 @@ class Overview extends React.Component<OverviewProps, OverviewState> {
private findBestTrials(): Trial[] { private findBestTrials(): Trial[] {
const bestTrials = TRIALS.sort(); const bestTrials = TRIALS.sort();
const { bestTrialEntries } = this.props;
if (this.props.metricGraphMode === 'max') { if (this.props.metricGraphMode === 'max') {
bestTrials.reverse().splice(10); bestTrials.reverse().splice(JSON.parse(bestTrialEntries));
} else { } else {
bestTrials.splice(10); bestTrials.splice(JSON.parse(bestTrialEntries));
} }
return bestTrials; return bestTrials;
} }
......
...@@ -142,53 +142,54 @@ class TrialsDetail extends React.Component<TrialsDetailProps, TrialDetailState> ...@@ -142,53 +142,54 @@ class TrialsDetail extends React.Component<TrialsDetailProps, TrialDetailState>
</Pivot> </Pivot>
</div> </div>
{/* trial table list */} {/* trial table list */}
<Stack horizontal className="panelTitle"> <div style={{ backgroundColor: '#fff' }}>
<span style={{ marginRight: 12 }}>{tableListIcon}</span> <Stack horizontal className="panelTitle" style={{ marginTop: 10 }}>
<span>Trial jobs</span> <span style={{ marginRight: 12 }}>{tableListIcon}</span>
</Stack> <span>Trial jobs</span>
<Stack horizontal className="allList"> </Stack>
<StackItem grow={50}> <Stack horizontal className="allList">
<DefaultButton <StackItem grow={50}>
text="Compare"
className="allList-compare"
// use child-component tableList's function, the function is in child-component.
onClick={(): void => { if (this.tableList) { this.tableList.compareBtn(); } }}
/>
</StackItem>
<StackItem grow={50}>
<Stack horizontal horizontalAlign="end" className="allList">
<DefaultButton <DefaultButton
className="allList-button-gap" text="Compare"
text="Add column" className="allList-compare"
onClick={(): void => { if (this.tableList) { this.tableList.addColumn(); } }} // use child-component tableList's function, the function is in child-component.
onClick={(): void => { if (this.tableList) { this.tableList.compareBtn(); } }}
/> />
<Dropdown </StackItem>
selectedKey={searchType} <StackItem grow={50}>
options={searchOptions} <Stack horizontal horizontalAlign="end" className="allList">
onChange={this.updateSearchFilterType} <DefaultButton
styles={{ root: { width: 150 } }} className="allList-button-gap"
/> text="Add column"
<input onClick={(): void => { if (this.tableList) { this.tableList.addColumn(); } }}
type="text" />
className="allList-search-input" <Dropdown
placeholder={`Search by ${this.state.searchType}`} selectedKey={searchType}
onChange={this.searchTrial} options={searchOptions}
style={{ width: 230 }} onChange={this.updateSearchFilterType}
ref={(text): any => (this.searchInput) = text} styles={{ root: { width: 150 } }}
/> />
</Stack> <input
type="text"
</StackItem> className="allList-search-input"
</Stack> placeholder={`Search by ${this.state.searchType}`}
<TableList onChange={this.searchTrial}
pageSize={tablePageSize} style={{ width: 230 }}
tableSource={source.map(trial => trial.tableRecord)} ref={(text): any => (this.searchInput) = text}
columnList={columnList} />
changeColumn={changeColumn} </Stack>
trialsUpdateBroadcast={this.props.trialsUpdateBroadcast} </StackItem>
// TODO: change any to specific type </Stack>
ref={(tabList): any => this.tableList = tabList} <TableList
/> pageSize={tablePageSize}
tableSource={source.map(trial => trial.tableRecord)}
columnList={columnList}
changeColumn={changeColumn}
trialsUpdateBroadcast={this.props.trialsUpdateBroadcast}
// TODO: change any to specific type
ref={(tabList): any => this.tableList = tabList}
/>
</div>
</div> </div>
); );
} }
......
...@@ -22,12 +22,16 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> { ...@@ -22,12 +22,16 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> {
<Stack.Item grow={3} className="padItem basic"> <Stack.Item grow={3} className="padItem basic">
<p>Name</p> <p>Name</p>
<div>{EXPERIMENT.profile.params.experimentName}</div> <div>{EXPERIMENT.profile.params.experimentName}</div>
</Stack.Item>
<Stack.Item grow={3} className="padItem basic">
<p>ID</p> <p>ID</p>
<div>{EXPERIMENT.profile.id}</div> <div>{EXPERIMENT.profile.id}</div>
</Stack.Item> </Stack.Item>
<Stack.Item grow={3} className="padItem basic"> <Stack.Item grow={3} className="padItem basic">
<p>Start time</p> <p>Start time</p>
<div className="nowrap">{formatTimestamp(EXPERIMENT.profile.startTime)}</div> <div className="nowrap">{formatTimestamp(EXPERIMENT.profile.startTime)}</div>
</Stack.Item>
<Stack.Item grow={3} className="padItem basic">
<p>End time</p> <p>End time</p>
<div className="nowrap">{formatTimestamp(EXPERIMENT.profile.endTime)}</div> <div className="nowrap">{formatTimestamp(EXPERIMENT.profile.endTime)}</div>
</Stack.Item> </Stack.Item>
...@@ -45,6 +49,8 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> { ...@@ -45,6 +49,8 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> {
{EXPERIMENT.profile.logDir || 'unknown'} {EXPERIMENT.profile.logDir || 'unknown'}
</TooltipHost> </TooltipHost>
</div> </div>
</Stack.Item>
<Stack.Item className="padItem basic">
<p>Training platform</p> <p>Training platform</p>
<div className="nowrap">{EXPERIMENT.profile.params.trainingServicePlatform}</div> <div className="nowrap">{EXPERIMENT.profile.params.trainingServicePlatform}</div>
</Stack.Item> </Stack.Item>
......
...@@ -4,7 +4,7 @@ import '../../static/style/overviewTitle.scss'; ...@@ -4,7 +4,7 @@ import '../../static/style/overviewTitle.scss';
interface Title1Props { interface Title1Props {
text: string; text: string;
icon?: string; icon?: string;
bgcolor?: string; fontColor?: string;
} }
class Title1 extends React.Component<Title1Props, {}> { class Title1 extends React.Component<Title1Props, {}> {
...@@ -14,11 +14,11 @@ class Title1 extends React.Component<Title1Props, {}> { ...@@ -14,11 +14,11 @@ class Title1 extends React.Component<Title1Props, {}> {
} }
render(): React.ReactNode { render(): React.ReactNode {
const { text, icon, bgcolor } = this.props; const { text, icon, fontColor } = this.props;
return ( return (
<Stack horizontal className="panelTitle" style={{ backgroundColor: bgcolor }}> <Stack horizontal className="panelTitle">
<img src={require(`../../static/img/icon/${icon}`)} alt="icon" /> <img src={require(`../../static/img/icon/${icon}`)} alt="icon" />
<span>{text}</span> <span style={{ color: fontColor }}>{text}</span>
</Stack> </Stack>
); );
} }
......
...@@ -3,7 +3,6 @@ import * as copy from 'copy-to-clipboard'; ...@@ -3,7 +3,6 @@ import * as copy from 'copy-to-clipboard';
import { Stack, PrimaryButton, Pivot, PivotItem } from 'office-ui-fabric-react'; import { Stack, PrimaryButton, Pivot, PivotItem } from 'office-ui-fabric-react';
import { Trial } from '../../static/model/trial'; import { Trial } from '../../static/model/trial';
import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { MANAGER_IP } from '../../static/const';
import JSONTree from 'react-json-tree'; import JSONTree from 'react-json-tree';
import PaiTrialLog from '../public-child/PaiTrialLog'; import PaiTrialLog from '../public-child/PaiTrialLog';
import TrialLog from '../public-child/TrialLog'; import TrialLog from '../public-child/TrialLog';
...@@ -60,31 +59,12 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -60,31 +59,12 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
const { isHidenInfo, typeInfo, info } = this.state; const { isHidenInfo, typeInfo, info } = this.state;
const trialId = this.props.trialId; const trialId = this.props.trialId;
const trial = TRIALS.getTrial(trialId); const trial = TRIALS.getTrial(trialId);
const trialLink: string = `${MANAGER_IP}/trial-jobs/${trialId}`;
const logPathRow = trial.info.logPath || 'This trial\'s log path is not available.'; const logPathRow = trial.info.logPath || 'This trial\'s log path is not available.';
const multiProgress = trial.info.hyperParameters === undefined ? 0 : trial.info.hyperParameters.length;
return ( return (
<Stack className="openRow"> <Stack className="openRow">
<Stack className="openRowContent"> <Stack className="openRowContent">
<Pivot> <Pivot>
<PivotItem headerText="Parameters" key="1" itemIcon="TestParameter"> <PivotItem headerText="Parameters" key="1" itemIcon="TestParameter">
{
EXPERIMENT.multiPhase
?
<Stack className="link">
{
`
Trails for multiphase experiment will return a set of parameters,
we are listing the latest parameter in webportal.
For the entire parameter set, please refer to the following "
`
}
<a href={trialLink} rel="noopener noreferrer" target="_blank">{trialLink}</a>{`".`}
<div>Current Phase: {multiProgress}.</div>
</Stack>
:
null
}
{ {
trial.info.hyperParameters !== undefined trial.info.hyperParameters !== undefined
? ?
......
...@@ -9,7 +9,7 @@ import { LineChart, blocked, copy } from '../Buttons/Icon'; ...@@ -9,7 +9,7 @@ import { LineChart, blocked, copy } from '../Buttons/Icon';
import { MANAGER_IP, COLUMNPro } from '../../static/const'; import { MANAGER_IP, COLUMNPro } from '../../static/const';
import { convertDuration, formatTimestamp, intermediateGraphOption, parseMetrics } from '../../static/function'; import { convertDuration, formatTimestamp, intermediateGraphOption, parseMetrics } from '../../static/function';
import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TableRecord } from '../../static/interface'; import { TableRecord, TrialJobInfo } from '../../static/interface';
import Details from '../overview/Details'; import Details from '../overview/Details';
import ChangeColumnComponent from '../Modals/ChangeColumnComponent'; import ChangeColumnComponent from '../Modals/ChangeColumnComponent';
import Compare from '../Modals/Compare'; import Compare from '../Modals/Compare';
...@@ -231,18 +231,23 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -231,18 +231,23 @@ class TableList extends React.Component<TableListProps, TableListState> {
) )
}; };
showIntermediateModal = async (id: string, event: React.SyntheticEvent<EventTarget>): Promise<void> => { showIntermediateModal = async (record: TrialJobInfo, event: React.SyntheticEvent<EventTarget>): Promise<void> => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const res = await axios.get(`${MANAGER_IP}/metric-data/${id}`); const res = await axios.get(`${MANAGER_IP}/metric-data/${record.jobId}`);
if (res.status === 200) { if (res.status === 200) {
const intermediateArr: number[] = []; const intermediateArr: number[] = [];
// support intermediate result is dict because the last intermediate result is // support intermediate result is dict because the last intermediate result is
// final result in a succeed trial, it may be a dict. // final result in a succeed trial, it may be a dict.
// get intermediate result dict keys array // get intermediate result dict keys array
const { intermediateKey } = this.state; const { intermediateKey } = this.state;
const otherkeys: string[] = [ ]; const otherkeys: string[] = [];
if (res.data.length !== 0) { // One trial job may contains multiple parameter id
// only show current trial's metric data
const metricDatas = res.data.filter(item => {
return item.parameterId == record.parameterId;
});
if (metricDatas.length !== 0) {
// just add type=number keys // just add type=number keys
const intermediateMetrics = parseMetrics(res.data[0].data); const intermediateMetrics = parseMetrics(res.data[0].data);
for (const key in intermediateMetrics) { for (const key in intermediateMetrics) {
...@@ -252,9 +257,10 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -252,9 +257,10 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
} }
// intermediateArr just store default val // intermediateArr just store default val
Object.keys(res.data).map(item => { metricDatas.map(item => {
if (res.data[item].type === 'PERIODICAL') {
const temp = parseMetrics(res.data[item].data); if (item.type === 'PERIODICAL') {
const temp = parseMetrics(item.data);
if (typeof temp === 'object') { if (typeof temp === 'object') {
intermediateArr.push(temp[intermediateKey]); intermediateArr.push(temp[intermediateKey]);
} else { } else {
...@@ -262,12 +268,12 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -262,12 +268,12 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
} }
}); });
const intermediate = intermediateGraphOption(intermediateArr, id); const intermediate = intermediateGraphOption(intermediateArr, record.id);
this.setState({ this.setState({
intermediateData: res.data, // store origin intermediate data for a trial intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate, intermediateOption: intermediate,
intermediateOtherKeys: otherkeys, intermediateOtherKeys: otherkeys,
intermediateId: id intermediateId: record.id
}); });
} }
this.setState({ modalVisible: true }); this.setState({ modalVisible: true });
...@@ -426,8 +432,6 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -426,8 +432,6 @@ class TableList extends React.Component<TableListProps, TableListState> {
// when user click [Add Column] need to use the function // when user click [Add Column] need to use the function
private initTableColumnList = (columnList: string[]): IColumn[] => { private initTableColumnList = (columnList: string[]): IColumn[] => {
// const { columnList } = this.props; // const { columnList } = this.props;
// [supportCustomizedTrial: true]
const supportCustomizedTrial = (EXPERIMENT.multiPhase === true) ? false : true;
const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status); const disabledAddCustomizedTrial = ['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status);
const showColumn: IColumn[] = []; const showColumn: IColumn[] = [];
for (const item of columnList) { for (const item of columnList) {
...@@ -479,7 +483,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -479,7 +483,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<PrimaryButton <PrimaryButton
className="detail-button-operation" className="detail-button-operation"
title="Intermediate" title="Intermediate"
onClick={this.showIntermediateModal.bind(this, record.id)} onClick={this.showIntermediateModal.bind(this, record)}
> >
{LineChart} {LineChart}
</PrimaryButton> </PrimaryButton>
...@@ -494,20 +498,14 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -494,20 +498,14 @@ class TableList extends React.Component<TableListProps, TableListState> {
<KillJob trial={record} /> <KillJob trial={record} />
} }
{/* Add a new trial-customized trial */} {/* Add a new trial-customized trial */}
{ <PrimaryButton
supportCustomizedTrial className="detail-button-operation"
? title="Customized trial"
<PrimaryButton onClick={this.setCustomizedTrial.bind(this, record.id)}
className="detail-button-operation" disabled={disabledAddCustomizedTrial}
title="Customized trial" >
onClick={this.setCustomizedTrial.bind(this, record.id)} {copy}
disabled={disabledAddCustomizedTrial} </PrimaryButton>
>
{copy}
</PrimaryButton>
:
null
}
</Stack> </Stack>
); );
}, },
...@@ -659,4 +657,4 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -659,4 +657,4 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
} }
export default TableList; export default TableList;
\ No newline at end of file
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_THRESHOLD = 100;
const METRIC_GROUP_UPDATE_SIZE = 20; const METRIC_GROUP_UPDATE_SIZE = 20;
const MANAGER_IP = `/api/v1/nni`; let MANAGER_IP = `/api/v1/nni`;
if (process.env.NODE_ENV == "development") {
MANAGER_IP = `//${window.location.hostname}:8080` + MANAGER_IP;
}
const DOWNLOAD_IP = `/logs`; const DOWNLOAD_IP = `/logs`;
const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html'; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html';
const trialJobStatus = [ const trialJobStatus = [
...@@ -34,8 +37,8 @@ const OPERATION = 'Operation'; ...@@ -34,8 +37,8 @@ const OPERATION = 'Operation';
const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', OPERATION]; const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', OPERATION];
// all choice column !dictory final // all choice column !dictory final
const COLUMNPro = ['Trial No.', 'ID', 'Start Time', 'End Time', 'Duration', 'Status', const COLUMNPro = ['Trial No.', 'ID', 'Start Time', 'End Time', 'Duration', 'Status',
'Intermediate result', 'Default', OPERATION]; 'Intermediate result', 'Default', OPERATION];
const CONCURRENCYTOOLTIP = 'Trial concurrency is the number of trials running concurrently.'; const CONCURRENCYTOOLTIP = 'Trial concurrency is the number of trials running concurrently.';
export { export {
MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro, WEBUIDOC, MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro, WEBUIDOC,
......
...@@ -18,6 +18,8 @@ interface TableRecord { ...@@ -18,6 +18,8 @@ interface TableRecord {
startTime: number; startTime: number;
endTime?: number; endTime?: number;
id: string; id: string;
jobId: string;
parameterId: string;
duration: number; duration: number;
status: string; status: string;
intermediateCount: number; intermediateCount: number;
...@@ -99,6 +101,7 @@ interface Intermedia { ...@@ -99,6 +101,7 @@ interface Intermedia {
interface MetricDataRecord { interface MetricDataRecord {
timestamp: number; timestamp: number;
trialJobId: string; trialJobId: string;
trialId: string;
parameterId: string; parameterId: string;
type: string; type: string;
sequence: number; sequence: number;
...@@ -107,6 +110,8 @@ interface MetricDataRecord { ...@@ -107,6 +110,8 @@ interface MetricDataRecord {
interface TrialJobInfo { interface TrialJobInfo {
id: string; id: string;
jobId: string;
parameterId: string;
sequenceId: number; sequenceId: number;
status: string; status: string;
startTime?: number; startTime?: number;
...@@ -126,7 +131,6 @@ interface ExperimentParams { ...@@ -126,7 +131,6 @@ interface ExperimentParams {
maxTrialNum: number; maxTrialNum: number;
searchSpace: string; searchSpace: string;
trainingServicePlatform: string; trainingServicePlatform: string;
multiPhase?: boolean;
multiThread?: boolean; multiThread?: boolean;
versionCheck?: boolean; versionCheck?: boolean;
logCollection?: string; logCollection?: string;
...@@ -189,4 +193,4 @@ export { ...@@ -189,4 +193,4 @@ export {
AccurPoint, DetailAccurPoint, TooltipForIntermediate, TooltipForAccuracy, AccurPoint, DetailAccurPoint, TooltipForIntermediate, TooltipForAccuracy,
Dimobj, ParaObj, Intermedia, MetricDataRecord, TrialJobInfo, ExperimentParams, Dimobj, ParaObj, Intermedia, MetricDataRecord, TrialJobInfo, ExperimentParams,
ExperimentProfile, NNIManagerStatus, EventMap ExperimentProfile, NNIManagerStatus, EventMap
}; };
\ No newline at end of file
...@@ -66,10 +66,6 @@ class Experiment { ...@@ -66,10 +66,6 @@ class Experiment {
return !!(this.profile.params.logCollection && this.profile.params.logCollection !== 'none'); return !!(this.profile.params.logCollection && this.profile.params.logCollection !== 'none');
} }
get multiPhase(): boolean {
return !!(this.profile.params.multiPhase);
}
get status(): string { get status(): string {
if (!this.statusField) { if (!this.statusField) {
throw Error('Experiment status not initialized'); throw Error('Experiment status not initialized');
......
...@@ -4,7 +4,7 @@ import { getFinal, formatAccuracy, metricAccuracy, parseMetrics, isArrayType } f ...@@ -4,7 +4,7 @@ import { getFinal, formatAccuracy, metricAccuracy, parseMetrics, isArrayType } f
class Trial implements TableObj { class Trial implements TableObj {
private metricsInitialized: boolean = false; private metricsInitialized: boolean = false;
private infoField: TrialJobInfo | undefined; private infoField: TrialJobInfo | undefined;
private intermediates: (MetricDataRecord | undefined)[] = [ ]; private intermediates: (MetricDataRecord | undefined)[] = [];
public final: MetricDataRecord | undefined; public final: MetricDataRecord | undefined;
private finalAcc: number | undefined; private finalAcc: number | undefined;
...@@ -29,7 +29,7 @@ class Trial implements TableObj { ...@@ -29,7 +29,7 @@ class Trial implements TableObj {
} }
get intermediateMetrics(): MetricDataRecord[] { get intermediateMetrics(): MetricDataRecord[] {
const ret: MetricDataRecord[] = [ ]; const ret: MetricDataRecord[] = [];
for (let i = 0; i < this.intermediates.length; i++) { for (let i = 0; i < this.intermediates.length; i++) {
if (this.intermediates[i]) { if (this.intermediates[i]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...@@ -80,6 +80,8 @@ class Trial implements TableObj { ...@@ -80,6 +80,8 @@ class Trial implements TableObj {
key: this.info.id, key: this.info.id,
sequenceId: this.info.sequenceId, sequenceId: this.info.sequenceId,
id: this.info.id, id: this.info.id,
jobId: this.info.jobId,
parameterId: this.info.parameterId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
startTime: this.info.startTime!, startTime: this.info.startTime!,
endTime: this.info.endTime, endTime: this.info.endTime,
...@@ -122,8 +124,8 @@ class Trial implements TableObj { ...@@ -122,8 +124,8 @@ class Trial implements TableObj {
get description(): Parameters { get description(): Parameters {
const ret: Parameters = { const ret: Parameters = {
parameters: { }, parameters: {},
intermediate: [ ], intermediate: [],
multiProgress: 1 multiProgress: 1
}; };
const tempHyper = this.info.hyperParameters; const tempHyper = this.info.hyperParameters;
...@@ -142,7 +144,7 @@ class Trial implements TableObj { ...@@ -142,7 +144,7 @@ class Trial implements TableObj {
ret.logPath = this.info.logPath; ret.logPath = this.info.logPath;
} }
const mediate: number[] = [ ]; const mediate: number[] = [];
for (const items of this.intermediateMetrics) { for (const items of this.intermediateMetrics) {
if (typeof parseMetrics(items.data) === 'object') { if (typeof parseMetrics(items.data) === 'object') {
mediate.push(parseMetrics(items.data).default); mediate.push(parseMetrics(items.data).default);
......
...@@ -6,13 +6,29 @@ import { Trial } from './trial'; ...@@ -6,13 +6,29 @@ import { Trial } from './trial';
function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> { function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> {
const ret = new Map<string, MetricDataRecord[]>(); const ret = new Map<string, MetricDataRecord[]>();
for (const metric of metrics) { for (const metric of metrics) {
if (ret.has(metric.trialJobId)) { const trialId = `${metric.trialJobId}-${metric.parameterId}`;
metric.trialId = trialId;
if (ret.has(trialId)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ret.get(metric.trialJobId)!.push(metric); ret.get(trialId)!.push(metric);
} else { } else {
ret.set(metric.trialJobId, [ metric ]); ret.set(trialId, [metric]);
} }
} }
// to compatiable with multi-trial in same job, fix offset of sequence
ret.forEach((trialMetrics) => {
let minSequenceNumber = Number.POSITIVE_INFINITY;
trialMetrics.map((item) => {
if (item.sequence < minSequenceNumber && item.type !== "FINAL") {
minSequenceNumber = item.sequence;
}
});
trialMetrics.map((item) => {
if (item.type !== "FINAL") {
item.sequence -= minSequenceNumber;
}
});
});
return ret; return ret;
} }
...@@ -31,7 +47,7 @@ class TrialManager { ...@@ -31,7 +47,7 @@ class TrialManager {
} }
public async update(lastTime?: boolean): Promise<boolean> { public async update(lastTime?: boolean): Promise<boolean> {
const [ infoUpdated, metricUpdated ] = await Promise.all([ this.updateInfo(), this.updateMetrics(lastTime) ]); const [infoUpdated, metricUpdated] = await Promise.all([this.updateInfo(), this.updateMetrics(lastTime)]);
return infoUpdated || metricUpdated; return infoUpdated || metricUpdated;
} }
...@@ -71,14 +87,14 @@ class TrialManager { ...@@ -71,14 +87,14 @@ class TrialManager {
public countStatus(): Map<string, number> { public countStatus(): Map<string, number> {
const cnt = new Map<string, number>([ const cnt = new Map<string, number>([
[ 'UNKNOWN', 0 ], ['UNKNOWN', 0],
[ 'WAITING', 0 ], ['WAITING', 0],
[ 'RUNNING', 0 ], ['RUNNING', 0],
[ 'SUCCEEDED', 0 ], ['SUCCEEDED', 0],
[ 'FAILED', 0 ], ['FAILED', 0],
[ 'USER_CANCELED', 0 ], ['USER_CANCELED', 0],
[ 'SYS_CANCELED', 0 ], ['SYS_CANCELED', 0],
[ 'EARLY_STOPPED', 0 ], ['EARLY_STOPPED', 0],
]); ]);
for (const trial of this.trials.values()) { for (const trial of this.trials.values()) {
if (trial.initialized()) { if (trial.initialized()) {
...@@ -89,19 +105,71 @@ class TrialManager { ...@@ -89,19 +105,71 @@ class TrialManager {
return cnt; return cnt;
} }
public static expandJobsToTrials(jobs: TrialJobInfo[]): TrialJobInfo[] {
const trials: TrialJobInfo[] = [];
for (const jobInfo of jobs as TrialJobInfo[]) {
if (jobInfo.hyperParameters) {
let trial: TrialJobInfo | undefined;
let lastTrial: TrialJobInfo | undefined;
for (let i = 0; i < jobInfo.hyperParameters.length; i++) {
const hyperParameters = jobInfo.hyperParameters[i]
const hpObject = JSON.parse(hyperParameters);
const parameterId = hpObject["parameter_id"];
trial = {
id: `${jobInfo.id}-${parameterId}`,
jobId: jobInfo.id,
parameterId: parameterId,
sequenceId: parameterId,
status: "SUCCEEDED",
startTime: jobInfo.startTime,
endTime: jobInfo.startTime,
hyperParameters: [hyperParameters],
logPath: jobInfo.logPath,
stderrPath: jobInfo.stderrPath,
};
if (jobInfo.finalMetricData) {
for (const metricData of jobInfo.finalMetricData) {
if (metricData.parameterId == parameterId) {
trial.finalMetricData = [metricData];
trial.endTime = metricData.timestamp;
break;
}
}
}
if (lastTrial) {
trial.startTime = lastTrial.endTime;
} else {
trial.startTime = jobInfo.startTime;
}
lastTrial = trial;
trials.push(trial);
}
if (lastTrial !== undefined) {
lastTrial.status = jobInfo.status;
lastTrial.endTime = jobInfo.endTime;
}
} else {
trials.push(jobInfo);
}
}
return trials;
}
private async updateInfo(): Promise<boolean> { private async updateInfo(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/trial-jobs`); const response = await axios.get(`${MANAGER_IP}/trial-jobs`);
let updated = false; let updated = false;
if (response.status === 200) { if (response.status === 200) {
for (const info of response.data as TrialJobInfo[]) { const newTrials = TrialManager.expandJobsToTrials(response.data);
if (this.trials.has(info.id)) { for (const trialInfo of newTrials as TrialJobInfo[]) {
if (this.trials.has(trialInfo.id)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
updated = this.trials.get(info.id)!.updateTrialJobInfo(info) || updated; updated = this.trials.get(trialInfo.id)!.updateTrialJobInfo(trialInfo) || updated;
} else { } else {
this.trials.set(info.id, new Trial(info, undefined)); this.trials.set(trialInfo.id, new Trial(trialInfo, undefined));
updated = true; updated = true;
} }
this.maxSequenceId = Math.max(this.maxSequenceId, info.sequenceId); this.maxSequenceId = Math.max(this.maxSequenceId, trialInfo.sequenceId);
} }
this.infoInitialized = true; this.infoInitialized = true;
} }
...@@ -146,7 +214,7 @@ class TrialManager { ...@@ -146,7 +214,7 @@ class TrialManager {
private doUpdateMetrics(allMetrics: MetricDataRecord[], latestOnly: boolean): boolean { private doUpdateMetrics(allMetrics: MetricDataRecord[], latestOnly: boolean): boolean {
let updated = false; let updated = false;
for (const [ trialId, metrics ] of groupMetricsByTrial(allMetrics).entries()) { for (const [trialId, metrics] of groupMetricsByTrial(allMetrics).entries()) {
if (this.trials.has(trialId)) { if (this.trials.has(trialId)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const trial = this.trials.get(trialId)!; const trial = this.trials.get(trialId)!;
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
padding: 15px 0; padding: 15px 0;
color: #212121; color: #212121;
width: 95%; width: 95%;
margin: 0 auto;
} }
.nowrap{ .nowrap{
......
$titleBgcolor: #b3b3b3;
$iconPaddingVal: 14px; $iconPaddingVal: 14px;
.overview .overviewBoder{
height: 100%;
border-right: 2px solid white;
}
.panelTitle{ .panelTitle{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 100%; width: 100%;
height: 38px; height: 38px;
padding: 0 $iconPaddingVal; padding: 4px $iconPaddingVal;
background: $titleBgcolor; box-sizing: border-box;
img{ img{
height: 22px; height: 22px;
/* (38 - 22 ) / 2 */ /* (38 - 22 ) / 2 */
...@@ -40,7 +34,6 @@ $iconPaddingVal: 14px; ...@@ -40,7 +34,6 @@ $iconPaddingVal: 14px;
} }
.top10bg{ .top10bg{
background-color: $titleBgcolor;
.top10Title{ .top10Title{
width: 160px; width: 160px;
......
$bg: #b3b3b3;
#tabsty{ #tabsty{
background-color: #fff;
.ms-Pivot{ .ms-Pivot{
.ms-Button{ .ms-Button{
padding: 0; padding: 0;
...@@ -7,13 +7,6 @@ $bg: #b3b3b3; ...@@ -7,13 +7,6 @@ $bg: #b3b3b3;
margin-right: 0; margin-right: 0;
border-right: 2px solid #fff; border-right: 2px solid #fff;
transition: 0.3s; transition: 0.3s;
&:hover{
background-color: $bg;
}
.ms-Button-flexContainer{
background-color: $bg;
}
} }
.ms-Pivot-link::before{ .ms-Pivot-link::before{
...@@ -45,10 +38,7 @@ $bg: #b3b3b3; ...@@ -45,10 +38,7 @@ $bg: #b3b3b3;
/* graph, title total height */ /* graph, title total height */
width: 100%; width: 100%;
height: 500px; height: 500px;
/* graph all title bg*/
.ms-FocusZone{
background-color: $bg;
}
.graph{ .graph{
height: 432px; height: 432px;
margin: 0 auto; margin: 0 auto;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment