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 (
......
...@@ -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,15 +134,12 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -139,15 +134,12 @@ 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;
...@@ -180,30 +172,22 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -180,30 +172,22 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
} }
}); });
} }
if (this._isMounted) {
this.setState({ filterSource: filterSource }); this.setState({ filterSource: filterSource });
}
this.drawIntermediate(filterSource); this.drawIntermediate(filterSource);
} }
const counts = this.state.clickCounts + 1; const counts = this.state.clickCounts + 1;
if (this._isMounted) {
this.setState({ isLoadconfirmBtn: false, clickCounts: counts }); 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,7 +352,6 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -361,7 +352,6 @@ 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);
...@@ -370,18 +360,15 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -370,18 +360,15 @@ class Para extends React.Component<ParaProps, ParaState> {
}); });
} }
} }
}
// get percent value number // get percent value number
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
getOption = (dataObj: ParaObj, lengthofTrials: number) => { getOption = (dataObj: ParaObj, lengthofTrials: number) => {
...@@ -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 (
......
// 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 {
return result;
} }
if (second > 0) {
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,
}; };
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