提交 311c5c16 编写于 作者: P ph

UI add GPU profiling module

上级 ee2cbbd1
...@@ -290,7 +290,7 @@ export default { ...@@ -290,7 +290,7 @@ export default {
getActive() { getActive() {
const str = this.$route.path.split('/'); const str = this.$route.path.split('/');
if (str.length > 2) { if (str.length > 2) {
if (str[1] === 'train-manage' || str[1] === 'profiling') { if (str[1] === 'train-manage' || str[1] === 'profiling' || str[1] === 'profiling-gpu') {
return '/summary-manage'; return '/summary-manage';
} else { } else {
return '/' + str[1]; return '/' + str[1];
......
...@@ -226,13 +226,15 @@ ...@@ -226,13 +226,15 @@
"currentCard": "Number of cards", "currentCard": "Number of cards",
"pie": "Pie", "pie": "Pie",
"bar": "Bar", "bar": "Bar",
"operatorStatistics": "Operator Statistics",
"operatorTypeStatistics": "Operator Type Statistics",
"allOperator": "All", "allOperator": "All",
"classificationOperator": "Type", "classificationOperator": "Type",
"card": " ", "card": " ",
"searchByType": "Enter operator type", "searchByType": "Enter operator type",
"searchByName": "Enter operator name" "searchByName": "Enter operator name",
"operatorInfo":"Operator",
"kernelInfo":"Kernel",
"searchByCoreName":"Enter kernel name",
"searchByCoreFullName":"Enter operator full name"
}, },
"profiling": { "profiling": {
"profilingDashboard": "Profiling Dashboard", "profilingDashboard": "Profiling Dashboard",
...@@ -389,7 +391,8 @@ ...@@ -389,7 +391,8 @@
"content31": "You can analyze whether the flow tiling policy is proper and whether the step interval and tail time are too long based on the timeline information.", "content31": "You can analyze whether the flow tiling policy is proper and whether the step interval and tail time are too long based on the timeline information.",
"content32": "You can also locate an operator and view and analyze its execution time." "content32": "You can also locate an operator and view and analyze its execution time."
}, },
"unit": "ms/time" "unit": "ms/time",
"gpuunit": "us/time"
}, },
"hardwareVisual": { "hardwareVisual": {
"processor": "Ascend AI Processor", "processor": "Ascend AI Processor",
...@@ -398,13 +401,13 @@ ...@@ -398,13 +401,13 @@
"allCpu": "Total CPUs:", "allCpu": "Total CPUs:",
"chipNameTip": "Chip name", "chipNameTip": "Chip name",
"deviceIdTip": "Chip ID", "deviceIdTip": "Chip ID",
"availableTip": "Available or not(for reference only)", "availableTip": "Is chip available(for reference only)",
"healthTip": "Chip health index", "healthTip": "Chip health status",
"ipTip": "Chip IP address", "ipTip": "Chip IP address",
"aicoreTip": "Chip usage", "aicoreTip": "Chip utilization",
"hbmTip": "Used HBM memory", "hbmTip": "Chip HBM usage",
"powerTip": "Chip power consumption", "powerTip": "Chip real-time power",
"temperatureTip": "Chip temperature", "temperatureTip": "Chip real-time temperature",
"cpuUserTip": "Time for running in user mode (%)", "cpuUserTip": "Time for running in user mode (%)",
"cpuSystemTip": "Time for running in kernel mode (%)", "cpuSystemTip": "Time for running in kernel mode (%)",
"cpuIdleTip": "Idle time (%)", "cpuIdleTip": "Idle time (%)",
...@@ -426,7 +429,16 @@ ...@@ -426,7 +429,16 @@
"availableFree": "The chip is available.", "availableFree": "The chip is available.",
"availableBusy": "The chip is occupied or unavailable.", "availableBusy": "The chip is occupied or unavailable.",
"failQueryChip": "An error occurs during chip information query.", "failQueryChip": "An error occurs during chip information query.",
"faliQuery": "Query error" "faliQuery": "Query error",
"name": "Name",
"npu": "ID",
"available": "Available",
"health": "Health",
"ipAddress": "IP Address",
"aiCore": "AI Core(%)",
"hbmUsage": "HBM Usage(MB)",
"power": "Power(W)",
"temp": "Temp(℃)"
}, },
"components": { "components": {
"summaryTitle": "Training selection", "summaryTitle": "Training selection",
......
...@@ -224,13 +224,15 @@ ...@@ -224,13 +224,15 @@
"currentCard": "当前卡片", "currentCard": "当前卡片",
"pie": "饼图", "pie": "饼图",
"bar": "柱状图", "bar": "柱状图",
"operatorStatistics": "算子统计",
"operatorTypeStatistics": "算子类别统计",
"allOperator": "全部", "allOperator": "全部",
"classificationOperator": "分类", "classificationOperator": "分类",
"card": "卡", "card": "卡",
"searchByType": "请输入算子类型搜索", "searchByType": "请输入算子类型搜索",
"searchByName": "请输入算子名称搜索" "searchByName": "请输入算子名称搜索",
"searchByCoreName": "请输入内核名称搜索",
"searchByCoreFullName": "请输入算子全名搜索",
"operatorInfo": "算子信息",
"kernelInfo": "内核信息"
}, },
"profiling": { "profiling": {
"profilingDashboard": "性能看板", "profilingDashboard": "性能看板",
...@@ -388,7 +390,8 @@ ...@@ -388,7 +390,8 @@
"content31": "您可以通过时间线信息分析流切分方法是否合理、迭代间隙和拖尾时间是否过长等;", "content31": "您可以通过时间线信息分析流切分方法是否合理、迭代间隙和拖尾时间是否过长等;",
"content32": "也可以具体定位到某个算子,查看分析它的执行时间。" "content32": "也可以具体定位到某个算子,查看分析它的执行时间。"
}, },
"unit": "ms/次" "unit": "ms/次",
"gpuunit": "us/次"
}, },
"hardwareVisual": { "hardwareVisual": {
"processor": "昇腾AI处理器", "processor": "昇腾AI处理器",
...@@ -396,14 +399,14 @@ ...@@ -396,14 +399,14 @@
"selectedCpu": "CPU-选中:", "selectedCpu": "CPU-选中:",
"allCpu": "CPU-总计:", "allCpu": "CPU-总计:",
"chipNameTip": "芯片名称", "chipNameTip": "芯片名称",
"deviceIdTip": "芯片号", "deviceIdTip": "芯片号",
"availableTip": "芯片是否空闲(仅供参考)", "availableTip": "芯片是否空闲(仅供参考)",
"healthTip": "芯片健康指数", "healthTip": "芯片健康指数",
"ipTip": "芯片IP地址", "ipTip": "芯片IP地址",
"aicoreTip": "芯片利用率", "aicoreTip": "芯片AI Core利用率",
"hbmTip": "芯片已用HBM内存", "hbmTip": "芯片已用HBM内存",
"powerTip": "芯片功耗", "powerTip": "芯片实时功率",
"temperatureTip": "芯片温度", "temperatureTip": "芯片实时温度",
"cpuUserTip": "运行于用户态的时间百分比", "cpuUserTip": "运行于用户态的时间百分比",
"cpuSystemTip": "运行于内核态的时间百分比", "cpuSystemTip": "运行于内核态的时间百分比",
"cpuIdleTip": "处于空闲状态的时间百分比", "cpuIdleTip": "处于空闲状态的时间百分比",
...@@ -425,7 +428,16 @@ ...@@ -425,7 +428,16 @@
"availableFree": "芯片空闲", "availableFree": "芯片空闲",
"availableBusy": "芯片已被占用或不可用", "availableBusy": "芯片已被占用或不可用",
"failQueryChip": "芯片信息查询有误", "failQueryChip": "芯片信息查询有误",
"faliQuery": "查询有误" "faliQuery": "查询有误",
"name":"名称",
"npu":"编号",
"available":"是否空闲",
"health":"健康指数",
"ipAddress":"IP 地址",
"aiCore":"AI Core利用率(%)",
"hbmUsage":"已用HBM内存(MB)",
"power":"功率(W)",
"temp":"温度(℃)"
}, },
"components": { "components": {
"summaryTitle": "训练选择", "summaryTitle": "训练选择",
......
...@@ -101,6 +101,22 @@ export default new Router({ ...@@ -101,6 +101,22 @@ export default new Router({
}, },
], ],
}, },
{
path: '/profiling-gpu',
component: () => import('./views/profiling-gpu/profiling.vue'),
redirect: '/profiling-gpu/profiling-dashboard',
children: [
{
path: 'profiling-dashboard',
component: () =>
import('./views/profiling-gpu/profiling-dashboard.vue'),
},
{
path: 'operator',
component: () => import('./views/profiling-gpu/operator.vue'),
},
],
},
{ {
path: '/hardware-visual', path: '/hardware-visual',
component: () => import('./views/train-manage/hardware-visual.vue'), component: () => import('./views/train-manage/hardware-visual.vue'),
......
<!--
Copyright 2020 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>
<div class="operator">
<div class="operator-title">{{$t('profiling.operatorDetail')}}</div>
<div class="cl-profiler">
<el-tabs v-model="apiType"
@tab-click="tabChange">
<el-tab-pane :label="$t('operator.operatorInfo')"
name="operator">
<div class="cl-profiler-top"
:class="{fullScreen:fullScreen}"
v-if="operatorCharts.data.length">
<div>
<el-radio-group class="chart-radio-group"
v-model="operatorCharts.type"
@change="operatorChartChange"
fill="#00A5A7"
text-color="#FFFFFF"
size="small">
<el-radio-button :label="0">
{{$t('operator.pie')}}
</el-radio-button>
<el-radio-button :label="1">
{{ $t('operator.bar')}}
</el-radio-button>
</el-radio-group>
</div>
<div class="cl-profiler-echarts">
<div id="operator-echarts"></div>
</div>
</div>
<div class="cl-profiler-bottom"
:class="{fullScreen:fullScreen}"
v-if="operatorCharts.data.length">
<img src="../../assets/images/full-screen.png"
:title="$t('graph.fullScreen')"
class="fullScreen"
@click="fullScreenControl(0)">
<div>
<el-radio-group v-model="statisticType"
@change="operatorTableChange"
fill="#00A5A7"
text-color="#FFFFFF"
size="small">
<el-radio-button :label="1">
{{$t('operator.allOperator')}}
</el-radio-button>
<el-radio-button :label="0">
{{$t('operator.classificationOperator')}}
</el-radio-button>
</el-radio-group>
<div class="cl-search-box">
<el-input v-model="searchByTypeInput"
v-if="!statisticType"
:placeholder="$t('operator.searchByType')"
clearable
@clear="searchOperatorList()"
@keyup.enter.native="searchOperatorList()"></el-input>
<el-input v-model="searchByNameInput"
v-if="statisticType"
:placeholder="$t('operator.searchByName')"
clearable
@clear="searchOperatorList()"
@keyup.enter.native="searchOperatorList()"></el-input>
</div>
</div>
<el-table v-show="!statisticType && opTypeCol && opTypeCol.length"
:data="opTypeList"
ref="expandTable"
@expand-change="expandTypeItem"
@sort-change="opTypeSortChange"
stripe
height="calc(100% - 40px)"
width="100%">
<el-table-column type="expand">
<template slot-scope="props">
<div class="expand-table">
<el-table :data="props.row.opDetailList"
stripe
ref="expandChild"
width="100%"
tooltip-effect="light"
@sort-change="(...args)=>{operatorDetailSortChange(props.row, ...args)}">
<el-table-column v-for="(ele, key) in props.row.opDetailCol"
:property="ele"
:key="key"
:sortable="ele === 'op_info' ? false : 'custom'"
show-overflow-tooltip>
<template slot="header">
<div class="custom-label"
:title="(ele==='op_total_time'||ele==='op_avg_time'||ele==='cuda_activity_cost_time')
?`${ele} (${$t('profiling.gpuunit')})`:ele">
{{(ele==='op_total_time'||ele==='op_avg_time'||ele==='cuda_activity_cost_time')
?`${ele} (${$t('profiling.gpuunit')})`:ele}}
</div>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="props.row.opDetailPage.offset + 1"
:page-size="props.row.opDetailPage.limit"
@current-change="(...args)=>{opDetailPageChange(props.row, ...args)}"
layout="total, prev, pager, next, jumper"
:total="props.row.pageTotal">
</el-pagination>
<div class="clear"></div>
</div>
</template>
</el-table-column>
<el-table-column v-for="(item, $index) in opTypeCol"
:property="item"
:key="$index"
sortable>
<template slot="header">
<div class="custom-label"
:title="(item==='total_time'||item==='avg_time')?`${item} (${$t('profiling.gpuunit')})`:item">
{{(item==='total_time'||item==='avg_time')?`${item} (${$t('profiling.gpuunit')})`:item}}
</div>
</template>
</el-table-column>
</el-table>
<el-table v-show="statisticType && opAllTypeList.opDetailCol && opAllTypeList.opDetailCol.length"
:data="opAllTypeList.opDetailList"
stripe
ref="opAllTable"
width="100%"
height="calc(100% - 80px)"
@sort-change="(...args)=>{operatorDetailSortChange(opAllTypeList, ...args)}"
tooltip-effect="light">
<el-table-column v-for="(item, $index) in opAllTypeList.opDetailCol"
:property="item"
:key="$index"
:sortable="item === 'op_info' ? false : 'custom'"
show-overflow-tooltip>
<template slot="header">
<div class="custom-label"
:title="(item==='op_total_time'||item==='op_avg_time'||item==='cuda_activity_cost_time')
?`${item} (${$t('profiling.gpuunit')})`:item">
{{(item==='op_total_time'||item==='op_avg_time'||item==='cuda_activity_cost_time')
?`${item} (${$t('profiling.gpuunit')})`:item}}
</div>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="statisticType"
v-if="opAllTypeList.opDetailList.length"
:current-page="opAllTypeList.opDetailPage.offset + 1"
:page-size="opAllTypeList.opDetailPage.limit"
@current-change="(...args)=>{opDetailPageChange(opAllTypeList, ...args)}"
layout="total, prev, pager, next, jumper"
:total="opAllTypeList.pageTotal">
</el-pagination>
</div>
<div class="image-noData"
v-if="operatorCharts.data.length === 0">
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<p>{{ initOver?$t("public.noData"):$t('public.dataLoading') }}</p>
</div>
</el-tab-pane>
<el-tab-pane :label="$t('operator.kernelInfo')"
class="core-tab"
name="core">
<div class="cl-profiler-top"
:class="{fullScreen:fullScreenKernel}"
v-if="coreCharts.data.length">
<div>
</div>
<div class="cl-profiler-echarts">
<div class
id="core-echarts"></div>
</div>
</div>
<div class="cl-profiler-bottom"
:class="{fullScreen:fullScreenKernel}"
v-if="coreCharts.data.length">
<img src="../../assets/images/full-screen.png"
:title="$t('graph.fullScreen')"
class="fullScreen"
@click="fullScreenControl(1)">
<div>
<div class="cl-search-box">
<el-input v-model="searchByCoreInput"
:placeholder="coreSearchType ? $t('operator.searchByCoreFullName')
: $t('operator.searchByCoreName')"
clearable
@clear="searchCoreList()"
@keyup.enter.native="searchCoreList()"></el-input>
</div>
<el-select v-model="coreSearchType"
class="core-search-type"
:placeholder="$t('public.select')"
@change="searchCoreList()">
<el-option v-for="item in coreSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<el-table v-show="coreList.opDetailCol && coreList.opDetailCol.length"
:data="coreList.opDetailList"
stripe
ref="opCoreTable"
width="100%"
height="calc(100% - 80px)"
tooltip-effect="light"
@sort-change="(...args)=>{coreDetailSortChange(coreList, ...args)}">
<el-table-column v-for="(item, $index) in coreList.opDetailCol"
:property="item"
:key="$index"
sortable="custom"
show-overflow-tooltip>
<template slot="header">
<div class="custom-label"
:title="(item==='total_duration'||item==='avg_duration'||item==='max_duration'
|| item==='min_duration')?`${item} (${$t('profiling.gpuunit')})`:item">
{{(item==='total_duration'||item==='avg_duration'||item==='max_duration'||item==='min_duration')
?`${item} (${$t('profiling.gpuunit')})`:item}}
</div>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="coreList.opDetailList.length"
:current-page="coreList.opDetailPage.offset + 1"
:page-size="coreList.opDetailPage.limit"
@current-change="(...args)=>{opCorePageChange(coreList, ...args)}"
layout="total, prev, pager, next, jumper"
:total="coreList.pageTotal">
</el-pagination>
</div>
<div class="image-noData"
v-if="coreCharts.data.length === 0">
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<p>{{initOver?$t("public.noData"):$t('public.dataLoading')}}</p>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import echarts from 'echarts';
import requestService from '../../services/request-service';
import CommonProperty from '../../common/common-property';
export default {
data() {
return {
apiType: 'operator',
currentCard: '',
coreCharts: {
type: 0,
id: 'core-echarts',
chartDom: null,
data: [],
}, // core chart
operatorCharts: {
type: 0,
id: 'operator-echarts',
chartDom: null,
data: [],
}, // operator chart
statisticType: 0, // operator table statistic type
searchByTypeInput: '', // search by operator type
searchByNameInput: '', // search by operator detail name
searchByCoreInput: '', // search by core name
opTypeCol: [], // table headers list of operator type
opTypeList: [], // table list of operator type
coreList: {
opDetailCol: [],
opDetailList: [],
pageTotal: 0,
opDetailPage: {
offset: 0,
limit: 15,
},
op_filter_condition: {},
op_sort_condition: {
name: 'avg_duration',
type: 'descending',
},
}, // table data of core
opAllTypeList: {
opDetailCol: [],
opDetailList: [],
pageTotal: 0,
opDetailPage: {
offset: 0,
limit: 15,
},
op_filter_condition: {},
op_sort_condition: {},
}, // table data of all operator details
profile_dir: '', // profile directory
train_id: '', // train id
op_filter_condition: {}, // operator type filter
op_sort_condition: {
name: 'avg_time',
type: 'descending',
}, // operator type filter
initOver: false,
objectType: 'object',
curActiveRow: {
rowItem: null,
childProp: null,
childOrder: null,
},
fullScreen: false,
fullScreenKernel: false,
coreSearchOptions: [
{
value: 0,
label: 'name',
},
{
value: 1,
label: 'op_full_name',
},
],
coreSearchType: 0,
};
},
watch: {
'$parent.curDashboardInfo': {
handler(newValue, oldValue) {
if (newValue.query.dir && newValue.query.id && newValue.curCardNum) {
this.profile_dir = newValue.query.dir;
this.train_id = newValue.query.id;
this.currentCard = newValue.curCardNum;
this.initOver = false;
this.cardChange();
}
if (newValue.initOver) {
this.initOver = true;
}
},
deep: true,
immediate: true,
},
},
destroyed() {
// Remove the listener of window size change
window.removeEventListener('resize', this.resizeCallback);
this.$bus.$off('collapse');
},
methods: {
resizeEchart() {
if (this.operatorCharts.chartDom) {
setTimeout(() => {
this.operatorCharts.chartDom.resize();
}, 300);
}
},
fullScreenControl(type) {
if (!type) {
this.fullScreen = !this.fullScreen;
if (this.operatorCharts.chartDom && !this.fullScreen) {
this.$nextTick(() => {
this.operatorCharts.chartDom.resize();
});
}
} else {
this.fullScreenKernel = !this.fullScreenKernel;
if (this.coreCharts.chartDom && !this.fullScreenKernel) {
this.$nextTick(() => {
this.coreCharts.chartDom.resize();
});
}
}
},
/**
* Current device change
*/
cardChange() {
if (this.apiType === 'operator') {
this.statisticType = 0;
this.clearOpData();
this.getOpTypeList();
} else if (this.apiType === 'core') {
this.clearCoreData();
this.getCoreList(true);
}
},
opTypeSortChange() {
this.$nextTick(() => {
const item = this.$refs['expandChild'];
if (item && this.curActiveRow.rowItem) {
item.sort(this.curActiveRow.childProp, this.curActiveRow.childOrder);
}
});
},
/**
* clear core data
*/
clearCoreData() {
this.searchByCoreInput = '';
this.coreList = {
opDetailCol: [],
opDetailList: [],
pageTotal: 0,
opDetailPage: {
offset: 0,
limit: 15,
},
op_filter_condition: {},
op_sort_condition: {
name: 'avg_duration',
type: 'descending',
},
};
},
/**
* clear operator data
*/
clearOpData() {
this.searchByTypeInput = '';
this.searchByNameInput = '';
this.op_filter_condition = {};
this.opTypeCol = [];
this.opTypeList = [];
this.opAllTypeList = {
opDetailCol: [],
opDetailList: [],
pageTotal: 0,
opDetailPage: {
offset: 0,
limit: 15,
},
op_filter_condition: {},
op_sort_condition: {},
};
},
/**
* get operator type list
*/
getOpTypeList() {
const params = {};
params.params = {
profile: this.profile_dir,
train_id: this.train_id,
};
params.body = {
op_type: 'gpu_op_type',
device_id: this.currentCard,
filter_condition: this.op_filter_condition,
sort_condition: this.op_sort_condition,
};
requestService
.getProfilerOpData(params)
.then((res) => {
this.initOver = true;
this.opTypeList = [];
if (res && res.data) {
this.opTypeCol = res.data.col_name;
if (res.data.object) {
res.data.object.forEach((k) => {
const object = {
isExpanded: false,
opDetailList: [],
opDetailCol: [],
opDetailPage: {
offset: 0,
limit: 15,
},
pageTotal: 0,
op_filter_condition: {
op_type: {
in: [k[0]],
},
},
op_sort_condition: {},
};
res.data.col_name.forEach((item, index) => {
object[item] = k[index];
});
this.opTypeList.push(object);
});
this.$nextTick(() => {
const elementItem = this.$refs['expandTable'];
if (elementItem) {
elementItem.sort(
this.op_sort_condition.name,
this.op_sort_condition.type,
);
}
});
if (
!this.operatorCharts.device_id ||
this.operatorCharts.device_id !== this.currentCard
) {
this.operatorCharts.device_id = this.currentCard;
this.operatorCharts.data = [];
res.data.object.forEach((k) => {
if (
this.operatorCharts.data &&
this.operatorCharts.data.length < 19
) {
this.operatorCharts.data.push({
name: k[0],
value: k[4],
percent: k[3],
});
} else {
if (!this.operatorCharts.data[19]) {
this.operatorCharts.data[19] = {
name: 'Other',
value: 0,
percent: 0,
};
}
this.operatorCharts.data[19].value += k[4];
this.operatorCharts.data[19].percent += k[3];
}
});
this.setOption(this.operatorCharts);
}
}
}
})
.catch(() => {
this.opTypeList = [];
this.initOver = true;
});
},
/**
* get operator detail list
* @param {Object} row type row
* @param {Boolean} isSort if sort
*/
getOperatorDetailList(row, isSort) {
const params = {};
params.params = {
profile: this.profile_dir,
train_id: this.train_id,
};
params.body = {
op_type: 'gpu_op_info',
device_id: this.currentCard,
filter_condition: row.op_filter_condition,
sort_condition: row.op_sort_condition,
group_condition: row.opDetailPage,
};
requestService
.getProfilerOpData(params)
.then((res) => {
if (res && res.data) {
this.formatterDetailData(row, res.data);
this.$nextTick(() => {
let item = null;
if (this.statisticType) {
item = this.$refs['opAllTable'];
} else {
item = this.$refs['expandChild'];
this.curActiveRow = {
rowItem: row,
childProp: row.op_sort_condition.name,
childOrder: row.op_sort_condition.type,
};
}
if (item && isSort) {
item.sort(
row.op_sort_condition.name,
row.op_sort_condition.type,
);
}
this.$refs.expandTable.doLayout();
});
}
})
.catch(() => {});
},
/**
* get core list
* @param {Boolean} isSort if sort
*/
getCoreList(isSort) {
const params = {};
params.params = {
profile: this.profile_dir,
train_id: this.train_id,
};
params.body = {
op_type: 'gpu_cuda_activity',
device_id: this.currentCard,
filter_condition: this.coreList.op_filter_condition,
sort_condition: this.coreList.op_sort_condition,
group_condition: this.coreList.opDetailPage,
};
requestService
.getProfilerOpData(params)
.then((res) => {
this.initOver = true;
if (res && res.data) {
if (res.data.object) {
if (
!this.coreCharts.device_id ||
this.coreCharts.device_id !== this.currentCard
) {
this.coreCharts.device_id = this.currentCard;
this.coreCharts.data = [];
res.data.object.forEach((k) => {
this.coreCharts.data.push({
name: k[0],
op_name: k[2],
value: k[8],
});
});
this.setOption(this.coreCharts);
}
this.formatterDetailData(this.coreList, res.data);
if (isSort) {
this.$nextTick(() => {
const item = this.$refs['opCoreTable'];
if (item) {
item.sort(
this.coreList.op_sort_condition.name,
this.coreList.op_sort_condition.type,
);
}
});
}
}
}
})
.catch(() => {
this.initOver = true;
});
},
/**
* operator detail list page change
* @param {Object} row table cell
* @param {Number} pageIndex current page
*/
opDetailPageChange(row, pageIndex) {
row.opDetailPage.offset = pageIndex - 1;
this.getOperatorDetailList(row, false);
},
/**
* core list page change
* @param {Object} row table cell
* @param {Number} pageIndex current page
*/
opCorePageChange(row, pageIndex) {
row.opDetailPage.offset = pageIndex - 1;
this.getCoreList(false);
},
/**
* get operator list by search
*/
searchOperatorList() {
if (this.statisticType) {
this.opAllTypeList.op_filter_condition = {};
if (this.searchByNameInput) {
this.opAllTypeList.op_filter_condition = {
op_name: {partial_match_str_in: [this.searchByNameInput]},
};
} else {
this.opAllTypeList.op_filter_condition = {};
}
this.opAllTypeList.opDetailPage.offset = 0;
this.getOperatorDetailList(this.opAllTypeList, false);
} else {
this.op_filter_condition = {};
if (this.searchByTypeInput) {
this.op_filter_condition = {
op_type: {partial_match_str_in: [this.searchByTypeInput]},
};
} else {
this.op_filter_condition = {};
}
if (this.curActiveRow.rowItem) {
this.curActiveRow = {
rowItem: null,
childProp: null,
childOrder: null,
};
}
this.getOpTypeList();
}
},
/**
* get core list by search
*/
searchCoreList() {
this.coreList.op_filter_condition = {};
if (this.searchByCoreInput) {
if (this.coreSearchType) {
this.coreList.op_filter_condition = {
op_full_name: {partial_match_str_in: [this.searchByCoreInput]},
};
} else {
this.coreList.op_filter_condition = {
name: {partial_match_str_in: [this.searchByCoreInput]},
};
}
} else {
this.coreList.op_filter_condition = {};
}
this.coreList.opDetailPage.offset = 0;
this.getCoreList(false);
},
/**
* operator detail sort
* @param {Object} row table cell
* @param {Object} column table cell
*/
operatorDetailSortChange(row, column) {
row.op_sort_condition = {
name: column.prop,
type: column.order,
};
row.opDetailPage.offset = 0;
this.getOperatorDetailList(row, false);
},
/**
* core detail sort
* @param {Object} row table cell
* @param {Object} column table cell
*/
coreDetailSortChange(row, column) {
row.op_sort_condition = {
name: column.prop,
type: column.order,
};
row.opDetailPage.offset = 0;
this.getCoreList(false);
},
/**
* format detail data
* @param {Object} row table cell
* @param {Object} detailsDataList table detail
*/
formatterDetailData(row, detailsDataList) {
row.opDetailList = [];
row.opDetailCol = detailsDataList.col_name;
row.pageTotal = detailsDataList.size;
if (detailsDataList.object) {
detailsDataList.object.forEach((k) => {
const data = {};
detailsDataList.col_name.forEach((item, index) => {
if (item === 'op_info') {
data[item] = JSON.stringify(k[index]);
} else {
data[item] = k[index];
}
});
row.opDetailList.push(data);
});
}
},
/**
* expand operator type table
* @param {Object} row table cell
*/
expandTypeItem(row) {
row.isExpanded = !row.isExpanded;
if (row.isExpanded) {
if (this.curActiveRow.rowItem) {
const item = this.$refs['expandTable'];
if (item) {
item.toggleRowExpansion(this.curActiveRow.rowItem, false);
}
}
this.curActiveRow = {
rowItem: row,
childProp: null,
childOrder: null,
};
row.opDetailList = [];
row.opDetailCol = [];
row.opDetailPage.offset = 0;
row.pageTotal = 0;
row.op_sort_condition = {
name: 'op_avg_time',
type: 'descending',
};
this.getOperatorDetailList(row, true);
} else {
this.curActiveRow = {
rowItem: null,
childProp: null,
childOrder: null,
};
}
},
/**
* tab change
*/
tabChange() {
if (
this.apiType === 'core' &&
this.coreCharts.device_id !== this.currentCard
) {
this.initOver = false;
this.clearCoreData();
this.getCoreList(true);
} else if (
this.apiType === 'operator' &&
this.operatorCharts.device_id !== this.currentCard
) {
this.initOver = false;
this.clearOpData();
this.getOpTypeList();
}
this.$nextTick(() => {
this.resizeCallback();
});
},
/**
* operator table type change
*/
operatorTableChange() {
if (this.statisticType && !this.opAllTypeList.opDetailCol.length) {
this.opAllTypeList.op_sort_condition = {
name: 'op_avg_time',
type: 'descending',
};
this.getOperatorDetailList(this.opAllTypeList, true);
}
},
/**
* core chart change
*/
coreChartChange() {
this.setOption(this.coreCharts);
},
/**
* operator chart change
*/
operatorChartChange() {
this.setOption(this.operatorCharts);
},
/**
* set chart option
* @param {Object} chart chart
*/
setOption(chart) {
const option = {};
const maxLabelLength = 20;
if (!chart.type) {
option.legend = {
data: [],
orient: 'vertical',
icon: 'circle',
formatter: (params) => {
let legendStr = '';
for (let i = 0; i < chart.data.length; i++) {
if (chart.data[i].name === params) {
const name =
chart.data[i].name.length > 10
? `${chart.data[i].name.slice(0, 10)}...`
: chart.data[i].name;
legendStr = `{a|${i + 1}}{b|${name} ${chart.data[
i
].value.toFixed(3)}}\n{c|${
chart.data[i].percent
? chart.data[i].percent.toFixed(2) + '%'
: ''
}}`;
}
}
return legendStr;
},
tooltip: {
show: true,
},
itemWidth: 18,
itemHeight: 18,
padding: [0, 50, 0, 0],
top: '5%',
left: '45%',
textStyle: {
padding: [15, 0, 0, 0],
rich: {
a: {
width: 24,
align: 'center',
padding: [0, 10, 3, -26],
color: '#FFF',
},
b: {
padding: [0, 0, 3, 0],
},
c: {
width: '100%',
padding: [0, 0, 5, 10],
color: '#9EA4B3',
fontSize: 12,
},
},
},
};
option.tooltip = {
trigger: 'item',
formatter: (params) => {
return `${params.marker} ${params.data.name} ${params.percent}%`;
},
confine: true,
};
option.series = [
{
type: 'pie',
center: ['23%', '60%'],
data: chart.data,
radius: '50%',
label: {
position: 'outer',
alignTo: 'labelLine',
formatter: (params) => {
return params.data.name &&
params.data.name.length > maxLabelLength
? `${params.data.name.slice(0, maxLabelLength)}...`
: params.data.name;
},
},
itemStyle: {
normal: {
color: function(params) {
return CommonProperty.pieColorArr[params.dataIndex];
},
},
},
},
];
chart.data.forEach((item) => {
option.legend.data.push(item.name);
});
} else if (chart.type) {
option.color = ['#6C92FA'];
option.tooltip = {
trigger: 'axis',
formatter: (params) => {
return `${params[0].axisValue}<br>${params[0].marker}${params[0].value}`;
},
confine: true,
};
option.series = [
{
type: 'bar',
barWidth: 30,
data: [],
},
];
option.xAxis = {
type: 'category',
axisLabel: {
interval: 0,
rotate: -30,
},
data: [],
};
option.grid = {
left: 70,
top: 20,
right: 0,
bottom: 50,
};
option.yAxis = {
type: 'value',
axisLabel: {
formatter: (value) => {
const yValueArr = [];
if (value < 1000000) {
yValueArr.push(value.toLocaleString());
} else {
yValueArr.push(value.toExponential());
}
return yValueArr;
},
},
};
chart.data.forEach((item) => {
const name = this.apiType === 'core' ? item.op_name : item.name;
option.xAxis.data.push(name);
option.series[0].data.push(item.value);
});
if (this.apiType === 'core') {
option.xAxis.axisLabel.formatter = (params, dataIndex) => {
const xAxisValue = chart.data[dataIndex].op_name;
return xAxisValue.replace(/^.+\//g, '');
};
}
}
this.$nextTick(() => {
const tmpDom = document.getElementById(chart.id);
if (tmpDom) {
chart.chartDom = echarts.init(tmpDom, null);
} else {
if (chart.chartDom) {
chart.chartDom.clear();
}
return;
}
chart.chartDom.setOption(option, true);
chart.chartDom.resize();
}, 10);
},
loadDataListChildren(tree, treeNode, resolve) {
setTimeout(() => {
resolve(tree.children);
});
},
/**
* window resize
*/
resizeCallback() {
if (this.operatorCharts.chartDom && this.apiType === 'operator') {
this.operatorCharts.chartDom.resize();
}
if (this.coreCharts.chartDom && this.apiType === 'core') {
this.coreCharts.chartDom.resize();
}
},
},
mounted() {
if (this.train_id) {
document.title = `${decodeURIComponent(this.train_id)}-${this.$t(
'profiling.operatorDetail',
)}-MindInsight`;
} else {
document.title = `${this.$t('profiling.operatorDetail')}-MindInsight`;
}
window.addEventListener('resize', this.resizeCallback, false);
setTimeout(() => {
this.$bus.$on('collapse', this.resizeEchart);
}, 500);
},
};
</script>
<style lang="scss">
.operator {
height: 100%;
}
.clear {
clear: both;
}
.el-tabs__item {
color: #6c7280;
font-size: 16px;
line-height: 36px;
height: 36px;
}
.el-tabs__item.is-active {
color: #00a5a7;
font-weight: bold;
}
.operator-title {
padding: 0 15px;
font-size: 16px;
font-weight: bold;
}
.cl-profiler {
height: calc(100% - 21px);
overflow-y: auto;
width: 100%;
background: #fff;
padding: 0 16px;
overflow: hidden;
.custom-label {
max-width: calc(100% - 25px);
padding: 0;
vertical-align: middle;
}
.el-tabs {
height: 100%;
.el-tabs__header {
margin-bottom: 10px;
}
}
.el-tabs__content {
height: calc(100% - 46px);
}
.el-tab-pane {
height: 100%;
}
.cl-search-box {
float: right;
margin-bottom: 10px;
margin-right: 20px;
}
.cl-profiler-top {
height: 45%;
}
.cl-profiler-top.fullScreen {
display: none;
}
.cl-profiler-bottom {
height: 55%;
padding-top: 10px;
.fullScreen {
float: right;
margin-top: 5px;
cursor: pointer;
}
}
.cl-profiler-bottom.fullScreen {
height: 100%;
}
.core-search-type {
float: right;
width: 130px;
margin-right: 10px;
}
.cl-profiler-echarts {
width: 100%;
height: calc(100% - 32px);
display: inline-block;
position: relative;
overflow: auto;
#core-echarts,
#operator-echarts {
width: 100%;
height: 100%;
min-width: 1300px;
min-height: 306px;
overflow: hidden;
}
}
.core-tab {
.cl-profiler-top {
height: calc(45% - 40px);
}
.cl-profiler-bottom {
height: calc(55% + 40px);
}
.cl-profiler-echarts {
height: 100%;
}
.cl-profiler-bottom.fullScreen {
height: 100%;
}
}
.chart-radio-group {
float: right;
}
.el-radio-group {
.el-radio-button--small .el-radio-button__inner {
height: 30px;
width: 70px;
font-size: 14px;
line-height: 10px;
}
}
.cl-profiler-bar {
display: inline-block;
width: calc(100% - 400px);
vertical-align: top;
height: 100%;
padding: 20px;
}
.cl-profiler-table-type {
display: inline-block;
width: calc(100% - 400px);
vertical-align: top;
height: 100%;
}
.el-pagination {
margin: 7px 0;
float: right;
}
.details-data-list {
.el-table {
th {
padding: 10px 0;
border-top: 1px solid #ebeef5;
.cell {
border-left: 1px solid #d9d8dd;
height: 14px;
line-height: 14px;
}
}
th:first-child {
.cell {
border-left: none;
}
}
th:nth-child(2),
td:nth-child(2) {
max-width: 30%;
}
td {
padding: 8px 0;
}
}
.el-table__row--level-0 td:first-child:after {
width: 20px;
height: 1px;
background: #ebeef5;
z-index: 11;
position: absolute;
left: 0;
bottom: -1px;
content: '';
display: block;
}
.el-table__row--level-1 {
td {
padding: 4px 0;
position: relative;
}
td:first-child::before {
width: 42px;
background: #f0fdfd;
border-right: 2px #00a5a7 solid;
z-index: 10;
position: absolute;
left: 0;
top: -1px;
bottom: 0px;
content: '';
display: block;
}
}
.el-table__row--level-1:first-child {
td:first-child::before {
bottom: 0;
}
}
}
.el-table__expanded-cell[class*='cell'] {
padding: 0;
}
.expand-table {
position: relative;
padding-left: 44px;
}
.expand-table::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #f0fdfd;
width: 42px;
border-right: 2px #00a5a7 solid;
}
.el-radio-button:last-child .el-radio-button__inner,
.el-radio-button:first-child .el-radio-button__inner {
border-radius: 0;
}
.image-noData {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
p {
font-size: 16px;
padding-top: 10px;
}
}
}
</style>
<!--
Copyright 2020 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>
<div class="pro-router-wrap">
<div class="pro-router-left">
<!-- Timeline display area -->
<div class="step-trace">
<div class="title-wrap">
<div class="title">{{ $t('profiling.stepTrace') }}</div>
</div>
<!-- Timeline SVG container -->
<div class="trace-container">
<div class="image-noData">
<div>
<img :src="require('@/assets/images/coming-soon.png')"
alt="" />
</div>
<p>{{$t("public.stayTuned")}}</p>
</div>
</div>
</div>
<!-- Process summary display area -->
<div class="minddata">
<div class="title-wrap">
<div class="title">{{ $t('profiling.mindData') }}</div>
</div>
<div class="image-noData">
<div>
<img :src="require('@/assets/images/coming-soon.png')"
alt="" />
</div>
<p>{{$t("public.stayTuned")}}</p>
</div>
</div>
</div>
<!-- Operator information display area -->
<div class="pro-router-right">
<div class="op-time-consume">
<div class="title-wrap">
<div class="title">{{ $t('profiling.rankOfOperator') }}</div>
<div class="view-detail">
<button @click="viewDetail('operator')"
:disabled="pieChart.noData && pieChart.data.length === 0"
:class="{disabled:pieChart.noData && pieChart.data.length === 0}">{{ $t('profiling.viewDetail') }}
<i class="el-icon-d-arrow-right"></i></button>
</div>
</div>
<div class="image-noData"
v-if="pieChart.noData">
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<p v-show="!pieChart.initOver">{{$t("public.dataLoading")}}</p>
<p v-show="pieChart.initOver">{{$t("public.noData")}}</p>
</div>
<div class="op-time-content">
<div id="pieChart"
class="pie-chart"
v-if="pieChart.data.length"></div>
<!-- Operator time consumption top5 -->
<div class="time-list"
v-if="pieChart.data.length">
<ul>
<li v-for="(item, index) in pieChart.topN"
:key="index"
class="item">
<span class="index"
:style="{'background-color': pieChart.colorList[index]}">{{index + 1}}</span>
<span class="name">{{item.name}}</span>
<span class="num">{{item.frequency + $t('profiling.times')}}</span>
<span class="time">
<span class="bar"
:style="{width: item.time / pieChart.topN[0].time * 100 + '%'}"></span>
<span class="value">{{item.time + $t('profiling.gpuunit')}}</span>
</span>
</li>
</ul>
</div>
</div>
</div>
<!-- Time line display area -->
<div class="time-line">
<div class="title-wrap">
<div class="title">{{ $t('profiling.timeLine') }}</div>
<div class="view-detail"
v-if="false">
<button @click="downloadPerfetto()"
:disabled="timeLine.waiting"
:class="{disabled:timeLine.waiting}">{{ $t('profiling.downloadTimeline') }}
</button>
</div>
<div class="tip-icon"
v-if="false">
<el-tooltip placement="bottom"
effect="light">
<div slot="content"
class="tooltip-container">
<div class="font-size-style">{{$t("profiling.features")}}</div>
<div class="font-style">{{$t("profiling.timelineTips.title1")}}</div>
<div>{{$t("profiling.timelineTips.content11")}}</div>
<div>{{$t("profiling.timelineTips.content12")}}</div>
<div>{{$t("profiling.timelineTips.content13")}}</div>
<br>
<div class="font-style">{{$t("profiling.timelineTips.title2")}}</div>
<div>
{{$t("profiling.timelineTips.content21.part1")}}
<b>{{$t("profiling.timelineTips.content21.part2")}}</b>
{{$t("profiling.timelineTips.content21.part3")}}
</div>
<div>{{$t("profiling.timelineTips.content22")}}</div>
<div>
{{$t("profiling.timelineTips.content23.part1")}}
<b>{{$t("profiling.timelineTips.content23.part2")}}</b>
{{$t("profiling.timelineTips.content23.part3")}}
<b>{{$t("profiling.timelineTips.content23.part4")}}</b>
{{$t("profiling.timelineTips.content23.part5")}}
<b>{{$t("profiling.timelineTips.content23.part6")}}</b>
{{$t("profiling.timelineTips.content23.part7")}}
</div>
<br>
<div class="font-style">{{$t("profiling.timelineTips.title3")}}</div>
<div>{{$t("profiling.timelineTips.content31")}}</div>
<div>{{$t("profiling.timelineTips.content32")}}</div>
</div>
<i class="el-icon-info"></i>
</el-tooltip>
</div>
</div>
<!-- Time line detail -->
<!-- v-if="!timelineInfo.noData" -->
<div class="timeline-info"
v-if="false">
<div class="info-line">
<span>{{$t('profiling.opTotalTime')}}</span><span>{{timelineInfo.totalTime}}ms</span>
</div>
<div class="info-line">
<span>{{$t('profiling.streamNum')}}</span><span>{{timelineInfo.streamNum}}</span>
</div>
<div class="info-line">
<span>{{$t('profiling.opNum')}}</span><span>{{timelineInfo.opNum}}</span></div>
<div class="info-line">
<span>{{$t('profiling.opTimes')}}</span><span>{{timelineInfo.opTimes}}{{$t('profiling.times')}}</span>
</div>
</div>
<!-- coming soon -->
<div class="image-noData">
<div>
<img :src="require('@/assets/images/coming-soon.png')"
alt="" />
</div>
<p> {{$t('public.stayTuned')}}</p>
</div>
<!-- v-if="timelineInfo.noData" -->
<div class="image-noData"
v-if="false">
<div>
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<p v-show="!timelineInfo.initOver">{{$t("public.dataLoading")}}</p>
<p v-show="timelineInfo.initOver">{{$t("public.noData")}}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import echarts from 'echarts';
import RequestService from '../../services/request-service';
import CommonProperty from '../../common/common-property';
export default {
data() {
return {
trainingJobId: this.$route.query.id, // Training job id
summaryPath: this.$route.query.dir, // Summary path data
relativePath: this.$route.query.path, // Relative path of summary log
currentCard: '', // Data of current card
pieChart: {
// Pie graph information of operators
chartDom: null,
data: [],
noData: true,
topN: [],
colorList: ['#6C92FA', '#6CBFFF', '#4EDED2', '#7ADFA0', '#A6DD82'],
initOver: false, // Is initialization complete
},
timeLine: {
// Time line data
data: null,
waiting: true, // Is it waiting for interface return
},
timelineInfo: {
// Time line information
totalTime: 0,
streamNum: 0,
opNum: 0, // Number of operators
opTimes: 0, // Operator time consuming
noData: true,
initOver: false, // Is initialization complete
},
};
},
mounted() {},
watch: {
// Monitor current card information
'$parent.curDashboardInfo': {
handler(newValue, oldValue) {
if (newValue.curCardNum === '') {
this.pieChart.noData = true;
this.pieChart.initOver = true;
this.timelineInfo.initOver = true;
}
if (newValue.query.dir && newValue.query.id && newValue.query.path) {
this.summaryPath = newValue.query.dir;
this.trainingJobId = newValue.query.id;
this.relativePath = newValue.query.path;
this.currentCard = newValue.curCardNum;
if (this.trainingJobId) {
document.title = `${decodeURIComponent(
this.trainingJobId,
)}-${this.$t('profiling.profilingDashboard')}-MindInsight`;
} else {
document.title = `${this.$t(
'profiling.profilingDashboard',
)}-MindInsight`;
}
this.pieChart.initOver = false;
this.timelineInfo.initOver = false;
this.init();
}
},
deep: true,
immediate: true,
},
},
methods: {
/**
* Initialization function
*/
init() {
this.queryTimeline();
this.initPieChart();
},
/**
* router link
* @param { String } path router path
*/
viewDetail(path) {
this.$router.push({
path,
query: {
id: this.trainingJobId,
dir: this.summaryPath,
path: this.relativePath,
},
});
},
/**
* chart setOption
*/
setPieOption() {
const option = {};
option.tooltip = {
trigger: 'item',
formatter: (params) => {
return `${params.data.name}<br>${params.marker}${params.percent}%`;
},
confine: true,
extraCssText: 'white-space:normal; word-break:break-word;',
};
option.series = [
{
type: 'pie',
center: ['50%', '50%'],
data: this.pieChart.data,
radius: '80%',
label: {
normal: {
show: false,
positionL: 'inner',
},
},
itemStyle: {
normal: {
color: function(params) {
return CommonProperty.pieColorArr[params.dataIndex];
},
},
},
},
];
this.$nextTick(() => {
const dom = document.getElementById('pieChart');
if (dom) {
this.pieChart.chartDom = echarts.init(dom, null);
} else {
if (this.pieChart.chartDom) {
this.pieChart.chartDom.clear();
}
return;
}
this.pieChart.chartDom.setOption(option, true);
this.pieChart.chartDom.resize();
}, 10);
},
/**
* init chart
*/
initPieChart() {
const params = {};
params.params = {
profile: this.summaryPath,
train_id: this.trainingJobId,
};
params.body = {
op_type: 'gpu_op_type',
device_id: this.currentCard,
filter_condition: {},
sort_condition: {
name: 'avg_time',
type: 'descending',
},
};
RequestService.getProfilerOpData(params)
.then((res) => {
this.pieChart.initOver = true;
if (res && res.data) {
if (res.data.object) {
this.pieChart.data = [];
res.data.object.forEach((item) => {
if (this.pieChart.data && this.pieChart.data.length < 5) {
this.pieChart.data.push({
name: item[0],
value: item[4],
frequency: item[1],
percent: item[3],
});
} else {
if (!this.pieChart.data[5]) {
this.pieChart.data[5] = {
name: 'Other',
value: 0,
percent: 0,
};
}
this.pieChart.data[5].value += item[4];
this.pieChart.data[5].percent += item[3];
}
});
this.setPieOption();
this.pieChart.noData = !!!this.pieChart.data.length;
this.pieChart.topN = this.pieChart.data
.slice(0, Math.min(this.pieChart.data.length, 5))
.map((i) => {
return {
name: i.name,
time: i.value,
frequency: i.frequency,
};
});
}
}
})
.catch(() => {
this.pieChart.noData = true;
this.pieChart.initOver = true;
});
},
/**
* Converts a string to data in uint8array format
* @param {String} str The string to be converted
* @return {Array}
*/
stringToUint8Array(str) {
const arr = [];
for (let i = 0, strLen = str.length; i < strLen; i++) {
arr.push(str.charCodeAt(i));
}
return new Uint8Array(arr);
},
/**
* Query the data of time line
*/
queryTimeline() {
this.timeLine.waiting = true;
const params = {
dir: this.relativePath,
device_id: this.currentCard,
target_device: 'gpu',
};
RequestService.queryTimlineInfo(params)
.then((res) => {
this.timelineInfo.initOver = true;
if (res && res.data) {
this.timelineInfo.noData = false;
this.timelineInfo.totalTime =
this.toFixedFun(res.data.total_time, 4) ||
(res.data.total_time === 0 ? 0 : '--');
this.timelineInfo.streamNum =
res.data.num_of_streams ||
(res.data.num_of_streams === 0 ? 0 : '--');
this.timelineInfo.opNum =
res.data.num_of_ops || (res.data.num_of_ops === 0 ? 0 : '--');
this.timelineInfo.opTimes =
res.data.op_exe_times || (res.data.op_exe_times === 0 ? 0 : '--');
} else {
this.timelineInfo.noData = true;
}
})
.catch(() => {
this.timelineInfo.noData = true;
this.timelineInfo.initOver = true;
});
RequestService.queryTimeline(params)
.then((res) => {
if (res && res.data && res.data.length) {
this.timeLine.data = this.stringToUint8Array(
JSON.stringify(res.data),
);
this.timeLine.waiting = false;
}
})
.catch(() => {});
},
/**
* Download Perfetto data file
*/
downloadPerfetto() {
const downloadLink = document.createElement('a');
downloadLink.download = this.getDocName();
downloadLink.style.display = 'none';
const blob = new Blob([this.timeLine.data]);
downloadLink.href = URL.createObjectURL(blob);
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
},
/**
* Generate a download file name
* @return {String}
*/
getDocName() {
const dealNumber = (value) => {
const prefix = value < 10 ? '0' : '';
return prefix + value;
};
const date = new Date();
const year = date.getFullYear();
const mouth = dealNumber(date.getMonth() + 1);
const day = dealNumber(date.getDate());
const hour = dealNumber(date.getHours());
const minute = dealNumber(date.getMinutes());
const second = dealNumber(date.getSeconds());
const millisecond = date.getMilliseconds();
const timestamp = `${year}${mouth}${day}${hour}${minute}${second}${millisecond}`;
return `timeline_${this.trainingJobId}_${this.currentCard}_${timestamp}.json`;
},
/**
* Keep the number with n decimal places.
* @param {Number} num
* @param {Number} pow Number of decimal places
* @return {Number}
*/
toFixedFun(num, pow) {
if (isNaN(num) || isNaN(pow) || !num || !pow) {
return num;
}
return Math.round(num * Math.pow(10, pow)) / Math.pow(10, pow);
},
},
destroyed() {
this.$bus.$off('collapse');
},
};
</script>
<style lang="scss">
.el-tooltip-popper {
max-width: 500px;
}
.tooltip-container {
line-height: 20px;
padding: 10px;
.font-style {
font-weight: bold;
}
.font-size-style {
font-weight: bold;
font-size: 16px;
}
}
.pro-router-wrap {
height: 100%;
& > div {
float: left;
height: 100%;
& > div {
border: 1px solid #eee;
border-radius: 4px;
}
.title-wrap {
padding: 15px;
.title {
float: left;
font-weight: bold;
font-size: 16px;
}
.tip-icon {
float: right;
margin-right: 10px;
font-size: 20px;
color: #6c7280;
.el-icon-warning {
cursor: pointer;
&:hover::before {
color: #00a5a7;
}
}
}
.view-detail {
float: right;
cursor: pointer;
color: #00a5a7;
font-size: 12px;
height: 24px;
line-height: 24px;
a {
color: #00a5a7 !important;
padding-right: 6px;
}
button {
color: #00a5a7;
border: none;
background-color: #fff;
cursor: pointer;
}
button.disabled {
cursor: not-allowed;
color: #c0c4cc;
}
}
&::after {
content: '';
clear: both;
display: block;
}
}
.coming-soon-content {
height: calc(100% - 50px);
position: relative;
.coming-soon-container {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
border-radius: 5px;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.coming-soon-text {
font-size: 16px;
}
}
}
.pro-router-left {
width: calc(100% - 400px);
padding-right: 15px;
.step-trace {
height: 45%;
margin-bottom: 15px;
.trace-container {
width: 100%;
height: calc(100% - 54px);
overflow: auto;
}
}
.minddata {
height: calc(55% - 15px);
}
}
.pro-router-right {
width: 400px;
.op-time-consume {
height: calc(60% - 15px);
margin-bottom: 15px;
.time-list {
height: calc(40% - 52px);
.item {
height: 25px;
line-height: 25px;
padding: 0 20px;
& > span {
display: inline-block;
height: 100%;
vertical-align: middle;
}
.index {
color: white;
background-color: rgb(108, 146, 250);
width: 20px;
height: 20px;
border-radius: 20px;
text-align: center;
vertical-align: middle;
line-height: 20px;
}
.name {
margin-left: 10px;
width: calc(50% - 30px);
text-overflow: ellipsis;
overflow: hidden;
}
.num {
width: 20%;
}
.time {
width: 30%;
position: relative;
span {
display: inline-block;
position: absolute;
left: 0;
height: 20px;
}
.bar {
background-color: #cceded;
top: 2px;
}
.value {
line-height: 25px;
height: 25px;
}
}
}
}
}
.time-line {
height: 40%;
// overflow: hidden;
.timeline-info {
width: 100%;
height: calc(100% - 54px);
padding-left: 36px;
}
.info-line {
line-height: 30px;
}
}
}
.op-time-content {
height: calc(100% - 54px);
overflow: auto;
}
.pie-chart {
width: 100%;
height: 260px;
overflow: hidden;
}
.image-noData {
width: 100%;
height: calc(100% - 52px);
min-height: 194px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
p {
font-size: 16px;
padding-top: 10px;
}
}
}
</style>
<!--
Copyright 2020 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>
<div class="prof-wrap">
<div class="prof-content">
<div class="prof-content-left"
:class="{collapse:collapse}">
<div class="helper"
v-show="!collapse">
<div class="cur-card">
<label>{{$t('profiling.curCard')}}</label>
<el-select v-model="curDashboardInfo.curCardNum"
class="card-select"
:placeholder="$t('public.select')"
@change="selectValueChange">
<el-option v-for="item in CardNumArr"
:key="item.value"
:label="item.value + $t('operator.card')"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="helper-title">
{{$t("profiling.smartHelper")}}
</div>
<div class="suggested-title">{{$t("profiling.suggestions")}}</div>
<div id="helper-tips"></div>
</div>
<div class="collapse-btn"
:class="{collapse:collapse}"
@click="collapseLeft()">
</div>
</div>
<div class="prof-content-right"
:class="{collapse:collapse}">
<router-view></router-view>
<div class="close"
@click="backToDdashboard"
v-if="$route.path !== '/profiling-gpu/profiling-dashboard'">
<img src="@/assets/images/close-page.png">
</div>
</div>
</div>
</div>
</template>
<script>
import RequestService from '../../services/request-service';
export default {
data() {
return {
tipsArrayList: [
'step_trace-iter_interval',
'minddata_pipeline-general',
'minddata_pipeline-dataset_op',
'minddata_pipeline-generator_op',
'minddata_pipeline-map_op',
'minddata_pipeline-batch_op',
'minddata_warning_op',
],
moreParameter: ['minddata_device_queue', 'minddata_get_next_queue'],
CardNumArr: [], // Card list
collapse: false,
curDashboardInfo: {
// Current Select card info
curCardNum: '',
query: {},
},
};
},
watch: {},
mounted() {
this.$nextTick(() => {
this.init();
});
},
methods: {
/**
* Init function
*/
init() {
if (this.$route.query && this.$route.query.id && this.$route.query.dir) {
this.curDashboardInfo.query.id = this.$route.query.id;
this.curDashboardInfo.query.dir = this.$route.query.dir;
this.curDashboardInfo.query.path = this.$route.query.path;
this.getDeviceList();
} else {
this.curDashboardInfo.query.trainingJobId = '';
this.curDashboardInfo.query.dir = '';
this.curDashboardInfo.query.path = '';
this.$message.error(this.$t('trainingDashboard.invalidId'));
}
},
/**
* When card mumber changed,request data again.
*/
selectValueChange() {
const helperDiv = document.getElementById('helper-tips');
helperDiv.innerHTML = '';
this.getDataOfProfileHelper();
},
/**
* Get card number list
*/
getDeviceList() {
const params = {
profile: this.curDashboardInfo.query.dir,
train_id: this.curDashboardInfo.query.id,
};
RequestService.getProfilerDeviceData(params)
.then(
(res) => {
if (res && res.data && res.data.length) {
const deviceList = res.data;
deviceList.forEach((item) => {
this.CardNumArr.push({
value: item,
});
});
this.curDashboardInfo.curCardNum = this.CardNumArr[0].value;
this.getDataOfProfileHelper();
} else {
this.CardNumArr = [];
this.curDashboardInfo.curCardNum = '';
this.curDashboardInfo.initOver = true;
}
},
(error) => {
this.CardNumArr = [];
this.curDashboardInfo.curCardNum = '';
this.curDashboardInfo.initOver = true;
},
)
.catch(() => {
this.curDashboardInfo.initOver = true;
});
},
/**
* Get profile helper data
*/
getDataOfProfileHelper() {
const params = {
train_id: this.curDashboardInfo.query.id,
profile: this.curDashboardInfo.query.dir,
device_id: this.curDashboardInfo.curCardNum.toString()
? this.curDashboardInfo.curCardNum.toString()
: '0',
};
RequestService.queryDataOfProfileHelper(params)
.then((resp) => {
if (resp && resp.data) {
const dataKeys = Object.keys(resp.data);
const helperDiv = document.getElementById('helper-tips');
helperDiv.innerHTML = '';
dataKeys.forEach((item) => {
if (
!this.tipsArrayList.includes(item) &&
!this.moreParameter.includes(item) &&
resp.data[item]
) {
this.$t(`profiling`)[item] = resp.data[item];
}
if (item.endsWith('type_label')) {
const divDom = document.createElement('div');
divDom.setAttribute('class', 'suggested-items-style');
divDom.innerHTML = `<div class="helper-icon"></div>
<div class="helper-container-title">
${this.$t(`profiling`)[item].desc}
</div>`;
helperDiv.appendChild(divDom);
} else if (this.tipsArrayList.includes(item)) {
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
const content = `${this.$t(`profiling`)[item].desc}`.replace(
`{n1}`,
resp.data[item][0],
);
divDom.innerHTML = `<div class="content-icon el-icon-caret-right"></div>
<div class="helper-content-style">${content}</div>`;
helperDiv.appendChild(divDom);
} else if (item === 'minddata_device_queue') {
const deviceEmpty =
resp.data['minddata_device_queue'][0] >= 0
? resp.data['minddata_device_queue'][0]
: '--';
const deviceTotal =
resp.data['minddata_device_queue'][1] >= 0
? resp.data['minddata_device_queue'][1]
: '--';
const deviceFull =
resp.data['minddata_device_queue'][2] >= 0
? resp.data['minddata_device_queue'][2]
: '--';
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
const content = `${this.$t(`profiling`)[item].desc}`
.replace(
`{n1}`,
`<span class="nowrap-style"> ${deviceEmpty}</span>`,
)
.replace(
`{n2}`,
`<span class="nowrap-style"> ${deviceTotal}</span>`,
)
.replace(
`{n3}`,
`<span class="nowrap-style"> ${deviceFull}</span>`,
)
.replace(
`{n4}`,
`<span class="nowrap-style"> ${deviceTotal}</span>`,
);
divDom.innerHTML = `<div class="content-icon el-icon-caret-right"></div>
<div class="helper-content-style">${content}</div>`;
helperDiv.appendChild(divDom);
} else if (item === 'minddata_get_next_queue') {
const getNextEmpty =
resp.data['minddata_get_next_queue'][0] >= 0
? resp.data['minddata_get_next_queue'][0]
: '--';
const getNextTotal =
resp.data['minddata_get_next_queue'][1] >= 0
? resp.data['minddata_get_next_queue'][1]
: '--';
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
const content = `${this.$t(`profiling`)[item].desc}`
.replace(
`{n1}`,
`<span class="nowrap-style"> ${getNextEmpty}</span>`,
)
.replace(
`{n2}`,
`<span class="nowrap-style"> ${getNextTotal}</span>`,
);
divDom.innerHTML = `<div class="content-icon el-icon-caret-right"></div>
<div class="helper-content-style">${content}</div>`;
helperDiv.appendChild(divDom);
} else if (this.$t(`profiling`)[item].anchor) {
if (this.$t(`profiling`)[item].anchor.length === 1) {
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
divDom.innerHTML = `<div class="content-icon el-icon-caret-right"></div>
<div class="helper-content-style">
<a target="_blank" href="${this.$t(`profiling`)[item].url[0]}">
${this.$t(`profiling`)[item].desc}</a></div>`;
helperDiv.appendChild(divDom);
} else {
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
const anchorList = this.$t(`profiling`)[item].anchor;
let anchorContent = this.$t(`profiling`)[item].desc;
for (let i = 0; i < anchorList.length; i++) {
const desc = anchorContent.relpace(
anchorList[i],
`<a target="_blank" href="${
this.$t(`profiling`)[item].url[i]
}">
${anchorList[i]}</a>`,
);
anchorContent = desc;
}
divDom.innerHTML = `<div class="content-icon el-icon-caret-right">
</div><div class="helper-content-style">${anchorContent}</div>`;
helperDiv.appendChild(divDom);
}
} else {
const divDom = document.createElement('div');
divDom.setAttribute('class', 'content-style');
divDom.innerHTML = `${this.$t(`profiling`)[item].desc}`;
helperDiv.appendChild(divDom);
}
});
}
})
.catch(() => {});
},
/**
* Router back to profiling-dashboard
*/
backToDdashboard() {
this.$router.push({
path: '/profiling-gpu/profiling-dashboard',
query: {
dir: this.curDashboardInfo.query.dir,
id: this.curDashboardInfo.query.id,
path: this.curDashboardInfo.query.path,
},
});
},
collapseLeft() {
this.collapse = !this.collapse;
this.$bus.$emit('collapse');
},
},
destroyed() {
this.$bus.$off('collapse');
},
};
</script>
<style lang="scss">
.prof-wrap {
height: 100%;
background: #fff;
.prof-content {
height: 100%;
padding: 24px 24px 24px 0;
& > div {
float: left;
height: 100%;
}
.prof-content-left {
width: 22%;
transition: width 0.2s;
position: relative;
.el-input__inner {
padding: 0 10px;
}
.helper {
padding: 32px;
height: 100%;
overflow-y: auto;
margin-left: 24px;
background: #edf0f5;
word-wrap: break-word;
.nowrap-style {
white-space: nowrap;
}
.cur-card {
margin-bottom: 32px;
.card-select {
width: calc(100% - 120px);
}
& > label {
margin-right: 14px;
}
}
.helper-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 32px;
.el-icon-rank {
float: right;
cursor: pointer;
}
}
.helper-container-title {
display: inline-block;
padding: 0 6px;
}
.helper-icon {
display: inline-block;
width: 6px;
height: 6px;
margin-top: 6px;
border-radius: 3px;
background-color: #00a5a7;
}
.suggested-title {
font-weight: bold;
margin-bottom: 20px;
font-size: 16px;
}
.container-bottom {
margin-bottom: 16px;
}
.suggested-items-style {
display: flex;
font-weight: bold;
margin-bottom: 6px;
margin-top: 10px;
}
.helper-content-style {
margin-left: 6px;
line-height: 20px;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 8;
}
}
.content-icon {
color: #00a5a7;
padding-top: 3px;
}
.content-style {
display: flex;
}
.collapse-btn {
position: absolute;
right: -21px;
width: 31px;
height: 100px;
top: 50%;
margin-top: -50px;
cursor: pointer;
line-height: 86px;
z-index: 1;
text-align: center;
background-image: url('../../assets/images/collapse-left.svg');
}
.collapse-btn.collapse {
background-image: url('../../assets/images/collapse-right.svg');
}
}
.prof-content-left.collapse {
width: 0;
}
.prof-content-right {
width: 78%;
padding-left: 20px;
transition: width 0.2s;
position: relative;
.close {
position: absolute;
right: 0;
top: -10px;
cursor: pointer;
}
}
.prof-content-right.collapse {
width: 100%;
}
}
}
</style>
...@@ -25,9 +25,6 @@ limitations under the License. ...@@ -25,9 +25,6 @@ limitations under the License.
:class="{fullScreen:fullScreen}" :class="{fullScreen:fullScreen}"
v-if="coreCharts.data.length"> v-if="coreCharts.data.length">
<div> <div>
<span class="profiler-title">
{{$t('operator.operatorTypeStatistics')}}
</span>
<el-radio-group class="chart-radio-group" <el-radio-group class="chart-radio-group"
v-model="coreCharts.type" v-model="coreCharts.type"
@change="coreChartChange" @change="coreChartChange"
...@@ -49,9 +46,6 @@ limitations under the License. ...@@ -49,9 +46,6 @@ limitations under the License.
<div class="cl-profiler-bottom" <div class="cl-profiler-bottom"
:class="{fullScreen:fullScreen}" :class="{fullScreen:fullScreen}"
v-if="coreCharts.data.length"> v-if="coreCharts.data.length">
<span class="profiler-title">
{{ $t('operator.operatorStatistics') }}
</span>
<img src="../../assets/images/full-screen.png" <img src="../../assets/images/full-screen.png"
:title="$t('graph.fullScreen')" :title="$t('graph.fullScreen')"
class="fullScreen" class="fullScreen"
...@@ -90,7 +84,7 @@ limitations under the License. ...@@ -90,7 +84,7 @@ limitations under the License.
@expand-change="expandTypeItem" @expand-change="expandTypeItem"
@sort-change="opTypeSortChange" @sort-change="opTypeSortChange"
stripe stripe
height="calc(100% - 75px)" height="calc(100% - 40px)"
width="100%"> width="100%">
<el-table-column type="expand"> <el-table-column type="expand">
<template slot-scope="props"> <template slot-scope="props">
...@@ -144,7 +138,7 @@ limitations under the License. ...@@ -144,7 +138,7 @@ limitations under the License.
stripe stripe
ref="opAllTable" ref="opAllTable"
width="100%" width="100%"
height="calc(100% - 114px)" height="calc(100% - 80px)"
@cell-click="showInfoDetail" @cell-click="showInfoDetail"
@sort-change="(...args)=>{coreDetailSortChange(opAllTypeList, ...args)}" @sort-change="(...args)=>{coreDetailSortChange(opAllTypeList, ...args)}"
tooltip-effect="light"> tooltip-effect="light">
...@@ -186,11 +180,6 @@ limitations under the License. ...@@ -186,11 +180,6 @@ limitations under the License.
name="cpu"> name="cpu">
<div class="cl-profiler-top" <div class="cl-profiler-top"
v-if="false"> v-if="false">
<div>
<span class="profiler-title">
{{ $t('operator.operatorStatistics') }}
</span>
</div>
<div class="cl-profiler-echarts"> <div class="cl-profiler-echarts">
<div class <div class
id="cpu-echarts"></div> id="cpu-echarts"></div>
...@@ -198,9 +187,6 @@ limitations under the License. ...@@ -198,9 +187,6 @@ limitations under the License.
</div> </div>
<div class="cl-profiler-bottom" <div class="cl-profiler-bottom"
v-if="cpuCharts.data.length"> v-if="cpuCharts.data.length">
<span class="profiler-title">
{{ $t('operator.operatorStatistics') }}
</span>
<div class="cl-search-box"> <div class="cl-search-box">
<el-input v-model="searchByCPUTypeInput" <el-input v-model="searchByCPUTypeInput"
:placeholder="$t('operator.searchByType')" :placeholder="$t('operator.searchByType')"
...@@ -324,7 +310,7 @@ export default { ...@@ -324,7 +310,7 @@ export default {
pageTotal: 0, pageTotal: 0,
opDetailPage: { opDetailPage: {
offset: 0, offset: 0,
limit: 8, limit: 15,
}, },
op_filter_condition: {}, op_filter_condition: {},
op_sort_condition: {}, op_sort_condition: {},
...@@ -444,7 +430,7 @@ export default { ...@@ -444,7 +430,7 @@ export default {
pageTotal: 0, pageTotal: 0,
opDetailPage: { opDetailPage: {
offset: 0, offset: 0,
limit: 8, limit: 15,
}, },
op_filter_condition: {}, op_filter_condition: {},
op_sort_condition: {}, op_sort_condition: {},
...@@ -480,7 +466,7 @@ export default { ...@@ -480,7 +466,7 @@ export default {
opDetailCol: [], opDetailCol: [],
opDetailPage: { opDetailPage: {
offset: 0, offset: 0,
limit: 8, limit: 15,
}, },
pageTotal: 0, pageTotal: 0,
op_filter_condition: { op_filter_condition: {
...@@ -871,6 +857,9 @@ export default { ...@@ -871,6 +857,9 @@ export default {
} }
return legendStr; return legendStr;
}, },
tooltip: {
show: true,
},
itemWidth: 18, itemWidth: 18,
itemHeight: 18, itemHeight: 18,
padding: [0, 50, 0, 0], padding: [0, 50, 0, 0],
...@@ -1186,12 +1175,6 @@ export default { ...@@ -1186,12 +1175,6 @@ export default {
height: 100%; height: 100%;
} }
} }
.profiler-title {
font-size: 16px;
font-weight: bold;
line-height: 32px;
display: inline-block;
}
.cl-profiler-echarts { .cl-profiler-echarts {
width: 100%; width: 100%;
height: calc(100% - 32px); height: calc(100% - 32px);
......
...@@ -1364,6 +1364,7 @@ export default { ...@@ -1364,6 +1364,7 @@ export default {
align-items: baseline; align-items: baseline;
.cell-container { .cell-container {
width: 20%; width: 20%;
min-width:110px;
padding: 20px 0; padding: 20px 0;
border: 2px solid transparent; border: 2px solid transparent;
.title { .title {
...@@ -1428,7 +1429,7 @@ export default { ...@@ -1428,7 +1429,7 @@ export default {
font-size: 12px; font-size: 12px;
line-height: 12px; line-height: 12px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: visible;
width: 100%; width: 100%;
text-align: center; text-align: center;
.line { .line {
......
...@@ -121,7 +121,8 @@ export default { ...@@ -121,7 +121,8 @@ export default {
train_id: this.curDashboardInfo.query.id, train_id: this.curDashboardInfo.query.id,
}; };
RequestService.getProfilerDeviceData(params) RequestService.getProfilerDeviceData(params)
.then((res) => { .then(
(res) => {
if (res && res.data && res.data.length) { if (res && res.data && res.data.length) {
const deviceList = res.data; const deviceList = res.data;
deviceList.forEach((item) => { deviceList.forEach((item) => {
...@@ -136,7 +137,13 @@ export default { ...@@ -136,7 +137,13 @@ export default {
this.curDashboardInfo.curCardNum = ''; this.curDashboardInfo.curCardNum = '';
this.curDashboardInfo.initOver = true; this.curDashboardInfo.initOver = true;
} }
}) },
(error) => {
this.CardNumArr = [];
this.curDashboardInfo.curCardNum = '';
this.curDashboardInfo.initOver = true;
},
)
.catch(() => { .catch(() => {
this.curDashboardInfo.initOver = true; this.curDashboardInfo.initOver = true;
}); });
...@@ -310,13 +317,13 @@ export default { ...@@ -310,13 +317,13 @@ export default {
background: #fff; background: #fff;
.prof-content { .prof-content {
height: 100%; height: 100%;
padding: 32px 32px 32px 0; padding: 24px 24px 24px 0;
& > div { & > div {
float: left; float: left;
height: 100%; height: 100%;
} }
.prof-content-left { .prof-content-left {
width: 25%; width: 22%;
transition: width 0.2s; transition: width 0.2s;
position: relative; position: relative;
.el-input__inner { .el-input__inner {
...@@ -326,7 +333,7 @@ export default { ...@@ -326,7 +333,7 @@ export default {
padding: 32px; padding: 32px;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
margin-left: 32px; margin-left: 24px;
background: #edf0f5; background: #edf0f5;
word-wrap: break-word; word-wrap: break-word;
.nowrap-style { .nowrap-style {
...@@ -415,7 +422,7 @@ export default { ...@@ -415,7 +422,7 @@ export default {
width: 0; width: 0;
} }
.prof-content-right { .prof-content-right {
width: 75%; width: 78%;
padding-left: 20px; padding-left: 20px;
transition: width 0.2s; transition: width 0.2s;
position: relative; position: relative;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册