"vscode:/vscode.git/clone" did not exist on "6fb61deddba49e2bcb24edc8a23f119d32871741"
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 {
class Duration extends React.Component<DurationProps, DurationState> {
public _isMounted = false;
constructor(props: DurationProps) {
super(props);
......@@ -142,15 +140,12 @@ class Duration extends React.Component<DurationProps, DurationState> {
trialId: trialId,
trialTime: trialTime
});
if (this._isMounted) {
this.setState({
durationSource: this.getOption(trialRun[0])
});
}
this.setState({
durationSource: this.getOption(trialRun[0])
});
}
componentDidMount() {
this._isMounted = true;
const { source } = this.props;
this.drawDurationGraph(source);
}
......@@ -187,10 +182,6 @@ class Duration extends React.Component<DurationProps, DurationState> {
return false;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { durationSource } = this.state;
return (
......@@ -206,4 +197,4 @@ class Duration extends React.Component<DurationProps, DurationState> {
}
}
export default Duration;
\ No newline at end of file
export default Duration;
......@@ -24,7 +24,6 @@ interface IntermediateProps {
class Intermediate extends React.Component<IntermediateProps, IntermediateState> {
static intervalMediate = 1;
public _isMounted = false;
public pointInput: HTMLInputElement | null;
public minValInput: HTMLInputElement | null;
public maxValInput: HTMLInputElement | null;
......@@ -45,12 +44,10 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
drawIntermediate = (source: Array<TableObj>) => {
if (source.length > 0) {
if (this._isMounted) {
this.setState(() => ({
length: source.length,
detailSource: source
}));
}
this.setState({
length: source.length,
detailSource: source
});
const trialIntermediate: Array<Intermedia> = [];
Object.keys(source).map(item => {
const temp = source[item];
......@@ -118,11 +115,9 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
},
series: trialIntermediate
};
if (this._isMounted) {
this.setState(() => ({
interSource: option
}));
}
this.setState({
interSource: option
});
} else {
const nullData = {
grid: {
......@@ -139,71 +134,60 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
name: 'Metric'
}
};
if (this._isMounted) {
this.setState(() => ({ interSource: nullData }));
}
this.setState({ interSource: nullData });
}
}
// confirm btn function [filter data]
filterLines = () => {
if (this._isMounted) {
const filterSource: Array<TableObj> = [];
this.setState({ isLoadconfirmBtn: true }, () => {
const { source } = this.props;
// get input value
const pointVal = this.pointInput !== null ? this.pointInput.value : '';
const minVal = this.minValInput !== null ? this.minValInput.value : '';
const maxVal = this.maxValInput !== null ? this.maxValInput.value : '';
// user not input message
if (pointVal === '' || minVal === '') {
alert('Please input filter message');
const filterSource: Array<TableObj> = [];
this.setState({ isLoadconfirmBtn: true }, () => {
const { source } = this.props;
// get input value
const pointVal = this.pointInput !== null ? this.pointInput.value : '';
const minVal = this.minValInput !== null ? this.minValInput.value : '';
const maxVal = this.maxValInput !== null ? this.maxValInput.value : '';
// user not input message
if (pointVal === '' || minVal === '') {
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 {
// 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 {
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 });
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);
}
});
}
});
}
this.setState({ filterSource: filterSource });
this.drawIntermediate(filterSource);
}
const counts = this.state.clickCounts + 1;
this.setState({ isLoadconfirmBtn: false, clickCounts: counts });
});
}
switchTurn = (checked: boolean) => {
if (this._isMounted) {
this.setState({ isFilter: checked });
}
this.setState({ isFilter: checked });
if (checked === false) {
this.drawIntermediate(this.props.source);
}
}
componentDidMount() {
this._isMounted = true;
const { source } = this.props;
this.drawIntermediate(source);
}
......@@ -272,10 +256,6 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
return false;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { interSource, isLoadconfirmBtn, isFilter } = this.state;
return (
......
......@@ -40,8 +40,6 @@ message.config({
class Para extends React.Component<ParaProps, ParaState> {
public _isMounted = false;
private chartMulineStyle = {
width: '100%',
height: 392,
......@@ -121,15 +119,12 @@ class Para extends React.Component<ParaProps, ParaState> {
this.swapGraph(paraData, swapAxisArr);
}
this.getOption(paraData, lengthofTrials);
if (this._isMounted === true) {
this.setState(() => ({ paraBack: paraData }));
}
this.setState({ paraBack: paraData });
}
hyperParaPic = (source: Array<TableObj>, searchSpace: string) => {
// filter succeed trials [{}, {}, {}]
const origin = source.filter(filterByStatus);
const dataSource: Array<TableObj> = JSON.parse(JSON.stringify(origin));
const dataSource = source.filter(filterByStatus);
const lenOfDataSource: number = dataSource.length;
const accPara: Array<number> = [];
// specific value array
......@@ -139,15 +134,13 @@ class Para extends React.Component<ParaProps, ParaState> {
// nest search space
let isNested: boolean = false;
Object.keys(searchRange).map(item => {
if (typeof searchRange[item]._value[0] === 'object') {
if (searchRange[item]._value && typeof searchRange[item]._value[0] === 'object') {
isNested = true;
return;
}
});
const dimName = Object.keys(searchRange);
if (this._isMounted === true) {
this.setState(() => ({ dimName: dimName }));
}
this.setState({ dimName: dimName });
const parallelAxis: Array<Dimobj> = [];
// search space range and specific value [only number]
......@@ -324,23 +317,21 @@ class Para extends React.Component<ParaProps, ParaState> {
color: ['#CA0000', '#FFC400', '#90EE90']
}
};
if (this._isMounted === true) {
this.setState({
paraNodata: 'No data',
option: optionOfNull,
sutrialCount: 0,
succeedRenderCount: 0
});
}
this.setState({
paraNodata: 'No data',
option: optionOfNull,
sutrialCount: 0,
succeedRenderCount: 0
});
} else {
Object.keys(dataSource).map(item => {
const temp = dataSource[item];
eachTrialParams.push(temp.description.parameters);
const trial = dataSource[item];
eachTrialParams.push(trial.description.parameters.error || '');
// may be a succeed trial hasn't final result
// all detail page may be break down if havn't if
if (temp.acc !== undefined) {
if (temp.acc.default !== undefined) {
accPara.push(temp.acc.default);
if (trial.acc !== undefined) {
if (trial.acc.default !== undefined) {
accPara.push(JSON.parse(trial.acc.default));
}
}
});
......@@ -361,14 +352,12 @@ class Para extends React.Component<ParaProps, ParaState> {
});
});
}
if (this._isMounted) {
// if not return final result
const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara);
const minVal = accPara.length === 0 ? 1 : Math.min(...accPara);
this.setState({ max: maxVal, min: minVal }, () => {
this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource);
});
}
// if not return final result
const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara);
const minVal = accPara.length === 0 ? 1 : Math.min(...accPara);
this.setState({ max: maxVal, min: minVal }, () => {
this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource);
});
}
}
......@@ -376,11 +365,9 @@ class Para extends React.Component<ParaProps, ParaState> {
percentNum = (value: string) => {
let vals = parseFloat(value);
if (this._isMounted) {
this.setState({ percent: vals }, () => {
this.reInit();
});
}
this.setState({ percent: vals }, () => {
this.reInit();
});
}
// deal with response data into pic data
......@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> {
}
};
// please wait the data
if (this._isMounted) {
this.setState(() => ({
option: optionown,
paraNodata: '',
succeedRenderCount: lengthofTrials,
sutrialCount: paralleData.length
}));
}
this.setState({
option: optionown,
paraNodata: '',
succeedRenderCount: lengthofTrials,
sutrialCount: paralleData.length
});
}
// get swap parallel axis
getSwapArr = (value: Array<string>) => {
if (this._isMounted) {
this.setState(() => ({ swapAxisArr: value }));
}
this.setState({ swapAxisArr: value });
}
reInit = () => {
......@@ -471,9 +453,7 @@ class Para extends React.Component<ParaProps, ParaState> {
swapReInit = () => {
const { clickCounts, succeedRenderCount } = this.state;
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 paralDim = paraBack.parallelAxis;
const paraData = paraBack.data;
......@@ -523,11 +503,9 @@ class Para extends React.Component<ParaProps, ParaState> {
});
this.getOption(paraBack, succeedRenderCount);
// please wait the data
if (this._isMounted) {
this.setState(() => ({
isLoadConfirm: false
}));
}
this.setState({
isLoadConfirm: false
});
}
sortDimY = (a: Dimobj, b: Dimobj) => {
......@@ -585,7 +563,6 @@ class Para extends React.Component<ParaProps, ParaState> {
}
componentDidMount() {
this._isMounted = true;
this.reInit();
}
......@@ -623,10 +600,6 @@ class Para extends React.Component<ParaProps, ParaState> {
return false;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { option, paraNodata, dimName, isLoadConfirm } = this.state;
return (
......@@ -687,4 +660,4 @@ class Para extends React.Component<ParaProps, ParaState> {
}
}
export default Para;
\ No newline at end of file
export default Para;
......@@ -2,14 +2,14 @@ import * as React from 'react';
import axios from 'axios';
import ReactEcharts from 'echarts-for-react';
import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select, Icon } from 'antd';
import { ColumnProps } from 'antd/lib/table';
const Option = Select.Option;
const CheckboxGroup = Checkbox.Group;
import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const';
import { convertDuration, intermediateGraphOption, killJob, filterByStatus } from '../../static/function';
import { TableObj, TrialJob } from '../../static/interface';
import { convertDuration, formatTimestamp, intermediateGraphOption, killJob } from '../../static/function';
import { TableRecord } from '../../static/interface';
import OpenRow from '../public-child/OpenRow';
import Compare from '../Modal/Compare';
import IntermediateVal from '../public-child/IntermediateVal'; // table default metric column
import '../../static/style/search.scss';
require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss');
......@@ -26,14 +26,11 @@ echarts.registerTheme('my_theme', {
});
interface TableListProps {
entries: number;
tableSource: Array<TableObj>;
updateList: Function;
platform: string;
logCollection: boolean;
isMultiPhase: boolean;
pageSize: number;
tableSource: Array<TableRecord>;
columnList: Array<string>; // user select columnKeys
changeColumn: (val: Array<string>) => void;
trialsUpdateBroadcast: number;
}
interface TableListState {
......@@ -41,7 +38,7 @@ interface TableListState {
modalVisible: boolean;
isObjFinal: boolean;
isShowColumn: boolean;
selectRows: Array<TableObj>;
selectRows: Array<TableRecord>;
isShowCompareModal: boolean;
selectedRowKeys: string[] | number[];
intermediateData: Array<object>; // a trial's intermediate results (include dict)
......@@ -56,10 +53,9 @@ interface ColumnIndex {
class TableList extends React.Component<TableListProps, TableListState> {
public _isMounted = false;
public intervalTrialLog = 10;
public _trialId: string;
public tables: Table<TableObj> | null;
public tables: Table<TableRecord> | null;
constructor(props: TableListProps) {
super(props);
......@@ -78,46 +74,35 @@ class TableList extends React.Component<TableListProps, TableListState> {
};
}
showIntermediateModal = (id: string) => {
axios(`${MANAGER_IP}/metric-data/${id}`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
const intermediateArr: number[] = [];
// support intermediate result is dict because the last intermediate result is
// final result in a succeed trial, it may be a dict.
// get intermediate result dict keys array
let otherkeys: Array<string> = ['default'];
if (res.data.length !== 0) {
otherkeys = Object.keys(JSON.parse(res.data[0].data));
}
// intermediateArr just store default val
Object.keys(res.data).map(item => {
const temp = JSON.parse(res.data[item].data);
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
}));
}
showIntermediateModal = async (id: string) => {
const res = await axios.get(`${MANAGER_IP}/metric-data/${id}`);
if (res.status === 200) {
const intermediateArr: number[] = [];
// support intermediate result is dict because the last intermediate result is
// final result in a succeed trial, it may be a dict.
// get intermediate result dict keys array
let otherkeys: Array<string> = ['default'];
if (res.data.length !== 0) {
otherkeys = Object.keys(JSON.parse(res.data[0].data));
}
// intermediateArr just store default val
Object.keys(res.data).map(item => {
const temp = JSON.parse(res.data[item].data);
if (typeof temp === 'object') {
intermediateArr.push(temp.default);
} else {
intermediateArr.push(temp);
}
});
if (this._isMounted) {
const intermediate = intermediateGraphOption(intermediateArr, id);
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
......@@ -147,37 +132,29 @@ class TableList extends React.Component<TableListProps, TableListState> {
}
const intermediate = intermediateGraphOption(intermediateArr, intermediateId);
// re-render
if (this._isMounted) {
this.setState(() => ({
intermediateOption: intermediate
}));
}
this.setState({
intermediateOption: intermediate
});
}
hideIntermediateModal = () => {
if (this._isMounted) {
this.setState({
modalVisible: false
});
}
this.setState({
modalVisible: false
});
}
hideShowColumnModal = () => {
if (this._isMounted) {
this.setState({
isShowColumn: false
});
}
this.setState({
isShowColumn: false
});
}
// click add column btn, just show the modal of addcolumn
addColumn = () => {
// show user select check button
if (this._isMounted) {
this.setState({
isShowColumn: true
});
}
this.setState({
isShowColumn: true
});
}
// checkbox for coloumn
......@@ -191,8 +168,8 @@ class TableList extends React.Component<TableListProps, TableListState> {
switch (checkedValues[m]) {
case 'Trial No.':
case 'ID':
case 'StartTime':
case 'EndTime':
case 'Start Time':
case 'End Time':
case 'Duration':
case 'Status':
case 'Operation':
......@@ -229,27 +206,17 @@ class TableList extends React.Component<TableListProps, TableListState> {
wantResult.push(want[i].name);
});
if (this._isMounted) {
this.props.changeColumn(wantResult);
}
this.props.changeColumn(wantResult);
}
openRow = (record: TableObj) => {
const { platform, logCollection, isMultiPhase } = this.props;
openRow = (record: TableRecord) => {
return (
<OpenRow
trainingPlatform={platform}
record={record}
logCollection={logCollection}
multiphase={isMultiPhase}
/>
<OpenRow trialId={record.id} />
);
}
fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableObj>) => {
if (this._isMounted === true) {
this.setState(() => ({ selectRows: selectedRows, selectedRowKeys: selected }));
}
fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableRecord>) => {
this.setState({ selectRows: selectedRows, selectedRowKeys: selected });
}
// open Compare-modal
compareBtn = () => {
......@@ -258,47 +225,33 @@ class TableList extends React.Component<TableListProps, TableListState> {
if (selectRows.length === 0) {
alert('Please select datas you want to compare!');
} else {
if (this._isMounted === true) {
this.setState({ isShowCompareModal: true });
}
this.setState({ isShowCompareModal: true });
}
}
// close Compare-modal
hideCompareModal = () => {
// close modal. clear select rows data, clear selected track
if (this._isMounted) {
this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
}
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { entries, tableSource, updateList, columnList } = this.props;
const { pageSize, columnList } = this.props;
const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
console.log('rerender table', tableSource);
const { intermediateOption, modalVisible, isShowColumn,
selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state;
const rowSelection = {
selectedRowKeys: selectedRowKeys,
onChange: (selected: string[] | number[], selectedRows: Array<TableObj>) => {
onChange: (selected: string[] | number[], selectedRows: Array<TableRecord>) => {
this.fillSelectedRowsTostate(selected, selectedRows);
}
};
let showTitle = COLUMNPro;
let bgColor = '';
const trialJob: Array<TrialJob> = [];
const showColumn: Array<object> = [];
// only succeed trials have final keys
if (tableSource.filter(filterByStatus).length >= 1) {
const temp = tableSource.filter(filterByStatus)[0].acc;
if (tableSource.filter(record => record.status === 'SUCCEEDED').length >= 1) {
const temp = tableSource.filter(record => record.status === 'SUCCEEDED')[0].accuracy;
if (temp !== undefined && typeof temp === 'object') {
if (this._isMounted) {
// concat default column and finalkeys
const item = Object.keys(temp);
// item: ['default', 'other-keys', 'maybe loss']
......@@ -311,169 +264,33 @@ class TableList extends React.Component<TableListProps, TableListState> {
});
showTitle = COLUMNPro.concat(want);
}
}
}
}
trialJobStatus.map(item => {
trialJob.push({
text: item,
value: item
});
});
Object.keys(columnList).map(key => {
const item = columnList[key];
for (const item of columnList) {
switch (item) {
case 'Trial No.':
showColumn.push({
title: 'Trial No.',
dataIndex: 'sequenceId',
key: 'sequenceId',
width: 120,
className: 'tableHead',
sorter: (a: TableObj, b: TableObj) => (a.sequenceId as number) - (b.sequenceId as number)
});
showColumn.push(SequenceIdColumnConfig);
break;
case 'ID':
showColumn.push({
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>
);
}
});
showColumn.push(IdColumnConfig);
break;
case 'StartTime':
showColumn.push({
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>
);
},
});
case 'Start Time':
showColumn.push(StartTimeColumnConfig);
break;
case 'EndTime':
showColumn.push({
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>
);
},
});
case 'End Time':
showColumn.push(EndTimeColumnConfig);
break;
case 'Duration':
showColumn.push({
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>
);
},
});
showColumn.push(DurationColumnConfig);
break;
case 'Status':
showColumn.push({
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)
});
showColumn.push(StatusColumnConfig);
break;
case 'Intermediate count':
showColumn.push({
title: 'Intermediate count',
dataIndex: 'progress',
key: 'progress',
width: 86,
render: (text: string, record: TableObj) => {
return (
<span>{`#${record.description.intermediate.length}`}</span>
);
},
});
showColumn.push(IntermediateCountColumnConfig);
break;
case 'Default':
showColumn.push({
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} />
);
}
});
showColumn.push(AccuracyColumnConfig);
break;
case 'Operation':
showColumn.push({
......@@ -481,7 +298,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
dataIndex: 'operation',
key: 'operation',
width: 120,
render: (text: string, record: TableObj) => {
render: (text: string, record: TableRecord) => {
let trialStatus = record.status;
const flag: boolean = (trialStatus === 'RUNNING') ? false : true;
return (
......@@ -499,7 +316,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<Popconfirm
title="Are you sure to cancel this trial?"
onConfirm={killJob.
bind(this, record.key, record.id, record.status, updateList)}
bind(this, record.key, record.id, record.status)}
>
<Button
type="default"
......@@ -522,7 +339,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
dataIndex: 'intermediate',
key: 'intermediate',
width: '16%',
render: (text: string, record: TableObj) => {
render: (text: string, record: TableRecord) => {
return (
<Button
type="primary"
......@@ -535,47 +352,24 @@ class TableList extends React.Component<TableListProps, TableListState> {
},
});
break;
default:
showColumn.push({
title: item,
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>
);
}
});
// FIXME
alert('Unexpected column type');
}
});
}
return (
<Row className="tableList">
<div id="tableList">
<Table
ref={(table: Table<TableObj> | null) => this.tables = table}
ref={(table: Table<TableRecord> | null) => this.tables = table}
columns={showColumn}
rowSelection={rowSelection}
expandedRowRender={this.openRow}
dataSource={tableSource}
className="commonTableStyle"
pagination={{ pageSize: entries }}
pagination={pageSize > 0 ? { pageSize } : false}
/>
{/* Intermediate Result Modal */}
<Modal
......@@ -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;
// 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 DOWNLOAD_IP = `/logs`;
const trialJobStatus = [
......@@ -65,9 +69,10 @@ const COLUMN_INDEX = [
// defatult selected column
const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', 'Operation'];
// 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'];
export {
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 { message } from 'antd';
import { MANAGER_IP } from './const';
import { FinalResult, FinalType, TableObj } from './interface';
import { MetricDataRecord, FinalType, TableObj } from './interface';
const convertTime = (num: number) => {
if (num <= 0) {
return '0';
}
if (num % 3600 === 0) {
return num / 3600 + 'h';
} else {
......@@ -15,24 +18,28 @@ const convertTime = (num: number) => {
// trial's duration, accurate to seconds for example 10min 30s
const convertDuration = (num: number) => {
if (num < 1) {
return '0s';
}
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 result = hour > 0 ? `${hour} h ${min} min ${second}s` : `${min} min ${second}s`;
if (hour <= 0 && min === 0 && second !== 0) {
return `${second}s`;
} else if (hour === 0 && min !== 0 && second === 0) {
return `${min}min`;
} else if (hour === 0 && min !== 0 && second !== 0) {
return `${min}min ${second}s`;
} else {
return result;
let result = [ ];
if (hour > 0) {
result.push(`${hour}h`);
}
if (minute > 0) {
result.push(`${minute}min`);
}
if (second > 0) {
result.push(`${second}s`);
}
return result.join(' ');
};
// get final result value
// draw Accuracy point graph
const getFinalResult = (final: Array<FinalResult>) => {
const getFinalResult = (final?: MetricDataRecord[]) => {
let acc;
let showDefault = 0;
if (final) {
......@@ -51,7 +58,7 @@ const getFinalResult = (final: Array<FinalResult>) => {
};
// get final result value // acc obj
const getFinal = (final: Array<FinalResult>) => {
const getFinal = (final?: MetricDataRecord[]) => {
let showDefault: FinalType;
if (final) {
showDefault = JSON.parse(final[final.length - 1].data);
......@@ -101,7 +108,7 @@ const intermediateGraphOption = (intermediateArr: number[], id: string) => {
};
// 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}`, {
method: 'DELETE',
headers: {
......@@ -113,7 +120,9 @@ const killJob = (key: number, id: string, status: string, updateList: Function)
message.destroy();
message.success('Cancel the job successfully');
// render the table
updateList();
if (updateList) {
updateList(); // FIXME
}
} else {
message.error('fail to cancel the job');
}
......@@ -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 {
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
interface TableObj {
key: number;
......@@ -12,6 +14,19 @@ interface TableObj {
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 {
_value: Array<number | string>;
_type: string;
......@@ -32,26 +47,6 @@ interface Parameters {
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
interface AccurPoint {
acc: number;
......@@ -74,21 +69,6 @@ interface TooltipForAccuracy {
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 {
dim: number;
name: string;
......@@ -108,10 +88,6 @@ interface ParaObj {
parallelAxis: Array<Dimobj>;
}
interface FinalResult {
data: string;
}
interface Intermedia {
name: string; // id
type: string;
......@@ -119,13 +95,93 @@ interface Intermedia {
hyperPara: object; // each trial hyperpara value
}
interface ExperimentInfo {
platform: string;
optimizeMode: string;
interface MetricDataRecord {
timestamp: number;
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 {
TableObj, Parameters, Experiment, AccurPoint, TrialNumber, TrialJob,
DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalResult, FinalType,
TooltipForIntermediate, SearchSpace, Intermedia, ExperimentInfo
TableObj, TableRecord, Parameters, ExperimentProfile, AccurPoint,
DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalType,
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 @@
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": false,
"forin": true,
......@@ -40,19 +36,9 @@
"static-before-instance",
"variables-before-functions"
],
"no-any": true,
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"log",
"error",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-console": false,
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-debugger": true,
......@@ -64,7 +50,6 @@
"no-switch-case-fall-through": true,
"no-trailing-whitespace": false,
"no-unused-expression": true,
"no-use-before-declare": true,
"one-line": [
true,
"check-catch",
......@@ -123,4 +108,4 @@
"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