fix: update profile

This commit is contained in:
Qiuxia Fan 2022-03-01 22:19:01 +08:00
parent 1ea3e4a04a
commit eab42fa1c6
15 changed files with 493 additions and 31 deletions

View File

@ -40,6 +40,7 @@ interface ProfileState {
currentSpan: SegmentSpan | Record<string, never>; currentSpan: SegmentSpan | Record<string, never>;
analyzeTrees: ProfileAnalyzationTrees; analyzeTrees: ProfileAnalyzationTrees;
taskLogs: TaskLog[]; taskLogs: TaskLog[];
highlightTop: boolean;
} }
export const traceStore = defineStore({ export const traceStore = defineStore({
@ -55,6 +56,7 @@ export const traceStore = defineStore({
currentSpan: {}, currentSpan: {},
analyzeTrees: [], analyzeTrees: [],
taskLogs: [], taskLogs: [],
highlightTop: true,
}), }),
actions: { actions: {
setConditions(data: { serviceId?: string; endpointName?: string }) { setConditions(data: { serviceId?: string; endpointName?: string }) {
@ -69,6 +71,9 @@ export const traceStore = defineStore({
setCurrentSegment(s: Trace) { setCurrentSegment(s: Trace) {
this.currentSegment = s; this.currentSegment = s;
}, },
setHighlightTop() {
this.highlightTop = !this.highlightTop;
},
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryServices").params({
layer, layer,

View File

@ -31,7 +31,7 @@ limitations under the License. -->
:key="item.i" :key="item.i"
@click="clickGrid(item)" @click="clickGrid(item)"
:class="{ active: dashboardStore.activedGridItem === item.i }" :class="{ active: dashboardStore.activedGridItem === item.i }"
drag-ignore-from="svg.d3-trace-tree" drag-ignore-from="svg.d3-trace-tree, .dragger"
> >
<component :is="item.type" :data="item" /> <component :is="item.type" :data="item" />
</grid-item> </grid-item>

View File

@ -32,13 +32,13 @@ import ThreadStack from "./components/ThreadStack.vue";
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content { .content {
height: 100%; height: calc(100% - 30px);
width: 100%; width: 100%;
} }
.item { .item {
height: 100%; height: 100%;
width: calc(100% - 290px); width: calc(100% - 300px);
} }
.list { .list {

View File

@ -13,14 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div <div class="profile-trace-dashboard" v-if="profileStore.currentSegment">
class="profile-trace-dashboard flex-v"
v-if="profileStore.currentSegment"
>
<div class="profile-trace-detail-wrapper"> <div class="profile-trace-detail-wrapper">
<Selector <Selector
size="small" size="small"
:value="traceId" :value="traceId || (traceIds[0] && traceIds[0].value) || ''"
:options="traceIds" :options="traceIds"
placeholder="Select a trace id" placeholder="Select a trace id"
@change="changeTraceId" @change="changeTraceId"
@ -159,7 +156,7 @@ function updateTimeRange() {
.profile-trace-dashboard { .profile-trace-dashboard {
padding: 5px; padding: 5px;
flex-shrink: 0; flex-shrink: 0;
height: calc(50% + 95); height: 50%;
overflow: auto; overflow: auto;
width: 100%; width: 100%;
} }

View File

@ -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>

View 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>

View 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>

View File

@ -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;
}

View File

@ -12,7 +12,33 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div>stack</div> <div v-if="profileStore.analyzeTrees.length" class="thread-stack">
<StackTable
:data="profileStore.analyzeTrees"
:highlightTop="profileStore.highlightTop"
/>
<div class="rk-trace-t-loading" v-show="loading">
<Icon iconName="spinner" />
</div>
<div v-if="message">{{ message }}</div>
</div>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import { ref } from "vue";
import StackTable from "./Stack/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
const profileStore = useProfileStore();
const loading = ref<boolean>(false);
const message = ref<string>("");
</script>
<style lang="scss" scoped>
.thread-stack {
padding: 5px 12px;
height: calc(50% - 50px);
overflow: auto;
width: 100%;
}
</style>

View File

@ -123,14 +123,16 @@ limitations under the License. -->
</div> </div>
</div> </div>
<div class="no-data" v-else>{{ t("noData") }}</div> <div class="no-data" v-else>{{ t("noData") }}</div>
<component <div class="trace-chart">
v-if="traceStore.currentTrace.endpointNames" <component
:is="displayMode" v-if="traceStore.currentTrace.endpointNames"
:data="traceStore.traceSpans" :is="displayMode"
:traceId="traceStore.currentTrace.traceIds[0].value" :data="traceStore.traceSpans"
:showBtnDetail="false" :traceId="traceStore.currentTrace.traceIds[0].value"
HeaderType="trace" :showBtnDetail="false"
/> HeaderType="trace"
/>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -227,6 +229,10 @@ export default defineComponent({
overflow: hidden; overflow: hidden;
} }
.trace-chart {
height: 100%;
}
.trace-detail-wrapper { .trace-detail-wrapper {
font-size: 12px; font-size: 12px;
padding: 5px 10px; padding: 5px 10px;

View File

@ -88,7 +88,7 @@ function downloadTrace() {
.charts { .charts {
overflow: auto; overflow: auto;
padding: 10px; padding: 10px;
height: calc(100% - 95px); height: 100%;
width: 100%; width: 100%;
} }

View File

@ -105,8 +105,7 @@ watch(
} }
.trace-table { .trace-table {
padding: 10px; height: 100%;
height: calc(100% - 95px);
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -30,7 +30,7 @@ limitations under the License. -->
</div> </div>
<div class="trace-header" v-else> <div class="trace-header" v-else>
<div class="method" :style="`width: ${method}px`"> <div class="method" :style="`width: ${method}px`">
<span class="r cp" ref="dragger"> <span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" /> <Icon iconName="settings_ethernet" size="sm" />
</span> </span>
{{ headerData[0].value }} {{ headerData[0].value }}
@ -158,6 +158,10 @@ function sortStatistics(key: string) {
width: 100%; width: 100%;
} }
.dragger {
float: right;
}
.trace-header { .trace-header {
white-space: nowrap; white-space: nowrap;
user-select: none; user-select: none;

View File

@ -15,7 +15,7 @@ limitations under the License. -->
<template> <template>
<div v-if="type === 'statistics'"> <div v-if="type === 'statistics'">
<div :class="['trace-item']" ref="traceItem"> <div class="trace-item">
<div :class="['method']"> <div :class="['method']">
<el-tooltip :content="data.groupRef.endpointName" placement="bottom"> <el-tooltip :content="data.groupRef.endpointName" placement="bottom">
<span> <span>
@ -49,7 +49,7 @@ limitations under the License. -->
</div> </div>
<div v-else> <div v-else>
<div <div
@click="viewSpanDetail" @click="selectSpan"
:class="[ :class="[
'trace-item', 'trace-item',
'level' + (data.level - 1), 'level' + (data.level - 1),
@ -106,7 +106,7 @@ limitations under the License. -->
<span>{{ data.serviceCode }}</span> <span>{{ data.serviceCode }}</span>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="application" v-show="type === 'profile'"> <div class="application" v-show="headerType === 'profile'">
<span @click="viewSpanDetail">{{ t("view") }}</span> <span @click="viewSpanDetail">{{ t("view") }}</span>
</div> </div>
</div> </div>
@ -120,6 +120,7 @@ limitations under the License. -->
:key="index" :key="index"
:data="child" :data="child"
:type="type" :type="type"
:headerType="headerType"
/> />
</div> </div>
<el-dialog <el-dialog
@ -183,7 +184,7 @@ export default defineComponent({
}); });
function toggle() { function toggle() {
displayChildren.value = !this.displayChildren.value; displayChildren.value = !displayChildren.value;
} }
function showSelectSpan() { function showSelectSpan() {
const items: any = document.querySelectorAll(".trace-item"); const items: any = document.querySelectorAll(".trace-item");
@ -196,11 +197,16 @@ export default defineComponent({
traceItem.value.style.background = "rgba(0, 0, 0, 1)"; traceItem.value.style.background = "rgba(0, 0, 0, 1)";
} }
function selectSpan() {
if (props.headerType === "profile") {
showSelectSpan();
return;
}
viewSpanDetail();
}
function viewSpanDetail() { function viewSpanDetail() {
showSelectSpan(); showSelectSpan();
if (props.headerType === "trace") { showDetail.value = true;
showDetail.value = true;
}
} }
watch( watch(
@ -218,6 +224,7 @@ export default defineComponent({
dateFormat, dateFormat,
showSelectSpan, showSelectSpan,
showDetail, showDetail,
selectSpan,
t, t,
}; };
}, },

View File

@ -86,7 +86,7 @@ function computedScale(i: number) {
overflow: auto; overflow: auto;
padding: 10px; padding: 10px;
position: relative; position: relative;
height: calc(100% - 95px); height: 100%;
width: 100%; width: 100%;
} }