blob: 42f6c1ebbf7c071d99dff05ba1ebe2deb7f87af0 [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>
<el-table ref="table" :data="threads"
:highlight-current-row="false"
stripe
:header-cell-style="headerCellStyle"
:cell-style='cellStyle'
row-key="rowKey"
lazy
v-loading="loading"
height="100%"
:indent=8
:load="loadStackInfo"
:span-method="tableSpanMethod">
<el-table-column label="Thread / Stack" width="450px" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.isThread" @click="$emit('setSelectedObjectId', scope.row.objectId)"
style="cursor: pointer">
<img :src="threadIcon"/> {{scope.row.object}}
</span>
<span v-if="scope.row.isStack">
<img :src="frameIcon"/> {{scope.row.stack}}
</span>
<span v-if="scope.row.isLocal" @click="$emit('setSelectedObjectId', scope.row.objectId)"
style="cursor: pointer">
<img :src="scope.row.icon">
<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.isOutbound" @click="$emit('setSelectedObjectId', scope.row.objectId)"
style="cursor: pointer">
<img :src="scope.row.icon">
<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.isThreadSummaryItem">
<img :src="sumIcon" v-if="currentSize >= totalSize"/>
<img :src="sumPlusIcon" @dblclick="fetchThreadDetails" style="cursor: pointer" v-else/>
{{ currentSize }} <strong> / </strong> {{totalSize}}
</span>
</template>
</el-table-column>
<el-table-column label="Name" prop="name" width="300px" show-overflow-tooltip>
</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-column label="Context Class Loader" prop="contextClassLoader" width="420px" show-overflow-tooltip>
</el-table-column>
<el-table-column label="Is Daemon" prop="daemon">
</el-table-column>
</el-table>
</template>
<script>
import axios from 'axios'
import {heapDumpService} from '../../util'
import {getOutboundIcon, ICONS} from './IconHealper'
let rowKey = 1
export default {
props: ['file'],
methods: {
loadStackInfo(tree, treeNode, resolve) {
if (tree.isThread) {
this.fetchStackTrace(tree.objectId, resolve)
} else if (tree.isStack) {
this.fetchLocals(tree.threadObjectId, tree.depth, tree.firstNonNativeFrame, resolve)
} else if (tree.isLocal || tree.isOutbound) {
this.fetchOutbounds(tree.rowKey, tree.objectId, 1, resolve)
} else if (tree.isOutboundsSummary) {
this.fetchOutbounds(tree.parentRowKey, tree.objectId, tree.nextPage, resolve)
}
},
tableSpanMethod(i) {
if (i.row.isStack) {
if (i.columnIndex === 0) {
return [1, 2];
} else if (i.columnIndex === 1) {
return [0, 0];
} else {
return [1, 1]
}
}
if (i.row.isLocal || i.row.isOutbound) {
if (i.columnIndex === 0) {
return [1, 2];
} else if (i.columnIndex === 1) {
return [0, 0];
} else {
return [1, 1]
}
}
},
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['table'].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
for (let i = 0; i < res.length; i++) {
loaded.push({
rowKey: rowKey++,
isOutbound: true,
objectId: res[i].objectId,
prefix: res[i].prefix,
label: res[i].label,
suffix: res[i].suffix,
shallowHeap: res[i].shallowSize,
retainedHeap: res[i].retainedSize,
icon: getOutboundIcon(res[i].gCRoot, res[i].objectType),
hasChildren: res[i].hasOutbound
})
}
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
})
},
fetchLocals(objId, depth, firstNonNativeFrame, resolve) {
this.loading = true
axios.get(heapDumpService(this.file, 'locals'), {
params: {
objectId: objId,
depth: depth,
firstNonNativeFrame: firstNonNativeFrame
}
}).then(resp => {
let res = resp.data
let locals = []
for (let i = 0; i < res.length; i++) {
locals.push({
rowKey: rowKey++,
isLocal: true,
objectId: res[i].objectId,
prefix: res[i].prefix,
label: res[i].label,
suffix: res[i].suffix,
shallowHeap: res[i].shallowSize,
retainedHeap: res[i].retainedSize,
icon: getOutboundIcon(res[i].gCRoot, res[i].objectType),
hasChildren: res[i].hasOutbound
})
}
resolve(locals)
this.loading = false
})
},
fetchStackTrace(objId, resolve) {
this.loading = true
axios.get(heapDumpService(this.file, 'stackTrace'), {
params: {
objectId: objId
}
}).then(resp => {
let res = resp.data
let stacks = []
let depth = 1
for (let i = 0; i < res.length; i++) {
stacks.push({
rowKey: rowKey++,
isStack: true,
threadObjectId: objId,
stack: res[i].stack,
depth: depth++,
hasChildren: res[i].hasLocal,
firstNonNativeFrame: res[i].firstNonNativeFrame
})
}
resolve(stacks)
this.loading = false
})
},
fetchThreadsData() {
this.loading = true
axios.get(heapDumpService(this.file, 'threadsSummary'), {}).then(resp => {
this.shallowHeap = resp.data.shallowHeap
this.retainedHeap = resp.data.retainedHeap
this.totalSize = resp.data.totalSize
this.fetchThreadDetails();
})
},
fetchThreadDetails() {
if (this.currentSize >= this.totalSize) {
return
}
this.loading = true
if (this.nextPage > 1) {
this.threads.splice(this.threads.length - 1, 1)
}
axios.get(heapDumpService(this.file, 'threads'), {
params: {
page: this.nextPage,
pageSize: this.pageSize
}
}).then(resp => {
let res = resp.data.data
let tmp = []
this.currentSize += res.length
for (let i = 0; i < res.length; i++) {
tmp.push({
rowKey: rowKey++,
isThread: true,
objectId: res[i].objectId,
object: res[i].object,
name: res[i].name,
shallowHeap: res[i].shallowSize,
retainedHeap: res[i].retainedSize,
contextClassLoader: res[i].contextClassLoader,
daemon: res[i].daemon + '',
hasChildren: res[i].hasStack
})
}
this.nextPage++
tmp.forEach(t => this.threads.push(t))
this.threads.push({
rowKey: rowKey++,
isThreadSummaryItem: true,
shallowHeap: this.shallowHeap,
retainedHeap: this.retainedHeap
})
this.loading = false
})
}
},
data() {
return {
loading: false,
threads: [],
ICONS,
threadIcon: require('../../assets/heap/thread.gif'),
frameIcon: require('../../assets/heap/stack_frame.gif'),
sumIcon: require('../../assets/heap/misc/sum.gif'),
sumPlusIcon: require('../../assets/heap/misc/sum_plus.gif'),
nextPage: 1,
pageSize: 25,
currentSize: 0,
totalSize: 0,
shallowHeap: 0,
retainedHeap: 0,
cellStyle: {padding: '4px', fontSize: '12px'},
headerCellStyle: {padding: 0, 'font-size': '12px', 'font-weight': 'normal'}
}
},
created() {
this.fetchThreadsData()
}
}
</script>