Commit c288a16e authored by Lijiao's avatar Lijiao Committed by chicm-ms
Browse files

[WebUI] Show trial log for pai and k8s (#580)

* [WebUI] Show trial log for pai and k8s

* fix lint

* Fix comments
parent d13964dc
......@@ -17,12 +17,17 @@
.headerCon{
min-width: 1024px;
}
.contentBox{
width: 100%;
background: #f2f2f2;
}
.content{
width: 86%;
min-width: 1024px;
margin: 0 auto;
margin-top: 74px;
margin-bottom: 30px;
background: #fff;
}
......@@ -6,18 +6,20 @@ import SlideBar from './components/SlideBar';
class App extends React.Component<{}, {}> {
render() {
return (
<Row className="nni">
<Row className="nni" style={{minHeight: window.innerHeight}}>
<Row className="header">
<Col span={1} />
<Col className="headerCon" span={22}>
<SlideBar />
</Col>
<Col span={1}/>
<Col span={1} />
</Row>
<Row className="contentBox">
<Row className="content">
{this.props.children}
</Row>
</Row>
</Row>
);
}
}
......
......@@ -215,6 +215,7 @@ class Overview extends React.Component<{}, OverviewState> {
case 'USER_CANCELED':
case 'SYS_CANCELED':
case 'EARLY_STOPPED':
profile.stopTrial += 1;
break;
case 'SUCCEEDED':
......@@ -461,7 +462,10 @@ class Overview extends React.Component<{}, OverviewState> {
</Row>
</Col>
<Col span={16} id="succeTable">
<SuccessTable tableSource={tableData} />
<SuccessTable
tableSource={tableData}
trainingPlatform={trialProfile.trainingServicePlatform}
/>
</Col>
</Row>
</div>
......
......@@ -21,6 +21,7 @@ interface TrialDetailState {
isHasSearch: boolean;
experimentStatus: string;
entriesTable: number;
experimentPlatform: string;
}
class TrialsDetail extends React.Component<{}, TrialDetailState> {
......@@ -43,6 +44,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
experimentStatus: '',
entriesTable: 20,
isHasSearch: false,
experimentPlatform: ''
};
}
// trial accuracy graph
......@@ -370,6 +372,26 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
alert('TableList component was not properly initialized.');
}
checkExperimentPlatform = () => {
axios(`${MANAGER_IP}/experiment`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
const trainingPlatform = res.data.params.trainingServicePlatform !== undefined
?
res.data.params.trainingServicePlatform
:
'';
if (this._isMounted) {
this.setState({
experimentPlatform: trainingPlatform
});
}
}
});
}
componentDidMount() {
this._isMounted = true;
......@@ -377,6 +399,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
this.drawPointGraph();
this.interTableList = window.setInterval(this.drawTableList, 10000);
this.interAccuracy = window.setInterval(this.drawPointGraph, 10000);
this.checkExperimentPlatform();
}
componentWillUnmount() {
......@@ -386,7 +409,10 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
}
render() {
const { accSource, accNodata, tableListSource, entriesTable, searchResultSource, isHasSearch } = this.state;
const { accSource, accNodata, tableListSource,
entriesTable, searchResultSource, isHasSearch,
experimentPlatform
} = this.state;
const titleOfacc = (
<Title1 text="Default Metric" icon="3.png" />
);
......@@ -463,6 +489,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
updateList={this.drawTableList}
searchResult={searchResultSource}
isHasSearch={isHasSearch}
platform={experimentPlatform}
ref={(tabList) => this.tableList = tabList}
/>
</div>
......
import * as React from 'react';
import { Row, Button } from 'antd';
import { DOWNLOAD_IP } from '../../static/const';
interface PaiTrialChildProps {
logString: string;
id: string;
showLogModal: Function;
isdisLogbtn?: boolean;
}
class PaiTrialChild extends React.Component<PaiTrialChildProps, {}> {
constructor(props: PaiTrialChildProps) {
super(props);
}
render() {
const { logString, id, showLogModal, isdisLogbtn } = this.props;
return (
<div>
{
logString === ''
?
<div />
:
<Row>
<Row>
<a
target="_blank"
href={`${DOWNLOAD_IP}/trial_${id}.log`}
style={{ marginRight: 10 }}
>
trial stdout
</a>
</Row>
<Row>
<Button
disabled={isdisLogbtn}
type="primary"
className="tableButton"
onClick={showLogModal.bind(this, id)}
>
View
</Button>
</Row>
</Row>
}
</div>
);
}
}
export default PaiTrialChild;
import * as React from 'react';
import { Row, Button } from 'antd';
import { DOWNLOAD_IP } from '../../static/const';
import PaiTrialChild from './PaiTrialChild';
interface PaitrialLogProps {
logStr: string;
id: string;
showLogModal: Function;
trialStatus?: string;
isdisLogbutton?: boolean;
}
class PaitrialLog extends React.Component<PaitrialLogProps, {}> {
constructor(props: PaitrialLogProps) {
super(props);
}
render() {
const { logStr, id, showLogModal,
isdisLogbutton
} = this.props;
const isTwopath = logStr.indexOf(',') !== -1
?
true
:
false;
return (
<div>
<div>
{
isTwopath
?
<Row>
<Row>
<a
target="_blank"
href={`${DOWNLOAD_IP}/trial_${id}.log`}
style={{ marginRight: 10 }}
>
trial stdout
</a>
<a target="_blank" href={logStr.split(',')[1]}>hdfsLog</a>
</Row>
<Row>
<Button
disabled={isdisLogbutton}
type="primary"
className="tableButton"
onClick={showLogModal.bind(this, id)}
>
View
</Button>
</Row>
</Row>
:
<PaiTrialChild
logString={logStr}
id={id}
showLogModal={showLogModal}
isdisLogbtn={isdisLogbutton}
/>
}
</div>
</div>
);
}
}
export default PaitrialLog;
import * as React from 'react';
import LogPathChild from './LogPathChild';
interface TrialLogProps {
logStr: string;
id: string;
}
class TrialLog extends React.Component<TrialLogProps, {}> {
constructor(props: TrialLogProps) {
super(props);
}
render() {
const { logStr } = this.props;
return (
<div>
<LogPathChild
eachLogpath={logStr}
logName="LogPath:"
/>
</div>
);
}
}
export default TrialLog;
import * as React from 'react';
import {
Row,
Col,
Popover,
Button,
message
Row, Col, Popover, Button, message
} from 'antd';
import axios from 'axios';
import { MANAGER_IP, CONTROLTYPE } from '../../static/const';
......@@ -92,7 +88,8 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
if (error.response.data.error) {
message.error(error.response.data.error);
} else {
message.error(`Update ${CONTROLTYPE[1].toLocaleLowerCase()} failed`);
message.error(
`Update ${CONTROLTYPE[1].toLocaleLowerCase()} failed`);
}
}
});
......@@ -179,7 +176,6 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
return (
<Row className="progress" id="barBack">
<Row className="basic lineBasic">
<Col span={12}>
<p>Status</p>
<div className="status">
<span className={status}>{status}</span>
......@@ -198,10 +194,29 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
<span />
}
</div>
</Row>
<ProgressBar
who="Duration"
percent={percent}
description={runDuration}
bgclass={status}
maxString={`MaxDuration: ${convertTime(trialProfile.maxDuration)}`}
/>
<ProgressBar
who="TrialNum"
percent={bar2Percent}
description={bar2.toString()}
bgclass={status}
maxString={`MaxTrialNumber: ${trialProfile.MaxTrialNum}`}
/>
<Row className="basic colorOfbasic mess">
<Col span={10}>
<p>best metric</p>
<div>{bestAccuracy.toFixed(6)}</div>
</Col>
<Col span={12}>
<Col span={14}>
{/* modify concurrency */}
<p>Concurrency</p>
<p>concurrency</p>
<Row className="inputBox">
<input
type="number"
......@@ -227,24 +242,6 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
</Row>
</Col>
</Row>
<ProgressBar
who="Duration"
percent={percent}
description={runDuration}
bgclass={status}
maxString={`MaxDuration: ${convertTime(trialProfile.maxDuration)}`}
/>
<ProgressBar
who="TrialNum"
percent={bar2Percent}
description={bar2.toString()}
bgclass={status}
maxString={`MaxTrialNumber: ${trialProfile.MaxTrialNum}`}
/>
<Row className="basic colorOfbasic mess">
<p>best metric</p>
<div>{bestAccuracy}</div>
</Row>
<Row className="mess">
<Col span={8}>
<Row className="basic colorOfbasic">
......
import * as React from 'react';
import axios from 'axios';
import JSONTree from 'react-json-tree';
import { Table } from 'antd';
import { Row, Modal, Input, Table, Tabs } from 'antd';
const TabPane = Tabs.TabPane;
const { TextArea } = Input;
import { DOWNLOAD_IP } from '../../static/const';
import { TableObj } from '../../static/interface';
import { convertDuration } from '../../static/function';
import PaiTrialLog from '../logPath/PaiTrialLog';
import TrialLog from '../logPath/TrialLog';
import '../../static/style/tableStatus.css';
import LogPath from '../logPath/LogPath';
import '../../static/style/tableList.scss';
interface SuccessTableProps {
tableSource: Array<TableObj>;
trainingPlatform: string;
}
class SuccessTable extends React.Component<SuccessTableProps, {}> {
interface SuccessTableState {
isShowLogModal: boolean;
logContent: string;
}
class SuccessTable extends React.Component<SuccessTableProps, SuccessTableState> {
public _isMounted = false;
constructor(props: SuccessTableProps) {
super(props);
this.state = {
isShowLogModal: false,
logContent: ''
};
}
showLogModalOverview = (id: string) => {
axios(`${DOWNLOAD_IP}/trial_${id}.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
if (this._isMounted) {
this.setState(() => ({
logContent: res.data
}));
}
}
})
.catch(error => {
if (error.response.status === 500) {
if (this._isMounted) {
this.setState(() => ({
logContent: 'failed to get log message'
}));
}
}
});
if (this._isMounted) {
this.setState({
isShowLogModal: true
});
}
}
hideLogModalOverview = () => {
if (this._isMounted) {
this.setState({
isShowLogModal: false,
logContent: '' // close modal, delete data
});
}
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { tableSource } = this.props;
const { tableSource, trainingPlatform } = this.props;
const { isShowLogModal, logContent } = this.state;
let bgColor = '';
const columns = [{
......@@ -114,6 +180,9 @@ class SuccessTable extends React.Component<SuccessTableProps, {}> {
'This trial\'s logPath are not available.';
return (
<pre id="description" className="hyperpar">
<Row className="openRowContent">
<Tabs tabPosition="left" className="card">
<TabPane tab="Parameters" key="1">
{
isHasParameters
?
......@@ -129,7 +198,22 @@ class SuccessTable extends React.Component<SuccessTableProps, {}> {
<span className="error">'This trial's parameters are not available.'</span>
</div>
}
<LogPath logStr={logPathRow}/>
</TabPane>
<TabPane tab="Log" key="2">
{
trainingPlatform === 'pai' || trainingPlatform === 'kubeflow'
?
<PaiTrialLog
logStr={logPathRow}
id={record.id}
showLogModal={this.showLogModalOverview}
/>
:
<TrialLog logStr={logPathRow} id={record.id} />
}
</TabPane>
</Tabs>
</Row>
</pre>
);
};
......@@ -142,6 +226,23 @@ class SuccessTable extends React.Component<SuccessTableProps, {}> {
className="commonTableStyle"
pagination={false}
/>
{/* trial log modal */}
<Modal
title="trial log"
visible={isShowLogModal}
onCancel={this.hideLogModalOverview}
footer={null}
destroyOnClose={true}
width="80%"
>
<div id="trialLogContent" style={{ height: window.innerHeight * 0.6 }}>
<TextArea
value={logContent}
disabled={true}
className="logcontent"
/>
</div>
</Modal>
</div>
);
}
......
......@@ -2,18 +2,24 @@ import * as React from 'react';
import axios from 'axios';
import JSONTree from 'react-json-tree';
import ReactEcharts from 'echarts-for-react';
import { Row, Table, Button, Popconfirm, Modal, message, Checkbox } from 'antd';
import {
Row, Input, Table, Tabs, Button, Popconfirm, Modal, message, Checkbox
} from 'antd';
const { TextArea } = Input;
const TabPane = Tabs.TabPane;
const CheckboxGroup = Checkbox.Group;
import { MANAGER_IP, trialJobStatus, COLUMN, COLUMN_INDEX } from '../../static/const';
import { MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMN, COLUMN_INDEX } from '../../static/const';
import { convertDuration } from '../../static/function';
import { TableObjFianl, TrialJob } from '../../static/interface';
import LogPath from '../logPath/LogPath';
import PaiTrialLog from '../logPath/PaiTrialLog';
import TrialLog from '../logPath/TrialLog';
import '../../static/style/search.scss';
require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss');
require('../../static/style/search.scss');
require('../../static/style/table.scss');
require('../../static/style/button.scss');
require('../../static/style/tableList.scss');
const echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line');
require('echarts/lib/component/tooltip');
......@@ -28,6 +34,7 @@ interface TableListProps {
searchResult: Array<TableObjFianl>;
updateList: Function;
isHasSearch: boolean;
platform: string;
}
interface TableListState {
......@@ -36,6 +43,8 @@ interface TableListState {
isObjFinal: boolean;
isShowColumn: boolean;
columnSelected: Array<string>; // user select columnKeys
logModal: boolean;
logMessage: string;
}
interface ColumnIndex {
......@@ -46,6 +55,9 @@ interface ColumnIndex {
class TableList extends React.Component<TableListProps, TableListState> {
public _isMounted = false;
public intervalTrialLog = 10;
public _trialId: string;
constructor(props: TableListProps) {
super(props);
......@@ -54,7 +66,9 @@ class TableList extends React.Component<TableListProps, TableListState> {
modalVisible: false,
isObjFinal: false,
isShowColumn: false,
logModal: false,
columnSelected: COLUMN,
logMessage: ''
};
}
......@@ -92,6 +106,51 @@ class TableList extends React.Component<TableListProps, TableListState> {
}
}
updateTrialLogMessage = (id: string) => {
this._trialId = id;
axios(`${DOWNLOAD_IP}/trial_${this._trialId}.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
if (this._isMounted) {
this.setState(() => ({
logMessage: res.data
}));
}
}
})
.catch(error => {
if (error.response.status === 500) {
if (this._isMounted) {
this.setState(() => ({
logMessage: 'failed to get log message'
}));
}
}
});
}
showLogModal = (id: string) => {
this.updateTrialLogMessage(id);
this.intervalTrialLog = window.setInterval(this.updateTrialLogMessage.bind(this, this._trialId), 10000);
if (this._isMounted) {
this.setState({
logModal: true
});
}
}
hideLogModal = () => {
window.clearInterval(this.intervalTrialLog);
if (this._isMounted) {
this.setState({
logModal: false,
logMessage: ''
});
}
}
hideShowColumnModal = () => {
if (this._isMounted) {
this.setState({
......@@ -235,11 +294,14 @@ class TableList extends React.Component<TableListProps, TableListState> {
render() {
const { entries, tableSource, searchResult, isHasSearch } = this.props;
const { entries, tableSource, searchResult, isHasSearch, platform } = this.props;
const { intermediateOption, modalVisible, isShowColumn, columnSelected,
logMessage, logModal
} = this.state;
let showTitle = COLUMN;
let bgColor = '';
const trialJob: Array<TrialJob> = [];
const showColumn: Array<object> = [];
if (tableSource.length >= 1) {
const temp = tableSource[0].acc;
if (temp !== undefined && typeof temp === 'object') {
......@@ -256,16 +318,12 @@ class TableList extends React.Component<TableListProps, TableListState> {
}
}
}
let bgColor = '';
const trialJob: Array<TrialJob> = [];
trialJobStatus.map(item => {
trialJob.push({
text: item,
value: item
});
});
const showColumn: Array<object> = [];
Object.keys(columnSelected).map(key => {
const item = columnSelected[key];
switch (item) {
......@@ -473,12 +531,20 @@ class TableList extends React.Component<TableListProps, TableListState> {
record.description.logPath
:
'This trial\'s logPath are not available.';
const isdisLogbutton = record.status === 'WAITING'
?
true
:
false;
return (
<pre id="allList" className="hyperpar">
<Row className="openRowContent">
<Tabs tabPosition="left" className="card">
<TabPane tab="Parameters" key="1">
{
isHasParameters
?
< JSONTree
<JSONTree
hideRoot={true}
shouldExpandNode={() => true} // default expandNode
getItemString={() => (<span />)} // remove the {} items
......@@ -490,7 +556,24 @@ class TableList extends React.Component<TableListProps, TableListState> {
<span className="error">'This trial's parameters are not available.'</span>
</div>
}
<LogPath logStr={logPathRow} />
</TabPane>
<TabPane tab="Log" key="2">
{
platform === 'pai' || platform === 'kubeflow'
?
<PaiTrialLog
logStr={logPathRow}
id={record.id}
showLogModal={this.showLogModal}
trialStatus={record.status}
isdisLogbutton={isdisLogbutton}
/>
:
<TrialLog logStr={logPathRow} id={record.id} />
}
</TabPane>
</Tabs>
</Row>
</pre>
);
};
......@@ -523,6 +606,24 @@ class TableList extends React.Component<TableListProps, TableListState> {
theme="my_theme"
/>
</Modal>
{/* trial log modal */}
<Modal
title="trial log"
visible={logModal}
onCancel={this.hideLogModal}
footer={null}
destroyOnClose={true}
width="80%"
>
<div id="trialLogContent" style={{ height: window.innerHeight * 0.6 }}>
<TextArea
value={logMessage}
disabled={true}
className="logcontent"
/>
</div>
</Modal>
</div>
{/* Add Column Modal */}
<Modal
......@@ -540,6 +641,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
className="titleColumn"
/>
</Modal>
</Row>
);
}
......
#barBack{
/* status: 'INITIALIZED' | 'RUNNING' | 'ERROR' | 'STOPPING' | 'STOPPED' | 'DONE' */
.RUNNING, .STOPPING, .INITIALIZED{
/* status: 'TUNER_NO_MORE_TRIAL' | 'NO_MORE_TRIAL' */
.RUNNING, .STOPPING, .INITIALIZED, .NO_MORE_TRIAL, .TUNER_NO_MORE_TRIAL{
/* specific status color */
color: #0071bc;
.ant-progress-bg {
......
......@@ -73,6 +73,7 @@
*/
.inputBox{
height: 26px;
margin-top: -3px;
.concurrencyInput{
width: 25%;
height: 26px;
......
$color: #0071bc;
.openRowContent{
.ant-tabs-vertical.ant-tabs-left .ant-tabs-bar .ant-tabs-tab{
text-align: left !important;
}
}
.openRowContent{
.ant-tabs-nav .ant-tabs-tab:hover{
color: $color;
}
}
.openRowContent{
margin-top: 15px;
.card{
.ant-tabs-vertical .ant-tabs-bar .ant-tabs-tab{
margin: 0;
padding: 8px 12px;
}
.ant-tabs-nav .ant-tabs-tab-active{
color: $color;
font-weight: 500;
background: #dedede;
border-left: 3px solid ;
}
.ant-tabs-tabpane{
background: #f2f2f2;
padding: 10px 20px;
}
}
}
.trialLog{
white-space: normal;
color: #212121;
}
#trialLogContent{
.ant-input-disabled{
color: #212121;
background-color: #fff;
cursor: pointer;
border: none;
}
.logcontent{
height: 100%;
}
}
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