mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-16 05:09:17 +00:00
feat: implement Profile in the dashboard (#19)
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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="profile">
|
||||
<div class="profile-header">
|
||||
<div class="thread" :style="`width: ${thread}px`">
|
||||
Thread Stack
|
||||
<span class="r cp dragger" ref="dragger">
|
||||
<Icon iconName="settings_ethernet" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="self">Duration (ms)</div>
|
||||
<div class="exec-ms">
|
||||
Self Duration (ms)
|
||||
<a
|
||||
class="profile-set-btn"
|
||||
@click="updateHighlightTop"
|
||||
title="Highlight top 10 slow methods"
|
||||
:style="{ color: highlightTop ? '#448dfe' : '#484b55' }"
|
||||
>
|
||||
top slow
|
||||
</a>
|
||||
</div>
|
||||
<div class="dump-count">Dump Count</div>
|
||||
</div>
|
||||
<TableItem
|
||||
:thread="thread"
|
||||
v-for="(item, index) in tableData"
|
||||
:data="item"
|
||||
:key="'key' + index"
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useProfileStore } from "@/store/modules/profile";
|
||||
import { ref, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import TableItem from "./Item.vue";
|
||||
|
||||
/* global defineProps */
|
||||
defineProps({
|
||||
tableData: { type: Array as PropType<any>, default: () => [] },
|
||||
highlightTop: { type: Boolean, default: false },
|
||||
});
|
||||
const dragger = ref<any>(null);
|
||||
const thread = ref<number>(500);
|
||||
const profileStore = useProfileStore();
|
||||
|
||||
onMounted(() => {
|
||||
dragger.value.onmousedown = (event: any) => {
|
||||
const diffX = event.clientX;
|
||||
const copy = thread.value;
|
||||
document.onmousemove = (documentEvent) => {
|
||||
const moveX = documentEvent.clientX - diffX;
|
||||
thread.value = copy + moveX;
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
function updateHighlightTop() {
|
||||
profileStore.setHighlightTop();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./profile.scss";
|
||||
|
||||
.dragger {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.profile {
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
|
||||
.profile-set-btn {
|
||||
font-size: 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 57px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.profile-header div {
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
border-right: 1px dotted silver;
|
||||
line-height: 30px;
|
||||
background-color: #f3f4f9;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
109
src/views/dashboard/related/profile/components/Stack/Index.vue
Normal file
109
src/views/dashboard/related/profile/components/Stack/Index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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="profile-detail-chart-table">
|
||||
<Container :tableData="tableData" :highlightTop="highlightTop">
|
||||
<div class="profile-tips" v-if="!tableData.length">{{ t("noData") }}</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import Container from "./Container.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
/* global defineProps */
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<any>, default: () => [] },
|
||||
highlightTop: { type: Boolean, default: false },
|
||||
});
|
||||
const tableData = ref<any>([]);
|
||||
|
||||
onMounted(() => {
|
||||
tableData.value = processTree();
|
||||
});
|
||||
function processTree() {
|
||||
if (!props.data.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const durationChildExcluded = props.data
|
||||
.map((d: any) => {
|
||||
return d.elements.map((item: any) => item.durationChildExcluded);
|
||||
})
|
||||
.flat(1);
|
||||
function compare(val: number, val1: number) {
|
||||
return val1 - val;
|
||||
}
|
||||
const topDur = durationChildExcluded
|
||||
.sort(compare)
|
||||
.filter((item: any, index: number) => index < 10 && item !== 0);
|
||||
const trees = [];
|
||||
|
||||
for (const item of props.data) {
|
||||
const newArr = sortArr(item.elements, topDur);
|
||||
trees.push(...newArr);
|
||||
}
|
||||
|
||||
return trees;
|
||||
}
|
||||
|
||||
function sortArr(arr: any[], topDur: any) {
|
||||
const copyArr = JSON.parse(JSON.stringify(arr));
|
||||
const obj: any = {};
|
||||
const res = [];
|
||||
for (const item of copyArr) {
|
||||
obj[item.id] = item;
|
||||
}
|
||||
for (const item of copyArr) {
|
||||
item.topDur =
|
||||
topDur.includes(item.durationChildExcluded) && props.highlightTop;
|
||||
if (item.parentId === "0") {
|
||||
res.push(item);
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (item.id === obj[key].parentId) {
|
||||
if (item.children) {
|
||||
item.children.push(obj[key]);
|
||||
} else {
|
||||
item.children = [obj[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.data, props.highlightTop],
|
||||
() => {
|
||||
if (!props.data.length) {
|
||||
tableData.value = [];
|
||||
return;
|
||||
}
|
||||
tableData.value = processTree();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-detail-chart-table {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
153
src/views/dashboard/related/profile/components/Stack/Item.vue
Normal file
153
src/views/dashboard/related/profile/components/Stack/Item.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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>
|
||||
<div
|
||||
:class="['profile-item', 'level' + data.parentId]"
|
||||
:style="{ color: data.topDur ? '#448dfe' : '#3d444f' }"
|
||||
>
|
||||
<div
|
||||
:class="['thread', 'level' + data.parentId]"
|
||||
:style="{
|
||||
'text-indent': data.parentId * 4 + 'px',
|
||||
width: `${thread}px`,
|
||||
}"
|
||||
>
|
||||
<Icon
|
||||
class="icon vm cp trans"
|
||||
:style="!displayChildren ? 'transform: rotate(-90deg);' : ''"
|
||||
@click.stop="toggle"
|
||||
v-if="data.children && data.children.length"
|
||||
iconName="arrow-down"
|
||||
/>
|
||||
<el-tooltip :content="data.codeSignature" placement="bottom">
|
||||
<span>
|
||||
{{ data.codeSignature }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="self">{{ data.duration }}</div>
|
||||
<div class="exec-ms">{{ data.durationChildExcluded }}</div>
|
||||
<div class="dump-count">{{ data.count }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="data.children && data.children.length && displayChildren"
|
||||
class="children-trace"
|
||||
>
|
||||
<table-item
|
||||
:thread="thread"
|
||||
v-for="(item, index) in data.children"
|
||||
:key="index"
|
||||
:data="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent, toRefs } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
|
||||
const props = {
|
||||
data: { type: Object as PropType<any>, default: () => ({}) },
|
||||
thread: { type: Number, default: 0 },
|
||||
};
|
||||
export default defineComponent({
|
||||
name: "TableItem",
|
||||
props,
|
||||
setup(props) {
|
||||
const displayChildren = ref<boolean>(true);
|
||||
function toggle() {
|
||||
displayChildren.value = !displayChildren.value;
|
||||
}
|
||||
return { toggle, displayChildren, ...toRefs(props) };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./profile.scss";
|
||||
|
||||
.profile-item.level0 {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #448dfe;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #448dfe;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
background: #448dfe;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-item {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-item.selected {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.profile-item:not(.level0):hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.profile-item > div {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-item > div.method {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.profile-item div.exec-percent {
|
||||
width: 10%;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
|
||||
.outer-progress_bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: rgb(63, 177, 227);
|
||||
position: relative;
|
||||
margin-top: 11px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.inner-progress_bar {
|
||||
position: absolute;
|
||||
background: rgb(110, 64, 170);
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
left: 0;
|
||||
border: none;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
.dump-count {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.exec-ms {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.self {
|
||||
width: 100px;
|
||||
}
|
Reference in New Issue
Block a user