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
...@@ -30,11 +30,11 @@ import { Deferred } from 'ts-deferred'; ...@@ -30,11 +30,11 @@ import { Deferred } from 'ts-deferred';
import { String } from 'typescript-string-operations'; import { String } from 'typescript-string-operations';
import * as component from '../../common/component'; import * as component from '../../common/component';
import { NNIError, NNIErrorNames } from '../../common/errors'; import { NNIError, NNIErrorNames } from '../../common/errors';
import { getExperimentId, getInitTrialSequenceId } from '../../common/experimentStartupInfo'; import { getExperimentId } from '../../common/experimentStartupInfo';
import { getLogger, Logger } from '../../common/log'; import { getLogger, Logger } from '../../common/log';
import { ObservableTimer } from '../../common/observableTimer'; import { ObservableTimer } from '../../common/observableTimer';
import { import {
HostJobApplicationForm, HyperParameters, JobApplicationForm, NNIManagerIpConfig, TrainingService, TrialJobApplicationForm, HyperParameters, NNIManagerIpConfig, TrainingService, TrialJobApplicationForm,
TrialJobDetail, TrialJobMetric TrialJobDetail, TrialJobMetric
} from '../../common/trainingService'; } from '../../common/trainingService';
import { import {
...@@ -172,9 +172,7 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -172,9 +172,7 @@ class RemoteMachineTrainingService implements TrainingService {
const deferred: Deferred<TrialJobDetail[]> = new Deferred<TrialJobDetail[]>(); const deferred: Deferred<TrialJobDetail[]> = new Deferred<TrialJobDetail[]>();
for (const [key, value] of this.trialJobsMap) { for (const [key, value] of this.trialJobsMap) {
if (value.form.jobType === 'TRIAL') { jobs.push(await this.getTrialJob(key));
jobs.push(await this.getTrialJob(key));
}
} }
deferred.resolve(jobs); deferred.resolve(jobs);
...@@ -228,33 +226,26 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -228,33 +226,26 @@ class RemoteMachineTrainingService implements TrainingService {
* @param form trial job description form * @param form trial job description form
*/ */
// tslint:disable-next-line:informative-docs // tslint:disable-next-line:informative-docs
public async submitTrialJob(form: JobApplicationForm): Promise<TrialJobDetail> { public async submitTrialJob(form: TrialJobApplicationForm): Promise<TrialJobDetail> {
if (this.trialConfig === undefined) { if (this.trialConfig === undefined) {
throw new Error('trial config is not initialized'); throw new Error('trial config is not initialized');
} }
if (form.jobType === 'HOST') { // Generate trial job id(random)
return this.runHostJob(<HostJobApplicationForm>form); const trialJobId: string = uniqueString(5);
} else if (form.jobType === 'TRIAL') { const trialWorkingFolder: string = unixPathJoin(this.remoteExpRootDir, 'trials', trialJobId);
// Generate trial job id(random)
const trialJobId: string = uniqueString(5);
const trialWorkingFolder: string = unixPathJoin(this.remoteExpRootDir, 'trials', trialJobId);
const trialJobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail( const trialJobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail(
trialJobId, trialJobId,
'WAITING', 'WAITING',
Date.now(), Date.now(),
trialWorkingFolder, trialWorkingFolder,
form, form
this.generateSequenceId() );
); this.jobQueue.push(trialJobId);
this.jobQueue.push(trialJobId); this.trialJobsMap.set(trialJobId, trialJobDetail);
this.trialJobsMap.set(trialJobId, trialJobDetail);
return Promise.resolve(trialJobDetail); return Promise.resolve(trialJobDetail);
} else {
return Promise.reject(new Error(`Job form not supported: ${JSON.stringify(form)}, jobType should be HOST or TRIAL.`));
}
} }
/** /**
...@@ -262,20 +253,16 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -262,20 +253,16 @@ class RemoteMachineTrainingService implements TrainingService {
* @param trialJobId trial job id * @param trialJobId trial job id
* @param form job application form * @param form job application form
*/ */
public async updateTrialJob(trialJobId: string, form: JobApplicationForm): Promise<TrialJobDetail> { public async updateTrialJob(trialJobId: string, form: TrialJobApplicationForm): Promise<TrialJobDetail> {
const trialJobDetail: undefined | TrialJobDetail = this.trialJobsMap.get(trialJobId); const trialJobDetail: undefined | TrialJobDetail = this.trialJobsMap.get(trialJobId);
if (trialJobDetail === undefined) { if (trialJobDetail === undefined) {
throw new Error(`updateTrialJob failed: ${trialJobId} not found`); throw new Error(`updateTrialJob failed: ${trialJobId} not found`);
} }
if (form.jobType === 'TRIAL') { const rmMeta: RemoteMachineMeta | undefined = (<RemoteMachineTrialJobDetail>trialJobDetail).rmMeta;
const rmMeta: RemoteMachineMeta | undefined = (<RemoteMachineTrialJobDetail>trialJobDetail).rmMeta; if (rmMeta !== undefined) {
if (rmMeta !== undefined) { await this.writeParameterFile(trialJobId, form.hyperParameters, rmMeta);
await this.writeParameterFile(trialJobId, (<TrialJobApplicationForm>form).hyperParameters, rmMeta);
} else {
throw new Error(`updateTrialJob failed: ${trialJobId} rmMeta not found`);
}
} else { } else {
throw new Error(`updateTrialJob failed: jobType ${form.jobType} not supported.`); throw new Error(`updateTrialJob failed: ${trialJobId} rmMeta not found`);
} }
return trialJobDetail; return trialJobDetail;
...@@ -558,7 +545,7 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -558,7 +545,7 @@ class RemoteMachineTrainingService implements TrainingService {
await this.allocateSSHClientForTrial(trialJobDetail); await this.allocateSSHClientForTrial(trialJobDetail);
await this.launchTrialOnScheduledMachine( await this.launchTrialOnScheduledMachine(
trialJobId, trialWorkingFolder, <TrialJobApplicationForm>trialJobDetail.form, rmScheduleInfo); trialJobId, trialWorkingFolder, trialJobDetail.form, rmScheduleInfo);
trialJobDetail.status = 'RUNNING'; trialJobDetail.status = 'RUNNING';
trialJobDetail.url = `file://${rmScheduleInfo.rmMeta.ip}:${trialWorkingFolder}`; trialJobDetail.url = `file://${rmScheduleInfo.rmMeta.ip}:${trialWorkingFolder}`;
...@@ -628,7 +615,7 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -628,7 +615,7 @@ class RemoteMachineTrainingService implements TrainingService {
trialWorkingFolder, trialWorkingFolder,
trialJobId, trialJobId,
getExperimentId(), getExperimentId(),
trialJobDetail.sequenceId.toString(), trialJobDetail.form.sequenceId.toString(),
this.isMultiPhase, this.isMultiPhase,
unixPathJoin(trialWorkingFolder, '.nni', 'jobpid'), unixPathJoin(trialWorkingFolder, '.nni', 'jobpid'),
command, command,
...@@ -657,38 +644,6 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -657,38 +644,6 @@ class RemoteMachineTrainingService implements TrainingService {
SSHClientUtility.remoteExeCommand(`bash ${unixPathJoin(trialWorkingFolder, 'run.sh')}`, sshClient); SSHClientUtility.remoteExeCommand(`bash ${unixPathJoin(trialWorkingFolder, 'run.sh')}`, sshClient);
} }
private async runHostJob(form: HostJobApplicationForm): Promise<TrialJobDetail> {
const rmMeta: RemoteMachineMeta = this.getRmMetaByHost(form.host);
const sshClientManager: SSHClientManager | undefined = this.machineSSHClientMap.get(rmMeta);
if (sshClientManager === undefined) {
throw new Error('sshClient not found.');
}
const sshClient: Client = sshClientManager.getFirstSSHClient();
const jobId: string = uniqueString(5);
const localDir: string = path.join(this.expRootDir, 'hostjobs-local', jobId);
const remoteDir: string = this.getHostJobRemoteDir(jobId);
await cpp.exec(`mkdir -p ${localDir}`);
await SSHClientUtility.remoteExeCommand(`mkdir -p ${remoteDir}`, sshClient);
const runScriptContent: string = String.Format(
HOST_JOB_SHELL_FORMAT, remoteDir, path.join(remoteDir, 'jobpid'), form.cmd, path.join(remoteDir, 'code')
);
await fs.promises.writeFile(path.join(localDir, 'run.sh'), runScriptContent, { encoding: 'utf8' });
await SSHClientUtility.copyFileToRemote(
path.join(localDir, 'run.sh'), unixPathJoin(remoteDir, 'run.sh'), sshClient);
// tslint:disable-next-line: no-floating-promises
SSHClientUtility.remoteExeCommand(`bash ${unixPathJoin(remoteDir, 'run.sh')}`, sshClient);
const jobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail(
jobId, 'RUNNING', Date.now(), remoteDir, form, this.generateSequenceId()
);
jobDetail.rmMeta = rmMeta;
jobDetail.startTime = Date.now();
this.trialJobsMap.set(jobId, jobDetail);
this.log.debug(`runHostJob: return: ${JSON.stringify(jobDetail)} `);
return jobDetail;
}
private getRmMetaByHost(host: string): RemoteMachineMeta { private getRmMetaByHost(host: string): RemoteMachineMeta {
for (const [rmMeta, client] of this.machineSSHClientMap.entries()) { for (const [rmMeta, client] of this.machineSSHClientMap.entries()) {
if (rmMeta.ip === host) { if (rmMeta.ip === host) {
...@@ -765,13 +720,7 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -765,13 +720,7 @@ class RemoteMachineTrainingService implements TrainingService {
} }
let jobpidPath: string; let jobpidPath: string;
if (trialJobDetail.form.jobType === 'TRIAL') { jobpidPath = unixPathJoin(trialJobDetail.workingDirectory, '.nni', 'jobpid');
jobpidPath = unixPathJoin(trialJobDetail.workingDirectory, '.nni', 'jobpid');
} else if (trialJobDetail.form.jobType === 'HOST') {
jobpidPath = unixPathJoin(this.getHostJobRemoteDir(jobId), 'jobpid');
} else {
throw new Error(`Job type not supported: ${trialJobDetail.form.jobType}`);
}
return jobpidPath; return jobpidPath;
} }
...@@ -791,14 +740,6 @@ class RemoteMachineTrainingService implements TrainingService { ...@@ -791,14 +740,6 @@ class RemoteMachineTrainingService implements TrainingService {
await SSHClientUtility.copyFileToRemote(localFilepath, unixPathJoin(trialWorkingFolder, fileName), sshClient); await SSHClientUtility.copyFileToRemote(localFilepath, unixPathJoin(trialWorkingFolder, fileName), sshClient);
} }
private generateSequenceId(): number {
if (this.trialSequenceId === -1) {
this.trialSequenceId = getInitTrialSequenceId();
}
return this.trialSequenceId++;
}
} }
export { RemoteMachineTrainingService }; export { RemoteMachineTrainingService };
...@@ -76,7 +76,7 @@ describe('Unit Test for LocalTrainingService', () => { ...@@ -76,7 +76,7 @@ describe('Unit Test for LocalTrainingService', () => {
// submit job // submit job
const form: TrialJobApplicationForm = { const form: TrialJobApplicationForm = {
jobType: 'TRIAL', sequenceId: 0,
hyperParameters: { hyperParameters: {
value: 'mock hyperparameters', value: 'mock hyperparameters',
index: 0 index: 0
...@@ -95,7 +95,7 @@ describe('Unit Test for LocalTrainingService', () => { ...@@ -95,7 +95,7 @@ describe('Unit Test for LocalTrainingService', () => {
// submit job // submit job
const form: TrialJobApplicationForm = { const form: TrialJobApplicationForm = {
jobType: 'TRIAL', sequenceId: 0,
hyperParameters: { hyperParameters: {
value: 'mock hyperparameters', value: 'mock hyperparameters',
index: 0 index: 0
...@@ -121,4 +121,4 @@ describe('Unit Test for LocalTrainingService', () => { ...@@ -121,4 +121,4 @@ describe('Unit Test for LocalTrainingService', () => {
it('Test multiphaseSupported', () => { it('Test multiphaseSupported', () => {
chai.expect(localTrainingService.isMultiPhaseJobSupported).to.be.equals(true) chai.expect(localTrainingService.isMultiPhaseJobSupported).to.be.equals(true)
}) })
}); });
\ No newline at end of file
...@@ -24,6 +24,7 @@ import * as chaiAsPromised from 'chai-as-promised'; ...@@ -24,6 +24,7 @@ import * as chaiAsPromised from 'chai-as-promised';
import * as fs from 'fs'; import * as fs from 'fs';
import * as tmp from 'tmp'; import * as tmp from 'tmp';
import * as component from '../../common/component'; import * as component from '../../common/component';
import { TrialJobApplicationForm } from '../../common/trainingService';
import { cleanupUnitTest, prepareUnitTest } from '../../common/utils'; import { cleanupUnitTest, prepareUnitTest } from '../../common/utils';
import { TrialConfigMetadataKey } from '../common/trialConfigMetadataKey'; import { TrialConfigMetadataKey } from '../common/trialConfigMetadataKey';
import { PAITrainingService } from '../pai/paiTrainingService'; import { PAITrainingService } from '../pai/paiTrainingService';
...@@ -84,12 +85,16 @@ describe('Unit Test for PAITrainingService', () => { ...@@ -84,12 +85,16 @@ describe('Unit Test for PAITrainingService', () => {
console.log(`paiCluster is ${paiCluster}`) console.log(`paiCluster is ${paiCluster}`)
await paiTrainingService.setClusterMetadata(TrialConfigMetadataKey.PAI_CLUSTER_CONFIG, paiCluster); await paiTrainingService.setClusterMetadata(TrialConfigMetadataKey.PAI_CLUSTER_CONFIG, paiCluster);
await paiTrainingService.setClusterMetadata(TrialConfigMetadataKey.TRIAL_CONFIG, paiTrialConfig); await paiTrainingService.setClusterMetadata(TrialConfigMetadataKey.TRIAL_CONFIG, paiTrialConfig);
const form: TrialJobApplicationForm = {
sequenceId: 0,
hyperParameters: { value: '', index: 0 }
};
try { try {
const trialDetail = await paiTrainingService.submitTrialJob({jobType : 'TRIAL'}); const trialDetail = await paiTrainingService.submitTrialJob(form);
chai.expect(trialDetail.status).to.be.equals('WAITING'); chai.expect(trialDetail.status).to.be.equals('WAITING');
} catch(error) { } catch(error) {
console.log('Submit job failed:' + error); console.log('Submit job failed:' + error);
chai.assert(error) chai.assert(error)
} }
}); });
}); });
\ No newline at end of file
...@@ -99,11 +99,11 @@ describe('Unit Test for RemoteMachineTrainingService', () => { ...@@ -99,11 +99,11 @@ describe('Unit Test for RemoteMachineTrainingService', () => {
await remoteMachineTrainingService.setClusterMetadata( await remoteMachineTrainingService.setClusterMetadata(
TrialConfigMetadataKey.TRIAL_CONFIG, `{"command":"sleep 1h && echo ","codeDir":"${localCodeDir}","gpuNum":1}`); TrialConfigMetadataKey.TRIAL_CONFIG, `{"command":"sleep 1h && echo ","codeDir":"${localCodeDir}","gpuNum":1}`);
const form: TrialJobApplicationForm = { const form: TrialJobApplicationForm = {
jobType: 'TRIAL', sequenceId: 0,
hyperParameters: { hyperParameters: {
value: 'mock hyperparameters', value: 'mock hyperparameters',
index: 0 index: 0
} }
}; };
const trialJob = await remoteMachineTrainingService.submitTrialJob(form); const trialJob = await remoteMachineTrainingService.submitTrialJob(form);
...@@ -137,7 +137,7 @@ describe('Unit Test for RemoteMachineTrainingService', () => { ...@@ -137,7 +137,7 @@ describe('Unit Test for RemoteMachineTrainingService', () => {
// submit job // submit job
const form: TrialJobApplicationForm = { const form: TrialJobApplicationForm = {
jobType: 'TRIAL', sequenceId: 0,
hyperParameters: { hyperParameters: {
value: 'mock hyperparameters', value: 'mock hyperparameters',
index: 0 index: 0
......
import * as React from 'react'; import * as React from 'react';
import { Row, Col } from 'antd'; import { Row, Col } from 'antd';
import axios from 'axios'; import { COLUMN } from './static/const';
import { COLUMN, MANAGER_IP } from './static/const'; import { EXPERIMENT, TRIALS } from './static/datamodel';
import './App.css'; import './App.css';
import SlideBar from './components/SlideBar'; import SlideBar from './components/SlideBar';
interface AppState { interface AppState {
interval: number; interval: number;
whichPageToFresh: string; columnList: Array<string>;
columnList: Array<string>; experimentUpdateBroadcast: number;
concurrency: number; trialsUpdateBroadcast: number;
} }
class App extends React.Component<{}, AppState> { class App extends React.Component<{}, AppState> {
public _isMounted: boolean; private timerId: number | null;
constructor(props: {}) {
super(props);
this.state = {
interval: 10, // sendons
whichPageToFresh: '',
columnList: COLUMN,
concurrency: 1
};
}
changeInterval = (interval: number) => { constructor(props: {}) {
if (this._isMounted === true) { super(props);
this.setState(() => ({ interval: interval })); this.state = {
interval: 10, // sendons
columnList: COLUMN,
experimentUpdateBroadcast: 0,
trialsUpdateBroadcast: 0,
};
} }
}
changeFresh = (fresh: string) => { async componentDidMount() {
// interval * 1000 await Promise.all([ EXPERIMENT.init(), TRIALS.init() ]);
if (this._isMounted === true) { this.setState(state => ({ experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1 }));
this.setState(() => ({ whichPageToFresh: fresh })); this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 }));
this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
} }
}
changeColumn = (columnList: Array<string>) => { changeInterval = (interval: number) => {
if (this._isMounted === true) { this.setState({ interval: interval });
this.setState(() => ({ columnList: columnList })); if (this.timerId === null && interval !== 0) {
window.setTimeout(this.refresh);
} else if (this.timerId !== null && interval === 0) {
window.clearTimeout(this.timerId);
}
} }
}
changeConcurrency = (val: number) => { // TODO: use local storage
if (this._isMounted === true) { changeColumn = (columnList: Array<string>) => {
this.setState(() => ({ concurrency: val })); this.setState({ columnList: columnList });
} }
}
getConcurrency = () => { render() {
axios(`${MANAGER_IP}/experiment`, { const { interval, columnList, experimentUpdateBroadcast, trialsUpdateBroadcast } = this.state;
method: 'GET' if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
}) return null; // TODO: render a loading page
.then(res => { }
if (res.status === 200) { const reactPropsChildren = React.Children.map(this.props.children, child =>
const params = res.data.params; React.cloneElement(
if (this._isMounted) { // tslint:disable-next-line:no-any
this.setState(() => ({ concurrency: params.trialConcurrency })); child as React.ReactElement<any>, {
} interval,
columnList, changeColumn: this.changeColumn,
experimentUpdateBroadcast,
trialsUpdateBroadcast,
})
);
return (
<Row className="nni" style={{ minHeight: window.innerHeight }}>
<Row className="header">
<Col span={1} />
<Col className="headerCon" span={22}>
<SlideBar changeInterval={this.changeInterval} />
</Col>
<Col span={1} />
</Row>
<Row className="contentBox">
<Row className="content">
{reactPropsChildren}
</Row>
</Row>
</Row>
);
}
private refresh = async () => {
const [ experimentUpdated, trialsUpdated ] = await Promise.all([ EXPERIMENT.update(), TRIALS.update() ]);
if (experimentUpdated) {
this.setState(state => ({ experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1 }));
}
if (trialsUpdated) {
this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 }));
} }
});
}
componentDidMount() { if ([ 'DONE', 'ERROR', 'STOPPED' ].includes(EXPERIMENT.status)) {
this._isMounted = true; // experiment finished, refresh once more to ensure consistency
this.getConcurrency(); if (this.state.interval > 0) {
} this.setState({ interval: 0 });
this.lastRefresh();
}
componentWillUnmount() { } else if (this.state.interval !== 0) {
this._isMounted = false; this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
} }
render() { }
const { interval, whichPageToFresh, columnList, concurrency } = this.state;
const reactPropsChildren = React.Children.map(this.props.children, child => private async lastRefresh() {
React.cloneElement( await EXPERIMENT.update();
// tslint:disable-next-line:no-any await TRIALS.update(true);
child as React.ReactElement<any>, { this.setState(state => ({ experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1 }));
interval, whichPageToFresh, this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 }));
columnList, changeColumn: this.changeColumn, }
concurrency, changeConcurrency: this.changeConcurrency
})
);
return (
<Row className="nni" style={{ minHeight: window.innerHeight }}>
<Row className="header">
<Col span={1} />
<Col className="headerCon" span={22}>
<SlideBar changeInterval={this.changeInterval} changeFresh={this.changeFresh} />
</Col>
<Col span={1} />
</Row>
<Row className="contentBox">
<Row className="content">
{reactPropsChildren}
</Row>
</Row>
</Row>
);
}
} }
export default App; export default App;
...@@ -2,12 +2,13 @@ import * as React from 'react'; ...@@ -2,12 +2,13 @@ import * as React from 'react';
import { Row, Modal } from 'antd'; import { Row, Modal } from 'antd';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import IntermediateVal from '../public-child/IntermediateVal'; import IntermediateVal from '../public-child/IntermediateVal';
import { TRIALS } from '../../static/datamodel';
import '../../static/style/compare.scss'; import '../../static/style/compare.scss';
import { TableObj, Intermedia, TooltipForIntermediate } from 'src/static/interface'; import { TableRecord, Intermedia, TooltipForIntermediate } from 'src/static/interface';
// the modal of trial compare // the modal of trial compare
interface CompareProps { interface CompareProps {
compareRows: Array<TableObj>; compareRows: Array<TableRecord>;
visible: boolean; visible: boolean;
cancelFunc: () => void; cancelFunc: () => void;
} }
...@@ -105,11 +106,12 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -105,11 +106,12 @@ class Compare extends React.Component<CompareProps, {}> {
// render table column --- // render table column ---
initColumn = () => { initColumn = () => {
const { compareRows } = this.props;
const idList: Array<string> = []; const idList: Array<string> = [];
const sequenceIdList: Array<number> = []; const sequenceIdList: Array<number> = [];
const durationList: Array<number> = []; const durationList: Array<number> = [];
const compareRows = this.props.compareRows.map(tableRecord => TRIALS.getTrial(tableRecord.id));
const parameterList: Array<object> = []; const parameterList: Array<object> = [];
let parameterKeys: Array<string> = []; let parameterKeys: Array<string> = [];
if (compareRows.length !== 0) { if (compareRows.length !== 0) {
...@@ -147,7 +149,7 @@ class Compare extends React.Component<CompareProps, {}> { ...@@ -147,7 +149,7 @@ class Compare extends React.Component<CompareProps, {}> {
const temp = compareRows[index]; const temp = compareRows[index];
return ( return (
<td className="value" key={index}> <td className="value" key={index}>
<IntermediateVal record={temp} /> <IntermediateVal trialId={temp.id} />
</td> </td>
); );
})} })}
......
...@@ -58,7 +58,7 @@ class ExperimentDrawer extends React.Component<ExpDrawerProps, ExpDrawerState> { ...@@ -58,7 +58,7 @@ class ExperimentDrawer extends React.Component<ExpDrawerProps, ExpDrawerState> {
trialMessage: trialMessagesArr trialMessage: trialMessagesArr
}; };
if (this._isCompareMount === true) { if (this._isCompareMount === true) {
this.setState(() => ({ experiment: JSON.stringify(result, null, 4) })); this.setState({ experiment: JSON.stringify(result, null, 4) });
} }
} }
})); }));
......
...@@ -51,13 +51,13 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> { ...@@ -51,13 +51,13 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
setDispatcher = (value: string) => { setDispatcher = (value: string) => {
if (this._isLogDrawer === true) { if (this._isLogDrawer === true) {
this.setState(() => ({ isLoadispatcher: false, dispatcherLogStr: value })); this.setState({ isLoadispatcher: false, dispatcherLogStr: value });
} }
} }
setNNImanager = (val: string) => { setNNImanager = (val: string) => {
if (this._isLogDrawer === true) { if (this._isLogDrawer === true) {
this.setState(() => ({ isLoading: false, nniManagerLogStr: val })); this.setState({ isLoading: false, nniManagerLogStr: val });
} }
} }
......
This diff is collapsed.
...@@ -26,7 +26,6 @@ interface SliderState { ...@@ -26,7 +26,6 @@ interface SliderState {
interface SliderProps extends FormComponentProps { interface SliderProps extends FormComponentProps {
changeInterval: (value: number) => void; changeInterval: (value: number) => void;
changeFresh: (value: string) => void;
} }
interface EventPer { interface EventPer {
...@@ -35,7 +34,6 @@ interface EventPer { ...@@ -35,7 +34,6 @@ interface EventPer {
class SlideBar extends React.Component<SliderProps, SliderState> { class SlideBar extends React.Component<SliderProps, SliderState> {
public _isMounted = false;
public divMenu: HTMLDivElement | null; public divMenu: HTMLDivElement | null;
public selectHTML: Select | null; public selectHTML: Select | null;
...@@ -57,32 +55,26 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -57,32 +55,26 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
method: 'GET' method: 'GET'
}) })
.then(res => { .then(res => {
if (res.status === 200 && this._isMounted) { if (res.status === 200) {
this.setState({ version: res.data }); this.setState({ version: res.data });
} }
}); });
} }
handleMenuClick = (e: EventPer) => { handleMenuClick = (e: EventPer) => {
if (this._isMounted) { this.setState({ menuVisible: false }); } this.setState({ menuVisible: false });
switch (e.key) { switch (e.key) {
// to see & download experiment parameters // to see & download experiment parameters
case '1': case '1':
if (this._isMounted === true) { this.setState({ isvisibleExperimentDrawer: true });
this.setState(() => ({ isvisibleExperimentDrawer: true }));
}
break; break;
// to see & download nnimanager log // to see & download nnimanager log
case '2': case '2':
if (this._isMounted === true) { this.setState({ activeKey: 'nnimanager', isvisibleLogDrawer: true });
this.setState(() => ({ activeKey: 'nnimanager', isvisibleLogDrawer: true }));
}
break; break;
// to see & download dispatcher log // to see & download dispatcher log
case '3': case '3':
if (this._isMounted === true) { this.setState({ isvisibleLogDrawer: true, activeKey: 'dispatcher' });
this.setState(() => ({ isvisibleLogDrawer: true, activeKey: 'dispatcher' }));
}
break; break;
case 'close': case 'close':
case '10': case '10':
...@@ -96,13 +88,10 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -96,13 +88,10 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
} }
handleVisibleChange = (flag: boolean) => { handleVisibleChange = (flag: boolean) => {
if (this._isMounted === true) { this.setState({ menuVisible: flag });
this.setState({ menuVisible: flag });
}
} }
getInterval = (value: string) => { getInterval = (value: string) => {
if (value === 'close') { if (value === 'close') {
this.props.changeInterval(0); this.props.changeInterval(0);
} else { } else {
...@@ -203,13 +192,9 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -203,13 +192,9 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
fresh = (event: React.SyntheticEvent<EventTarget>) => { fresh = (event: React.SyntheticEvent<EventTarget>) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (this._isMounted) { this.setState({ isdisabledFresh: true }, () => {
this.setState({ isdisabledFresh: true }, () => { setTimeout(() => { this.setState({ isdisabledFresh: false }); }, 1000);
const whichPage = window.location.pathname; });
this.props.changeFresh(whichPage);
setTimeout(() => { this.setState(() => ({ isdisabledFresh: false })); }, 1000);
});
}
} }
desktopHTML = () => { desktopHTML = () => {
...@@ -330,27 +315,18 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -330,27 +315,18 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
} }
// close log drawer (nnimanager.dispatcher) // close log drawer (nnimanager.dispatcher)
closeLogDrawer = () => { closeLogDrawer = () => {
if (this._isMounted === true) { this.setState({ isvisibleLogDrawer: false, activeKey: '' });
this.setState(() => ({ isvisibleLogDrawer: false, activeKey: '' }));
}
} }
// close download experiment parameters drawer // close download experiment parameters drawer
closeExpDrawer = () => { closeExpDrawer = () => {
if (this._isMounted === true) { this.setState({ isvisibleExperimentDrawer: false });
this.setState(() => ({ isvisibleExperimentDrawer: false }));
}
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
this.getNNIversion(); this.getNNIversion();
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const mobile = (<MediaQuery maxWidth={884}>{this.mobileHTML()}</MediaQuery>); const mobile = (<MediaQuery maxWidth={884}>{this.mobileHTML()}</MediaQuery>);
const tablet = (<MediaQuery minWidth={885} maxWidth={1241}>{this.tabeltHTML()}</MediaQuery>); const tablet = (<MediaQuery minWidth={885} maxWidth={1241}>{this.tabeltHTML()}</MediaQuery>);
...@@ -376,4 +352,4 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -376,4 +352,4 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
} }
} }
export default Form.create<FormComponentProps>()(SlideBar); export default Form.create<FormComponentProps>()(SlideBar);
\ No newline at end of file
This diff is collapsed.
import { Col, Row, Tooltip } from 'antd';
import * as React from 'react'; import * as React from 'react';
import { import { EXPERIMENT } from '../../static/datamodel';
Row, Col, import { formatTimestamp } from '../../static/function';
Tooltip
} from 'antd';
import { Experiment } from '../../static/interface';
interface BasicInfoProps { interface BasicInfoProps {
trialProfile: Experiment; experimentUpdateBroadcast: number;
status: string;
} }
class BasicInfo extends React.Component<BasicInfoProps, {}> { class BasicInfo extends React.Component<BasicInfoProps, {}> {
constructor(props: BasicInfoProps) { constructor(props: BasicInfoProps) {
super(props); super(props);
} }
render() { render() {
const { trialProfile } = this.props;
return ( return (
<Row className="main"> <Row className="main">
<Col span={8} className="padItem basic"> <Col span={8} className="padItem basic">
<p>Name</p> <p>Name</p>
<div>{trialProfile.experName}</div> <div>{EXPERIMENT.profile.params.experimentName}</div>
<p>ID</p> <p>ID</p>
<div>{trialProfile.id}</div> <div>{EXPERIMENT.profile.id}</div>
</Col> </Col>
<Col span={8} className="padItem basic"> <Col span={8} className="padItem basic">
<p>Start time</p> <p>Start time</p>
<div className="nowrap"> <div className="nowrap">{formatTimestamp(EXPERIMENT.profile.startTime)}</div>
{new Date(trialProfile.startTime).toLocaleString('en-US')}
</div>
<p>End time</p> <p>End time</p>
<div className="nowrap"> <div className="nowrap">{formatTimestamp(EXPERIMENT.profile.endTime)}</div>
{
trialProfile.endTime
?
new Date(trialProfile.endTime).toLocaleString('en-US')
:
'none'
}
</div>
</Col> </Col>
<Col span={8} className="padItem basic"> <Col span={8} className="padItem basic">
<p>Log directory</p> <p>Log directory</p>
<div className="nowrap"> <div className="nowrap">
<Tooltip placement="top" title={trialProfile.logDir}> <Tooltip placement="top" title={EXPERIMENT.profile.logDir || ''}>
{trialProfile.logDir} {EXPERIMENT.profile.logDir || 'unknown'}
</Tooltip> </Tooltip>
</div> </div>
<p>Training platform</p> <p>Training platform</p>
<div className="nowrap"> <div className="nowrap">{EXPERIMENT.profile.params.trainingServicePlatform}</div>
{
trialProfile.trainingServicePlatform
?
trialProfile.trainingServicePlatform
:
'none'
}
</div>
</Col> </Col>
</Row> </Row>
); );
} }
} }
export default BasicInfo; export default BasicInfo;
\ No newline at end of file
import * as React from 'react';
import { Button, Row } from 'antd';
interface ConcurrencyInputProps {
value: number;
updateValue: (val: string) => void;
}
interface ConcurrencyInputStates {
editting: boolean;
}
class ConcurrencyInput extends React.Component<ConcurrencyInputProps, ConcurrencyInputStates> {
private input = React.createRef<HTMLInputElement>();
constructor(props: ConcurrencyInputProps) {
super(props);
this.state = { editting: false };
}
save = () => {
if (this.input.current !== null) {
this.props.updateValue(this.input.current.value);
this.setState({ editting: false });
}
}
cancel = () => {
this.setState({ editting: false });
}
edit = () => {
this.setState({ editting: true });
}
render() {
if (this.state.editting) {
return (
<Row className="inputBox">
<input
type="number"
className="concurrencyInput"
defaultValue={this.props.value.toString()}
ref={this.input}
/>
<Button
type="primary"
className="tableButton editStyle"
onClick={this.save}
>
Save
</Button>
<Button
type="primary"
onClick={this.cancel}
style={{ display: 'inline-block', marginLeft: 1 }}
className="tableButton editStyle"
>
Cancel
</Button>
</Row>
);
} else {
return (
<Row className="inputBox">
<input
type="number"
className="concurrencyInput"
disabled={true}
value={this.props.value}
/>
<Button
type="primary"
className="tableButton editStyle"
onClick={this.edit}
>
Edit
</Button>
</Row>
);
}
}
}
export default ConcurrencyInput;
import * as React from 'react'; import * as React from 'react';
import { Row, Col, Popover, Button, message } from 'antd'; import { Row, Col, Popover, message } from 'antd';
import axios from 'axios'; import axios from 'axios';
import { MANAGER_IP, CONTROLTYPE } from '../../static/const'; import { MANAGER_IP } from '../../static/const';
import { Experiment, TrialNumber } from '../../static/interface'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { convertTime } from '../../static/function'; import { convertTime } from '../../static/function';
import ConcurrencyInput from './NumInput';
import ProgressBar from './ProgressItem'; import ProgressBar from './ProgressItem';
import LogDrawer from '../Modal/LogDrawer'; import LogDrawer from '../Modal/LogDrawer';
import '../../static/style/progress.scss'; import '../../static/style/progress.scss';
import '../../static/style/probar.scss'; import '../../static/style/probar.scss';
interface ProgressProps { interface ProgressProps {
trialProfile: Experiment;
concurrency: number; concurrency: number;
trialNumber: TrialNumber;
bestAccuracy: number; bestAccuracy: number;
status: string;
errors: string;
changeConcurrency: (val: number) => void; changeConcurrency: (val: number) => void;
experimentUpdateBroadcast: number;
} }
interface ProgressState { interface ProgressState {
btnName: string;
isEnable: boolean;
userInputVal: string; // get user input
cancelSty: string;
isShowLogDrawer: boolean; isShowLogDrawer: boolean;
} }
class Progressed extends React.Component<ProgressProps, ProgressState> { class Progressed extends React.Component<ProgressProps, ProgressState> {
public conInput: HTMLInputElement | null;
public _isMounted = false;
constructor(props: ProgressProps) { constructor(props: ProgressProps) {
super(props); super(props);
this.state = { this.state = {
btnName: 'Edit',
isEnable: true,
userInputVal: this.props.trialProfile.runConcurren.toString(),
cancelSty: 'none',
isShowLogDrawer: false isShowLogDrawer: false
}; };
} }
editTrialConcurrency = () => { editTrialConcurrency = async (userInput: string) => {
const { btnName } = this.state; if (!userInput.match(/^[1-9]\d*$/)) {
if (this._isMounted) { message.error('Please enter a positive integer!', 2);
if (btnName === 'Edit') { return;
// user click edit
this.setState(() => ({
isEnable: false,
btnName: 'Save',
cancelSty: 'inline-block'
}));
} else {
// user click save button
axios(`${MANAGER_IP}/experiment`, {
method: 'GET'
})
.then(rese => {
if (rese.status === 200) {
const { userInputVal } = this.state;
const experimentFile = rese.data;
const trialConcurrency = experimentFile.params.trialConcurrency;
if (userInputVal !== undefined) {
if (userInputVal === trialConcurrency.toString() || userInputVal === '0') {
message.destroy();
message.info(
`trialConcurrency's value is ${trialConcurrency}, you did not modify it`, 2);
} else {
experimentFile.params.trialConcurrency = parseInt(userInputVal, 10);
// rest api, modify trial concurrency value
axios(`${MANAGER_IP}/experiment`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
data: experimentFile,
params: {
update_type: CONTROLTYPE[1]
}
}).then(res => {
if (res.status === 200) {
message.destroy();
message.success(`Update ${CONTROLTYPE[1].toLocaleLowerCase()}
successfully`);
this.props.changeConcurrency(parseInt(userInputVal, 10));
}
})
.catch(error => {
if (error.response.status === 500) {
if (error.response.data.error) {
message.error(error.response.data.error);
} else {
message.error(
`Update ${CONTROLTYPE[1].toLocaleLowerCase()} failed`);
}
}
});
// btn -> edit
this.setState(() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none'
}));
}
}
}
});
}
}
}
cancelFunction = () => {
const { trialProfile } = this.props;
if (this._isMounted) {
this.setState(
() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none',
}));
} }
if (this.conInput !== null) { const newConcurrency = parseInt(userInput, 10);
this.conInput.value = trialProfile.runConcurren.toString(); if (newConcurrency === this.props.concurrency) {
message.info(`Trial concurrency has not changed`, 2);
return;
} }
}
getUserTrialConcurrency = (event: React.ChangeEvent<HTMLInputElement>) => { const newProfile = Object.assign({}, EXPERIMENT.profile);
const value = event.target.value; newProfile.params.trialConcurrency = newConcurrency;
if (value.match(/^[1-9]\d*$/) || value === '') {
this.setState(() => ({ // rest api, modify trial concurrency value
userInputVal: value try {
})); const res = await axios.put(`${MANAGER_IP}/experiment`, newProfile, {
} else { params: { update_type: 'TRIAL_CONCURRENCY' }
message.error('Please enter a positive integer!', 2); });
if (this.conInput !== null) { if (res.status === 200) {
const { trialProfile } = this.props; message.success(`Successfully updated trial concurrency`);
this.conInput.value = trialProfile.runConcurren.toString(); // NOTE: should we do this earlier in favor of poor networks?
this.props.changeConcurrency(newConcurrency);
}
} catch (error) {
if (error.response && error.response.data.error) {
message.error(`Failed to update trial concurrency\n${error.response.data.error}`);
} else if (error.response) {
message.error(`Failed to update trial concurrency\nServer responsed ${error.response.status}`);
} else if (error.message) {
message.error(`Failed to update trial concurrency\n${error.message}`);
} else {
message.error(`Failed to update trial concurrency\nUnknown error`);
} }
} }
} }
isShowDrawer = () => { isShowDrawer = () => {
if (this._isMounted === true) { this.setState({ isShowLogDrawer: true });
this.setState(() => ({ isShowLogDrawer: true }));
}
} }
closeDrawer = () => { closeDrawer = () => {
if (this._isMounted === true) { this.setState({ isShowLogDrawer: false });
this.setState(() => ({ isShowLogDrawer: false }));
}
} }
componentWillReceiveProps() { render() {
const { trialProfile } = this.props; const { bestAccuracy } = this.props;
if (this.conInput !== null) { const { isShowLogDrawer } = this.state;
this.conInput.value = trialProfile.runConcurren.toString();
}
}
componentDidMount() { const count = TRIALS.countStatus();
this._isMounted = true; const stoppedCount = count.get('USER_CANCELED')! + count.get('SYS_CANCELED')! + count.get('EARLY_STOPPED')!;
} const bar2 = count.get('RUNNING')! + count.get('SUCCEEDED')! + count.get('FAILED')! + stoppedCount;
componentWillUnmount() { const bar2Percent = (bar2 / EXPERIMENT.profile.params.maxTrialNum) * 100;
this._isMounted = false; const percent = (EXPERIMENT.profile.execDuration / EXPERIMENT.profile.params.maxExecDuration) * 100;
} const remaining = convertTime(EXPERIMENT.profile.params.maxExecDuration - EXPERIMENT.profile.execDuration);
const maxDuration = convertTime(EXPERIMENT.profile.params.maxExecDuration);
const maxTrialNum = convertTime(EXPERIMENT.profile.params.maxTrialNum);
const execDuration = convertTime(EXPERIMENT.profile.execDuration);
render() {
const { trialProfile, trialNumber, bestAccuracy, status, errors } = this.props;
const { isEnable, btnName, cancelSty, isShowLogDrawer } = this.state;
const bar2 = trialNumber.totalCurrentTrial - trialNumber.waitTrial - trialNumber.unknowTrial;
const bar2Percent = (bar2 / trialProfile.MaxTrialNum) * 100;
const percent = (trialProfile.execDuration / trialProfile.maxDuration) * 100;
const runDuration = convertTime(trialProfile.execDuration);
const temp = trialProfile.maxDuration - trialProfile.execDuration;
let remaining;
let errorContent; let errorContent;
if (temp < 0) { if (EXPERIMENT.error) {
remaining = '0';
} else {
remaining = convertTime(temp);
}
if (errors !== '') {
errorContent = ( errorContent = (
<div className="errors"> <div className="errors">
{errors} {EXPERIMENT.error}
<div><a href="#" onClick={this.isShowDrawer}>Learn about</a></div> <div><a href="#" onClick={this.isShowDrawer}>Learn about</a></div>
</div> </div>
); );
...@@ -196,9 +103,9 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -196,9 +103,9 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
<Row className="basic lineBasic"> <Row className="basic lineBasic">
<p>Status</p> <p>Status</p>
<div className="status"> <div className="status">
<span className={status}>{status}</span> <span className={EXPERIMENT.status}>{EXPERIMENT.status}</span>
{ {
status === 'ERROR' EXPERIMENT.status === 'ERROR'
? ?
<Popover <Popover
placement="rightTop" placement="rightTop"
...@@ -216,26 +123,26 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -216,26 +123,26 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
<ProgressBar <ProgressBar
who="Duration" who="Duration"
percent={percent} percent={percent}
description={runDuration} description={execDuration}
bgclass={status} bgclass={EXPERIMENT.status}
maxString={`Max duration: ${convertTime(trialProfile.maxDuration)}`} maxString={`Max duration: ${maxDuration}`}
/> />
<ProgressBar <ProgressBar
who="Trial numbers" who="Trial numbers"
percent={bar2Percent} percent={bar2Percent}
description={bar2.toString()} description={bar2.toString()}
bgclass={status} bgclass={EXPERIMENT.status}
maxString={`Max trial number: ${trialProfile.MaxTrialNum}`} maxString={`Max trial number: ${maxTrialNum}`}
/> />
<Row className="basic colorOfbasic mess"> <Row className="basic colorOfbasic mess">
<p>Best metric</p> <p>Best metric</p>
<div>{bestAccuracy.toFixed(6)}</div> <div>{isNaN(bestAccuracy) ? 'N/A' : bestAccuracy.toFixed(6)}</div>
</Row> </Row>
<Row className="mess"> <Row className="mess">
<Col span={6}> <Col span={6}>
<Row className="basic colorOfbasic"> <Row className="basic colorOfbasic">
<p>Spent</p> <p>Spent</p>
<div>{convertTime(trialProfile.execDuration)}</div> <div>{execDuration}</div>
</Row> </Row>
</Col> </Col>
<Col span={6}> <Col span={6}>
...@@ -247,54 +154,32 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -247,54 +154,32 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
<Col span={12}> <Col span={12}>
{/* modify concurrency */} {/* modify concurrency */}
<p>Concurrency</p> <p>Concurrency</p>
<Row className="inputBox"> <ConcurrencyInput value={this.props.concurrency} updateValue={this.editTrialConcurrency} />
<input
type="number"
disabled={isEnable}
onChange={this.getUserTrialConcurrency}
className="concurrencyInput"
ref={(input) => this.conInput = input}
/>
<Button
type="primary"
className="tableButton editStyle"
onClick={this.editTrialConcurrency}
>{btnName}
</Button>
<Button
type="primary"
onClick={this.cancelFunction}
style={{ display: cancelSty, marginLeft: 1 }}
className="tableButton editStyle"
>
Cancel
</Button>
</Row>
</Col> </Col>
</Row> </Row>
<Row className="mess"> <Row className="mess">
<Col span={6}> <Col span={6}>
<Row className="basic colorOfbasic"> <Row className="basic colorOfbasic">
<p>Running</p> <p>Running</p>
<div>{trialNumber.runTrial}</div> <div>{count.get('RUNNING')}</div>
</Row> </Row>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Row className="basic colorOfbasic"> <Row className="basic colorOfbasic">
<p>Succeeded</p> <p>Succeeded</p>
<div>{trialNumber.succTrial}</div> <div>{count.get('SUCCEEDED')}</div>
</Row> </Row>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Row className="basic"> <Row className="basic">
<p>Stopped</p> <p>Stopped</p>
<div>{trialNumber.stopTrial}</div> <div>{stoppedCount}</div>
</Row> </Row>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Row className="basic"> <Row className="basic">
<p>Failed</p> <p>Failed</p>
<div>{trialNumber.failTrial}</div> <div>{count.get('FAILED')}</div>
</Row> </Row>
</Col> </Col>
</Row> </Row>
...@@ -309,4 +194,4 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -309,4 +194,4 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
} }
} }
export default Progressed; export default Progressed;
\ No newline at end of file
...@@ -2,131 +2,83 @@ import * as React from 'react'; ...@@ -2,131 +2,83 @@ import * as React from 'react';
import { Table } from 'antd'; import { Table } from 'antd';
import OpenRow from '../public-child/OpenRow'; import OpenRow from '../public-child/OpenRow';
import DefaultMetric from '../public-child/DefaultMetrc'; import DefaultMetric from '../public-child/DefaultMetrc';
import { TableObj } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
import { TableRecord } from '../../static/interface';
import { convertDuration } from '../../static/function'; import { convertDuration } from '../../static/function';
import '../../static/style/tableStatus.css'; import '../../static/style/tableStatus.css';
import '../../static/style/openRow.scss'; import '../../static/style/openRow.scss';
interface SuccessTableProps { interface SuccessTableProps {
tableSource: Array<TableObj>; trialIds: string[];
trainingPlatform: string;
logCollection: boolean;
multiphase: boolean;
} }
class SuccessTable extends React.Component<SuccessTableProps, {}> { function openRow(record: TableRecord) {
return (
public _isMounted = false; <OpenRow trialId={record.id} />
);
}
class SuccessTable extends React.Component<SuccessTableProps, {}> {
constructor(props: SuccessTableProps) { constructor(props: SuccessTableProps) {
super(props); super(props);
}
openRow = (record: TableObj) => {
const { trainingPlatform, logCollection, multiphase } = this.props;
return (
<OpenRow
trainingPlatform={trainingPlatform}
record={record}
logCollection={logCollection}
multiphase={multiphase}
/>
);
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
} }
render() { render() {
const { tableSource } = this.props; const columns = [
{
let bgColor = ''; title: 'Trial No.',
const columns = [{ dataIndex: 'sequenceId',
title: 'Trial No.', width: 140,
dataIndex: 'sequenceId', className: 'tableHead'
key: 'sequenceId', }, {
width: 140, title: 'ID',
className: 'tableHead' dataIndex: 'id',
}, { width: 60,
title: 'ID', className: 'tableHead leftTitle',
dataIndex: 'id', render: (text: string, record: TableRecord) => {
key: 'id', return (
width: 60, <div>{record.id}</div>
className: 'tableHead leftTitle', );
render: (text: string, record: TableObj) => { },
return ( }, {
<div>{record.id}</div> title: 'Duration',
); dataIndex: 'duration',
}, width: 140,
}, { render: (text: string, record: TableRecord) => {
title: 'Duration', return (
dataIndex: 'duration', <div className="durationsty"><div>{convertDuration(record.duration)}</div></div>
key: 'duration', );
width: 140, },
sorter: (a: TableObj, b: TableObj) => (a.duration as number) - (b.duration as number), }, {
render: (text: string, record: TableObj) => { title: 'Status',
let duration; dataIndex: 'status',
if (record.duration !== undefined) { width: 150,
// duration is nagative number(-1) & 0-1 className: 'tableStatus',
if (record.duration > 0 && record.duration < 1 || record.duration < 0) { render: (text: string, record: TableRecord) => {
duration = `${record.duration}s`; return (
} else { <div className={`${record.status} commonStyle`}>{record.status}</div>
duration = convertDuration(record.duration); );
}
} else {
duration = 0;
} }
return ( }, {
<div className="durationsty"><div>{duration}</div></div> title: 'Default metric',
); dataIndex: 'accuracy',
}, render: (text: string, record: TableRecord) => {
}, { return (
title: 'Status', <DefaultMetric trialId={record.id} />
dataIndex: 'status', );
key: 'status',
width: 150,
className: 'tableStatus',
render: (text: string, record: TableObj) => {
bgColor = record.status;
return (
<div className={`${bgColor} commonStyle`}>
{record.status}
</div>
);
}
}, {
title: 'Default metric',
dataIndex: 'acc',
key: 'acc',
sorter: (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;
} }
},
render: (text: string, record: TableObj) => {
return (
<DefaultMetric record={record} />
);
} }
}]; ];
return ( return (
<div className="tabScroll" > <div className="tabScroll" >
<Table <Table
columns={columns} columns={columns}
expandedRowRender={this.openRow} expandedRowRender={openRow}
dataSource={tableSource} dataSource={TRIALS.table(this.props.trialIds)}
className="commonTableStyle" className="commonTableStyle"
pagination={false} pagination={false}
/> />
</div > </div>
); );
} }
} }
......
import * as React from 'react'; import * as React from 'react';
import MonacoEditor from 'react-monaco-editor'; import MonacoEditor from 'react-monaco-editor';
import { MONACO } from '../../static/const'; import { MONACO } from '../../static/const';
import { EXPERIMENT } from '../../static/datamodel';
interface TrialInfoProps { interface TrialInfoProps {
experiment: object; experimentUpdateBroadcast: number;
concurrency: number;
} }
class TrialInfo extends React.Component<TrialInfoProps, {}> { class TrialInfo extends React.Component<TrialInfoProps, {}> {
...@@ -12,32 +14,21 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> { ...@@ -12,32 +14,21 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> {
super(props); super(props);
} }
componentWillReceiveProps(nextProps: TrialInfoProps) { render() {
const experiments = nextProps.experiment; const blacklist = [
Object.keys(experiments).map(key => { 'id', 'logDir', 'startTime', 'endTime',
switch (key) { 'experimentName', 'searchSpace', 'trainingServicePlatform'
case 'id': ];
case 'logDir': // tslint:disable-next-line:no-any
case 'startTime': const filter = (key: string, val: any) => {
case 'endTime': if (key === 'trialConcurrency') {
experiments[key] = undefined; return this.props.concurrency;
break;
case 'params':
const params = experiments[key];
Object.keys(params).map(item => {
if (item === 'experimentName' || item === 'searchSpace'
|| item === 'trainingServicePlatform') {
params[item] = undefined;
}
});
break;
default:
} }
}); return blacklist.includes(key) ? undefined : val;
} };
const profile = JSON.stringify(EXPERIMENT.profile, filter, 2);
render() { // FIXME: highlight not working?
const { experiment } = this.props;
return ( return (
<div className="profile"> <div className="profile">
<MonacoEditor <MonacoEditor
...@@ -45,7 +36,7 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> { ...@@ -45,7 +36,7 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> {
height="361" height="361"
language="json" language="json"
theme="vs-light" theme="vs-light"
value={JSON.stringify(experiment, null, 2)} value={profile}
options={MONACO} options={MONACO}
/> />
</div> </div>
......
import * as React from 'react'; import * as React from 'react';
import { TableObj } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
import { formatAccuracy } from '../../static/function';
interface DefaultMetricProps { interface DefaultMetricProps {
record: TableObj; trialId: string;
} }
class DefaultMetric extends React.Component<DefaultMetricProps, {}> { class DefaultMetric extends React.Component<DefaultMetricProps, {}> {
constructor(props: DefaultMetricProps) { constructor(props: DefaultMetricProps) {
super(props); super(props);
} }
render() { render() {
const { record } = this.props; const accuracy = TRIALS.getTrial(this.props.trialId).accuracy;
let accuracy;
if (record.acc !== undefined) {
accuracy = record.acc.default;
}
let wei = 0;
if (accuracy !== undefined) {
if (accuracy.toString().indexOf('.') !== -1) {
wei = accuracy.toString().length - accuracy.toString().indexOf('.') - 1;
}
}
return ( return (
<div> <div>{accuracy !== undefined ? formatAccuracy(accuracy) : '--'}</div>
{
record.acc !== undefined && record.acc.default !== undefined
?
wei > 6
?
JSON.parse(record.acc.default).toFixed(6)
:
record.acc.default
:
'--'
}
</div>
); );
} }
} }
export default DefaultMetric; export default DefaultMetric;
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { TableObj } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
interface IntermediateValProps { interface IntermediateValProps {
record: TableObj; trialId: string;
} }
class IntermediateVal extends React.Component<IntermediateValProps, {}> { class IntermediateVal extends React.Component<IntermediateValProps, {}> {
constructor(props: IntermediateValProps) { constructor(props: IntermediateValProps) {
super(props); super(props);
} }
render() { render() {
const { record } = this.props;
const interArr = record.description.intermediate;
let lastVal;
let wei = 0;
if (interArr !== undefined) {
lastVal = interArr[interArr.length - 1];
}
let result: string = JSON.stringify(lastVal);
if (lastVal !== undefined) {
if (lastVal.toString().indexOf('.') !== -1) {
wei = lastVal.toString().length - lastVal.toString().indexOf('.') - 1;
if (wei > 6) {
result = `${lastVal.toFixed(6)}`;
}
}
// some trial haven't final result
if (record.acc !== undefined) {
if (record.acc.default !== undefined) {
result = `${result} (FINAL)`;
}
} else {
result = `${result} (LATEST)`;
}
} else {
result = '--';
}
return ( return (
<div>{result}</div> <div>{TRIALS.getTrial(this.props.trialId).formatLatestAccuracy()}</div>
); );
} }
} }
......
...@@ -2,7 +2,8 @@ import * as React from 'react'; ...@@ -2,7 +2,8 @@ import * as React from 'react';
import * as copy from 'copy-to-clipboard'; import * as copy from 'copy-to-clipboard';
import PaiTrialLog from '../public-child/PaiTrialLog'; import PaiTrialLog from '../public-child/PaiTrialLog';
import TrialLog from '../public-child/TrialLog'; import TrialLog from '../public-child/TrialLog';
import { TableObj } from '../../static/interface'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { Trial } from '../../static/model/trial';
import { Row, Tabs, Button, message, Modal } from 'antd'; import { Row, Tabs, Button, message, Modal } from 'antd';
import { MANAGER_IP } from '../../static/const'; import { MANAGER_IP } from '../../static/const';
import '../../static/style/overview.scss'; import '../../static/style/overview.scss';
...@@ -11,10 +12,7 @@ import JSONTree from 'react-json-tree'; ...@@ -11,10 +12,7 @@ import JSONTree from 'react-json-tree';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
interface OpenRowProps { interface OpenRowProps {
trainingPlatform: string; trialId: string;
record: TableObj;
logCollection: boolean;
multiphase: boolean;
} }
interface OpenRowState { interface OpenRowState {
...@@ -24,7 +22,6 @@ interface OpenRowState { ...@@ -24,7 +22,6 @@ interface OpenRowState {
class OpenRow extends React.Component<OpenRowProps, OpenRowState> { class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
public _isMounted: boolean;
constructor(props: OpenRowProps) { constructor(props: OpenRowProps) {
super(props); super(props);
this.state = { this.state = {
...@@ -33,20 +30,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -33,20 +30,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
}; };
} }
showFormatModal = (record: TableObj) => { showFormatModal = (trial: Trial) => {
// get copy parameters // get copy parameters
const params = JSON.stringify(record.description.parameters, null, 4); const params = JSON.stringify(trial.info.hyperParameters, null, 4);
// open modal with format string // open modal with format string
if (this._isMounted === true) { this.setState({ isShowFormatModal: true, formatStr: params });
this.setState(() => ({ isShowFormatModal: true, formatStr: params }));
}
} }
hideFormatModal = () => { hideFormatModal = () => {
// close modal, destroy state format string data // close modal, destroy state format string data
if (this._isMounted === true) { this.setState({ isShowFormatModal: false, formatStr: '' });
this.setState(() => ({ isShowFormatModal: false, formatStr: '' }));
}
} }
copyParams = () => { copyParams = () => {
...@@ -62,68 +55,54 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -62,68 +55,54 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
this.hideFormatModal(); this.hideFormatModal();
} }
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { trainingPlatform, record, logCollection, multiphase } = this.props;
const { isShowFormatModal, formatStr } = this.state; const { isShowFormatModal, formatStr } = this.state;
const trialId = this.props.trialId;
const trial = TRIALS.getTrial(trialId);
let isClick = false; let isClick = false;
let isHasParameters = true; const trialLink: string = `${MANAGER_IP}/trial-jobs/${trialId}`;
if (record.description.parameters.error) { const logPathRow = trial.info.logPath || 'This trial\'s log path is not available.';
isHasParameters = false; const multiProgress = trial.info.hyperParameters === undefined ? 0 : trial.info.hyperParameters.length;
}
const openRowDataSource = record.description.parameters;
const trialink: string = `${MANAGER_IP}/trial-jobs/${record.id}`;
const logPathRow = record.description.logPath !== undefined
?
record.description.logPath
:
'This trial\'s log path are not available.';
return ( return (
<Row className="openRowContent hyperpar"> <Row className="openRowContent hyperpar">
<Tabs tabPosition="left" className="card"> <Tabs tabPosition="left" className="card">
<TabPane tab="Parameters" key="1"> <TabPane tab="Parameters" key="1">
{ {
multiphase EXPERIMENT.multiPhase
? ?
<Row className="link"> <Row className="link">
Trails for multiphase experiment will return a set of parameters, Trails for multiphase experiment will return a set of parameters,
we are listing the latest parameter in webportal. we are listing the latest parameter in webportal.
<br /> <br />
For the entire parameter set, please refer to the following " For the entire parameter set, please refer to the following "
<a href={trialink} target="_blank">{trialink}</a>". <a href={trialLink} target="_blank">{trialLink}</a>".
<br/> <br/>
Current Phase: {record.description.multiProgress}. Current Phase: {multiProgress}.
</Row> </Row>
: :
<div /> <div />
} }
{ {
isHasParameters trial.info.hyperParameters !== undefined
? ?
<Row id="description"> <Row id="description">
<Row className="bgHyper"> <Row className="bgHyper">
{ {
isClick isClick
? ?
<pre>{JSON.stringify(openRowDataSource, null, 4)}</pre> <pre>{JSON.stringify(trial.info.hyperParameters, null, 4)}</pre>
: :
<JSONTree <JSONTree
hideRoot={true} hideRoot={true}
shouldExpandNode={() => true} // default expandNode shouldExpandNode={() => true} // default expandNode
getItemString={() => (<span />)} // remove the {} items getItemString={() => (<span />)} // remove the {} items
data={openRowDataSource} data={trial.info.hyperParameters}
/> />
} }
</Row> </Row>
<Row className="copy"> <Row className="copy">
<Button <Button
onClick={this.showFormatModal.bind(this, record)} onClick={this.showFormatModal.bind(this, trial)}
> >
Copy as json Copy as json
</Button> </Button>
...@@ -138,15 +117,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -138,15 +117,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
</TabPane> </TabPane>
<TabPane tab="Log" key="2"> <TabPane tab="Log" key="2">
{ {
trainingPlatform !== 'local' // FIXME: this should not be handled in web UI side
EXPERIMENT.trainingServicePlatform !== 'local'
? ?
<PaiTrialLog <PaiTrialLog
logStr={logPathRow} logStr={logPathRow}
id={record.id} id={trialId}
logCollection={logCollection} logCollection={EXPERIMENT.logCollectionEnabled}
/> />
: :
<TrialLog logStr={logPathRow} id={record.id} /> <TrialLog logStr={logPathRow} id={trialId} />
} }
</TabPane> </TabPane>
</Tabs> </Tabs>
...@@ -170,4 +150,4 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -170,4 +150,4 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
} }
} }
export default OpenRow; export default OpenRow;
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { Switch } from 'antd'; import { Switch } from 'antd';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import { filterByStatus } from '../../static/function'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TableObj, DetailAccurPoint, TooltipForAccuracy } from '../../static/interface'; import { Trial } from '../../static/model/trial';
import { TooltipForAccuracy } from '../../static/interface';
require('echarts/lib/chart/scatter'); require('echarts/lib/chart/scatter');
require('echarts/lib/component/tooltip'); require('echarts/lib/component/tooltip');
require('echarts/lib/component/title'); require('echarts/lib/component/title');
interface DefaultPointProps { interface DefaultPointProps {
showSource: Array<TableObj>; trialIds: string[];
height: number; visible: boolean;
whichGraph: string; trialsUpdateBroadcast: number;
optimize: string;
} }
interface DefaultPointState { interface DefaultPointState {
defaultSource: object; bestCurveEnabled: boolean;
accNodata: string;
succeedTrials: number;
isViewBestCurve: boolean;
} }
class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> { class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> {
public _isDefaultMounted = false;
constructor(props: DefaultPointProps) { constructor(props: DefaultPointProps) {
super(props); super(props);
this.state = { this.state = { bestCurveEnabled: false };
defaultSource: {},
accNodata: '',
succeedTrials: 10000000,
isViewBestCurve: false
};
}
defaultMetric = (succeedSource: Array<TableObj>, isCurve: boolean) => {
const { optimize } = this.props;
const accSource: Array<DetailAccurPoint> = [];
const drawSource: Array<TableObj> = succeedSource.filter(filterByStatus);
const lengthOfSource = drawSource.length;
const tooltipDefault = lengthOfSource === 0 ? 'No data' : '';
if (this._isDefaultMounted === true) {
this.setState(() => ({
succeedTrials: lengthOfSource,
accNodata: tooltipDefault
}));
}
if (lengthOfSource === 0) {
const nullGraph = {
grid: {
left: '8%'
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
}
};
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: nullGraph
}));
}
} else {
const resultList: Array<number | object>[] = [];
// lineListDefault: [[sequenceId, default metric], []]
const lineListDefault: Array<number>[] = [];
Object.keys(drawSource).map(item => {
const temp = drawSource[item];
if (temp.acc !== undefined) {
if (temp.acc.default !== undefined) {
const searchSpace = temp.description.parameters;
lineListDefault.push([temp.sequenceId, temp.acc.default]);
accSource.push({
acc: temp.acc.default,
index: temp.sequenceId,
searchSpace: searchSpace
});
}
}
});
// deal with best metric line
const bestCurve: Array<number | object>[] = []; // best curve data source
if (lineListDefault[0] !== undefined) {
bestCurve.push([lineListDefault[0][0], lineListDefault[0][1], accSource[0].searchSpace]);
}
if (optimize === 'maximize') {
for (let i = 1; i < lineListDefault.length; i++) {
const val = lineListDefault[i][1];
const latest = bestCurve[bestCurve.length - 1][1];
if (val >= latest) {
bestCurve.push([lineListDefault[i][0], val, accSource[i].searchSpace]);
} else {
bestCurve.push([lineListDefault[i][0], latest, accSource[i].searchSpace]);
}
}
} else {
for (let i = 1; i < lineListDefault.length; i++) {
const val = lineListDefault[i][1];
const latest = bestCurve[bestCurve.length - 1][1];
if (val <= latest) {
bestCurve.push([lineListDefault[i][0], val, accSource[i].searchSpace]);
} else {
bestCurve.push([lineListDefault[i][0], latest, accSource[i].searchSpace]);
}
}
}
Object.keys(accSource).map(item => {
const items = accSource[item];
let temp: Array<number | object>;
temp = [items.index, items.acc, items.searchSpace];
resultList.push(temp);
});
// isViewBestCurve: false show default metric graph
// isViewBestCurve: true show best curve
if (isCurve === true) {
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: this.drawBestcurve(bestCurve, resultList)
}));
}
} else {
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: this.drawDefaultMetric(resultList)
}));
}
}
}
}
drawBestcurve = (realDefault: Array<number | object>[], resultList: Array<number | object>[]) => {
return {
grid: {
left: '8%'
},
tooltip: {
trigger: 'item',
enterable: true,
position: function (point: Array<number>, data: TooltipForAccuracy) {
if (data.data[0] < realDefault.length / 2) {
return [point[0], 80];
} else {
return [point[0] - 300, 80];
}
},
formatter: function (data: TooltipForAccuracy) {
const result = '<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Optimization curve: ' + data.data[1] + '</div>' +
'<div>Parameters: ' +
'<pre>' + JSON.stringify(data.data[2], null, 4) + '</pre>' +
'</div>' +
'</div>';
return result;
}
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true
},
series: [
{
type: 'line',
lineStyle: { color: '#FF6600' },
data: realDefault
},
{
symbolSize: 6,
type: 'scatter',
data: resultList
}]
};
}
drawDefaultMetric = (resultList: Array<number | object>[]) => {
return {
grid: {
left: '8%'
},
tooltip: {
trigger: 'item',
enterable: true,
position: function (point: Array<number>, 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 = '<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Default metric: ' + data.data[1] + '</div>' +
'<div>Parameters: ' +
'<pre>' + JSON.stringify(data.data[2], null, 4) + '</pre>' +
'</div>' +
'</div>';
return result;
}
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true
},
series: [{
symbolSize: 6,
type: 'scatter',
data: resultList
}]
};
} }
loadDefault = (checked: boolean) => { loadDefault = (checked: boolean) => {
// checked: true show best metric curve this.setState({ bestCurveEnabled: checked });
const { showSource } = this.props;
if (this._isDefaultMounted === true) {
this.defaultMetric(showSource, checked);
// ** deal with data and then update view layer
this.setState(() => ({ isViewBestCurve: checked }));
}
}
// update parent component state
componentWillReceiveProps(nextProps: DefaultPointProps) {
const { whichGraph, showSource } = nextProps;
const { isViewBestCurve } = this.state;
if (whichGraph === '1') {
this.defaultMetric(showSource, isViewBestCurve);
}
} }
shouldComponentUpdate(nextProps: DefaultPointProps, nextState: DefaultPointState) { shouldComponentUpdate(nextProps: DefaultPointProps, nextState: DefaultPointState) {
const { whichGraph } = nextProps; return nextProps.visible;
if (whichGraph === '1') {
const { succeedTrials, isViewBestCurve } = nextState;
const succTrial = this.state.succeedTrials;
const isViewBestCurveBefore = this.state.isViewBestCurve;
if (isViewBestCurveBefore !== isViewBestCurve) {
return true;
}
if (succeedTrials !== succTrial) {
return true;
}
}
// only whichGraph !== '1', default metric can't update
return false;
}
componentDidMount() {
this._isDefaultMounted = true;
}
componentWillUnmount() {
this._isDefaultMounted = false;
} }
render() { render() {
const { height } = this.props; const graph = this.generateGraph();
const { defaultSource, accNodata } = this.state; const accNodata = (graph === EmptyGraph ? 'No data' : '');
return ( return (
<div> <div>
<div className="default-metric"> <div className="default-metric">
...@@ -282,10 +45,10 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> ...@@ -282,10 +45,10 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState>
</div> </div>
</div> </div>
<ReactEcharts <ReactEcharts
option={defaultSource} option={graph}
style={{ style={{
width: '100%', width: '100%',
height: height, height: 402,
margin: '0 auto', margin: '0 auto',
}} }}
theme="my_theme" theme="my_theme"
...@@ -295,6 +58,102 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> ...@@ -295,6 +58,102 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState>
</div> </div>
); );
} }
private generateGraph() {
const trials = TRIALS.getTrials(this.props.trialIds).filter(trial => trial.sortable);
if (trials.length === 0) {
return EmptyGraph;
}
const graph = generateGraphConfig(trials[trials.length - 1].sequenceId);
if (this.state.bestCurveEnabled) {
(graph as any).series = [ generateBestCurveSeries(trials), generateScatterSeries(trials) ];
} else {
(graph as any).series = [ generateScatterSeries(trials) ];
}
return graph;
}
}
const EmptyGraph = {
grid: {
left: '8%'
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
}
};
function generateGraphConfig(maxSequenceId: number) {
return {
grid: {
left: '8%',
},
tooltip: {
trigger: 'item',
enterable: true,
position: (point: Array<number>, data: TooltipForAccuracy) => (
[ (data.data[0] < maxSequenceId ? point[0] : (point[0] - 300)), 80 ]
),
formatter: (data: TooltipForAccuracy) => (
'<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Default metric: ' + data.data[1] + '</div>' +
'<div>Parameters: <pre>' + JSON.stringify(data.data[2], null, 4) + '</pre></div>' +
'</div>'
),
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true,
},
series: undefined,
};
}
function generateScatterSeries(trials: Trial[]) {
const data = trials.map(trial => [
trial.sequenceId,
trial.accuracy,
trial.info.hyperParameters,
]);
return {
symbolSize: 6,
type: 'scatter',
data,
};
}
function generateBestCurveSeries(trials: Trial[]) {
let best = trials[0];
const data = [[ best.sequenceId, best.accuracy, best.info.hyperParameters ]];
for (let i = 1; i < trials.length; i++) {
const trial = trials[i];
const delta = trial.accuracy! - best.accuracy!;
const better = (EXPERIMENT.optimizeMode === 'minimize') ? (delta < 0) : (delta > 0);
if (better) {
data.push([ trial.sequenceId, trial.accuracy, trial.info.hyperParameters ]);
best = trial;
} else {
data.push([ trial.sequenceId, best.accuracy, trial.info.hyperParameters ]);
}
}
return {
type: 'line',
lineStyle: { color: '#FF6600' },
data,
};
} }
export default DefaultPoint; export default DefaultPoint;
\ 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