"vscode:/vscode.git/clone" did not exist on "0b3683bf6d2d21313ccc9621ec7911ae6fb9ce0e"
Unverified Commit 1e2a2e29 authored by Lijiaoa's avatar Lijiaoa Committed by GitHub
Browse files

Show trial error message (#2424)

parent 95b5e5f3
...@@ -21,7 +21,7 @@ class App extends React.Component<{}, AppState> { ...@@ -21,7 +21,7 @@ class App extends React.Component<{}, AppState> {
private timerId!: number | undefined; private timerId!: number | undefined;
private dataFormatimer!: number; private dataFormatimer!: number;
private firstLoad: boolean = false; // when click refresh selector options private firstLoad: boolean = false; // when click refresh selector options
constructor(props: {}) { constructor(props: {}) {
super(props); super(props);
this.state = { this.state = {
...@@ -49,8 +49,8 @@ class App extends React.Component<{}, AppState> { ...@@ -49,8 +49,8 @@ class App extends React.Component<{}, AppState> {
} }
getFinalDataFormat = (): void => { getFinalDataFormat = (): void => {
for(let i = 0; this.state.isillegalFinal === false; i++){ for (let i = 0; this.state.isillegalFinal === false; i++) {
if(TRIALS.succeededTrials()[0] !== undefined && TRIALS.succeededTrials()[0].final !== undefined){ if (TRIALS.succeededTrials()[0] !== undefined && TRIALS.succeededTrials()[0].final !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const oneSucceedTrial = JSON.parse(JSON.parse(TRIALS.succeededTrials()[0].final!.data)); const oneSucceedTrial = JSON.parse(JSON.parse(TRIALS.succeededTrials()[0].final!.data));
if (typeof oneSucceedTrial === 'number' || oneSucceedTrial.hasOwnProperty('default')) { if (typeof oneSucceedTrial === 'number' || oneSucceedTrial.hasOwnProperty('default')) {
...@@ -71,14 +71,14 @@ class App extends React.Component<{}, AppState> { ...@@ -71,14 +71,14 @@ class App extends React.Component<{}, AppState> {
} }
changeInterval = (interval: number): void => { changeInterval = (interval: number): void => {
window.clearTimeout(this.timerId); window.clearTimeout(this.timerId);
if (interval === 0) { if (interval === 0) {
return; return;
} }
// setState will trigger page refresh at once. // setState will trigger page refresh at once.
// setState is asyc, interval not update to (this.state.interval) at once. // setState is asyc, interval not update to (this.state.interval) at once.
this.setState({interval}, () => { this.setState({ interval }, () => {
this.firstLoad = true; this.firstLoad = true;
this.refresh(); this.refresh();
}); });
...@@ -96,7 +96,7 @@ class App extends React.Component<{}, AppState> { ...@@ -96,7 +96,7 @@ class App extends React.Component<{}, AppState> {
// overview best trial module // overview best trial module
changeEntries = (entries: string): void => { changeEntries = (entries: string): void => {
this.setState({bestTrialEntries: entries}); this.setState({ bestTrialEntries: entries });
} }
render(): React.ReactNode { render(): React.ReactNode {
...@@ -106,15 +106,25 @@ class App extends React.Component<{}, AppState> { ...@@ -106,15 +106,25 @@ class App extends React.Component<{}, AppState> {
if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) { if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
return null; // TODO: render a loading page return null; // TODO: render a loading page
} }
const errorList = [
{ errorWhere: TRIALS.jobListError(), errorMessage: TRIALS.getJobErrorMessage() },
{ errorWhere: EXPERIMENT.experimentError(), errorMessage: EXPERIMENT.getExperimentMessage() },
{ errorWhere: EXPERIMENT.statusError(), errorMessage: EXPERIMENT.getStatusMessage() },
{ errorWhere: TRIALS.MetricDataError(), errorMessage: TRIALS.getMetricDataErrorMessage() },
{ errorWhere: TRIALS.latestMetricDataError(), errorMessage: TRIALS.getLatestMetricDataErrorMessage() },
{ errorWhere: TRIALS.metricDataRangeError(), errorMessage: TRIALS.metricDataRangeErrorMessage() }
];
const reactPropsChildren = React.Children.map(this.props.children, child => const reactPropsChildren = React.Children.map(this.props.children, child =>
React.cloneElement( React.cloneElement(
child as React.ReactElement<any>, { child as React.ReactElement<any>, {
interval, interval,
columnList, changeColumn: this.changeColumn, columnList, changeColumn: this.changeColumn,
experimentUpdateBroadcast, experimentUpdateBroadcast,
trialsUpdateBroadcast, trialsUpdateBroadcast,
metricGraphMode, changeMetricGraphMode: this.changeMetricGraphMode, metricGraphMode, changeMetricGraphMode: this.changeMetricGraphMode,
bestTrialEntries, changeEntries: this.changeEntries bestTrialEntries, changeEntries: this.changeEntries
}) })
); );
...@@ -127,6 +137,16 @@ class App extends React.Component<{}, AppState> { ...@@ -127,6 +137,16 @@ class App extends React.Component<{}, AppState> {
</div> </div>
<Stack className="contentBox"> <Stack className="contentBox">
<Stack className="content"> <Stack className="content">
{/* if api has error field, show error message */}
{
errorList.map((item, key) => {
return (
item.errorWhere && <div key={key} className="warning">
<MessageInfo info={item.errorMessage} typeInfo="error" />
</div>
);
})
}
{isillegalFinal && <div className="warning"> {isillegalFinal && <div className="warning">
<MessageInfo info={expWarningMessage} typeInfo="warning" /> <MessageInfo info={expWarningMessage} typeInfo="warning" />
</div>} </div>}
...@@ -149,18 +169,20 @@ class App extends React.Component<{}, AppState> { ...@@ -149,18 +169,20 @@ class App extends React.Component<{}, AppState> {
if (trialsUpdated) { if (trialsUpdated) {
this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 })); this.setState(state => ({ trialsUpdateBroadcast: state.trialsUpdateBroadcast + 1 }));
} }
} else { } else {
this.firstLoad = false; this.firstLoad = false;
} }
if (['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status)) { // experiment status and /trial-jobs api's status could decide website update
if (['DONE', 'ERROR', 'STOPPED'].includes(EXPERIMENT.status) || TRIALS.jobListError()) {
// experiment finished, refresh once more to ensure consistency // experiment finished, refresh once more to ensure consistency
this.setState({ interval: 0 }); this.setState({ interval: 0 });
this.lastRefresh(); this.lastRefresh();
return; return;
} }
this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000); this.timerId = window.setTimeout(this.refresh, this.state.interval * 1000);
} }
......
...@@ -18,7 +18,7 @@ class MessageInfo extends React.Component<MessageInfoProps, {}> { ...@@ -18,7 +18,7 @@ class MessageInfo extends React.Component<MessageInfoProps, {}> {
return ( return (
<MessageBar <MessageBar
messageBarType={MessageBarType[typeInfo]} messageBarType={MessageBarType[typeInfo]}
isMultiline={false} isMultiline={true}
className={className} className={className}
> >
{info} {info}
......
...@@ -22,7 +22,7 @@ class ProgressBar extends React.Component<ProItemProps, {}> { ...@@ -22,7 +22,7 @@ class ProgressBar extends React.Component<ProItemProps, {}> {
<div> <div>
<Stack horizontal className={`probar ${bgclass}`}> <Stack horizontal className={`probar ${bgclass}`}>
<div className="name">{who}</div> <div className="name">{who}</div>
<div className="showProgress" style={{ width: '80%' }}> <div className="showProgress" style={{ width: '78%' }}>
<ProgressIndicator <ProgressIndicator
barHeight={30} barHeight={30}
percentComplete={percent} percentComplete={percent}
...@@ -32,7 +32,7 @@ class ProgressBar extends React.Component<ProItemProps, {}> { ...@@ -32,7 +32,7 @@ class ProgressBar extends React.Component<ProItemProps, {}> {
<StackItem className="right" grow={70}>{maxString}</StackItem> <StackItem className="right" grow={70}>{maxString}</StackItem>
</Stack> </Stack>
</div> </div>
<div className="description" style={{ width: '20%' }}>{description}</div> <div className="description" style={{ width: '22%' }}>{description}</div>
</Stack> </Stack>
<br /> <br />
</div> </div>
......
...@@ -3,6 +3,19 @@ import axios from 'axios'; ...@@ -3,6 +3,19 @@ import axios from 'axios';
import { MANAGER_IP } from './const'; import { MANAGER_IP } from './const';
import { MetricDataRecord, FinalType, TableObj } from './interface'; import { MetricDataRecord, FinalType, TableObj } from './interface';
async function requestAxios(url: string) {
const response = await axios.get(url);
if (response.status === 200) {
if (response.data.error !== undefined) {
throw new Error(`API ${url} ${response.data.error}`);
} else {
return response.data as any;
}
} else {
throw new Error(`API ${url} ${response.status} error`);
}
}
const convertTime = (num: number): string => { const convertTime = (num: number): string => {
if (num <= 0) { if (num <= 0) {
return '0'; return '0';
...@@ -219,5 +232,5 @@ export { ...@@ -219,5 +232,5 @@ export {
convertTime, convertDuration, getFinalResult, getFinal, downFile, convertTime, convertDuration, getFinalResult, getFinal, downFile,
intermediateGraphOption, killJob, filterByStatus, filterDuration, intermediateGraphOption, killJob, filterByStatus, filterDuration,
formatAccuracy, formatTimestamp, metricAccuracy, parseMetrics, formatAccuracy, formatTimestamp, metricAccuracy, parseMetrics,
isArrayType isArrayType, requestAxios
}; };
import axios from 'axios'; import axios from 'axios';
import { MANAGER_IP } from '../const'; import { MANAGER_IP } from '../const';
import { ExperimentProfile, NNIManagerStatus } from '../interface'; import { ExperimentProfile, NNIManagerStatus } from '../interface';
import { requestAxios } from '../function';
function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProfile): boolean { function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProfile): boolean {
if (!profile1 || !profile2) { if (!profile1 || !profile2) {
...@@ -14,32 +15,87 @@ function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProf ...@@ -14,32 +15,87 @@ function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProf
class Experiment { class Experiment {
private profileField?: ExperimentProfile = undefined; private profileField?: ExperimentProfile = undefined;
private statusField?: NNIManagerStatus = undefined; private statusField?: NNIManagerStatus = undefined;
private isexperimentError: boolean = false;
private experimentErrorMessage: string = '';
private isStatusError: boolean = false;
private statusErrorMessage: string = '';
public async init(): Promise<void> { public async init(): Promise<void> {
while (!this.profileField || !this.statusField) { while (!this.profileField || !this.statusField) {
if (this.isexperimentError) {
return;
}
if (this.isStatusError) {
return;
}
await this.update(); await this.update();
} }
} }
public experimentError(): boolean {
return this.isexperimentError;
}
public statusError(): boolean {
return this.isStatusError;
}
public getExperimentMessage(): string {
return this.experimentErrorMessage;
}
public getStatusMessage(): string {
return this.statusErrorMessage;
}
public async update(): Promise<boolean> { public async update(): Promise<boolean> {
const profilePromise = axios.get(`${MANAGER_IP}/experiment`);
const statusPromise = axios.get(`${MANAGER_IP}/check-status`);
const [ profileResponse, statusResponse ] = await Promise.all([ profilePromise, statusPromise ]);
let updated = false; let updated = false;
if (statusResponse.status === 200) {
updated = JSON.stringify(this.statusField) === JSON.stringify(statusResponse.data); await requestAxios(`${MANAGER_IP}/experiment`)
this.statusField = statusResponse.data; .then(data => {
} updated = updated || compareProfiles(this.profileField, data);
if (profileResponse.status === 200) { this.profileField = data;
updated = updated || compareProfiles(this.profileField, profileResponse.data); })
this.profileField = profileResponse.data; .catch(error => {
} this.isexperimentError = true;
this.experimentErrorMessage = `${error.message}`;
updated = true;
});
await requestAxios(`${MANAGER_IP}/check-status`)
.then(data => {
updated = JSON.stringify(this.statusField) === JSON.stringify(data);
this.statusField = data;
})
.catch(error => {
this.isStatusError = true;
this.statusErrorMessage = `${error.message}`;
updated = true;
});
return updated; return updated;
} }
get profile(): ExperimentProfile { get profile(): ExperimentProfile {
if (!this.profileField) { if (!this.profileField) {
throw Error('Experiment profile not initialized'); // throw Error('Experiment profile not initialized');
// set initProfile to prevent page broken
const initProfile = {
data: {
"id": "", "revision": 0, "execDuration": 0,
"logDir": "", "nextSequenceId": 0,
"params": {
"authorName": "", "experimentName": "", "trialConcurrency": 0, "maxExecDuration": 0, "maxTrialNum": 0, "searchSpace": "null",
"trainingServicePlatform": "", "tuner": {
"builtinTunerName": "TPE",
"classArgs": { "optimize_mode": "" }, "checkpointDir": ""
},
"versionCheck": true, "clusterMetaData": [{ "key": "", "value": "" },
{ "key": "", "value": "" }]
}, "startTime": 0, "endTime": 0
}
};
this.profileField = initProfile.data as any;
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.profileField!; return this.profileField!;
...@@ -68,7 +124,9 @@ class Experiment { ...@@ -68,7 +124,9 @@ class Experiment {
get status(): string { get status(): string {
if (!this.statusField) { if (!this.statusField) {
throw Error('Experiment status not initialized'); // throw Error('Experiment status not initialized');
// this.statusField.status = '';
return '';
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.statusField!.status; return this.statusField!.status;
......
...@@ -2,6 +2,7 @@ import axios from 'axios'; ...@@ -2,6 +2,7 @@ import axios from 'axios';
import { MANAGER_IP, METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE } from '../const'; import { MANAGER_IP, METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE } from '../const';
import { MetricDataRecord, TableRecord, TrialJobInfo } from '../interface'; import { MetricDataRecord, TableRecord, TrialJobInfo } from '../interface';
import { Trial } from './trial'; import { Trial } from './trial';
import { requestAxios } from '../function';
function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> { function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> {
const ret = new Map<string, MetricDataRecord[]>(); const ret = new Map<string, MetricDataRecord[]>();
...@@ -39,9 +40,20 @@ class TrialManager { ...@@ -39,9 +40,20 @@ class TrialManager {
private maxSequenceId: number = 0; private maxSequenceId: number = 0;
private doingBatchUpdate: boolean = false; private doingBatchUpdate: boolean = false;
private batchUpdatedAfterReading: boolean = false; private batchUpdatedAfterReading: boolean = false;
private isJobListError: boolean = false; // trial-jobs api error filed
private jobErrorMessage: string = ''; // trial-jobs error message
private isMetricdataError: boolean = false; // metric-data api error filed
private MetricdataErrorMessage: string = ''; // metric-data error message
private isLatestMetricdataError: boolean = false; // metric-data-latest api error filed
private latestMetricdataErrorMessage: string = ''; // metric-data-latest error message
private isMetricdataRangeError: boolean = false; // metric-data-range api error filed
private metricdataRangeErrorMessage: string = ''; // metric-data-latest error message
public async init(): Promise<void> { public async init(): Promise<void> {
while (!this.infoInitialized || !this.metricInitialized) { while (!this.infoInitialized || !this.metricInitialized) {
if (this.isMetricdataError) {
return;
}
await this.update(); await this.update();
} }
} }
...@@ -156,24 +168,70 @@ class TrialManager { ...@@ -156,24 +168,70 @@ class TrialManager {
return trials; return trials;
} }
// if this.jobListError = true, show trial error message [/trial-jobs]
public jobListError(): boolean {
return this.isJobListError;
}
// trial error message's content [/trial-jobs]
public getJobErrorMessage(): string {
return this.jobErrorMessage;
}
// [/metric-data]
public MetricDataError(): boolean {
return this.isMetricdataError;
}
// [/metric-data]
public getMetricDataErrorMessage(): string {
return this.MetricdataErrorMessage;
}
// [/metric-data-latest]
public latestMetricDataError(): boolean {
return this.isLatestMetricdataError;
}
// [/metric-data-latest]
public getLatestMetricDataErrorMessage(): string {
return this.latestMetricdataErrorMessage;
}
public metricDataRangeError(): boolean {
return this.isMetricdataRangeError;
}
public metricDataRangeErrorMessage(): string {
return this.metricdataRangeErrorMessage;
}
private async updateInfo(): Promise<boolean> { private async updateInfo(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/trial-jobs`);
let updated = false; let updated = false;
if (response.status === 200) { requestAxios(`${MANAGER_IP}/trial-jobs`)
const newTrials = TrialManager.expandJobsToTrials(response.data); .then(data => {
for (const trialInfo of newTrials as TrialJobInfo[]) { const newTrials = TrialManager.expandJobsToTrials(data as any);
if (this.trials.has(trialInfo.id)) { for (const trialInfo of newTrials as TrialJobInfo[]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (this.trials.has(trialInfo.id)) {
updated = this.trials.get(trialInfo.id)!.updateTrialJobInfo(trialInfo) || updated; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
} else { updated = this.trials.get(trialInfo.id)!.updateTrialJobInfo(trialInfo) || updated;
this.trials.set(trialInfo.id, new Trial(trialInfo, undefined)); } else {
updated = true; this.trials.set(trialInfo.id, new Trial(trialInfo, undefined));
updated = true;
}
this.maxSequenceId = Math.max(this.maxSequenceId, trialInfo.sequenceId);
} }
this.maxSequenceId = Math.max(this.maxSequenceId, trialInfo.sequenceId); this.infoInitialized = true;
} })
this.infoInitialized = true; .catch(error => {
} this.isJobListError = true;
return updated; this.jobErrorMessage = error.message;
this.infoInitialized = true;
updated = true;
});
return updated;
} }
private async updateMetrics(lastTime?: boolean): Promise<boolean> { private async updateMetrics(lastTime?: boolean): Promise<boolean> {
...@@ -188,13 +246,25 @@ class TrialManager { ...@@ -188,13 +246,25 @@ class TrialManager {
} }
private async updateAllMetrics(): Promise<boolean> { private async updateAllMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data`); return requestAxios(`${MANAGER_IP}/metric-data`)
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], false); .then(data => this.doUpdateMetrics(data as any, false))
.catch(error => {
this.isMetricdataError = true;
this.MetricdataErrorMessage = `${error.message}`;
this.doUpdateMetrics([], false);
return true;
});
} }
private async updateLatestMetrics(): Promise<boolean> { private async updateLatestMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data-latest`); return requestAxios(`${MANAGER_IP}/metric-data-latest`)
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], true); .then(data => this.doUpdateMetrics(data as any, true))
.catch(error => {
this.isLatestMetricdataError = true;
this.latestMetricdataErrorMessage = `${error.message}`;
this.doUpdateMetrics([], true);
return true;
});
} }
private async updateManyMetrics(): Promise<void> { private async updateManyMetrics(): Promise<void> {
...@@ -202,12 +272,16 @@ class TrialManager { ...@@ -202,12 +272,16 @@ class TrialManager {
return; return;
} }
this.doingBatchUpdate = true; this.doingBatchUpdate = true;
for (let i = 0; i < this.maxSequenceId; i += METRIC_GROUP_UPDATE_SIZE) { for (let i = 0; i < this.maxSequenceId && this.isMetricdataRangeError === false; i += METRIC_GROUP_UPDATE_SIZE) {
const response = await axios.get(`${MANAGER_IP}/metric-data-range/${i}/${i + METRIC_GROUP_UPDATE_SIZE}`); requestAxios(`${MANAGER_IP}/metric-data-range/${i}/${i + METRIC_GROUP_UPDATE_SIZE}`)
if (response.status === 200) { .then(data => {
const updated = this.doUpdateMetrics(response.data as MetricDataRecord[], false); const updated = this.doUpdateMetrics(data as any, false);
this.batchUpdatedAfterReading = this.batchUpdatedAfterReading || updated; this.batchUpdatedAfterReading = this.batchUpdatedAfterReading || updated;
} })
.catch(error => {
this.isMetricdataRangeError = true;
this.metricdataRangeErrorMessage = `${error.message}`;
});
} }
this.doingBatchUpdate = false; this.doingBatchUpdate = false;
} }
......
...@@ -46,6 +46,9 @@ ...@@ -46,6 +46,9 @@
.description{ .description{
line-height: 34px; line-height: 34px;
margin-left: 6px; margin-left: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
......
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