blob: 54642b585b8e3fbb17787690fce8d98fe3e99272 [file] [log] [blame]
<!--
Copyright (c) 2020 Contributors to the Eclipse Foundation
See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0
SPDX-License-Identifier: EPL-2.0
-->
<template xmlns:v-contextmenu="http://www.w3.org/1999/xhtml">
<div style="height: 100%; position: relative">
<v-contextmenu ref="contextmenu">
<v-contextmenu-submenu :title="$t('jifa.heap.ref.object.label')">
<v-contextmenu-item
@click="$emit('outgoingRefsOfObj', contextMenuTargetObjectId, contextMenuTargetObjectLabel)">
{{$t('jifa.heap.ref.object.outgoing')}}
</v-contextmenu-item>
<v-contextmenu-item
@click="$emit('incomingRefsOfObj', contextMenuTargetObjectId, contextMenuTargetObjectLabel)">
{{$t('jifa.heap.ref.object.incoming')}}
</v-contextmenu-item>
</v-contextmenu-submenu>
<v-contextmenu-submenu :title="$t('jifa.heap.ref.type.label')">
<v-contextmenu-item
@click="$emit('outgoingRefsOfClass', contextMenuTargetObjectId, contextMenuTargetObjectLabel)">
{{$t('jifa.heap.ref.type.outgoing')}}
</v-contextmenu-item>
<v-contextmenu-item
@click="$emit('incomingRefsOfClass', contextMenuTargetObjectId, contextMenuTargetObjectLabel)">
{{$t('jifa.heap.ref.type.incoming')}}
</v-contextmenu-item>
</v-contextmenu-submenu>
<v-contextmenu-item divider></v-contextmenu-item>
<v-contextmenu-item
@click="$emit('pathToGCRootsOfObj', contextMenuTargetObjectId, contextMenuTargetObjectLabel)">
{{$t('jifa.heap.pathToGCRoots')}}
</v-contextmenu-item>
</v-contextmenu>
<div style="height: 45px; margin-top: 5px">
<el-autocomplete
v-model="oql"
:fetch-suggestions="queryHistory"
placeholder="Object Query Language ..."
:trigger-on-focus="false"
prefix-icon="el-icon-edit"
@keyup.enter.native="search"
clearable
style="display: flex"
>
<template slot="append">
<el-button :icon="searching ? 'el-icon-loading':'el-icon-search'" :disabled="searching" @click="search">
</el-button>
</template>
</el-autocomplete>
</div>
<div align="left" style="height: 25px; margin-bottom: 5px">
<a href="https://help.eclipse.org/oxygen/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Freference%2Foqlsyntax.html&cp=66_4_2"
target="_blank" style="font-size: 12px; font-weight: bold; color: #909399">
OQL Help
</a>
</div>
<div v-loading="loading" style="position: absolute; top: 80px; left: 0; right: 0; bottom: 0;">
<el-table v-if="isTreeResult"
ref='treeResultTable' :data="treeTableData"
:highlight-current-row="false"
stripe
:header-cell-style="headerCellStyle"
:cell-style='cellStyle'
row-key="rowKey"
lazy
:indent=8
height="100%"
:load="loadOutbounds">
<el-table-column label="Class Name" width="800px" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.isResult" @click="$emit('setSelectedObjectId', scope.row.objectId)"
style="cursor: pointer"
@contextmenu="contextMenuTargetObjectId = scope.row.objectId; contextMenuTargetObjectLabel = scope.row.label"
v-contextmenu:contextmenu>
<img :src="scope.row.icon" style="margin-right: 5px"/>
<strong>{{ scope.row.prefix }}</strong>
{{ scope.row.label }}
<span style="font-weight: bold; color: #909399">
{{ scope.row.suffix }}
</span>
</span>
<span v-if="scope.row.isOutboundsSummary">
<img :src="ICONS.misc.sumIcon" v-if="scope.row.currentSize >= scope.row.totalSize"/>
<img :src="ICONS.misc.sumPlusIcon"
@dblclick="fetchOutbounds(scope.row.parentRowKey, scope.row.objectId, scope.row.nextPage, scope.row.resolve)"
style="cursor: pointer"
v-else/>
{{ scope.row.currentSize }} <strong> / </strong> {{ scope.row.totalSize }}
</span>
<span v-if="scope.row.isSummaryItem">
<img :src="ICONS.misc.sumIcon" v-if="treeResult.length >= totalSize"/>
<img :src="ICONS.misc.sumPlusIcon" @dblclick="fetchResult(scope.row.oql)" style="cursor: pointer" v-else/>
{{ treeResult.length }} <strong> / </strong> {{totalSize}}
</span>
</template>
</el-table-column>
<el-table-column label="Shallow Heap" prop="shallowHeap">
</el-table-column>
<el-table-column label="Retained Heap" prop="retainedHeap">
</el-table-column>
</el-table>
<el-table v-if="isTableResult"
ref='tableResultTable' :data="tableTableData"
:highlight-current-row="false"
stripe
:header-cell-style="headerCellStyle"
:cell-style='cellStyle'
row-key="rowKey"
lazy
:indent=8
height="100%">
<el-table-column v-for="(column, index) in tableResultColumns" :label="column" v-bind:key="column">
<template slot-scope="scope">
<span v-if="scope.row.isResult" @click="$emit('setSelectedObjectId', scope.row.objectId)"
style="cursor: pointer"
@contextmenu="contextMenuTargetObjectId = scope.row.objectId; contextMenuTargetObjectLabel = scope.row.values[index] ? scope.row.values[index] : 'null'"
v-contextmenu:contextmenu>
{{ scope.row.values[index] ? scope.row.values[index] : 'null' }}
</span>
<span v-if="scope.row.isSummaryItem && index === 0">
<img :src="ICONS.misc.sumIcon" v-if="tableResult.length >= totalSize"/>
<img :src="ICONS.misc.sumPlusIcon" @dblclick="fetchResult(scope.row.oql)" style="cursor: pointer" v-else/>
{{ tableResult.length }} <strong> / </strong> {{totalSize}}
</span>
</template>
</el-table-column>
</el-table>
<el-input v-if="isTextResult"
v-model="textResult"
type="textarea"
readonly
autosize>
</el-input>
</div>
</div>
</template>
<script>
import axios from 'axios'
import {getOutboundIcon, ICONS} from "./IconHealper";
import {heapDumpService} from "../../util";
let rowKey = 1
// oql result type
const TREE = 1
const TABLE = 2
const TEXT = 3
export default {
props: ['file'],
data() {
return {
ICONS,
cellStyle: {padding: '4px', fontSize: '12px'},
headerCellStyle: {padding: 0, 'font-size': '12px', 'font-weight': 'normal'},
searching: false,
loading: false,
oql: '',
nextPage: 1,
pageSize: 25,
totalSize: 0,
resultType: 0,
contextMenuTargetObjectId: null,
contextMenuTargetObjectLabel: null,
treeResult: [],
treeTableData: [],
tableResult: [],
tableResultColumns: [],
tableTableData: [],
textResult: '',
isTreeResult: false,
isTableResult: false,
isTextResult: false,
historyOQLs: []
}
},
methods: {
adjustDataByResultType(type) {
this.isTreeResult = type === TREE
this.isTableResult = type === TABLE
this.isTextResult = type === TEXT
},
fetchResult(oql) {
if (!oql || oql.length === 0) {
return
}
this.loading = true
axios.get(heapDumpService(this.file, 'oql'), {
params: {
oql: oql,
page: this.nextPage,
pageSize: this.pageSize,
}
}).then(resp => {
this.adjustDataByResultType(resp.data.type)
if (this.isTreeResult) {
this.putHistory(oql)
this.totalSize = resp.data.pv.totalSize
let data = resp.data.pv.data
data.forEach(d => {
this.treeResult.push({
rowKey: rowKey++,
icon: getOutboundIcon(d.gCRoot, d.objectType),
prefix: d.prefix,
label: d.label,
suffix: d.suffix,
shallowHeap: d.shallowSize,
retainedHeap: d.retainedSize,
hasChildren: d.hasOutbound,
objectId: d.objectId,
isResult: true
})
})
this.treeTableData = this.treeResult.concat({
rowKey: rowKey++,
oql: oql,
isSummaryItem: true
})
this.nextPage++
} else if (this.isTableResult) {
this.putHistory(oql)
this.totalSize = resp.data.pv.totalSize
this.tableResultColumns = resp.data.columns
let data = resp.data.pv.data
data.forEach(d => {
this.tableResult.push({
rowKey: rowKey++,
objectId: d.objectId,
values: d.values,
hasChildren: false,
isResult: true
})
})
this.tableTableData = this.tableResult.concat({
rowKey: rowKey++,
oql: oql,
isSummaryItem: true
})
this.nextPage++
} else if (this.isTextResult) {
this.textResult = resp.data.text
}
this.searching = false
this.loading = false
})
},
loadOutbounds(tree, treeNode, resolve) {
this.fetchOutbounds(tree.rowKey, tree.objectId, 1, resolve)
},
fetchOutbounds(parentRowKey, objectId, page, resolve) {
this.loading = true
axios.get(heapDumpService(this.file, 'outbounds'), {
params: {
objectId: objectId,
page: page,
pageSize: this.pageSize,
}
}).then(resp => {
let loadedLen = 0;
let loaded = this.$refs['treeResultTable'].store.states.lazyTreeNodeMap[parentRowKey]
let callResolve = false
if (loaded) {
loadedLen = loaded.length
if (loadedLen > 0) {
loaded.splice(--loadedLen, 1)
}
} else {
loaded = []
callResolve = true;
}
let res = resp.data.data
res.forEach(d => {
loaded.push({
rowKey: rowKey++,
icon: getOutboundIcon(d.gCRoot, d.objectType),
prefix: d.prefix,
label: d.label,
suffix: d.suffix,
shallowHeap: d.shallowSize,
retainedHeap: d.retainedSize,
hasChildren: d.hasOutbound,
objectId: d.objectId,
isResult: true
})
})
loaded.push({
rowKey: rowKey++,
objectId: objectId,
parentRowKey: parentRowKey,
isOutboundsSummary: true,
nextPage: page + 1,
currentSize: loadedLen + res.length,
totalSize: resp.data.totalSize,
resolve: resolve,
})
if (callResolve) {
resolve(loaded)
}
this.loading = false
})
},
clear() {
this.nextPage = 1
this.totalSize = 0
this.treeResult = []
this.treeTableData = []
this.tableResult = []
this.tableResultColumns = []
this.tableTableData = []
this.textResult = ''
},
search() {
if (this.oql) {
this.searching = true
this.clear()
this.fetchResult(this.oql)
}
},
putHistory(oql) {
let target = oql.trim()
for (let i = 0; i < this.historyOQLs.length; i++) {
if (this.historyOQLs[i].value === target) {
return
}
}
this.historyOQLs.push({value: target})
if (this.historyOQLs.length > 11) {
this.historyOQLs.shift()
}
},
queryHistory(queryString, cb) {
let history = this.historyOQLs;
let results = queryString ? history.filter(this.createFilter(queryString)) : history;
cb(results);
},
createFilter(queryString) {
return (history) => {
return (history.value.toLowerCase().indexOf(queryString.toLowerCase().trim()) === 0);
};
},
}
}
</script>