"examples/vscode:/vscode.git/clone" did not exist on "225aca612e7e175a2c9ea4f73ba4140005a29759"
Unverified Commit 3e62e60b authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Refactor web UI to support incremental metric loading (#1557)

* Refactor web UI to support incremental metric loading

* refactor

* Remove host job

* Move sequence ID to NNI manager

* implement incremental loading
parent 99f7d79c
...@@ -22,8 +22,6 @@ interface DurationState { ...@@ -22,8 +22,6 @@ interface DurationState {
class Duration extends React.Component<DurationProps, DurationState> { class Duration extends React.Component<DurationProps, DurationState> {
public _isMounted = false;
constructor(props: DurationProps) { constructor(props: DurationProps) {
super(props); super(props);
...@@ -142,15 +140,12 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -142,15 +140,12 @@ class Duration extends React.Component<DurationProps, DurationState> {
trialId: trialId, trialId: trialId,
trialTime: trialTime trialTime: trialTime
}); });
if (this._isMounted) { this.setState({
this.setState({ durationSource: this.getOption(trialRun[0])
durationSource: this.getOption(trialRun[0]) });
});
}
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
const { source } = this.props; const { source } = this.props;
this.drawDurationGraph(source); this.drawDurationGraph(source);
} }
...@@ -187,10 +182,6 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -187,10 +182,6 @@ class Duration extends React.Component<DurationProps, DurationState> {
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { durationSource } = this.state; const { durationSource } = this.state;
return ( return (
...@@ -206,4 +197,4 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -206,4 +197,4 @@ class Duration extends React.Component<DurationProps, DurationState> {
} }
} }
export default Duration; export default Duration;
\ No newline at end of file
...@@ -24,7 +24,6 @@ interface IntermediateProps { ...@@ -24,7 +24,6 @@ interface IntermediateProps {
class Intermediate extends React.Component<IntermediateProps, IntermediateState> { class Intermediate extends React.Component<IntermediateProps, IntermediateState> {
static intervalMediate = 1; static intervalMediate = 1;
public _isMounted = false;
public pointInput: HTMLInputElement | null; public pointInput: HTMLInputElement | null;
public minValInput: HTMLInputElement | null; public minValInput: HTMLInputElement | null;
public maxValInput: HTMLInputElement | null; public maxValInput: HTMLInputElement | null;
...@@ -45,12 +44,10 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -45,12 +44,10 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
drawIntermediate = (source: Array<TableObj>) => { drawIntermediate = (source: Array<TableObj>) => {
if (source.length > 0) { if (source.length > 0) {
if (this._isMounted) { this.setState({
this.setState(() => ({ length: source.length,
length: source.length, detailSource: source
detailSource: source });
}));
}
const trialIntermediate: Array<Intermedia> = []; const trialIntermediate: Array<Intermedia> = [];
Object.keys(source).map(item => { Object.keys(source).map(item => {
const temp = source[item]; const temp = source[item];
...@@ -118,11 +115,9 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -118,11 +115,9 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
}, },
series: trialIntermediate series: trialIntermediate
}; };
if (this._isMounted) { this.setState({
this.setState(() => ({ interSource: option
interSource: option });
}));
}
} else { } else {
const nullData = { const nullData = {
grid: { grid: {
...@@ -139,71 +134,60 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -139,71 +134,60 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
name: 'Metric' name: 'Metric'
} }
}; };
if (this._isMounted) { this.setState({ interSource: nullData });
this.setState(() => ({ interSource: nullData }));
}
} }
} }
// confirm btn function [filter data] // confirm btn function [filter data]
filterLines = () => { filterLines = () => {
if (this._isMounted) { const filterSource: Array<TableObj> = [];
const filterSource: Array<TableObj> = []; this.setState({ isLoadconfirmBtn: true }, () => {
this.setState({ isLoadconfirmBtn: true }, () => { const { source } = this.props;
const { source } = this.props; // get input value
// get input value const pointVal = this.pointInput !== null ? this.pointInput.value : '';
const pointVal = this.pointInput !== null ? this.pointInput.value : ''; const minVal = this.minValInput !== null ? this.minValInput.value : '';
const minVal = this.minValInput !== null ? this.minValInput.value : ''; const maxVal = this.maxValInput !== null ? this.maxValInput.value : '';
const maxVal = this.maxValInput !== null ? this.maxValInput.value : ''; // user not input message
// user not input message if (pointVal === '' || minVal === '') {
if (pointVal === '' || minVal === '') { alert('Please input filter message');
alert('Please input filter message'); } else {
// user not input max value
const position = JSON.parse(pointVal);
const min = JSON.parse(minVal);
if (maxVal === '') {
Object.keys(source).map(item => {
const temp = source[item];
const val = temp.description.intermediate[position - 1];
if (val >= min) {
filterSource.push(temp);
}
});
} else { } else {
// user not input max value const max = JSON.parse(maxVal);
const position = JSON.parse(pointVal); Object.keys(source).map(item => {
const min = JSON.parse(minVal); const temp = source[item];
if (maxVal === '') { const val = temp.description.intermediate[position - 1];
Object.keys(source).map(item => { if (val >= min && val <= max) {
const temp = source[item]; filterSource.push(temp);
const val = temp.description.intermediate[position - 1]; }
if (val >= min) { });
filterSource.push(temp);
}
});
} else {
const max = JSON.parse(maxVal);
Object.keys(source).map(item => {
const temp = source[item];
const val = temp.description.intermediate[position - 1];
if (val >= min && val <= max) {
filterSource.push(temp);
}
});
}
if (this._isMounted) {
this.setState({ filterSource: filterSource });
}
this.drawIntermediate(filterSource);
}
const counts = this.state.clickCounts + 1;
if (this._isMounted) {
this.setState({ isLoadconfirmBtn: false, clickCounts: counts });
} }
}); this.setState({ filterSource: filterSource });
} this.drawIntermediate(filterSource);
}
const counts = this.state.clickCounts + 1;
this.setState({ isLoadconfirmBtn: false, clickCounts: counts });
});
} }
switchTurn = (checked: boolean) => { switchTurn = (checked: boolean) => {
if (this._isMounted) { this.setState({ isFilter: checked });
this.setState({ isFilter: checked });
}
if (checked === false) { if (checked === false) {
this.drawIntermediate(this.props.source); this.drawIntermediate(this.props.source);
} }
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
const { source } = this.props; const { source } = this.props;
this.drawIntermediate(source); this.drawIntermediate(source);
} }
...@@ -272,10 +256,6 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -272,10 +256,6 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { interSource, isLoadconfirmBtn, isFilter } = this.state; const { interSource, isLoadconfirmBtn, isFilter } = this.state;
return ( return (
......
...@@ -40,8 +40,6 @@ message.config({ ...@@ -40,8 +40,6 @@ message.config({
class Para extends React.Component<ParaProps, ParaState> { class Para extends React.Component<ParaProps, ParaState> {
public _isMounted = false;
private chartMulineStyle = { private chartMulineStyle = {
width: '100%', width: '100%',
height: 392, height: 392,
...@@ -121,15 +119,12 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -121,15 +119,12 @@ class Para extends React.Component<ParaProps, ParaState> {
this.swapGraph(paraData, swapAxisArr); this.swapGraph(paraData, swapAxisArr);
} }
this.getOption(paraData, lengthofTrials); this.getOption(paraData, lengthofTrials);
if (this._isMounted === true) { this.setState({ paraBack: paraData });
this.setState(() => ({ paraBack: paraData }));
}
} }
hyperParaPic = (source: Array<TableObj>, searchSpace: string) => { hyperParaPic = (source: Array<TableObj>, searchSpace: string) => {
// filter succeed trials [{}, {}, {}] // filter succeed trials [{}, {}, {}]
const origin = source.filter(filterByStatus); const dataSource = source.filter(filterByStatus);
const dataSource: Array<TableObj> = JSON.parse(JSON.stringify(origin));
const lenOfDataSource: number = dataSource.length; const lenOfDataSource: number = dataSource.length;
const accPara: Array<number> = []; const accPara: Array<number> = [];
// specific value array // specific value array
...@@ -139,15 +134,13 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -139,15 +134,13 @@ class Para extends React.Component<ParaProps, ParaState> {
// nest search space // nest search space
let isNested: boolean = false; let isNested: boolean = false;
Object.keys(searchRange).map(item => { Object.keys(searchRange).map(item => {
if (typeof searchRange[item]._value[0] === 'object') { if (searchRange[item]._value && typeof searchRange[item]._value[0] === 'object') {
isNested = true; isNested = true;
return; return;
} }
}); });
const dimName = Object.keys(searchRange); const dimName = Object.keys(searchRange);
if (this._isMounted === true) { this.setState({ dimName: dimName });
this.setState(() => ({ dimName: dimName }));
}
const parallelAxis: Array<Dimobj> = []; const parallelAxis: Array<Dimobj> = [];
// search space range and specific value [only number] // search space range and specific value [only number]
...@@ -324,23 +317,21 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -324,23 +317,21 @@ class Para extends React.Component<ParaProps, ParaState> {
color: ['#CA0000', '#FFC400', '#90EE90'] color: ['#CA0000', '#FFC400', '#90EE90']
} }
}; };
if (this._isMounted === true) { this.setState({
this.setState({ paraNodata: 'No data',
paraNodata: 'No data', option: optionOfNull,
option: optionOfNull, sutrialCount: 0,
sutrialCount: 0, succeedRenderCount: 0
succeedRenderCount: 0 });
});
}
} else { } else {
Object.keys(dataSource).map(item => { Object.keys(dataSource).map(item => {
const temp = dataSource[item]; const trial = dataSource[item];
eachTrialParams.push(temp.description.parameters); eachTrialParams.push(trial.description.parameters.error || '');
// may be a succeed trial hasn't final result // may be a succeed trial hasn't final result
// all detail page may be break down if havn't if // all detail page may be break down if havn't if
if (temp.acc !== undefined) { if (trial.acc !== undefined) {
if (temp.acc.default !== undefined) { if (trial.acc.default !== undefined) {
accPara.push(temp.acc.default); accPara.push(JSON.parse(trial.acc.default));
} }
} }
}); });
...@@ -361,14 +352,12 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -361,14 +352,12 @@ class Para extends React.Component<ParaProps, ParaState> {
}); });
}); });
} }
if (this._isMounted) { // if not return final result
// if not return final result const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara);
const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara); const minVal = accPara.length === 0 ? 1 : Math.min(...accPara);
const minVal = accPara.length === 0 ? 1 : Math.min(...accPara); this.setState({ max: maxVal, min: minVal }, () => {
this.setState({ max: maxVal, min: minVal }, () => { this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource);
this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource); });
});
}
} }
} }
...@@ -376,11 +365,9 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -376,11 +365,9 @@ class Para extends React.Component<ParaProps, ParaState> {
percentNum = (value: string) => { percentNum = (value: string) => {
let vals = parseFloat(value); let vals = parseFloat(value);
if (this._isMounted) { this.setState({ percent: vals }, () => {
this.setState({ percent: vals }, () => { this.reInit();
this.reInit(); });
});
}
} }
// deal with response data into pic data // deal with response data into pic data
...@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
}; };
// please wait the data // please wait the data
if (this._isMounted) { this.setState({
this.setState(() => ({ option: optionown,
option: optionown, paraNodata: '',
paraNodata: '', succeedRenderCount: lengthofTrials,
succeedRenderCount: lengthofTrials, sutrialCount: paralleData.length
sutrialCount: paralleData.length });
}));
}
} }
// get swap parallel axis // get swap parallel axis
getSwapArr = (value: Array<string>) => { getSwapArr = (value: Array<string>) => {
this.setState({ swapAxisArr: value });
if (this._isMounted) {
this.setState(() => ({ swapAxisArr: value }));
}
} }
reInit = () => { reInit = () => {
...@@ -471,9 +453,7 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -471,9 +453,7 @@ class Para extends React.Component<ParaProps, ParaState> {
swapReInit = () => { swapReInit = () => {
const { clickCounts, succeedRenderCount } = this.state; const { clickCounts, succeedRenderCount } = this.state;
const val = clickCounts + 1; const val = clickCounts + 1;
if (this._isMounted) { this.setState({ isLoadConfirm: true, clickCounts: val, });
this.setState({ isLoadConfirm: true, clickCounts: val, });
}
const { paraBack, swapAxisArr } = this.state; const { paraBack, swapAxisArr } = this.state;
const paralDim = paraBack.parallelAxis; const paralDim = paraBack.parallelAxis;
const paraData = paraBack.data; const paraData = paraBack.data;
...@@ -523,11 +503,9 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -523,11 +503,9 @@ class Para extends React.Component<ParaProps, ParaState> {
}); });
this.getOption(paraBack, succeedRenderCount); this.getOption(paraBack, succeedRenderCount);
// please wait the data // please wait the data
if (this._isMounted) { this.setState({
this.setState(() => ({ isLoadConfirm: false
isLoadConfirm: false });
}));
}
} }
sortDimY = (a: Dimobj, b: Dimobj) => { sortDimY = (a: Dimobj, b: Dimobj) => {
...@@ -585,7 +563,6 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -585,7 +563,6 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
this.reInit(); this.reInit();
} }
...@@ -623,10 +600,6 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -623,10 +600,6 @@ class Para extends React.Component<ParaProps, ParaState> {
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { option, paraNodata, dimName, isLoadConfirm } = this.state; const { option, paraNodata, dimName, isLoadConfirm } = this.state;
return ( return (
...@@ -687,4 +660,4 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -687,4 +660,4 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
} }
export default Para; export default Para;
\ No newline at end of file
...@@ -2,14 +2,14 @@ import * as React from 'react'; ...@@ -2,14 +2,14 @@ import * as React from 'react';
import axios from 'axios'; import axios from 'axios';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select, Icon } from 'antd'; import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select, Icon } from 'antd';
import { ColumnProps } from 'antd/lib/table';
const Option = Select.Option; const Option = Select.Option;
const CheckboxGroup = Checkbox.Group; const CheckboxGroup = Checkbox.Group;
import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const'; import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const';
import { convertDuration, intermediateGraphOption, killJob, filterByStatus } from '../../static/function'; import { convertDuration, formatTimestamp, intermediateGraphOption, killJob } from '../../static/function';
import { TableObj, TrialJob } from '../../static/interface'; import { TableRecord } from '../../static/interface';
import OpenRow from '../public-child/OpenRow'; import OpenRow from '../public-child/OpenRow';
import Compare from '../Modal/Compare'; import Compare from '../Modal/Compare';
import IntermediateVal from '../public-child/IntermediateVal'; // table default metric column
import '../../static/style/search.scss'; import '../../static/style/search.scss';
require('../../static/style/tableStatus.css'); require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss'); require('../../static/style/logPath.scss');
...@@ -26,14 +26,11 @@ echarts.registerTheme('my_theme', { ...@@ -26,14 +26,11 @@ echarts.registerTheme('my_theme', {
}); });
interface TableListProps { interface TableListProps {
entries: number; pageSize: number;
tableSource: Array<TableObj>; tableSource: Array<TableRecord>;
updateList: Function;
platform: string;
logCollection: boolean;
isMultiPhase: boolean;
columnList: Array<string>; // user select columnKeys columnList: Array<string>; // user select columnKeys
changeColumn: (val: Array<string>) => void; changeColumn: (val: Array<string>) => void;
trialsUpdateBroadcast: number;
} }
interface TableListState { interface TableListState {
...@@ -41,7 +38,7 @@ interface TableListState { ...@@ -41,7 +38,7 @@ interface TableListState {
modalVisible: boolean; modalVisible: boolean;
isObjFinal: boolean; isObjFinal: boolean;
isShowColumn: boolean; isShowColumn: boolean;
selectRows: Array<TableObj>; selectRows: Array<TableRecord>;
isShowCompareModal: boolean; isShowCompareModal: boolean;
selectedRowKeys: string[] | number[]; selectedRowKeys: string[] | number[];
intermediateData: Array<object>; // a trial's intermediate results (include dict) intermediateData: Array<object>; // a trial's intermediate results (include dict)
...@@ -56,10 +53,9 @@ interface ColumnIndex { ...@@ -56,10 +53,9 @@ interface ColumnIndex {
class TableList extends React.Component<TableListProps, TableListState> { class TableList extends React.Component<TableListProps, TableListState> {
public _isMounted = false;
public intervalTrialLog = 10; public intervalTrialLog = 10;
public _trialId: string; public _trialId: string;
public tables: Table<TableObj> | null; public tables: Table<TableRecord> | null;
constructor(props: TableListProps) { constructor(props: TableListProps) {
super(props); super(props);
...@@ -78,46 +74,35 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -78,46 +74,35 @@ class TableList extends React.Component<TableListProps, TableListState> {
}; };
} }
showIntermediateModal = (id: string) => { showIntermediateModal = async (id: string) => {
const res = await axios.get(`${MANAGER_IP}/metric-data/${id}`);
axios(`${MANAGER_IP}/metric-data/${id}`, { if (res.status === 200) {
method: 'GET' const intermediateArr: number[] = [];
}) // support intermediate result is dict because the last intermediate result is
.then(res => { // final result in a succeed trial, it may be a dict.
if (res.status === 200) { // get intermediate result dict keys array
const intermediateArr: number[] = []; let otherkeys: Array<string> = ['default'];
// support intermediate result is dict because the last intermediate result is if (res.data.length !== 0) {
// final result in a succeed trial, it may be a dict. otherkeys = Object.keys(JSON.parse(res.data[0].data));
// get intermediate result dict keys array }
let otherkeys: Array<string> = ['default']; // intermediateArr just store default val
if (res.data.length !== 0) { Object.keys(res.data).map(item => {
otherkeys = Object.keys(JSON.parse(res.data[0].data)); const temp = JSON.parse(res.data[item].data);
} if (typeof temp === 'object') {
// intermediateArr just store default val intermediateArr.push(temp.default);
Object.keys(res.data).map(item => { } else {
const temp = JSON.parse(res.data[item].data); intermediateArr.push(temp);
if (typeof temp === 'object') {
intermediateArr.push(temp.default);
} else {
intermediateArr.push(temp);
}
});
const intermediate = intermediateGraphOption(intermediateArr, id);
if (this._isMounted) {
this.setState(() => ({
intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate,
intermediateOtherKeys: otherkeys,
intermediateId: id
}));
}
} }
}); });
if (this._isMounted) { const intermediate = intermediateGraphOption(intermediateArr, id);
this.setState({ this.setState({
modalVisible: true intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate,
intermediateOtherKeys: otherkeys,
intermediateId: id
}); });
} }
this.setState({ modalVisible: true });
} }
// intermediate button click -> intermediate graph for each trial // intermediate button click -> intermediate graph for each trial
...@@ -147,37 +132,29 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -147,37 +132,29 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
const intermediate = intermediateGraphOption(intermediateArr, intermediateId); const intermediate = intermediateGraphOption(intermediateArr, intermediateId);
// re-render // re-render
if (this._isMounted) { this.setState({
this.setState(() => ({ intermediateOption: intermediate
intermediateOption: intermediate });
}));
}
} }
hideIntermediateModal = () => { hideIntermediateModal = () => {
if (this._isMounted) { this.setState({
this.setState({ modalVisible: false
modalVisible: false });
});
}
} }
hideShowColumnModal = () => { hideShowColumnModal = () => {
if (this._isMounted) { this.setState({
this.setState({ isShowColumn: false
isShowColumn: false });
});
}
} }
// click add column btn, just show the modal of addcolumn // click add column btn, just show the modal of addcolumn
addColumn = () => { addColumn = () => {
// show user select check button // show user select check button
if (this._isMounted) { this.setState({
this.setState({ isShowColumn: true
isShowColumn: true });
});
}
} }
// checkbox for coloumn // checkbox for coloumn
...@@ -191,8 +168,8 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -191,8 +168,8 @@ class TableList extends React.Component<TableListProps, TableListState> {
switch (checkedValues[m]) { switch (checkedValues[m]) {
case 'Trial No.': case 'Trial No.':
case 'ID': case 'ID':
case 'StartTime': case 'Start Time':
case 'EndTime': case 'End Time':
case 'Duration': case 'Duration':
case 'Status': case 'Status':
case 'Operation': case 'Operation':
...@@ -229,27 +206,17 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -229,27 +206,17 @@ class TableList extends React.Component<TableListProps, TableListState> {
wantResult.push(want[i].name); wantResult.push(want[i].name);
}); });
if (this._isMounted) { this.props.changeColumn(wantResult);
this.props.changeColumn(wantResult);
}
} }
openRow = (record: TableObj) => { openRow = (record: TableRecord) => {
const { platform, logCollection, isMultiPhase } = this.props;
return ( return (
<OpenRow <OpenRow trialId={record.id} />
trainingPlatform={platform}
record={record}
logCollection={logCollection}
multiphase={isMultiPhase}
/>
); );
} }
fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableObj>) => { fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableRecord>) => {
if (this._isMounted === true) { this.setState({ selectRows: selectedRows, selectedRowKeys: selected });
this.setState(() => ({ selectRows: selectedRows, selectedRowKeys: selected }));
}
} }
// open Compare-modal // open Compare-modal
compareBtn = () => { compareBtn = () => {
...@@ -258,47 +225,33 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -258,47 +225,33 @@ class TableList extends React.Component<TableListProps, TableListState> {
if (selectRows.length === 0) { if (selectRows.length === 0) {
alert('Please select datas you want to compare!'); alert('Please select datas you want to compare!');
} else { } else {
if (this._isMounted === true) { this.setState({ isShowCompareModal: true });
this.setState({ isShowCompareModal: true });
}
} }
} }
// close Compare-modal // close Compare-modal
hideCompareModal = () => { hideCompareModal = () => {
// close modal. clear select rows data, clear selected track // close modal. clear select rows data, clear selected track
if (this._isMounted) {
this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] }); this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
}
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
} }
render() { render() {
const { pageSize, columnList } = this.props;
const { entries, tableSource, updateList, columnList } = this.props; const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
console.log('rerender table', tableSource);
const { intermediateOption, modalVisible, isShowColumn, const { intermediateOption, modalVisible, isShowColumn,
selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state; selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state;
const rowSelection = { const rowSelection = {
selectedRowKeys: selectedRowKeys, selectedRowKeys: selectedRowKeys,
onChange: (selected: string[] | number[], selectedRows: Array<TableObj>) => { onChange: (selected: string[] | number[], selectedRows: Array<TableRecord>) => {
this.fillSelectedRowsTostate(selected, selectedRows); this.fillSelectedRowsTostate(selected, selectedRows);
} }
}; };
let showTitle = COLUMNPro; let showTitle = COLUMNPro;
let bgColor = '';
const trialJob: Array<TrialJob> = [];
const showColumn: Array<object> = []; const showColumn: Array<object> = [];
// only succeed trials have final keys // only succeed trials have final keys
if (tableSource.filter(filterByStatus).length >= 1) { if (tableSource.filter(record => record.status === 'SUCCEEDED').length >= 1) {
const temp = tableSource.filter(filterByStatus)[0].acc; const temp = tableSource.filter(record => record.status === 'SUCCEEDED')[0].accuracy;
if (temp !== undefined && typeof temp === 'object') { if (temp !== undefined && typeof temp === 'object') {
if (this._isMounted) {
// concat default column and finalkeys // concat default column and finalkeys
const item = Object.keys(temp); const item = Object.keys(temp);
// item: ['default', 'other-keys', 'maybe loss'] // item: ['default', 'other-keys', 'maybe loss']
...@@ -311,169 +264,33 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -311,169 +264,33 @@ class TableList extends React.Component<TableListProps, TableListState> {
}); });
showTitle = COLUMNPro.concat(want); showTitle = COLUMNPro.concat(want);
} }
}
} }
} }
trialJobStatus.map(item => { for (const item of columnList) {
trialJob.push({
text: item,
value: item
});
});
Object.keys(columnList).map(key => {
const item = columnList[key];
switch (item) { switch (item) {
case 'Trial No.': case 'Trial No.':
showColumn.push({ showColumn.push(SequenceIdColumnConfig);
title: 'Trial No.',
dataIndex: 'sequenceId',
key: 'sequenceId',
width: 120,
className: 'tableHead',
sorter: (a: TableObj, b: TableObj) => (a.sequenceId as number) - (b.sequenceId as number)
});
break; break;
case 'ID': case 'ID':
showColumn.push({ showColumn.push(IdColumnConfig);
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 60,
className: 'tableHead leftTitle',
// the sort of string
sorter: (a: TableObj, b: TableObj): number => a.id.localeCompare(b.id),
render: (text: string, record: TableObj) => {
return (
<div>{record.id}</div>
);
}
});
break; break;
case 'StartTime': case 'Start Time':
showColumn.push({ showColumn.push(StartTimeColumnConfig);
title: 'StartTime',
dataIndex: 'startTime',
key: 'startTime',
width: 160,
render: (text: string, record: TableObj) => {
const start = record.startTime !== undefined ? record.startTime : -1;
return (
<span>
{
start !== -1
?
new Date(start).toLocaleString('en-US')
:
'--'
}
</span>
);
},
});
break; break;
case 'EndTime': case 'End Time':
showColumn.push({ showColumn.push(EndTimeColumnConfig);
title: 'EndTime',
dataIndex: 'endTime',
key: 'endTime',
width: 160,
render: (text: string, record: TableObj) => {
const end = record.endTime !== undefined ? record.endTime : -1;
return (
<span>
{
end !== -1
?
new Date(end).toLocaleString('en-US')
:
'--'
}
</span>
);
},
});
break; break;
case 'Duration': case 'Duration':
showColumn.push({ showColumn.push(DurationColumnConfig);
title: 'Duration',
dataIndex: 'duration',
key: 'duration',
width: 100,
// the sort of number
sorter: (a: TableObj, b: TableObj) => (a.duration as number) - (b.duration as number),
render: (text: string, record: TableObj) => {
let duration;
if (record.duration !== undefined) {
// duration is nagative number(-1) & 0-1
if (record.duration > 0 && record.duration < 1 || record.duration < 0) {
duration = `${record.duration}s`;
} else {
duration = convertDuration(record.duration);
}
} else {
duration = 0;
}
return (
<div className="durationsty"><div>{duration}</div></div>
);
},
});
break; break;
case 'Status': case 'Status':
showColumn.push({ showColumn.push(StatusColumnConfig);
title: 'Status',
dataIndex: 'status',
key: 'status',
width: 150,
className: 'tableStatus',
render: (text: string, record: TableObj) => {
bgColor = record.status;
return (
<span className={`${bgColor} commonStyle`}>{record.status}</span>
);
},
filters: trialJob,
onFilter: (value: string, record: TableObj) => {
return record.status.indexOf(value) === 0;
},
// onFilter: (value: string, record: TableObj) => record.status.indexOf(value) === 0,
sorter: (a: TableObj, b: TableObj): number => a.status.localeCompare(b.status)
});
break; break;
case 'Intermediate count': case 'Intermediate count':
showColumn.push({ showColumn.push(IntermediateCountColumnConfig);
title: 'Intermediate count',
dataIndex: 'progress',
key: 'progress',
width: 86,
render: (text: string, record: TableObj) => {
return (
<span>{`#${record.description.intermediate.length}`}</span>
);
},
});
break; break;
case 'Default': case 'Default':
showColumn.push({ showColumn.push(AccuracyColumnConfig);
title: 'Default metric',
className: 'leftTitle',
dataIndex: 'acc',
key: 'acc',
width: 120,
sorter: (a: TableObj, b: TableObj) => {
const oneArr = a.description.intermediate;
const otherArr = b.description.intermediate;
const one = (oneArr[oneArr.length - 1] !== undefined) ? oneArr[oneArr.length - 1] : 0;
const other = (otherArr[otherArr.length - 1] !== undefined)
? otherArr[otherArr.length - 1] : 0;
return one - other;
},
render: (text: string, record: TableObj) => {
return (
<IntermediateVal record={record} />
);
}
});
break; break;
case 'Operation': case 'Operation':
showColumn.push({ showColumn.push({
...@@ -481,7 +298,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -481,7 +298,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
dataIndex: 'operation', dataIndex: 'operation',
key: 'operation', key: 'operation',
width: 120, width: 120,
render: (text: string, record: TableObj) => { render: (text: string, record: TableRecord) => {
let trialStatus = record.status; let trialStatus = record.status;
const flag: boolean = (trialStatus === 'RUNNING') ? false : true; const flag: boolean = (trialStatus === 'RUNNING') ? false : true;
return ( return (
...@@ -499,7 +316,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -499,7 +316,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<Popconfirm <Popconfirm
title="Are you sure to cancel this trial?" title="Are you sure to cancel this trial?"
onConfirm={killJob. onConfirm={killJob.
bind(this, record.key, record.id, record.status, updateList)} bind(this, record.key, record.id, record.status)}
> >
<Button <Button
type="default" type="default"
...@@ -522,7 +339,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -522,7 +339,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
dataIndex: 'intermediate', dataIndex: 'intermediate',
key: 'intermediate', key: 'intermediate',
width: '16%', width: '16%',
render: (text: string, record: TableObj) => { render: (text: string, record: TableRecord) => {
return ( return (
<Button <Button
type="primary" type="primary"
...@@ -535,47 +352,24 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -535,47 +352,24 @@ class TableList extends React.Component<TableListProps, TableListState> {
}, },
}); });
break; break;
default: default:
showColumn.push({ // FIXME
title: item, alert('Unexpected column type');
dataIndex: item,
key: item,
width: 150,
render: (text: string, record: TableObj) => {
const temp = record.acc;
let decimals = 0;
let other = '';
if (temp !== undefined) {
if (temp[item].toString().indexOf('.') !== -1) {
decimals = temp[item].toString().length - temp[item].toString().indexOf('.') - 1;
if (decimals > 6) {
other = `${temp[item].toFixed(6)}`;
} else {
other = temp[item].toString();
}
}
} else {
other = '--';
}
return (
<div>{other}</div>
);
}
});
} }
}); }
return ( return (
<Row className="tableList"> <Row className="tableList">
<div id="tableList"> <div id="tableList">
<Table <Table
ref={(table: Table<TableObj> | null) => this.tables = table} ref={(table: Table<TableRecord> | null) => this.tables = table}
columns={showColumn} columns={showColumn}
rowSelection={rowSelection} rowSelection={rowSelection}
expandedRowRender={this.openRow} expandedRowRender={this.openRow}
dataSource={tableSource} dataSource={tableSource}
className="commonTableStyle" className="commonTableStyle"
pagination={{ pageSize: entries }} pagination={pageSize > 0 ? { pageSize } : false}
/> />
{/* Intermediate Result Modal */} {/* Intermediate Result Modal */}
<Modal <Modal
...@@ -640,4 +434,93 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -640,4 +434,93 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
} }
const SequenceIdColumnConfig: ColumnProps<TableRecord> = {
title: 'Trial No.',
dataIndex: 'sequenceId',
width: 120,
className: 'tableHead',
sorter: (a, b) => a.sequenceId - b.sequenceId
};
const IdColumnConfig: ColumnProps<TableRecord> = {
title: 'ID',
dataIndex: 'id',
width: 60,
className: 'tableHead leftTitle',
sorter: (a, b) => a.id.localeCompare(b.id),
render: (text, record) => (
<div>{record.id}</div>
)
};
const StartTimeColumnConfig: ColumnProps<TableRecord> = {
title: 'Start Time',
dataIndex: 'startTime',
width: 160,
render: (text, record) => (
<span>{formatTimestamp(record.startTime)}</span>
)
};
const EndTimeColumnConfig: ColumnProps<TableRecord> = {
title: 'End Time',
dataIndex: 'endTime',
width: 160,
render: (text, record) => (
<span>{formatTimestamp(record.endTime, '--')}</span>
)
};
const DurationColumnConfig: ColumnProps<TableRecord> = {
title: 'Duration',
dataIndex: 'duration',
width: 100,
sorter: (a, b) => a.duration - b.duration,
render: (text, record) => (
<div className="durationsty"><div>{convertDuration(record.duration)}</div></div>
)
};
const StatusColumnConfig: ColumnProps<TableRecord> = {
title: 'Status',
dataIndex: 'status',
width: 150,
className: 'tableStatus',
render: (text, record) => (
<span className={`${record.status} commonStyle`}>{record.status}</span>
),
sorter: (a, b) => a.status.localeCompare(b.status),
filters: trialJobStatus.map(status => ({ text: status, value: status })),
onFilter: (value, record) => (record.status === value)
};
const IntermediateCountColumnConfig: ColumnProps<TableRecord> = {
title: 'Intermediate count',
dataIndex: 'intermediateCount',
width: 86,
render: (text, record) => (
<span>{`#${record.intermediateCount}`}</span>
)
};
const AccuracyColumnConfig: ColumnProps<TableRecord> = {
title: 'Default metric',
className: 'leftTitle',
dataIndex: 'accuracy',
width: 120,
sorter: (a, b, sortOrder) => {
if (a.accuracy === undefined) {
return sortOrder === 'ascend' ? -1 : 1;
} else if (b.accuracy === undefined) {
return sortOrder === 'ascend' ? 1 : -1;
} else {
return a.accuracy - b.accuracy;
}
},
render: (text, record) => (
// TODO: is this needed?
<div>{record.latestAccuracy}</div>
)
};
export default TableList; export default TableList;
// when there are more trials than this threshold, metrics will be updated in group of this size to avoid freezing
const METRIC_GROUP_UPDATE_THRESHOLD = 100;
const METRIC_GROUP_UPDATE_SIZE = 20;
const MANAGER_IP = `/api/v1/nni`; const MANAGER_IP = `/api/v1/nni`;
const DOWNLOAD_IP = `/logs`; const DOWNLOAD_IP = `/logs`;
const trialJobStatus = [ const trialJobStatus = [
...@@ -65,9 +69,10 @@ const COLUMN_INDEX = [ ...@@ -65,9 +69,10 @@ const COLUMN_INDEX = [
// defatult selected column // defatult selected column
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', 'StartTime', 'EndTime', 'Duration', 'Status', const COLUMNPro = ['Trial No.', 'ID', 'Start Time', 'End Time', 'Duration', 'Status',
'Intermediate count', 'Default', 'Operation']; 'Intermediate count', 'Default', 'Operation'];
export { export {
MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro, MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro,
CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX, DRAWEROPTION CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX, DRAWEROPTION,
METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE,
}; };
import { Experiment } from './model/experiment';
import { TrialManager } from './model/trialmanager';
const EXPERIMENT = new Experiment();
const TRIALS = new TrialManager();
export { EXPERIMENT, TRIALS };
import axios from 'axios'; import axios from 'axios';
import { message } from 'antd'; import { message } from 'antd';
import { MANAGER_IP } from './const'; import { MANAGER_IP } from './const';
import { FinalResult, FinalType, TableObj } from './interface'; import { MetricDataRecord, FinalType, TableObj } from './interface';
const convertTime = (num: number) => { const convertTime = (num: number) => {
if (num <= 0) {
return '0';
}
if (num % 3600 === 0) { if (num % 3600 === 0) {
return num / 3600 + 'h'; return num / 3600 + 'h';
} else { } else {
...@@ -15,24 +18,28 @@ const convertTime = (num: number) => { ...@@ -15,24 +18,28 @@ const convertTime = (num: number) => {
// trial's duration, accurate to seconds for example 10min 30s // trial's duration, accurate to seconds for example 10min 30s
const convertDuration = (num: number) => { const convertDuration = (num: number) => {
if (num < 1) {
return '0s';
}
const hour = Math.floor(num / 3600); const hour = Math.floor(num / 3600);
const min = Math.floor(num / 60 % 60); const minute = Math.floor(num / 60 % 60);
const second = Math.floor(num % 60); const second = Math.floor(num % 60);
const result = hour > 0 ? `${hour} h ${min} min ${second}s` : `${min} min ${second}s`; let result = [ ];
if (hour <= 0 && min === 0 && second !== 0) { if (hour > 0) {
return `${second}s`; result.push(`${hour}h`);
} else if (hour === 0 && min !== 0 && second === 0) { }
return `${min}min`; if (minute > 0) {
} else if (hour === 0 && min !== 0 && second !== 0) { result.push(`${minute}min`);
return `${min}min ${second}s`; }
} else { if (second > 0) {
return result; result.push(`${second}s`);
} }
return result.join(' ');
}; };
// get final result value // get final result value
// draw Accuracy point graph // draw Accuracy point graph
const getFinalResult = (final: Array<FinalResult>) => { const getFinalResult = (final?: MetricDataRecord[]) => {
let acc; let acc;
let showDefault = 0; let showDefault = 0;
if (final) { if (final) {
...@@ -51,7 +58,7 @@ const getFinalResult = (final: Array<FinalResult>) => { ...@@ -51,7 +58,7 @@ const getFinalResult = (final: Array<FinalResult>) => {
}; };
// get final result value // acc obj // get final result value // acc obj
const getFinal = (final: Array<FinalResult>) => { const getFinal = (final?: MetricDataRecord[]) => {
let showDefault: FinalType; let showDefault: FinalType;
if (final) { if (final) {
showDefault = JSON.parse(final[final.length - 1].data); showDefault = JSON.parse(final[final.length - 1].data);
...@@ -101,7 +108,7 @@ const intermediateGraphOption = (intermediateArr: number[], id: string) => { ...@@ -101,7 +108,7 @@ const intermediateGraphOption = (intermediateArr: number[], id: string) => {
}; };
// kill job // kill job
const killJob = (key: number, id: string, status: string, updateList: Function) => { const killJob = (key: number, id: string, status: string, updateList?: Function) => {
axios(`${MANAGER_IP}/trial-jobs/${id}`, { axios(`${MANAGER_IP}/trial-jobs/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
...@@ -113,7 +120,9 @@ const killJob = (key: number, id: string, status: string, updateList: Function) ...@@ -113,7 +120,9 @@ const killJob = (key: number, id: string, status: string, updateList: Function)
message.destroy(); message.destroy();
message.success('Cancel the job successfully'); message.success('Cancel the job successfully');
// render the table // render the table
updateList(); if (updateList) {
updateList(); // FIXME
}
} else { } else {
message.error('fail to cancel the job'); message.error('fail to cancel the job');
} }
...@@ -160,7 +169,22 @@ const downFile = (content: string, fileName: string) => { ...@@ -160,7 +169,22 @@ const downFile = (content: string, fileName: string) => {
} }
}; };
function formatTimestamp(timestamp?: number, placeholder?: string = 'N/A'): string {
return timestamp ? new Date(timestamp).toLocaleString('en-US') : placeholder;
}
function metricAccuracy(metric: MetricDataRecord): number {
const data = JSON.parse(metric.data);
return typeof data === 'number' ? data : NaN;
}
function formatAccuracy(accuracy: number): string {
// TODO: how to format NaN?
return accuracy.toFixed(6).replace(/0+$/, '').replace(/\.$/, '');
}
export { export {
convertTime, convertDuration, getFinalResult, getFinal, downFile, convertTime, convertDuration, getFinalResult, getFinal, downFile,
intermediateGraphOption, killJob, filterByStatus, filterDuration intermediateGraphOption, killJob, filterByStatus, filterDuration,
formatAccuracy, formatTimestamp, metricAccuracy,
}; };
// tslint:disable:no-any
// draw accuracy graph data interface // draw accuracy graph data interface
interface TableObj { interface TableObj {
key: number; key: number;
...@@ -12,6 +14,19 @@ interface TableObj { ...@@ -12,6 +14,19 @@ interface TableObj {
endTime?: number; endTime?: number;
} }
interface TableRecord {
key: string;
sequenceId: number;
startTime: number;
endTime?: number;
id: string;
duration: number;
status: string;
intermediateCount: number;
accuracy?: number;
latestAccuracy: string; // formatted string
}
interface SearchSpace { interface SearchSpace {
_value: Array<number | string>; _value: Array<number | string>;
_type: string; _type: string;
...@@ -32,26 +47,6 @@ interface Parameters { ...@@ -32,26 +47,6 @@ interface Parameters {
multiProgress?: number; multiProgress?: number;
} }
interface Experiment {
id: string;
author: string;
revision?: number;
experName: string;
logDir?: string;
runConcurren: number;
maxDuration: number;
execDuration: number;
MaxTrialNum: number;
startTime: number;
endTime?: number;
trainingServicePlatform: string;
tuner: object;
assessor?: object;
advisor?: object;
clusterMetaData?: object;
logCollection?: string;
}
// trial accuracy // trial accuracy
interface AccurPoint { interface AccurPoint {
acc: number; acc: number;
...@@ -74,21 +69,6 @@ interface TooltipForAccuracy { ...@@ -74,21 +69,6 @@ interface TooltipForAccuracy {
data: Array<number | object>; data: Array<number | object>;
} }
interface TrialNumber {
succTrial: number;
failTrial: number;
stopTrial: number;
waitTrial: number;
runTrial: number;
unknowTrial: number;
totalCurrentTrial: number;
}
interface TrialJob {
text: string;
value: string;
}
interface Dimobj { interface Dimobj {
dim: number; dim: number;
name: string; name: string;
...@@ -108,10 +88,6 @@ interface ParaObj { ...@@ -108,10 +88,6 @@ interface ParaObj {
parallelAxis: Array<Dimobj>; parallelAxis: Array<Dimobj>;
} }
interface FinalResult {
data: string;
}
interface Intermedia { interface Intermedia {
name: string; // id name: string; // id
type: string; type: string;
...@@ -119,13 +95,93 @@ interface Intermedia { ...@@ -119,13 +95,93 @@ interface Intermedia {
hyperPara: object; // each trial hyperpara value hyperPara: object; // each trial hyperpara value
} }
interface ExperimentInfo { interface MetricDataRecord {
platform: string; timestamp: number;
optimizeMode: string; trialJobId: string;
parameterId: string;
type: string;
sequence: number;
data: string;
}
interface TrialJobInfo {
id: string;
sequenceId: number;
status: string;
startTime?: number;
endTime?: number;
hyperParameters?: string[];
logPath?: string;
finalMetricData?: MetricDataRecord[];
stderrPath?: string;
}
interface ExperimentParams {
authorName: string;
experimentName: string;
description?: string;
trialConcurrency: number;
maxExecDuration: number; // seconds
maxTrialNum: number;
searchSpace: string;
trainingServicePlatform: string;
multiPhase?: boolean;
multiThread?: boolean;
versionCheck?: boolean;
logCollection?: string;
tuner?: {
className: string;
builtinTunerName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
includeIntermediateResults?: boolean;
};
assessor?: {
className: string;
builtinAssessorName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
};
advisor?: {
className: string;
builtinAdvisorName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
};
clusterMetaData?: {
key: string;
value: string;
}[];
}
interface ExperimentProfile {
params: ExperimentParams;
id: string;
execDuration: number;
logDir?: string;
startTime?: number;
endTime?: number;
maxSequenceId: number;
revision: number;
}
interface NNIManagerStatus {
status: string;
errors: string[];
} }
export { export {
TableObj, Parameters, Experiment, AccurPoint, TrialNumber, TrialJob, TableObj, TableRecord, Parameters, ExperimentProfile, AccurPoint,
DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalResult, FinalType, DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalType,
TooltipForIntermediate, SearchSpace, Intermedia, ExperimentInfo TooltipForIntermediate, SearchSpace, Intermedia, MetricDataRecord, TrialJobInfo,
NNIManagerStatus,
}; };
import axios from 'axios';
import { MANAGER_IP } from '../const';
import { ExperimentProfile, NNIManagerStatus } from '../interface';
function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProfile): boolean {
if (!profile1 || !profile2) {
return false;
}
const copy1 = Object.assign({}, profile1, { execDuration: undefined });
const copy2 = Object.assign({}, profile2, { execDuration: undefined });
return JSON.stringify(copy1) === JSON.stringify(copy2);
}
class Experiment {
private profileField?: ExperimentProfile = undefined;
private statusField?: NNIManagerStatus = undefined;
public async init(): Promise<void> {
while (!this.profileField || !this.statusField) {
await this.update();
}
}
public async update(): Promise<boolean> {
const profilePromise = axios.get(`${MANAGER_IP}/experiment`);
const statusPromise = axios.get(`${MANAGER_IP}/check-status`);
const [ profileResponse, statusResponse ] = await Promise.all([ profilePromise, statusPromise ]);
let updated = false;
if (statusResponse.status === 200) {
updated = JSON.stringify(this.statusField) === JSON.stringify(statusResponse.data);
this.statusField = statusResponse.data;
}
if (profileResponse.status === 200) {
updated = updated || compareProfiles(this.profileField, profileResponse.data);
this.profileField = profileResponse.data;
}
return updated;
}
get profile(): ExperimentProfile {
if (!this.profileField) {
throw Error('Experiment profile not initialized');
}
return this.profileField!;
}
get trialConcurrency(): number {
return this.profile.params.trialConcurrency;
}
get optimizeMode(): string {
const tuner = this.profile.params.tuner;
return (tuner && tuner.classArgs && tuner.classArgs.optimize_mode) ? tuner.classArgs.optimize_mode : 'unknown';
}
get trainingServicePlatform(): string {
return this.profile.params.trainingServicePlatform;
}
get searchSpace(): object {
return JSON.parse(this.profile.params.searchSpace);
}
get logCollectionEnabled(): boolean {
return !!(this.profile.params.logCollection && this.profile.params.logCollection !== 'none');
}
get multiPhase(): boolean {
return !!(this.profile.params.multiPhase);
}
get status(): string {
if (!this.statusField) {
throw Error('Experiment status not initialized');
}
return this.statusField!.status;
}
get error(): string {
if (!this.statusField) {
throw Error('Experiment status not initialized');
}
return this.statusField!.errors[0] || '';
}
}
export { Experiment };
import { MetricDataRecord, TrialJobInfo, TableObj, TableRecord, Parameters, FinalType } from '../interface';
import { getFinal, formatAccuracy, metricAccuracy } from '../function';
class Trial implements TableObj {
private metricsInitialized: boolean = false;
private infoField: TrialJobInfo | undefined;
private intermediates: (MetricDataRecord | undefined)[] = [ ];
private final: MetricDataRecord | undefined;
private finalAcc: number | undefined;
constructor(info?: TrialJobInfo, metrics?: MetricDataRecord[]) {
this.infoField = info;
if (metrics) {
this.updateMetrics(metrics);
}
}
public compareAccuracy(otherTrial: Trial): number | undefined {
if (!this.sortable || !otherTrial.sortable) {
return undefined;
}
return this.finalAcc! - otherTrial.finalAcc!;
}
get info(): TrialJobInfo {
return this.infoField!;
}
get intermediateMetrics(): MetricDataRecord[] {
const ret: MetricDataRecord[] = [ ];
for (let i = 0; i < this.intermediates.length; i++) {
if (this.intermediates[i]) {
ret.push(this.intermediates[i]!);
} else {
break;
}
}
return ret;
}
get accuracy(): number | undefined {
return this.finalAcc;
}
get sortable(): boolean {
return this.finalAcc !== undefined && !isNaN(this.finalAcc);
}
/* table obj start */
get tableRecord(): TableRecord {
const endTime = this.info.endTime || new Date().getTime();
const duration = (endTime - this.info.startTime!) / 1000;
return {
key: this.info.id,
sequenceId: this.info.sequenceId,
id: this.info.id,
startTime: this.info.startTime!,
endTime: this.info.endTime,
duration,
status: this.info.status,
intermediateCount: this.intermediates.length,
accuracy: this.finalAcc,
latestAccuracy: this.formatLatestAccuracy(),
};
}
get key(): number {
return this.info.sequenceId;
}
get sequenceId(): number {
return this.info.sequenceId;
}
get id(): string {
return this.info.id;
}
get duration(): number {
const endTime = this.info.endTime || new Date().getTime();
return (endTime - this.info.startTime!) / 1000;
}
get status(): string {
return this.info.status;
}
get acc(): FinalType | undefined {
return getFinal(this.info.finalMetricData);
}
get description(): Parameters {
let ret: Parameters = {
parameters: { },
intermediate: [ ],
multiProgress: 1
};
const tempHyper = this.info.hyperParameters;
if (tempHyper !== undefined) {
const getPara = JSON.parse(tempHyper[tempHyper.length - 1]).parameters;
ret.multiProgress = tempHyper.length;
if (typeof getPara === 'string') {
ret.parameters = JSON.parse(getPara);
} else {
ret.parameters = getPara;
}
} else {
ret.parameters = { error: 'This trial\'s parameters are not available.' };
}
if (this.info.logPath !== undefined) {
ret.logPath = this.info.logPath;
}
const mediate: number[] = [ ];
for (const items of this.intermediateMetrics) {
if (typeof JSON.parse(items.data) === 'object') {
mediate.push(JSON.parse(items.data).default);
} else {
mediate.push(JSON.parse(items.data));
}
}
ret.intermediate = mediate;
return ret;
}
get color(): string | undefined {
return undefined;
}
/* table obj end */
public initialized(): boolean {
return !!(this.infoField && this.metricsInitialized);
}
public updateMetrics(metrics: MetricDataRecord[]): boolean {
// parameter `metrics` must contain all known metrics of this trial
this.metricsInitialized = true;
const prevMetricCnt = this.intermediates.length + (this.final ? 1 : 0);
if (metrics.length <= prevMetricCnt) {
return false;
}
for (const metric of metrics) {
if (metric.type === 'PERIODICAL') {
this.intermediates[metric.sequence] = metric;
} else {
this.final = metric;
this.finalAcc = metricAccuracy(metric);
}
}
return true;
}
public updateLatestMetrics(metrics: MetricDataRecord[]): boolean {
// this method is effectively identical to `updateMetrics`, but has worse performance
this.metricsInitialized = true;
let updated = false;
for (const metric of metrics) {
if (metric.type === 'PERIODICAL') {
updated = updated || !this.intermediates[metric.sequence];
this.intermediates[metric.sequence] = metric;
} else {
updated = updated || !this.final;
this.final = metric;
this.finalAcc = metricAccuracy(metric);
}
}
return updated;
}
public updateTrialJobInfo(trialJobInfo: TrialJobInfo): boolean {
const same = (this.infoField && this.infoField.status === trialJobInfo.status);
this.infoField = trialJobInfo;
if (trialJobInfo.finalMetricData) {
this.final = trialJobInfo.finalMetricData[trialJobInfo.finalMetricData.length - 1];
this.finalAcc = metricAccuracy(this.final);
}
return !same;
}
public formatLatestAccuracy(): string { // TODO: this should be private
if (this.accuracy !== undefined) {
return `${formatAccuracy(this.accuracy)} (FINAL)`;
} else if (this.intermediates.length === 0) {
return '--';
} else {
const latest = this.intermediates[this.intermediates.length - 1]!;
return `${formatAccuracy(metricAccuracy(latest))} (LATEST)`;
}
}
}
export { Trial };
import axios from 'axios';
import { MANAGER_IP, METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE } from '../const';
import { MetricDataRecord, TableRecord, TrialJobInfo } from '../interface';
import { Trial } from './trial';
class TrialManager {
private trials: Map<string, Trial> = new Map<string, Trial>();
private infoInitialized: boolean = false;
private metricInitialized: boolean = false;
private maxSequenceId: number = 0;
private doingBatchUpdate: boolean = false;
private batchUpdatedAfterReading: boolean = false;
public async init(): Promise<void> {
while (!this.infoInitialized || !this.metricInitialized) {
await this.update();
}
}
public async update(lastTime?: boolean): Promise<boolean> {
const [ infoUpdated, metricUpdated ] = await Promise.all([ this.updateInfo(), this.updateMetrics(lastTime) ]);
return infoUpdated || metricUpdated;
}
public getTrial(trialId: string): Trial {
return this.trials.get(trialId)!;
}
public getTrials(trialIds: string[]): Trial[] {
return trialIds.map(trialId => this.trials.get(trialId)!);
}
public table(trialIds: string[]): TableRecord[] {
return trialIds.map(trialId => this.trials.get(trialId)!.tableRecord);
}
public toArray(): Trial[] {
const trials = Array.from(this.trials.values()).filter(trial => trial.initialized());
return trials.sort((trial1, trial2) => trial1.sequenceId - trial2.sequenceId);
}
public filter(callback: (trial: Trial) => boolean): Trial[] {
const trials = Array.from(this.trials.values()).filter(trial => trial.initialized() && callback(trial));
return trials.sort((trial1, trial2) => trial1.sequenceId - trial2.sequenceId);
}
public succeededTrials(): Trial[] {
return this.filter(trial => trial.status === 'SUCCEEDED');
}
public sort(): Trial[] {
return this.filter(trial => trial.sortable).sort((trial1, trial2) => trial1.compareAccuracy(trial2)!);
}
public countStatus(): Map<string, number> {
const cnt = new Map<string, number>([
[ 'UNKNOWN', 0 ],
[ 'WAITING', 0 ],
[ 'RUNNING', 0 ],
[ 'SUCCEEDED', 0 ],
[ 'FAILED', 0 ],
[ 'USER_CANCELED', 0 ],
[ 'SYS_CANCELED', 0 ],
[ 'EARLY_STOPPED', 0 ],
]);
for (const trial of this.trials.values()) {
if (trial.initialized()) {
cnt.set(trial.info.status, cnt.get(trial.info.status)! + 1);
}
}
return cnt;
}
private async updateInfo(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/trial-jobs`);
let updated = false;
if (response.status === 200) {
for (const info of response.data as TrialJobInfo[]) {
if (this.trials.has(info.id)) {
updated = this.trials.get(info.id)!.updateTrialJobInfo(info) || updated;
} else {
this.trials.set(info.id, new Trial(info, undefined));
updated = true;
}
this.maxSequenceId = Math.max(this.maxSequenceId, info.sequenceId);
}
this.infoInitialized = true;
}
return updated;
}
private async updateMetrics(lastTime?: boolean): Promise<boolean> {
if (this.trials.size < METRIC_GROUP_UPDATE_THRESHOLD || lastTime) {
return await this.updateAllMetrics();
} else {
this.updateManyMetrics();
const ret = (await this.updateLatestMetrics()) || this.batchUpdatedAfterReading;
this.batchUpdatedAfterReading = false;
return ret;
}
}
private async updateAllMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data`);
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], false);
}
private async updateLatestMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data-latest`);
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], true);
}
private async updateManyMetrics(): Promise<void> {
if (this.doingBatchUpdate) {
return;
}
this.doingBatchUpdate = true;
for (let i = 0; i < this.maxSequenceId; i += METRIC_GROUP_UPDATE_SIZE) {
const response = await axios.get(`${MANAGER_IP}/metric-data-range/${i}/${i + METRIC_GROUP_UPDATE_SIZE}`);
if (response.status === 200) {
const updated = this.doUpdateMetrics(response.data as MetricDataRecord[], false);
this.batchUpdatedAfterReading = this.batchUpdatedAfterReading || updated;
}
}
this.doingBatchUpdate = false;
}
private doUpdateMetrics(allMetrics: MetricDataRecord[], latestOnly: boolean): boolean {
let updated = false;
for (const [ trialId, metrics ] of groupMetricsByTrial(allMetrics).entries()) {
if (this.trials.has(trialId)) {
const trial = this.trials.get(trialId)!;
updated = (latestOnly ? trial.updateLatestMetrics(metrics) : trial.updateMetrics(metrics)) || updated;
} else {
this.trials.set(trialId, new Trial(undefined, metrics));
updated = true;
}
}
this.metricInitialized = true;
return updated;
}
}
function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> {
const ret = new Map<string, MetricDataRecord[]>();
for (const metric of metrics) {
if (ret.has(metric.trialJobId)) {
ret.get(metric.trialJobId)!.push(metric);
} else {
ret.set(metric.trialJobId, [ metric ]);
}
}
return ret;
}
export { TrialManager };
...@@ -11,10 +11,6 @@ ...@@ -11,10 +11,6 @@
], ],
"ban": false, "ban": false,
"class-name": true, "class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true, "curly": true,
"eofline": false, "eofline": false,
"forin": true, "forin": true,
...@@ -40,19 +36,9 @@ ...@@ -40,19 +36,9 @@
"static-before-instance", "static-before-instance",
"variables-before-functions" "variables-before-functions"
], ],
"no-any": true,
"no-arg": true, "no-arg": true,
"no-bitwise": true, "no-bitwise": true,
"no-console": [ "no-console": false,
true,
"log",
"error",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-consecutive-blank-lines": true, "no-consecutive-blank-lines": true,
"no-construct": true, "no-construct": true,
"no-debugger": true, "no-debugger": true,
...@@ -64,7 +50,6 @@ ...@@ -64,7 +50,6 @@
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-trailing-whitespace": false, "no-trailing-whitespace": false,
"no-unused-expression": true, "no-unused-expression": true,
"no-use-before-declare": true,
"one-line": [ "one-line": [
true, true,
"check-catch", "check-catch",
...@@ -123,4 +108,4 @@ ...@@ -123,4 +108,4 @@
"check-typecast" "check-typecast"
] ]
} }
} }
\ No newline at end of file
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