|
|
|
@@ -0,0 +1,522 @@ |
|
|
|
<!-- |
|
|
|
Copyright 2021 Huawei Technologies Co., Ltd.All Rights Reserved. |
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
you may not use this file except in compliance with the License. |
|
|
|
You may obtain a copy of the License at |
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software |
|
|
|
distributed under the License is distributed on an "AS IS" BASIS, |
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
|
|
See the License for the specific language governing permissions and |
|
|
|
limitations under the License. |
|
|
|
--> |
|
|
|
<template> |
|
|
|
<!-- cl-cluster--> |
|
|
|
<div class="cl-cluster"> |
|
|
|
<div class="cl-cluster-bk"> |
|
|
|
<div class="cl-cluster-title"> |
|
|
|
<div class="cl-cluster-title-left">{{$t("profilingCluster.titleText")}}</div> |
|
|
|
<div class="path-message"> |
|
|
|
<span>{{$t('symbols.leftbracket')}}</span> |
|
|
|
<span>{{$t('trainingDashboard.summaryDirPath')}}</span> |
|
|
|
<span>{{summaryPath}}</span> |
|
|
|
<span>{{$t('symbols.rightbracket')}}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="cl-step-filter"> |
|
|
|
<label>{{stepTip}}</label> |
|
|
|
<el-input class="step-input" clearable |
|
|
|
@clear="viewStepFilter" |
|
|
|
v-model.number="step.showStep"></el-input> |
|
|
|
<el-button @click="viewStepFilter">{{$t("public.sure")}}</el-button> |
|
|
|
</div> |
|
|
|
<div class="cl-cluster-chart" |
|
|
|
ref="clusterChart"> |
|
|
|
</div> |
|
|
|
<div class="cl-cluster-table"> |
|
|
|
<el-table :data="tableData" |
|
|
|
height="100%" |
|
|
|
width="100%" |
|
|
|
ref="table" |
|
|
|
stripe |
|
|
|
@sort-change="tabeSortChange"> |
|
|
|
<el-table-column width="120" |
|
|
|
prop="rank_id" |
|
|
|
:label="$t('profilingCluster.rankID')"> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column prop="iteration_interval" |
|
|
|
sortable="custom"> |
|
|
|
<template slot="header"> |
|
|
|
<span class="thSpan">|</span> |
|
|
|
<span :title="`${$t('profiling.iterationGapTime')}(ms)`">{{$t("profiling.iterationGapTime")}}(ms)</span> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column prop="fp_and_bp" |
|
|
|
sortable="custom"> |
|
|
|
<template slot="header"> |
|
|
|
<span class="thSpan">|</span> |
|
|
|
<span :title="`${$t('profiling.fpBpTime')}(ms)`">{{$t("profiling.fpBpTime")}}(ms)</span> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column prop="tail" |
|
|
|
sortable="custom"> |
|
|
|
<template slot="header"> |
|
|
|
<span class="thSpan">|</span> |
|
|
|
<span :title="`${$t('profiling.tailTime')}(ms)`">{{$t("profiling.tailTime")}}(ms)</span> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column fixed="right" |
|
|
|
width="180"> |
|
|
|
<template slot="header"> |
|
|
|
<span class="thSpan">|</span>{{$t("summaryManage.operation")}} |
|
|
|
</template> |
|
|
|
<template slot-scope="scope"> |
|
|
|
<el-button type="text" |
|
|
|
size="small" |
|
|
|
@click="viewProfilingDetail(scope.row)">{{$t("profiling.viewDetail")}}</el-button> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
</el-table> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="cl-cluster-page"> |
|
|
|
<el-pagination @current-change="currentPageChange" |
|
|
|
@size-change="currentPageSizeChange" |
|
|
|
:page-sizes="pageSizes" |
|
|
|
:page-size="group_condition.limit" |
|
|
|
:current-page="group_condition.offset+1" |
|
|
|
layout="total,sizes,prev,pager,next,jumper" |
|
|
|
:total="totalCount"> |
|
|
|
</el-pagination> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="no-data-img" |
|
|
|
v-show="!chartData.length"> |
|
|
|
<div> |
|
|
|
<img :src="require('@/assets/images/nodata.png')" alt=""> |
|
|
|
<p>{{initOver?$t("public.noData"):$t("public.dataLoading")}}</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import echarts from 'echarts'; |
|
|
|
import RequestService from '../../services/request-service'; |
|
|
|
|
|
|
|
export default { |
|
|
|
data() { |
|
|
|
return { |
|
|
|
summaryPath: '', |
|
|
|
trainingJobId: this.$route.query.id, // ID of the current training job |
|
|
|
chartObj: null, // chart obj |
|
|
|
chartData: [], // chart data |
|
|
|
chartOption: { // chart option |
|
|
|
color: ['#6B92FA', '#6CBFFF', '#F6DF66'], // bar color |
|
|
|
tooltip: { |
|
|
|
trigger: 'axis', |
|
|
|
axisPointer: { |
|
|
|
type: 'shadow', |
|
|
|
}, |
|
|
|
}, |
|
|
|
legend: { |
|
|
|
right: 70, |
|
|
|
top: 8, |
|
|
|
data: '', |
|
|
|
}, |
|
|
|
grid: { |
|
|
|
top: 35, |
|
|
|
left: 80, |
|
|
|
right: 80, |
|
|
|
}, |
|
|
|
dataset: {}, |
|
|
|
xAxis: { |
|
|
|
name: this.$t('profilingCluster.rankID'), |
|
|
|
nameTextStyle: { |
|
|
|
align: 'left', |
|
|
|
padding: [0, 5], |
|
|
|
color: '#9EA4B3', |
|
|
|
}, |
|
|
|
type: 'category', |
|
|
|
axisLine: { |
|
|
|
lineStyle: { |
|
|
|
color: '#E6EBF5', |
|
|
|
width: 2, |
|
|
|
}, |
|
|
|
}, |
|
|
|
axisLabel: { |
|
|
|
color: '#9EA4B3', |
|
|
|
}, |
|
|
|
}, |
|
|
|
yAxis: { |
|
|
|
name: this.$t('profilingCluster.timeTitle'), |
|
|
|
nameGap: 20, |
|
|
|
nameTextStyle: { |
|
|
|
align: 'right', |
|
|
|
padding: [0, 5], |
|
|
|
color: '#9EA4B3', |
|
|
|
}, |
|
|
|
axisLine: { |
|
|
|
lineStyle: { |
|
|
|
color: '#E6EBF5', |
|
|
|
width: 2, |
|
|
|
}, |
|
|
|
}, |
|
|
|
axisLabel: { |
|
|
|
color: '#9EA4B3', |
|
|
|
formatter: (value) => { |
|
|
|
return value.toExponential(4); |
|
|
|
}, |
|
|
|
}, |
|
|
|
splitLine: { |
|
|
|
lineStyle: { |
|
|
|
color: ['#E6EBF5'], |
|
|
|
width: 1, |
|
|
|
type: 'dashed', |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
series: [ |
|
|
|
{type: 'bar', barWidth: 8}, |
|
|
|
{type: 'bar', barWidth: 8}, |
|
|
|
{type: 'bar', barWidth: 8}, |
|
|
|
], |
|
|
|
dataZoom: [], |
|
|
|
}, |
|
|
|
chartResizeTimer: null, // delay after the window size is changed |
|
|
|
tableData: [], // table data |
|
|
|
initOver: false, // init over |
|
|
|
pageSizes: [10, 20, 50], |
|
|
|
group_condition: { |
|
|
|
offset: 0, |
|
|
|
limit: 10, |
|
|
|
}, |
|
|
|
sort_condition: { |
|
|
|
name: 'iteration_interval', |
|
|
|
type: 'descending', |
|
|
|
}, |
|
|
|
totalCount: 0, |
|
|
|
step: { |
|
|
|
maxStep: '', |
|
|
|
filterStep: '', |
|
|
|
showStep: '', |
|
|
|
}, |
|
|
|
stepTip: this.$t('profiling.stepInputTip'), |
|
|
|
stepInfoCol: { |
|
|
|
iteration_interval: this.$t('profiling.iterationGapTime'), |
|
|
|
fp_and_bp: this.$t('profiling.fpBpTime'), |
|
|
|
tail: this.$t('profiling.tailTime'), |
|
|
|
}, |
|
|
|
}; |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
if (!this.trainingJobId) { |
|
|
|
this.$message.error(this.$t('trainingDashboard.invalidId')); |
|
|
|
document.title = `${this.$t('profilingCluster.clusterView')}-MindInsight`; |
|
|
|
this.initOver = true; |
|
|
|
return; |
|
|
|
} |
|
|
|
this.summaryPath = decodeURIComponent(this.trainingJobId); |
|
|
|
document.title = `${this.summaryPath}-${this.$t( |
|
|
|
'profilingCluster.clusterView', |
|
|
|
)}-MindInsight`; |
|
|
|
|
|
|
|
// adding a Listener |
|
|
|
window.addEventListener('resize', this.resizeCallback, false); |
|
|
|
this.chartOption.legend.data = Object.values(this.stepInfoCol); |
|
|
|
this.queryStepTraceInfo(true, true); |
|
|
|
}, |
|
|
|
destroyed() { |
|
|
|
// remove the size of a window and change the listener |
|
|
|
window.removeEventListener('resize', this.resizeCallback); |
|
|
|
// remove chart calculation delay |
|
|
|
if (this.chartResizeTimer) { |
|
|
|
clearTimeout(this.chartResizeTimer); |
|
|
|
this.chartResizeTimer = null; |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
/** |
|
|
|
* initialize |
|
|
|
* @param {Boolean} isInit whether get all data |
|
|
|
* @param {Boolean} isSort whether sort table |
|
|
|
*/ |
|
|
|
|
|
|
|
queryStepTraceInfo(isInit, isSort) { |
|
|
|
const params = {}; |
|
|
|
params.params = { |
|
|
|
train_id: this.trainingJobId, |
|
|
|
}; |
|
|
|
params.body = { |
|
|
|
sort_condition: this.sort_condition, |
|
|
|
filter_condition: {}, |
|
|
|
}; |
|
|
|
if (!isInit) { |
|
|
|
params.body.group_condition = this.group_condition; |
|
|
|
} |
|
|
|
if (this.step.filterStep !== '') { |
|
|
|
params.body.filter_condition = {step_id: this.step.filterStep}; |
|
|
|
} |
|
|
|
RequestService.getClusterInfo(params) |
|
|
|
.then((res) => { |
|
|
|
if (res && res.data && res.data.step_trace && res.data.step_trace.length) { |
|
|
|
this.initOver = true; |
|
|
|
this.step.maxStep = res.data.total_step_num; |
|
|
|
this.stepTip = this.$t('profiling.stepInputTip', {max: this.step.maxStep}); |
|
|
|
this.totalCount = res.data.size; |
|
|
|
const tempChartData = []; |
|
|
|
if (isInit) { |
|
|
|
res.data.step_trace.forEach((item)=>{ |
|
|
|
const chartItem = [item.rank_id].concat(item.step_trace_info); |
|
|
|
tempChartData.push(chartItem); |
|
|
|
}); |
|
|
|
this.chartData = tempChartData; |
|
|
|
this.initChart(); |
|
|
|
} |
|
|
|
if (isSort) { |
|
|
|
this.$nextTick(()=>{ |
|
|
|
const tableDom = this.$refs.table; |
|
|
|
if (tableDom) { |
|
|
|
tableDom.sort(this.sort_condition.name, this.sort_condition.type); |
|
|
|
} |
|
|
|
}); |
|
|
|
} else { |
|
|
|
const tempTableData = res.data.step_trace.slice(0, this.group_condition.limit); |
|
|
|
this.initTable(tempTableData); |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
this.initOver = true; |
|
|
|
this.chartData = []; |
|
|
|
this.tableData = []; |
|
|
|
}); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* init table data |
|
|
|
* @param {Array} resData response data |
|
|
|
*/ |
|
|
|
initTable(resData) { |
|
|
|
this.tableData = []; |
|
|
|
resData.forEach((item) => { |
|
|
|
const tableItem = {}; |
|
|
|
tableItem.rank_id = item.rank_id; |
|
|
|
tableItem.host_ip = item.host_ip; |
|
|
|
tableItem.profiler_dir = item.profiler_dir; |
|
|
|
tableItem.device_id = item.device_id; |
|
|
|
const stepTraceInfo = item.step_trace_info; |
|
|
|
tableItem.iteration_interval = stepTraceInfo[0]; |
|
|
|
tableItem.fp_and_bp = stepTraceInfo[1]; |
|
|
|
tableItem.tail = stepTraceInfo[2]; |
|
|
|
this.tableData.push(tableItem); |
|
|
|
}); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* init chart |
|
|
|
*/ |
|
|
|
initChart() { |
|
|
|
this.chartOption.dataset = { |
|
|
|
dimensions: ['rankID'].concat(Object.values(this.stepInfoCol)), |
|
|
|
source: this.chartData, |
|
|
|
}, |
|
|
|
this.chartOption.dataZoom = this.chartData.length > 25 // show bar numbers as default |
|
|
|
? [ |
|
|
|
{ |
|
|
|
show: true, |
|
|
|
startValue: 0, |
|
|
|
endValue: 25, |
|
|
|
}, |
|
|
|
] |
|
|
|
: [], |
|
|
|
this.$nextTick(()=> { |
|
|
|
if (!this.chartObj) { |
|
|
|
this.chartObj = echarts.init(this.$refs.clusterChart, null); |
|
|
|
} |
|
|
|
this.chartObj.setOption(this.chartOption, true); |
|
|
|
}); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* window resize |
|
|
|
*/ |
|
|
|
|
|
|
|
resizeCallback() { |
|
|
|
if (this.chartResizeTimer) { |
|
|
|
clearTimeout(this.chartResizeTimer); |
|
|
|
this.chartResizeTimer = null; |
|
|
|
} |
|
|
|
|
|
|
|
this.chartResizeTimer = setTimeout(() => { |
|
|
|
if (this.chartObj) { |
|
|
|
this.chartObj.resize(); |
|
|
|
} |
|
|
|
}, 200); |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* route jump |
|
|
|
* @param {Object} row |
|
|
|
*/ |
|
|
|
|
|
|
|
viewProfilingDetail(row) { |
|
|
|
const path = this.$route.path.indexOf('profiling-gpu-cluster') > 0 ? '/profiling-gpu' : '/profiling'; |
|
|
|
const routeUrl = this.$router.resolve({ |
|
|
|
path: path, |
|
|
|
query: { |
|
|
|
id: this.trainingJobId + '/cluster_profiler/' + row.host_ip, |
|
|
|
dir: row.profiler_dir, |
|
|
|
path: this.trainingJobId + '/cluster_profiler/' + row.host_ip, |
|
|
|
deviceid: row.device_id.toString(), |
|
|
|
}, |
|
|
|
}); |
|
|
|
window.open(routeUrl.href, '_blank'); |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* current page change |
|
|
|
* @param {Number} val current page |
|
|
|
*/ |
|
|
|
currentPageChange(val) { |
|
|
|
this.group_condition.offset = val - 1; |
|
|
|
this.queryStepTraceInfo(false, false); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* current page size change |
|
|
|
* @param {Number} pageSize current page size |
|
|
|
*/ |
|
|
|
currentPageSizeChange(pageSize) { |
|
|
|
this.group_condition.offset = 0; |
|
|
|
this.group_condition.limit = pageSize; |
|
|
|
this.queryStepTraceInfo(false, false); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* table sort change |
|
|
|
* @param {Object} column current column |
|
|
|
*/ |
|
|
|
tabeSortChange(column) { |
|
|
|
this.sort_condition = { |
|
|
|
name: column.prop, |
|
|
|
type: column.order, |
|
|
|
}; |
|
|
|
this.group_condition.offset = 0; |
|
|
|
this.queryStepTraceInfo(false, false); |
|
|
|
}, |
|
|
|
/** |
|
|
|
* filter step to overview |
|
|
|
*/ |
|
|
|
viewStepFilter() { |
|
|
|
if (/^[0-9]*[1-9][0-9]*$/.test(this.step.showStep) && |
|
|
|
this.step.showStep <= this.step.maxStep) { |
|
|
|
this.step.filterStep = this.step.showStep; |
|
|
|
this.group_condition.offset = 0; |
|
|
|
this.queryStepTraceInfo(true, false); |
|
|
|
} else if (this.step.showStep === '') { |
|
|
|
this.step.filterStep = ''; |
|
|
|
this.group_condition.offset = 0; |
|
|
|
this.queryStepTraceInfo(true, false); // show average data |
|
|
|
} else { |
|
|
|
this.step.showStep = this.step.filterStep; |
|
|
|
this.$message.error(this.$t('profiling.inputError').replace('{max}', this.step.maxStep)); |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}; |
|
|
|
</script> |
|
|
|
<style> |
|
|
|
.cl-cluster { |
|
|
|
height: 100%; |
|
|
|
background-color: #fff; |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-bk { |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
padding: 0 32px 24px 32px; |
|
|
|
} |
|
|
|
.cl-cluster .no-data-img { |
|
|
|
background: #fff; |
|
|
|
text-align: center; |
|
|
|
position: absolute; |
|
|
|
top: 56px; |
|
|
|
bottom: 0px; |
|
|
|
width: 100%; |
|
|
|
z-index: 999; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
.cl-cluster .no-data-img p { |
|
|
|
font-size: 16px; |
|
|
|
padding-top: 10px; |
|
|
|
} |
|
|
|
.cl-cluster .el-table th > .cell { |
|
|
|
color: #282b33; |
|
|
|
} |
|
|
|
.cl-cluster .el-table td > .cell { |
|
|
|
margin-left: 10px; |
|
|
|
} |
|
|
|
.cl-cluster .el-table td:first-child .cell { |
|
|
|
margin-left: 0; |
|
|
|
} |
|
|
|
.cl-cluster .el-table th { |
|
|
|
user-select: auto; |
|
|
|
} |
|
|
|
.cl-cluster .thSpan { |
|
|
|
color: #d4d9e6; |
|
|
|
margin-right: 8px; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-title { |
|
|
|
height: 56px; |
|
|
|
line-height: 56px; |
|
|
|
position: relative; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-title .cl-cluster-title-left { |
|
|
|
display: inline-block; |
|
|
|
font-size: 20px; |
|
|
|
font-weight: bold; |
|
|
|
left: 0; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-title .path-message { |
|
|
|
display: inline-block; |
|
|
|
line-height: 20px; |
|
|
|
padding: 18px 0; |
|
|
|
font-weight: bold; |
|
|
|
margin-left: 5px; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-chart { |
|
|
|
height: 280px; |
|
|
|
margin-top: 5px; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-table { |
|
|
|
flex: 1; |
|
|
|
margin-top: 20px; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
.cl-cluster .cl-cluster-page { |
|
|
|
padding-top: 10px; |
|
|
|
text-align: right; |
|
|
|
} |
|
|
|
.cl-cluster .cl-step-filter .cl-step-filter { |
|
|
|
display: inline-block; |
|
|
|
} |
|
|
|
.cl-cluster .cl-step-filter .el-input { |
|
|
|
width: 120px; |
|
|
|
margin: 0 20px; |
|
|
|
} |
|
|
|
.cl-cluster .cl-step-filter .el-button { |
|
|
|
border: 1px solid #00a5a7; |
|
|
|
border-radius: 2px; |
|
|
|
background-color: white; |
|
|
|
color: #00a5a7; |
|
|
|
padding: 7px 15px; |
|
|
|
} |
|
|
|
.cl-cluster .cl-step-filter .el-button:hover { |
|
|
|
background: rgb(230, 246, 246); |
|
|
|
} |
|
|
|
</style> |