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])
});
}
}
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 (
......
......@@ -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(() => ({
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(() => ({
this.setState({
interSource: option
}));
}
});
} else {
const nullData = {
grid: {
......@@ -139,15 +134,12 @@ 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;
......@@ -180,30 +172,22 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
}
});
}
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 });
}
});
}
}
switchTurn = (checked: boolean) => {
if (this._isMounted) {
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
});
}
} 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,7 +352,6 @@ 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);
......@@ -370,18 +360,15 @@ class Para extends React.Component<ParaProps, ParaState> {
});
}
}
}
// get percent value number
percentNum = (value: string) => {
let vals = parseFloat(value);
if (this._isMounted) {
this.setState({ percent: vals }, () => {
this.reInit();
});
}
}
// deal with response data into pic data
getOption = (dataObj: ParaObj, lengthofTrials: number) => {
......@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> {
}
};
// please wait the data
if (this._isMounted) {
this.setState(() => ({
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, });
}
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(() => ({
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 (
......
// 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,
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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