From a65532ca7ab1c90ff54e49e318a0aabb607d337d Mon Sep 17 00:00:00 2001 From: horizon365 Date: Thu, 21 Mar 2019 17:05:56 +0800 Subject: [PATCH 1/6] Wrong spell (#893) Wrong spell in INFO log. --- src/nni_manager/core/nnimanager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nni_manager/core/nnimanager.ts b/src/nni_manager/core/nnimanager.ts index c96302cd92..45549b96ef 100644 --- a/src/nni_manager/core/nnimanager.ts +++ b/src/nni_manager/core/nnimanager.ts @@ -630,7 +630,7 @@ class NNIManager implements Manager { } private async onTunerCommand(commandType: string, content: string): Promise { - this.log.info(`NNIManaer received command from dispatcher: ${commandType}, ${content}`); + this.log.info(`NNIManager received command from dispatcher: ${commandType}, ${content}`); switch (commandType) { case INITIALIZED: // Tuner is intialized, search space is set, request tuner to generate hyper parameters From 70695fe0ce70602c917f1a85b48f6f6c3d6d7cab Mon Sep 17 00:00:00 2001 From: Lijiao <35484733+lvybriage@users.noreply.github.com> Date: Fri, 22 Mar 2019 10:45:48 +0800 Subject: [PATCH 2/6] [WebUI] Add intermediate result and optimize hyper-parameter graph (#892) * [WebUI] Add intermediate result and optimize hyper-parameter graph * update * fix comment --- src/webui/src/components/Overview.tsx | 9 +- src/webui/src/components/SlideBar.tsx | 2 +- src/webui/src/components/TrialsDetail.tsx | 364 +++++++----------- src/webui/src/components/overview/Title1.tsx | 2 +- .../trial-detail/DefaultMetricPoint.tsx | 139 +++++++ .../src/components/trial-detail/Duration.tsx | 99 ++--- .../components/trial-detail/Intermeidate.tsx | 291 ++++++++++++++ .../src/components/trial-detail/Para.tsx | 334 ++++++++-------- .../src/components/trial-detail/TableList.tsx | 37 +- src/webui/src/static/interface.ts | 24 +- src/webui/src/static/style/overviewTitle.scss | 14 +- src/webui/src/static/style/para.scss | 36 +- 12 files changed, 841 insertions(+), 510 deletions(-) create mode 100644 src/webui/src/components/trial-detail/DefaultMetricPoint.tsx create mode 100644 src/webui/src/components/trial-detail/Intermeidate.tsx diff --git a/src/webui/src/components/Overview.tsx b/src/webui/src/components/Overview.tsx index 9e236b57f6..7acbbc9bbe 100644 --- a/src/webui/src/components/Overview.tsx +++ b/src/webui/src/components/Overview.tsx @@ -2,10 +2,7 @@ import * as React from 'react'; import axios from 'axios'; import { Row, Col } from 'antd'; import { MANAGER_IP } from '../static/const'; -import { - Experiment, TableObj, - Parameters, TrialNumber -} from '../static/interface'; +import { Experiment, TableObj, Parameters, TrialNumber } from '../static/interface'; import { getFinal } from '../static/function'; import SuccessTable from './overview/SuccessTable'; import Title1 from './overview/Title1'; @@ -117,7 +114,9 @@ class Overview extends React.Component<{}, OverviewState> { clusterMetaData: clusterMetaData ? clusterMetaData : undefined }); // search space format loguniform max and min - const searchSpace = JSON.parse(sessionData.params.searchSpace); + const temp = sessionData.params.searchSpace; + const searchSpace = temp !== undefined + ? JSON.parse(temp) : {}; Object.keys(searchSpace).map(item => { const key = searchSpace[item]._type; let value = searchSpace[item]._value; diff --git a/src/webui/src/components/SlideBar.tsx b/src/webui/src/components/SlideBar.tsx index b24b7fdee2..fd98562606 100644 --- a/src/webui/src/components/SlideBar.tsx +++ b/src/webui/src/components/SlideBar.tsx @@ -141,6 +141,7 @@ class SlideBar extends React.Component<{}, SliderState> { } }); } + getNNIversion = () => { axios(`${MANAGER_IP}/version`, { method: 'GET' @@ -233,7 +234,6 @@ class SlideBar extends React.Component<{}, SliderState> { Version: {version} - ); } diff --git a/src/webui/src/components/TrialsDetail.tsx b/src/webui/src/components/TrialsDetail.tsx index e87c8317a0..63c5c702d1 100644 --- a/src/webui/src/components/TrialsDetail.tsx +++ b/src/webui/src/components/TrialsDetail.tsx @@ -1,14 +1,15 @@ import * as React from 'react'; import axios from 'axios'; import { MANAGER_IP } from '../static/const'; -import { Row, Col, Tabs, Input, Select, Button } from 'antd'; +import { Row, Col, Tabs, Input, Select, Button, Icon } from 'antd'; const Option = Select.Option; -import { TableObj, Parameters, DetailAccurPoint, TooltipForAccuracy } from '../static/interface'; -import { getFinalResult, getFinal } from '../static/function'; -import Accuracy from './overview/Accuracy'; +import { TableObj, Parameters } from '../static/interface'; +import { getFinal } from '../static/function'; +import DefaultPoint from './trial-detail/DefaultMetricPoint'; import Duration from './trial-detail/Duration'; import Title1 from './overview/Title1'; import Para from './trial-detail/Para'; +import Intermediate from './trial-detail/Intermeidate'; import TableList from './trial-detail/TableList'; const TabPane = Tabs.TabPane; import '../static/style/trialsDetail.scss'; @@ -22,6 +23,8 @@ interface TrialDetailState { experimentStatus: string; entriesTable: number; experimentPlatform: string; + searchSpace: string; + defaultMetric?: Array; } class TrialsDetail extends React.Component<{}, TrialDetailState> { @@ -33,6 +36,26 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { public tableList: TableList | null; + private titleOfacc = ( + + ); + + private titleOfhyper = ( + + ); + + private titleOfDuration = ( + + ); + + private titleOfIntermediate = ( + // +
+ + Intermediate Result +
+ ); + constructor(props: {}) { super(props); @@ -44,103 +67,29 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { experimentStatus: '', entriesTable: 20, isHasSearch: false, - experimentPlatform: '' + experimentPlatform: '', + searchSpace: '', + defaultMetric: [0, 1] }; } - // trial accuracy graph - drawPointGraph = () => { - axios(`${MANAGER_IP}/trial-jobs`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200 && this._isMounted) { - const accData = res.data; - const accSource: Array = []; - Object.keys(accData).map(item => { - if (accData[item].status === 'SUCCEEDED' && accData[item].finalMetricData) { - let searchSpace: object = {}; - const acc = getFinalResult(accData[item].finalMetricData); - if (accData[item].hyperParameters) { - searchSpace = JSON.parse(accData[item].hyperParameters).parameters; - } - accSource.push({ - acc: acc, - index: accData[item].sequenceId, - searchSpace: JSON.stringify(searchSpace) - }); - } - }); - const resultList: Array[] = []; - Object.keys(accSource).map(item => { - const items = accSource[item]; - let temp: Array; - temp = [items.index, items.acc, JSON.parse(items.searchSpace)]; - resultList.push(temp); - }); - const allAcuracy = { - tooltip: { - trigger: 'item', - enterable: true, - position: function (point: Array, data: TooltipForAccuracy) { - if (data.data[0] < resultList.length / 2) { - return [point[0], 80]; - } else { - return [point[0] - 300, 80]; - } - }, - formatter: function (data: TooltipForAccuracy) { - const result = '
' + - '
Trial No: ' + data.data[0] + '
' + - '
Default Metric: ' + data.data[1] + '
' + - '
Parameters: ' + - '
' + JSON.stringify(data.data[2], null, 4) + '
' + - '
' + - '
'; - return result; - } - }, - xAxis: { - name: 'Trial', - type: 'category', - }, - yAxis: { - name: 'Default Metric', - type: 'value', - }, - series: [{ - symbolSize: 6, - type: 'scatter', - data: resultList - }] - }; - - this.setState({ accSource: allAcuracy }, () => { - if (resultList.length === 0) { - this.setState({ - accNodata: 'No data' - }); - } else { - this.setState({ - accNodata: '' - }); - } - }); - } - }); - } - - drawTableList = () => { + getDetailSource = () => { this.isOffIntervals(); - axios.get(`${MANAGER_IP}/trial-jobs`) - .then(res => { + axios + .all([ + axios.get(`${MANAGER_IP}/trial-jobs`), + axios.get(`${MANAGER_IP}/metric-data`) + ]) + .then(axios.spread((res, res1) => { if (res.status === 200) { const trialJobs = res.data; + const metricSource = res1.data; const trialTable: Array = []; Object.keys(trialJobs).map(item => { // only succeeded trials have finalMetricData let desc: Parameters = { - parameters: {} + parameters: {}, + intermediate: [] }; let duration = 0; const id = trialJobs[item].id !== undefined @@ -171,7 +120,23 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { if (trialJobs[item].logPath !== undefined) { desc.logPath = trialJobs[item].logPath; } + const acc = getFinal(trialJobs[item].finalMetricData); + // deal with intermediate result list + const mediate: Array = []; + Object.keys(metricSource).map(key => { + const items = metricSource[key]; + if (items.trialJobId === id) { + // succeed trial, last intermediate result is final result + // final result format may be object + if (typeof JSON.parse(items.data) === 'object') { + mediate.push(JSON.parse(items.data).default); + } else { + mediate.push(JSON.parse(items.data)); + } + } + }); + desc.intermediate = mediate; trialTable.push({ key: trialTable.length, sequenceId: trialJobs[item].sequenceId, @@ -182,7 +147,33 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { description: desc }); }); - // search part data + // get acc max and min for hyper-parameters graph color bar max and min + const sortSource = JSON.parse(JSON.stringify(trialTable)); + sortSource.sort((a: TableObj, b: TableObj) => { + if (a.acc !== undefined && b.acc !== undefined) { + return JSON.parse(a.acc.default) - JSON.parse(b.acc.default); + } else { + return NaN; + } + }); + + if (this._isMounted && sortSource !== undefined) { + + const hyperMin = sortSource[0].acc !== undefined + ? + sortSource[0].acc.default + : + '0'; + const max = sortSource[sortSource.length - 1].acc; + let maxResult = '1'; + if (max !== undefined) { + maxResult = max.default; + } + this.setState(() => ({ + defaultMetric: [JSON.parse(hyperMin), JSON.parse(maxResult)] + })); + } + // update search data result const { searchResultSource } = this.state; if (searchResultSource.length !== 0) { const temp: Array = []; @@ -211,111 +202,29 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { })); } } - }); - } - - // update all data in table - drawAllTableList = () => { - this.isOffIntervals(); - axios.get(`${MANAGER_IP}/trial-jobs`) - .then(res => { - if (res.status === 200) { - const trialJobs = res.data; - const trialTable: Array = []; - Object.keys(trialJobs).map(item => { - // only succeeded trials have finalMetricData - let desc: Parameters = { - parameters: {} - }; - let duration = 0; - const id = trialJobs[item].id !== undefined - ? trialJobs[item].id - : ''; - const status = trialJobs[item].status !== undefined - ? trialJobs[item].status - : ''; - const begin = trialJobs[item].startTime; - const end = trialJobs[item].endTime; - if (begin) { - if (end) { - duration = (end - begin) / 1000; - } else { - duration = (new Date().getTime() - begin) / 1000; - } - } - if (trialJobs[item].hyperParameters !== undefined) { - const getPara = JSON.parse(trialJobs[item].hyperParameters[0]).parameters; - if (typeof getPara === 'string') { - desc.parameters = JSON.parse(getPara); - } else { - desc.parameters = getPara; - } - } else { - desc.parameters = { error: 'This trial\'s parameters are not available.' }; - } - if (trialJobs[item].logPath !== undefined) { - desc.logPath = trialJobs[item].logPath; - } - const acc = getFinal(trialJobs[item].finalMetricData); - trialTable.push({ - key: trialTable.length, - sequenceId: trialJobs[item].sequenceId, - id: id, - status: status, - duration: duration, - acc: acc, - description: desc - }); - }); - if (this._isMounted) { - this.setState(() => ({ - tableListSource: trialTable, - searchResultSource: trialTable - })); - } - } - }); - } - - callback = (key: string) => { - - switch (key) { - case '1': - window.clearInterval(Para.intervalIDPara); - window.clearInterval(Duration.intervalDuration); - this.drawPointGraph(); - this.interAccuracy = window.setInterval(this.drawPointGraph, 10000); - break; - - case '2': - this.isOffIntervals(); - window.clearInterval(this.interAccuracy); - window.clearInterval(Duration.intervalDuration); - break; - - case '3': - this.isOffIntervals(); - window.clearInterval(this.interAccuracy); - window.clearInterval(Para.intervalIDPara); - break; - - default: - } + })); } // search a trial by trial No. & trial id searchTrial = (event: React.ChangeEvent) => { const targetValue = event.target.value; if (targetValue === '' || targetValue === ' ') { - this.drawAllTableList(); - this.interAllTableList = window.setInterval(this.drawAllTableList, 10000); + const { tableListSource } = this.state; + if (this._isMounted) { + this.setState(() => ({ + isHasSearch: false, + tableListSource: tableListSource, + })); + } } else { - window.clearInterval(this.interAllTableList); const { tableListSource } = this.state; const searchResultList: Array = []; Object.keys(tableListSource).map(key => { const item = tableListSource[key]; - if (item.sequenceId.toString() === targetValue || item.id.includes(targetValue)) { + if (item.sequenceId.toString() === targetValue + || item.id.includes(targetValue) + || item.status.includes(targetValue) + ) { searchResultList.push(item); } }); @@ -328,6 +237,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { } } + // close timer isOffIntervals = () => { axios(`${MANAGER_IP}/check-status`, { method: 'GET' @@ -338,11 +248,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { case 'DONE': case 'ERROR': case 'STOPPED': - window.clearInterval(this.interAccuracy); window.clearInterval(this.interTableList); - window.clearInterval(Duration.intervalDuration); - window.clearInterval(Para.intervalIDPara); - window.clearInterval(this.interAllTableList); break; default: } @@ -362,7 +268,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { this.setState(() => ({ entriesTable: 100 })); break; case 'all': - this.setState(() => ({ entriesTable: 100000 })); + const { tableListSource } = this.state; + this.setState(() => ({ entriesTable: tableListSource.length })); break; default: } @@ -379,13 +286,14 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { .then(res => { if (res.status === 200) { const trainingPlatform = res.data.params.trainingServicePlatform !== undefined - ? - res.data.params.trainingServicePlatform - : - ''; + ? + res.data.params.trainingServicePlatform + : + ''; if (this._isMounted) { this.setState({ - experimentPlatform: trainingPlatform + experimentPlatform: trainingPlatform, + searchSpace: res.data.params.searchSpace }); } } @@ -395,56 +303,55 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { componentDidMount() { this._isMounted = true; - this.drawTableList(); - this.drawPointGraph(); - this.interTableList = window.setInterval(this.drawTableList, 10000); - this.interAccuracy = window.setInterval(this.drawPointGraph, 10000); + this.getDetailSource(); + this.interTableList = window.setInterval(this.getDetailSource, 10000); this.checkExperimentPlatform(); } componentWillUnmount() { this._isMounted = false; window.clearInterval(this.interTableList); - window.clearInterval(this.interAccuracy); } render() { - const { accSource, accNodata, tableListSource, - entriesTable, searchResultSource, isHasSearch, - experimentPlatform + + const { + tableListSource, searchResultSource, isHasSearch, + entriesTable, experimentPlatform, searchSpace, + defaultMetric } = this.state; - const titleOfacc = ( - - ); - const titleOfhyper = ( - - ); - const titleOfDuration = ( - - ); + const source = isHasSearch ? searchResultSource : tableListSource; return (
- - + + - + + + + + - - + + - - + +
{/* trial table list */} - + show @@ -472,12 +379,11 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { - {/* Search: */} @@ -485,10 +391,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { this.tableList = tabList} /> diff --git a/src/webui/src/components/overview/Title1.tsx b/src/webui/src/components/overview/Title1.tsx index 5af571ab08..cdaeef3481 100644 --- a/src/webui/src/components/overview/Title1.tsx +++ b/src/webui/src/components/overview/Title1.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; interface Title1Props { text: string; - icon: string; + icon?: string; bgcolor?: string; } diff --git a/src/webui/src/components/trial-detail/DefaultMetricPoint.tsx b/src/webui/src/components/trial-detail/DefaultMetricPoint.tsx new file mode 100644 index 0000000000..c53db0a558 --- /dev/null +++ b/src/webui/src/components/trial-detail/DefaultMetricPoint.tsx @@ -0,0 +1,139 @@ +import * as React from 'react'; +import ReactEcharts from 'echarts-for-react'; +import { TableObj, DetailAccurPoint, TooltipForAccuracy } from '../../static/interface'; +require('echarts/lib/chart/scatter'); +require('echarts/lib/component/tooltip'); +require('echarts/lib/component/title'); + +interface DefaultPointProps { + showSource: Array; + height: number; +} + +interface DefaultPointState { + defaultSource: object; + accNodata: string; +} + +class DefaultPoint extends React.Component { + public _isMounted = false; + + constructor(props: DefaultPointProps) { + super(props); + this.state = { + defaultSource: {}, + accNodata: 'No data' + }; + } + + defaultMetric = (showSource: Array) => { + const accSource: Array = []; + Object.keys(showSource).map(item => { + const temp = showSource[item]; + if (temp.status === 'SUCCEEDED' && temp.acc.default !== undefined) { + const searchSpace = temp.description.parameters; + accSource.push({ + acc: temp.acc.default, + index: temp.sequenceId, + searchSpace: JSON.stringify(searchSpace) + }); + } + }); + const resultList: Array[] = []; + Object.keys(accSource).map(item => { + const items = accSource[item]; + let temp: Array; + temp = [items.index, items.acc, JSON.parse(items.searchSpace)]; + resultList.push(temp); + }); + + const allAcuracy = { + grid: { + left: '8%' + }, + tooltip: { + trigger: 'item', + enterable: true, + position: function (point: Array, data: TooltipForAccuracy) { + if (data.data[0] < resultList.length / 2) { + return [point[0], 80]; + } else { + return [point[0] - 300, 80]; + } + }, + formatter: function (data: TooltipForAccuracy) { + const result = '
' + + '
Trial No: ' + data.data[0] + '
' + + '
Default Metric: ' + data.data[1] + '
' + + '
Parameters: ' + + '
' + JSON.stringify(data.data[2], null, 4) + '
' + + '
' + + '
'; + return result; + } + }, + xAxis: { + name: 'Trial', + type: 'category', + }, + yAxis: { + name: 'Default Metric', + type: 'value', + }, + series: [{ + symbolSize: 6, + type: 'scatter', + data: resultList + }] + }; + if (this._isMounted === true) { + this.setState({ defaultSource: allAcuracy }, () => { + if (resultList.length === 0) { + this.setState({ + accNodata: 'No data' + }); + } else { + this.setState({ + accNodata: '' + }); + } + }); + } + } + + // update parent component state + componentWillReceiveProps(nextProps: DefaultPointProps) { + const showSource = nextProps.showSource; + this.defaultMetric(showSource); + } + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + + render() { + const { height } = this.props; + const { defaultSource, accNodata } = this.state; + return ( +
+ +
{accNodata}
+
+ ); + } +} + +export default DefaultPoint; \ No newline at end of file diff --git a/src/webui/src/components/trial-detail/Duration.tsx b/src/webui/src/components/trial-detail/Duration.tsx index 367f916faa..d1405a04fe 100644 --- a/src/webui/src/components/trial-detail/Duration.tsx +++ b/src/webui/src/components/trial-detail/Duration.tsx @@ -1,43 +1,38 @@ import * as React from 'react'; -import axios from 'axios'; -import { MANAGER_IP } from '../../static/const'; import ReactEcharts from 'echarts-for-react'; -const echarts = require('echarts/lib/echarts'); +import { TableObj } from 'src/static/interface'; require('echarts/lib/chart/bar'); require('echarts/lib/component/tooltip'); require('echarts/lib/component/title'); -echarts.registerTheme('my_theme', { - color: '#3c8dbc' -}); interface Runtrial { trialId: Array; trialTime: Array; } +interface DurationProps { + source: Array; +} + interface DurationState { durationSource: {}; } -class Duration extends React.Component<{}, DurationState> { +class Duration extends React.Component { - static intervalDuration = 1; public _isMounted = false; - constructor(props: {}) { - super(props); + constructor(props: DurationProps) { + super(props); this.state = { - durationSource: {} }; } getOption = (dataObj: Runtrial) => { - let xAxis = dataObj.trialTime; - let yAxis = dataObj.trialId; - let option = { + return { tooltip: { trigger: 'axis', axisPointer: { @@ -50,6 +45,7 @@ class Duration extends React.Component<{}, DurationState> { left: '1%', right: '4%' }, + dataZoom: [{ type: 'slider', name: 'trial', @@ -69,65 +65,52 @@ class Duration extends React.Component<{}, DurationState> { yAxis: { name: 'Trial', type: 'category', - data: yAxis + data: dataObj.trialId }, series: [{ type: 'bar', - data: xAxis + data: dataObj.trialTime }] }; - return option; } - drawRunGraph = () => { - - axios(`${MANAGER_IP}/trial-jobs`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200) { - const trialJobs = res.data; - const trialId: Array = []; - const trialTime: Array = []; - const trialRun: Array = []; - Object.keys(trialJobs).map(item => { - if (trialJobs[item].status !== 'WAITING') { - let duration: number = 0; - const end = trialJobs[item].endTime; - const start = trialJobs[item].startTime; - if (start && end) { - duration = (end - start) / 1000; - } else { - duration = (new Date().getTime() - start) / 1000; - } - trialId.push(trialJobs[item].sequenceId); - trialTime.push(duration); - } - }); - trialRun.push({ - trialId: trialId, - trialTime: trialTime - }); - if (this._isMounted && res.status === 200) { - this.setState({ - durationSource: this.getOption(trialRun[0]) - }); - } - } + drawDurationGraph = (trialJobs: Array) => { + + const trialId: Array = []; + const trialTime: Array = []; + const trialRun: Array = []; + Object.keys(trialJobs).map(item => { + const temp = trialJobs[item]; + if (temp.status !== 'WAITING') { + trialId.push(temp.sequenceId); + trialTime.push(temp.duration); + } + }); + trialRun.push({ + trialId: trialId, + trialTime: trialTime + }); + if (this._isMounted) { + this.setState({ + durationSource: this.getOption(trialRun[0]) }); + } } - componentDidMount() { + componentWillReceiveProps(nextProps: DurationProps) { + const trialJobs = nextProps.source; + this.drawDurationGraph(trialJobs); + } + componentDidMount() { this._isMounted = true; - this.drawRunGraph(); - Duration.intervalDuration = window.setInterval(this.drawRunGraph, 10000); + // init: user don't search + const {source} = this.props; + this.drawDurationGraph(source); } componentWillUnmount() { - this._isMounted = false; - window.clearInterval(Duration.intervalDuration); } render() { @@ -136,7 +119,7 @@ class Duration extends React.Component<{}, DurationState> {
diff --git a/src/webui/src/components/trial-detail/Intermeidate.tsx b/src/webui/src/components/trial-detail/Intermeidate.tsx new file mode 100644 index 0000000000..dd19625266 --- /dev/null +++ b/src/webui/src/components/trial-detail/Intermeidate.tsx @@ -0,0 +1,291 @@ +import * as React from 'react'; +import { Row, Col, Button, Switch } from 'antd'; +import { TooltipForIntermediate, TableObj } from '../../static/interface'; +import ReactEcharts from 'echarts-for-react'; +require('echarts/lib/component/tooltip'); +require('echarts/lib/component/title'); + +interface Intermedia { + name: string; // id + type: string; + data: Array; // intermediate data + hyperPara: object; // each trial hyperpara value +} +interface IntermediateState { + interSource: object; + filterSource: Array; + eachIntermediateNum: number; // trial's intermediate number count + isLoadconfirmBtn: boolean; + isFilter: boolean; +} + +interface IntermediateProps { + source: Array; +} + +class Intermediate extends React.Component { + + static intervalMediate = 1; + public _isMounted = false; + public pointInput: HTMLInputElement | null; + public minValInput: HTMLInputElement | null; + public maxValInput: HTMLInputElement | null; + + constructor(props: IntermediateProps) { + super(props); + this.state = { + interSource: {}, + filterSource: [], + eachIntermediateNum: 1, + isLoadconfirmBtn: false, + isFilter: false + }; + } + + initMediate = () => { + const option = { + grid: { + left: '5%', + top: 40, + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + }, + yAxis: { + type: 'value', + name: 'Scape' + } + }; + if (this._isMounted) { + this.setState(() => ({ + interSource: option + })); + } + } + + drawIntermediate = (source: Array) => { + if (source.length > 0) { + const trialIntermediate: Array = []; + Object.keys(source).map(item => { + const temp = source[item]; + trialIntermediate.push({ + name: temp.id, + data: temp.description.intermediate, + type: 'line', + hyperPara: temp.description.parameters + }); + }); + // find max intermediate number + trialIntermediate.sort((a, b) => { return (b.data.length - a.data.length); }); + const legend: Array = []; + // max length + const length = trialIntermediate[0].data.length; + const xAxis: Array = []; + Object.keys(trialIntermediate).map(item => { + const temp = trialIntermediate[item]; + legend.push(temp.name); + }); + for (let i = 1; i <= length; i++) { + xAxis.push(i); + } + const option = { + tooltip: { + trigger: 'item', + enterable: true, + position: function (point: Array, data: TooltipForIntermediate) { + if (data.dataIndex < length / 2) { + return [point[0], 80]; + } else { + return [point[0] - 300, 80]; + } + }, + formatter: function (data: TooltipForIntermediate) { + const trialId = data.seriesName; + let obj = {}; + const temp = trialIntermediate.find(key => key.name === trialId); + if (temp !== undefined) { + obj = temp.hyperPara; + } + return '
' + + '
Trial Id: ' + trialId + '
' + + '
Intermediate: ' + data.data + '
' + + '
Parameters: ' + + '
' + JSON.stringify(obj, null, 4) + '
' + + '
' + + '
'; + } + }, + grid: { + left: '5%', + top: 40, + containLabel: true + }, + xAxis: { + type: 'category', + name: 'Scape', + boundaryGap: false, + data: xAxis + }, + yAxis: { + type: 'value', + name: 'Intermediate' + }, + series: trialIntermediate + }; + if (this._isMounted) { + this.setState(() => ({ + interSource: option + })); + } + } else { + this.initMediate(); + } + } + + // confirm btn function [filter data] + filterLines = () => { + if (this._isMounted) { + const filterSource: Array = []; + 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 { + 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); + } + this.setState({ isLoadconfirmBtn: false }); + }); + } + } + + 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); + } + + componentWillReceiveProps(nextProps: IntermediateProps) { + const { isFilter, filterSource } = this.state; + if (isFilter === true) { + const pointVal = this.pointInput !== null ? this.pointInput.value : ''; + const minVal = this.minValInput !== null ? this.minValInput.value : ''; + if (pointVal === '' && minVal === '') { + this.drawIntermediate(nextProps.source); + } else { + this.drawIntermediate(filterSource); + } + } else { + this.drawIntermediate(nextProps.source); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + + render() { + const { interSource, isLoadconfirmBtn, isFilter } = this.state; + + return ( +
+ {/* style in para.scss */} + + + + {/* filter message */} + filter + + + { + isFilter + ? +
+ + Scape + this.pointInput = input} + className="strange" + /> + + + Intermediate Result + this.minValInput = input} + /> + - + this.maxValInput = input} + /> + + +
+ : + + } +
+ + + +
+ ); + } +} + +export default Intermediate; \ No newline at end of file diff --git a/src/webui/src/components/trial-detail/Para.tsx b/src/webui/src/components/trial-detail/Para.tsx index 1faa6f1abe..0625ad9b33 100644 --- a/src/webui/src/components/trial-detail/Para.tsx +++ b/src/webui/src/components/trial-detail/Para.tsx @@ -1,10 +1,7 @@ import * as React from 'react'; -import axios from 'axios'; -import { MANAGER_IP } from '../../static/const'; import ReactEcharts from 'echarts-for-react'; import { Row, Col, Select, Button, message } from 'antd'; -import { ParaObj, VisualMapValue, Dimobj } from '../../static/interface'; -import { getFinalResult } from '../../static/function'; +import { ParaObj, Dimobj, TableObj, SearchSpace } from '../../static/interface'; const Option = Select.Option; require('echarts/lib/chart/parallel'); require('echarts/lib/component/tooltip'); @@ -20,12 +17,14 @@ interface ParaState { swapAxisArr: Array; percent: number; paraNodata: string; - visualValue: VisualMapValue; + barColorMax: number; + barColorMin: number; } -interface SearchSpace { - _value: Array; - _type: string; +interface ParaProps { + dataSource: Array; + expSearchSpace: string; + defaultMetric: Array | undefined; } message.config({ @@ -33,12 +32,18 @@ message.config({ duration: 2, }); -class Para extends React.Component<{}, ParaState> { +class Para extends React.Component { - static intervalIDPara = 4; public _isMounted = false; - constructor(props: {}) { + private chartMulineStyle = { + width: '100%', + height: 392, + margin: '0 auto', + padding: '0 15 10 15' + }; + + constructor(props: ParaProps) { super(props); this.state = { option: {}, @@ -53,28 +58,37 @@ class Para extends React.Component<{}, ParaState> { swapAxisArr: [], percent: 0, paraNodata: '', - visualValue: { - minAccuracy: 0, - maxAccuracy: 1 - } + barColorMax: this.props.defaultMetric !== undefined + ? + this.props.defaultMetric[1] + : + 1, + barColorMin: this.props.defaultMetric !== undefined + ? + this.props.defaultMetric[0] + : + 1 }; } + componentDidMount() { + + this._isMounted = true; + this.reInit(); + } + getParallelAxis = ( dimName: Array, searchRange: SearchSpace, - parallelAxis: Array, accPara: Array, + accPara: Array, eachTrialParams: Array, paraYdata: number[][] ) => { if (this._isMounted) { this.setState(() => ({ - dimName: dimName, - visualValue: { - minAccuracy: accPara.length !== 0 ? Math.min(...accPara) : 0, - maxAccuracy: accPara.length !== 0 ? Math.max(...accPara) : 1 - } + dimName: dimName })); } + const parallelAxis: Array = []; // search space range and specific value [only number] for (let i = 0; i < dimName.length; i++) { const searchKey = searchRange[dimName[i]]; @@ -107,7 +121,24 @@ class Para extends React.Component<{}, ParaState> { dim: i, name: dimName[i], type: 'category', - data: data + data: data, + boundaryGap: true, + axisLine: { + lineStyle: { + type: 'dotted', // axis type,solid,dashed,dotted + width: 1 + } + }, + axisTick: { + show: true, + interval: 0, + alignWithLabel: true, + }, + axisLabel: { + show: true, + interval: 0, + // rotate: 30 + }, }); break; // support log distribute @@ -143,100 +174,70 @@ class Para extends React.Component<{}, ParaState> { } paraYdata.push(temp); }); + // add acc + Object.keys(paraYdata).map(item => { + paraYdata[item].push(accPara[item]); + }); + // according acc to sort ydata + if (paraYdata.length !== 0) { + const len = paraYdata[0].length - 1; + paraYdata.sort((a, b) => b[len] - a[len]); + } + const paraData = { + parallelAxis: parallelAxis, + data: paraYdata + }; + const { percent, swapAxisArr } = this.state; + // need to cut down the data + if (percent !== 0) { + const linesNum = paraData.data.length; + const len = Math.floor(linesNum * percent); + paraData.data.length = len; + } + // need to swap the yAxis + if (swapAxisArr.length >= 2) { + this.swapGraph(paraData, swapAxisArr); + } + this.getOption(paraData); } - hyperParaPic = () => { - axios - .all([ - axios.get(`${MANAGER_IP}/trial-jobs`), - axios.get(`${MANAGER_IP}/experiment`) - ]) - .then(axios.spread((res, res1) => { - if (res.status === 200 && res1.status === 200) { - if (res.data.length !== 0) { - const accParaData = res.data; - const accPara: Array = []; - // specific value array - const eachTrialParams: Array = []; - const parallelAxis: Array = []; - const paraYdata: number[][] = []; - // experiment interface search space obj - const searchRange = JSON.parse(res1.data.params.searchSpace); - const reallySearchKeys = Object.keys(searchRange); - // trial-jobs interface list - Object.keys(accParaData).map(item => { - if (accParaData[item].status === 'SUCCEEDED') { - const finalData = accParaData[item].finalMetricData; - if (finalData && accParaData[item].hyperParameters) { - const result = getFinalResult(finalData); - accPara.push(result); - // get dim and every line specific number - const temp = JSON.parse(accParaData[item].hyperParameters).parameters; - eachTrialParams.push(temp); - } - } - }); - const dimName = reallySearchKeys; - this.getParallelAxis(dimName, searchRange, parallelAxis, accPara, eachTrialParams, paraYdata); - - // add acc - Object.keys(paraYdata).map(item => { - paraYdata[item].push(accPara[item]); - }); - - // according acc to sort ydata - if (paraYdata.length !== 0) { - const len = paraYdata[0].length - 1; - paraYdata.sort((a, b) => b[len] - a[len]); - } - if (this._isMounted) { - this.setState(() => ({ - paraBack: { - parallelAxis: parallelAxis, - data: paraYdata - } - })); - } - const { percent, swapAxisArr, paraBack } = this.state; - // need to cut down the data - if (percent !== 0) { - const linesNum = paraBack.data.length; - const len = Math.floor(linesNum * percent); - paraBack.data.length = len; - } - // need to swap the yAxis - if (swapAxisArr.length >= 2) { - this.swapGraph(paraBack, swapAxisArr); - } - this.getOption(paraBack); - } - } - })); + hyperParaPic = (dataSource: Array, searchSpace: string) => { + const accPara: Array = []; + // specific value array + const eachTrialParams: Array = []; + const paraYdata: number[][] = []; + // experiment interface search space obj + const searchRange = JSON.parse(searchSpace); + const dimName = Object.keys(searchRange); + // trial-jobs interface list + Object.keys(dataSource).map(item => { + const temp = dataSource[item]; + if (temp.status === 'SUCCEEDED') { + accPara.push(temp.acc.default); + eachTrialParams.push(temp.description.parameters); + } + }); + this.getParallelAxis(dimName, searchRange, accPara, eachTrialParams, paraYdata); } // get percent value number percentNum = (value: string) => { - window.clearInterval(Para.intervalIDPara); let vals = parseFloat(value); if (this._isMounted) { - this.setState(() => ({ - percent: vals - })); + this.setState({ percent: vals }, () => { + this.reInit(); + }); } - this.hyperParaPic(); - Para.intervalIDPara = window.setInterval(this.hyperParaPic, 10000); } // deal with response data into pic data getOption = (dataObj: ParaObj) => { - const { visualValue } = this.state; + const {barColorMax, barColorMin} = this.state; let parallelAxis = dataObj.parallelAxis; let paralleData = dataObj.data; - const maxAccuracy = visualValue.maxAccuracy; - const minAccuracy = visualValue.minAccuracy; let visualMapObj = {}; - if (maxAccuracy === minAccuracy) { + if (barColorMax === barColorMin) { visualMapObj = { type: 'continuous', precision: 3, @@ -247,10 +248,9 @@ class Para extends React.Component<{}, ParaState> { bottom: '20px', type: 'continuous', precision: 3, - min: visualValue.minAccuracy, - max: visualValue.maxAccuracy, - color: ['#CA0000', '#FFC400', '#90EE90'], - calculable: true + min: barColorMin, + max: barColorMax, + color: ['#CA0000', '#FFC400', '#90EE90'] }; } let optionown = { @@ -317,11 +317,9 @@ class Para extends React.Component<{}, ParaState> { } } - swapBtn = () => { - - window.clearInterval(Para.intervalIDPara); - this.hyperParaPic(); - Para.intervalIDPara = window.setInterval(this.hyperParaPic, 10000); + reInit = () => { + const { dataSource, expSearchSpace } = this.props; + this.hyperParaPic(dataSource, expSearchSpace); } sortDimY = (a: Dimobj, b: Dimobj) => { @@ -330,79 +328,73 @@ class Para extends React.Component<{}, ParaState> { // deal with after swap data into pic swapGraph = (paraBack: ParaObj, swapAxisArr: string[]) => { - - if (swapAxisArr.length >= 2) { - const paralDim = paraBack.parallelAxis; - const paraData = paraBack.data; - let temp: number; - let dim1: number; - let dim2: number; - let bool1: boolean = false; - let bool2: boolean = false; - let bool3: boolean = false; - Object.keys(paralDim).map(item => { - const paral = paralDim[item]; - switch (paral.name) { - case swapAxisArr[0]: - dim1 = paral.dim; - bool1 = true; - break; - - case swapAxisArr[1]: - dim2 = paral.dim; - bool2 = true; - break; - - default: - } - if (bool1 && bool2) { - bool3 = true; + const paralDim = paraBack.parallelAxis; + const paraData = paraBack.data; + let temp: number; + let dim1: number; + let dim2: number; + let bool1: boolean = false; + let bool2: boolean = false; + let bool3: boolean = false; + Object.keys(paralDim).map(item => { + const paral = paralDim[item]; + switch (paral.name) { + case swapAxisArr[0]: + dim1 = paral.dim; + bool1 = true; + break; + + case swapAxisArr[1]: + dim2 = paral.dim; + bool2 = true; + break; + + default: + } + if (bool1 && bool2) { + bool3 = true; + } + }); + // swap dim's number + Object.keys(paralDim).map(item => { + if (bool3) { + if (paralDim[item].name === swapAxisArr[0]) { + paralDim[item].dim = dim2; } - }); - // swap dim's number - Object.keys(paralDim).map(item => { - if (bool3) { - if (paralDim[item].name === this.state.swapAxisArr[0]) { - paralDim[item].dim = dim2; - } - if (paralDim[item].name === this.state.swapAxisArr[1]) { - paralDim[item].dim = dim1; - } + if (paralDim[item].name === swapAxisArr[1]) { + paralDim[item].dim = dim1; } - }); - paralDim.sort(this.sortDimY); - // swap data array - Object.keys(paraData).map(paraItem => { - - temp = paraData[paraItem][dim1]; - paraData[paraItem][dim1] = paraData[paraItem][dim2]; - paraData[paraItem][dim2] = temp; - }); - } + } + }); + paralDim.sort(this.sortDimY); + // swap data array + Object.keys(paraData).map(paraItem => { + + temp = paraData[paraItem][dim1]; + paraData[paraItem][dim1] = paraData[paraItem][dim2]; + paraData[paraItem][dim2] = temp; + }); } - componentDidMount() { + componentWillReceiveProps(nextProps: ParaProps) { + const dataSource = nextProps.dataSource; + const expSearchSpace = nextProps.expSearchSpace; + const metricMax = nextProps.defaultMetric !== undefined + ? + nextProps.defaultMetric[1] + : + 1; + this.setState({ barColorMax: metricMax }); + this.hyperParaPic(dataSource, expSearchSpace); - this._isMounted = true; - // default draw all data pic - this.hyperParaPic(); - Para.intervalIDPara = window.setInterval(this.hyperParaPic, 10000); } componentWillUnmount() { - this._isMounted = false; - window.clearInterval(Para.intervalIDPara); } render() { const { option, paraNodata, dimName } = this.state; - const chartMulineStyle = { - width: '100%', - height: 392, - margin: '0 auto', - padding: '0 15 10 15' - }; return ( @@ -439,7 +431,7 @@ class Para extends React.Component<{}, ParaState> { @@ -449,8 +441,8 @@ class Para extends React.Component<{}, ParaState> {
{paraNodata}
diff --git a/src/webui/src/components/trial-detail/TableList.tsx b/src/webui/src/components/trial-detail/TableList.tsx index ce1cbb2990..3feb60c203 100644 --- a/src/webui/src/components/trial-detail/TableList.tsx +++ b/src/webui/src/components/trial-detail/TableList.tsx @@ -2,9 +2,8 @@ import * as React from 'react'; import axios from 'axios'; import ReactEcharts from 'echarts-for-react'; import { - Row, Input, Table, Button, Popconfirm, Modal, Checkbox + Row, Table, Button, Popconfirm, Modal, Checkbox } from 'antd'; -const { TextArea } = Input; const CheckboxGroup = Checkbox.Group; import { MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMN, COLUMN_INDEX } from '../../static/const'; import { convertDuration, intermediateGraphOption, killJob } from '../../static/function'; @@ -29,9 +28,7 @@ echarts.registerTheme('my_theme', { interface TableListProps { entries: number; tableSource: Array; - searchResult: Array; updateList: Function; - isHasSearch: boolean; platform: string; } @@ -78,6 +75,7 @@ class TableList extends React.Component { .then(res => { if (res.status === 200) { const intermediateArr: number[] = []; + // support intermediate result is dict Object.keys(res.data).map(item => { const temp = JSON.parse(res.data[item].data); if (typeof temp === 'object') { @@ -244,10 +242,8 @@ class TableList extends React.Component { render() { - const { entries, tableSource, searchResult, isHasSearch, updateList } = this.props; - const { intermediateOption, modalVisible, isShowColumn, columnSelected, - logMessage, logModal - } = this.state; + const { entries, tableSource, updateList } = this.props; + const { intermediateOption, modalVisible, isShowColumn, columnSelected} = this.state; let showTitle = COLUMN; let bgColor = ''; const trialJob: Array = []; @@ -340,7 +336,10 @@ class TableList extends React.Component { ); }, filters: trialJob, - onFilter: (value: string, record: TableObj) => record.status.indexOf(value) === 0, + 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; @@ -453,7 +452,7 @@ class TableList extends React.Component { @@ -475,24 +474,6 @@ class TableList extends React.Component { theme="my_theme" /> - - {/* trial log modal */} - -
-