mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 08:25:25 +00:00
feat: add trace table
This commit is contained in:
parent
8c7d708257
commit
77cfae3149
@ -31,6 +31,7 @@ limitations under the License. -->
|
||||
:key="item.i"
|
||||
@click="clickGrid(item)"
|
||||
:class="{ active: dashboardStore.activedGridItem === item.i }"
|
||||
drag-ignore-from="svg.d3-trace-tree"
|
||||
>
|
||||
<component :is="item.type" :data="item" />
|
||||
</grid-item>
|
||||
|
@ -100,6 +100,8 @@ limitations under the License. -->
|
||||
:is="displayMode"
|
||||
:data="traceStore.traceSpans"
|
||||
:traceId="traceStore.currentTrace.traceIds[0].value"
|
||||
:showBtnDetail="false"
|
||||
HeaderType="trace"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -25,7 +25,7 @@ import ListGraph from "../utils/d3-trace-list";
|
||||
import TreeGraph from "../utils/d3-trace-tree";
|
||||
import { Span } from "@/types/trace";
|
||||
|
||||
/* global defineProps, Nullable*/
|
||||
/* global defineProps, Nullable, defineExpose*/
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<Span[]>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
@ -38,7 +38,9 @@ const segmentId = ref<any>([]);
|
||||
const currentSpan = ref<Array<Span>>([]);
|
||||
const tree = ref<any>(null);
|
||||
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
||||
|
||||
defineExpose({
|
||||
tree,
|
||||
});
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
changeTree();
|
||||
|
@ -24,18 +24,18 @@ limitations under the License. -->
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding: 10px 0">
|
||||
<a class="trace-tree-btn mr-10" @click="tree.setDefault()">
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.setDefault()">
|
||||
{{ t("default") }}
|
||||
</a>
|
||||
<a class="trace-tree-btn mr-10" @click="tree.getTopSlow()">
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopSlow()">
|
||||
{{ t("topSlow") }}
|
||||
</a>
|
||||
<a class="trace-tree-btn mr-10" @click="tree.getTopChild()">
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopChild()">
|
||||
{{ t("topChildren") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="trace-tree">
|
||||
<Graph :data="data" :traceId="traceId" type="Tree" />
|
||||
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -54,7 +54,7 @@ const props = defineProps({
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const list = ref<string[]>([]);
|
||||
const tree = ref<any>(null);
|
||||
const charts = ref<any>(null);
|
||||
|
||||
onMounted(() => {
|
||||
list.value = Array.from(new Set(props.data.map((i: Span) => i.serviceCode)));
|
||||
|
@ -17,8 +17,10 @@
|
||||
|
||||
import List from "./List.vue";
|
||||
import Tree from "./Tree.vue";
|
||||
import Table from "./table/Index.vue";
|
||||
|
||||
export default {
|
||||
List,
|
||||
Tree,
|
||||
Table,
|
||||
};
|
||||
|
128
src/views/dashboard/related/trace/components/table/Index.vue
Normal file
128
src/views/dashboard/related/trace/components/table/Index.vue
Normal 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="trace-detail-chart-table">
|
||||
<div class="rk-trace-t-loading" v-show="loading">
|
||||
<Icon iconName="spinner" size="sm" />
|
||||
</div>
|
||||
<TableContainer :tableData="tableData" :type="HeaderType">
|
||||
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import copy from "@/utils/copy";
|
||||
import TableContainer from "./TableContainer.vue";
|
||||
import traceTable from "../../utils/trace-table";
|
||||
|
||||
/* global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<any>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
showBtnDetail: { type: Boolean, default: false },
|
||||
HeaderType: { type: String, default: "" },
|
||||
});
|
||||
const emit = defineEmits(["select", "view", "load"]);
|
||||
const loading = ref<boolean>(true);
|
||||
const tableData = ref<any>([]);
|
||||
const showDetail = ref<boolean>(false);
|
||||
const currentSpan = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
tableData.value = formatData(
|
||||
traceTable.changeTree(props.data, props.traceId)
|
||||
);
|
||||
loading.value = false;
|
||||
emit("select", handleSelectSpan);
|
||||
emit("view", handleViewSpan);
|
||||
emit("load", () => {
|
||||
loading.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
function formatData(arr: any[], level = 1, totalExec?: number) {
|
||||
for (const item of arr) {
|
||||
item.level = level;
|
||||
totalExec = totalExec || item.endTime - item.startTime;
|
||||
item.totalExec = totalExec;
|
||||
if (item.children && item.children.length > 0) {
|
||||
formatData(item.children, level + 1, totalExec);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function handleSelectSpan(data: any[]) {
|
||||
currentSpan.value = data;
|
||||
if (!props.showBtnDetail) {
|
||||
showDetail.value = true;
|
||||
}
|
||||
emit("select", data);
|
||||
}
|
||||
|
||||
function showCurrentSpanDetail(title: string, text: string) {
|
||||
const textLineNumber = text.split("\n").length;
|
||||
let textHeight = textLineNumber * 20.2 + 10;
|
||||
const tmpHeight = window.innerHeight * 0.9;
|
||||
textHeight = textHeight >= tmpHeight ? tmpHeight : textHeight;
|
||||
// this.$modal.show('dialog', {
|
||||
// title,
|
||||
// text: `<div style="height:${textHeight}px">${text}</div>`,
|
||||
// buttons: [
|
||||
// {
|
||||
// title: 'Copy',
|
||||
// handler: () => {
|
||||
// this.copy(text);
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: 'Close',
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
}
|
||||
function handleViewSpan() {
|
||||
showDetail.value = true;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!props.data.length) {
|
||||
tableData.value = [];
|
||||
return;
|
||||
}
|
||||
tableData.value = formatData(
|
||||
traceTable.changeTree(props.data, props.traceId)
|
||||
);
|
||||
loading.value = false;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dialog-c-text {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.trace-tips {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,160 @@
|
||||
<!-- 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="trace">
|
||||
<div class="trace-header">
|
||||
<div class="method" :style="`width: ${method}px`">
|
||||
<span class="r cp" ref="dragger">
|
||||
<svg class="icon">
|
||||
<use xlink:href="#settings_ethernet"></use>
|
||||
</svg>
|
||||
</span>
|
||||
{{ headerData[0].value }}
|
||||
</div>
|
||||
<div
|
||||
:class="item.label"
|
||||
v-for="(item, index) in headerData.slice(1)"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
<table-item
|
||||
:method="method"
|
||||
v-for="(item, index) in tableData"
|
||||
:data="item"
|
||||
:key="'key' + index"
|
||||
:type="type"
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import TableItem from "./TableItem.vue";
|
||||
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
|
||||
import { Option } from "@/types/app";
|
||||
|
||||
/* global defineProps, Nullable */
|
||||
const props = defineProps({
|
||||
tableData: { type: Array as PropType<any>, default: () => [] },
|
||||
type: { type: String, default: "" },
|
||||
});
|
||||
const method = ref<number>(300);
|
||||
const componentKey = ref<number>(300);
|
||||
const flag = ref<boolean>(true);
|
||||
const dragger = ref<Nullable<HTMLSpanElement>>(null);
|
||||
let headerData: (Option & { key?: string })[] = TraceConstant;
|
||||
if (props.type === "profile") {
|
||||
headerData = ProfileConstant;
|
||||
}
|
||||
if (props.type === "statistics") {
|
||||
headerData = StatisticsConstant;
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.type === "statistics") {
|
||||
return;
|
||||
}
|
||||
const drag: any = dragger.value;
|
||||
drag.onmousedown = (event: any) => {
|
||||
const diffX = event.clientX;
|
||||
const copy = method.value;
|
||||
document.onmousemove = (documentEvent) => {
|
||||
const moveX = documentEvent.clientX - diffX;
|
||||
method.value = copy + moveX;
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
function sortStatistics(key: string) {
|
||||
const element = props.tableData;
|
||||
for (let i = 0; i < element.length; i++) {
|
||||
for (let j = 0; j < element.length - i - 1; j++) {
|
||||
let val1;
|
||||
let val2;
|
||||
if (key === "maxTime") {
|
||||
val1 = element[j].maxTime;
|
||||
val2 = element[j + 1].maxTime;
|
||||
}
|
||||
if (key === "minTime") {
|
||||
val1 = element[j].minTime;
|
||||
val2 = element[j + 1].minTime;
|
||||
}
|
||||
if (key === "avgTime") {
|
||||
val1 = element[j].avgTime;
|
||||
val2 = element[j + 1].avgTime;
|
||||
}
|
||||
if (key === "sumTime") {
|
||||
val1 = element[j].sumTime;
|
||||
val2 = element[j + 1].sumTime;
|
||||
}
|
||||
if (key === "count") {
|
||||
val1 = element[j].count;
|
||||
val2 = element[j + 1].count;
|
||||
}
|
||||
if (flag.value) {
|
||||
if (val1 < val2) {
|
||||
const tmp = element[j];
|
||||
element[j] = element[j + 1];
|
||||
element[j + 1] = tmp;
|
||||
}
|
||||
} else {
|
||||
if (val1 > val2) {
|
||||
const tmp = element[j];
|
||||
element[j] = element[j + 1];
|
||||
element[j + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tableData = element;
|
||||
this.componentKey += 1;
|
||||
this.flag = !this.flag;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./table.scss";
|
||||
|
||||
.trace {
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.trace-header {
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.trace-header div {
|
||||
display: inline-block;
|
||||
background-color: #f3f4f9;
|
||||
padding: 0 4px;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
289
src/views/dashboard/related/trace/components/table/TableItem.vue
Normal file
289
src/views/dashboard/related/trace/components/table/TableItem.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<!-- 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 v-if="type === 'statistics'">
|
||||
<div :class="['trace-item']" ref="traceItem">
|
||||
<div :class="['method']">
|
||||
<el-tooltip :content="data.groupRef.endpointName" placement="bottom">
|
||||
<span>
|
||||
{{ data.groupRef.endpointName }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div :class="['type']">
|
||||
<el-tooltip :content="data.groupRef.type" placement="bottom">
|
||||
<span>
|
||||
{{ data.groupRef.type }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="max-time">
|
||||
{{ data.maxTime }}
|
||||
</div>
|
||||
<div class="min-time">
|
||||
{{ data.minTime }}
|
||||
</div>
|
||||
<div class="sum-time">
|
||||
{{ data.sumTime }}
|
||||
</div>
|
||||
<div class="avg-time">
|
||||
{{ parseInt(data.avgTime) }}
|
||||
</div>
|
||||
<div class="count">
|
||||
{{ data.count }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
@click="showSelectSpan"
|
||||
:class="[
|
||||
'trace-item',
|
||||
'level' + (data.level - 1),
|
||||
{ 'trace-item-error': data.isError },
|
||||
]"
|
||||
ref="traceItem"
|
||||
>
|
||||
<div
|
||||
:class="['method', 'level' + (data.level - 1)]"
|
||||
:style="{
|
||||
'text-indent': (data.level - 1) * 10 + 'px',
|
||||
width: `${method}px`,
|
||||
}"
|
||||
>
|
||||
<svg
|
||||
class="icon vm cp trans"
|
||||
:style="!displayChildren ? 'transform: rotate(-90deg);' : ''"
|
||||
@click.stop="toggle"
|
||||
v-if="data.children && data.children.length"
|
||||
>
|
||||
<use xlink:href="#arrow-down"></use>
|
||||
</svg>
|
||||
<el-tooltip :content="data.endpointName" placement="bottom">
|
||||
<span>
|
||||
{{ data.endpointName }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="start-time">
|
||||
{{ dateFormat(data.startTime) }}
|
||||
</div>
|
||||
<div class="exec-ms">
|
||||
{{
|
||||
data.endTime - data.startTime ? data.endTime - data.startTime : "0"
|
||||
}}
|
||||
</div>
|
||||
<div class="exec-percent">
|
||||
<div class="outer-progress_bar" :style="{ width: outterPercent }">
|
||||
<div
|
||||
class="inner-progress_bar"
|
||||
:style="{ width: innerPercent }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="self">
|
||||
{{ data.dur ? data.dur + "" : "0" }}
|
||||
</div>
|
||||
<div class="api">
|
||||
<el-tooltip :content="data.component || '-'" placement="bottom">
|
||||
<span>{{ data.component || "-" }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="application">
|
||||
<el-tooltip :content="data.serviceCode || '-'" placement="bottom">
|
||||
<span>{{ data.serviceCode }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="application" v-show="type === 'profile'">
|
||||
<span @click="viewSpanDetail">{{ $t("view") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="data.children && data.children.length > 0 && displayChildren"
|
||||
class="children-trace"
|
||||
>
|
||||
<table-item
|
||||
:method="method"
|
||||
v-for="(child, index) in data.children"
|
||||
:key="index"
|
||||
:data="child"
|
||||
:type="type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { ref, watch, computed, defineComponent } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
|
||||
const props = {
|
||||
data: { type: Object as PropType<any>, default: () => ({}) },
|
||||
method: { type: Number, default: 0 },
|
||||
type: { type: String, default: "" },
|
||||
};
|
||||
export default defineComponent({
|
||||
name: "TableItem",
|
||||
props,
|
||||
emits: ["select"],
|
||||
setup(props, { emit }) {
|
||||
/* global Nullable */
|
||||
const displayChildren = ref<boolean>(true);
|
||||
const traceItem = ref<Nullable<HTMLDivElement>>(null);
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
const selfTime = computed(() => (props.data.dur ? props.data.dur : 0));
|
||||
const execTime = computed(() =>
|
||||
props.data.endTime - props.data.startTime
|
||||
? props.data.endTime - props.data.startTime
|
||||
: 0
|
||||
);
|
||||
const outterPercent = computed(() => {
|
||||
if (props.data.level === 1) {
|
||||
return "100%";
|
||||
} else {
|
||||
const data = props.data;
|
||||
const exec =
|
||||
data.endTime - data.startTime ? data.endTime - data.startTime : 0;
|
||||
let result = (exec / data.totalExec) * 100;
|
||||
result = result > 100 ? 100 : result;
|
||||
const resultStr = result.toFixed(4) + "%";
|
||||
return resultStr === "0.0000%" ? "0.9%" : resultStr;
|
||||
}
|
||||
});
|
||||
const innerPercent = computed(() => {
|
||||
const result = (selfTime.value / execTime.value) * 100;
|
||||
const resultStr = result.toFixed(4) + "%";
|
||||
return resultStr === "0.0000%" ? "0.9%" : resultStr;
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
displayChildren.value = !this.displayChildren.value;
|
||||
}
|
||||
function showSelectSpan() {
|
||||
const items: any = document.querySelectorAll(".trace-item");
|
||||
for (const item of items) {
|
||||
item.style.background = "#fff";
|
||||
}
|
||||
if (!traceItem.value) {
|
||||
return;
|
||||
}
|
||||
traceItem.value.style.background = "rgba(0, 0, 0, 0.1)";
|
||||
}
|
||||
function viewSpanDetail() {
|
||||
showSelectSpan();
|
||||
emit("select", props.data);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
showSelectSpan();
|
||||
}
|
||||
);
|
||||
return {
|
||||
displayChildren,
|
||||
outterPercent,
|
||||
innerPercent,
|
||||
viewSpanDetail,
|
||||
toggle,
|
||||
dateFormat,
|
||||
showSelectSpan,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./table.scss";
|
||||
|
||||
.trace-item.level0 {
|
||||
color: #448dfe;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #448dfe;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
background: #448dfe;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.trace-item-error {
|
||||
color: #e54c17;
|
||||
}
|
||||
|
||||
.trace-item {
|
||||
// display: flex;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.trace-item.selected {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.trace-item:not(.level0):hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.trace-item > div {
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.trace-item > div.method {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.trace-item div.exec-percent {
|
||||
width: 100px;
|
||||
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>
|
120
src/views/dashboard/related/trace/components/table/data.ts
Normal file
120
src/views/dashboard/related/trace/components/table/data.ts
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const ProfileConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Span",
|
||||
},
|
||||
{
|
||||
label: "start-time",
|
||||
value: "Start Time",
|
||||
},
|
||||
{
|
||||
label: "exec-ms",
|
||||
value: "Exec(ms)",
|
||||
},
|
||||
{
|
||||
label: "exec-percent",
|
||||
value: "Exec(%)",
|
||||
},
|
||||
{
|
||||
label: "self",
|
||||
value: "Self(ms)",
|
||||
},
|
||||
{
|
||||
label: "api",
|
||||
value: "API",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Service",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Operation",
|
||||
},
|
||||
];
|
||||
|
||||
export const TraceConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Method",
|
||||
},
|
||||
{
|
||||
label: "start-time",
|
||||
value: "Start Time",
|
||||
},
|
||||
{
|
||||
label: "exec-ms",
|
||||
value: "Exec(ms)",
|
||||
},
|
||||
{
|
||||
label: "exec-percent",
|
||||
value: "Exec(%)",
|
||||
},
|
||||
{
|
||||
label: "self",
|
||||
value: "Self(ms)",
|
||||
},
|
||||
{
|
||||
label: "api",
|
||||
value: "API",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Service",
|
||||
},
|
||||
];
|
||||
|
||||
export const StatisticsConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Endpoint Name",
|
||||
key: "endpointName",
|
||||
},
|
||||
{
|
||||
label: "type",
|
||||
value: "Type",
|
||||
key: "type",
|
||||
},
|
||||
{
|
||||
label: "max-time",
|
||||
value: "Max Time(ms)",
|
||||
key: "maxTime",
|
||||
},
|
||||
{
|
||||
label: "min-time",
|
||||
value: "Min Time(ms)",
|
||||
key: "minTime",
|
||||
},
|
||||
{
|
||||
label: "sum-time",
|
||||
value: "Sum Time(ms)",
|
||||
key: "sumTime",
|
||||
},
|
||||
{
|
||||
label: "avg-time",
|
||||
value: "Avg Time(ms)",
|
||||
key: "avgTime",
|
||||
},
|
||||
{
|
||||
label: "count",
|
||||
value: "Hits",
|
||||
key: "count",
|
||||
},
|
||||
];
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
.argument {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.start-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.exec-ms {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.exec-percent {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.self {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.api {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.agent {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.application {
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.max-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.method {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.avg-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.min-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.sum-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.type {
|
||||
width: 60px;
|
||||
}
|
@ -239,7 +239,6 @@ export default class ListGraph {
|
||||
)
|
||||
.on("click", (d: any) => {
|
||||
this.click(d, this);
|
||||
// (d3 as any).event.stopPropagation();
|
||||
});
|
||||
node
|
||||
.transition()
|
||||
|
@ -18,7 +18,6 @@
|
||||
import * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import { Trace, Span } from "@/types/trace";
|
||||
import { style } from "d3";
|
||||
|
||||
export default class TraceMap {
|
||||
private i = 0;
|
||||
@ -53,10 +52,11 @@ export default class TraceMap {
|
||||
this.topSlow = [];
|
||||
this.topChild = [];
|
||||
this.width = el.clientWidth - 20;
|
||||
this.height = el.clientHeight;
|
||||
this.height = el.clientHeight - 30;
|
||||
this.body = d3
|
||||
.select(this.el)
|
||||
.append("svg")
|
||||
.attr("class", "d3-trace-tree")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height);
|
||||
this.tip = (d3tip as any)()
|
||||
@ -186,7 +186,6 @@ export default class TraceMap {
|
||||
}
|
||||
})
|
||||
.on("click", function (d: any) {
|
||||
(d3 as any).event.stopPropagation();
|
||||
that.handleSelectSpan(d);
|
||||
});
|
||||
|
||||
@ -289,7 +288,6 @@ export default class TraceMap {
|
||||
)
|
||||
.attr("cursor", "pointer")
|
||||
.on("click", (d: any) => {
|
||||
(d3 as any).event.stopPropagation();
|
||||
click(d);
|
||||
});
|
||||
const nodeExit = node
|
||||
@ -320,9 +318,9 @@ export default class TraceMap {
|
||||
const o = { x: source.x0, y: source.y0 };
|
||||
return diagonal(o, o);
|
||||
})
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.1)")
|
||||
.style("stroke-width", 1.5)
|
||||
.style("fill", "none")
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.1)");
|
||||
.style("fill", "none");
|
||||
|
||||
const linkUpdate = linkEnter.merge(link);
|
||||
linkUpdate
|
||||
|
338
src/views/dashboard/related/trace/utils/trace-table.ts
Normal file
338
src/views/dashboard/related/trace/utils/trace-table.ts
Normal file
@ -0,0 +1,338 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
Ref,
|
||||
Span,
|
||||
StatisticsSpan,
|
||||
StatisticsGroupRef,
|
||||
TraceTreeRef,
|
||||
} from "@/types/trace";
|
||||
import lodash from "lodash";
|
||||
|
||||
export default class TraceUtil {
|
||||
public static buildTraceDataList(data: Span[]): string[] {
|
||||
return Array.from(new Set(data.map((span: Span) => span.serviceCode)));
|
||||
}
|
||||
|
||||
public static changeTree(data: Span[], cureentTraceId: string) {
|
||||
const segmentIdList: Span[] = [];
|
||||
const traceTreeRef: any = this.changeTreeCore(data, cureentTraceId);
|
||||
traceTreeRef.segmentIdGroup.forEach((segmentId: string) => {
|
||||
if (traceTreeRef.segmentMap.get(segmentId).refs) {
|
||||
traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => {
|
||||
if (ref.traceId === cureentTraceId) {
|
||||
this.traverseTree(
|
||||
traceTreeRef.segmentMap.get(ref.parentSegmentId) as Span,
|
||||
ref.parentSpanId,
|
||||
ref.parentSegmentId,
|
||||
traceTreeRef.segmentMap.get(segmentId) as Span
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// set a breakpoint at this line
|
||||
traceTreeRef.segmentMap.forEach((value: Span) => {
|
||||
if ((value.refs && value.refs.length === 0) || !value.refs) {
|
||||
segmentIdList.push(value as Span);
|
||||
}
|
||||
});
|
||||
segmentIdList.forEach((segmentId: Span) => {
|
||||
this.collapse(segmentId);
|
||||
});
|
||||
return segmentIdList;
|
||||
}
|
||||
|
||||
public static changeStatisticsTree(
|
||||
data: Span[],
|
||||
cureentTraceId: string
|
||||
): Map<string, Span[]> {
|
||||
const result = new Map<string, Span[]>();
|
||||
const traceTreeRef = this.changeTreeCore(data, cureentTraceId);
|
||||
traceTreeRef.segmentMap.forEach((span, segmentId) => {
|
||||
const groupRef = span.endpointName + ":" + span.type;
|
||||
if (span.children && span.children.length > 0) {
|
||||
this.calculationChildren(span.children, result);
|
||||
this.collapse(span);
|
||||
}
|
||||
if (result.get(groupRef) === undefined) {
|
||||
result.set(groupRef, []);
|
||||
result.get(groupRef)!.push(span);
|
||||
} else {
|
||||
result.get(groupRef)!.push(span);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static changeTreeCore(
|
||||
data: Span[],
|
||||
cureentTraceId: string
|
||||
): TraceTreeRef {
|
||||
// set a breakpoint at this line
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
segmentMap: new Map(),
|
||||
segmentIdGroup: [],
|
||||
};
|
||||
}
|
||||
const segmentGroup: any = {};
|
||||
const segmentMap: Map<string, Span> = new Map();
|
||||
const segmentIdGroup: string[] = [];
|
||||
const fixSpans: Span[] = [];
|
||||
const segmentHeaders: Span[] = [];
|
||||
data.forEach((span) => {
|
||||
if (span.parentSpanId === -1) {
|
||||
segmentHeaders.push(span);
|
||||
} else {
|
||||
const index = data.findIndex((patchSpan: Span) => {
|
||||
return (
|
||||
patchSpan.segmentId === span.segmentId &&
|
||||
patchSpan.spanId === span.spanId - 1
|
||||
);
|
||||
});
|
||||
const fixSpanKeyContent = {
|
||||
traceId: span.traceId,
|
||||
segmentId: span.segmentId,
|
||||
spanId: span.spanId - 1,
|
||||
parentSpanId: span.spanId - 2,
|
||||
};
|
||||
if (index === -1 && !lodash.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${span.segmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${span.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${span.spanId - 1}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
segmentHeaders.forEach((span) => {
|
||||
if (span.refs && span.refs.length) {
|
||||
span.refs.forEach((ref) => {
|
||||
const index = data.findIndex((patchSpan: Span) => {
|
||||
return (
|
||||
ref.parentSegmentId === patchSpan.segmentId &&
|
||||
ref.parentSpanId === patchSpan.spanId
|
||||
);
|
||||
});
|
||||
if (index === -1) {
|
||||
// create a known broken node.
|
||||
const parentSpanId: number = ref.parentSpanId;
|
||||
const fixSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: parentSpanId,
|
||||
parentSpanId: parentSpanId > -1 ? 0 : -1,
|
||||
};
|
||||
if (lodash.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${parentSpanId}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
// if root broken node is not exist, create a root broken node.
|
||||
if (fixSpanKeyContent.parentSpanId > -1) {
|
||||
const fixRootSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: 0,
|
||||
parentSpanId: -1,
|
||||
};
|
||||
if (!lodash.find(fixSpans, fixRootSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixRootSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #0`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
[...fixSpans, ...data].forEach((fixSpan: Span) => {
|
||||
fixSpan.label = fixSpan.endpointName || "no operation name";
|
||||
fixSpan.children = [];
|
||||
const id = fixSpan.segmentId || "top";
|
||||
if (segmentGroup[id] === undefined) {
|
||||
segmentIdGroup.push(id);
|
||||
segmentGroup[id] = [];
|
||||
segmentGroup[id].push(fixSpan);
|
||||
} else {
|
||||
segmentGroup[id].push(fixSpan);
|
||||
}
|
||||
});
|
||||
|
||||
segmentIdGroup.forEach((segmentId: string) => {
|
||||
const currentSegmentSet = segmentGroup[segmentId].sort(
|
||||
(a: Span, b: Span) => b.parentSpanId - a.parentSpanId
|
||||
);
|
||||
currentSegmentSet.forEach((curSegment: Span) => {
|
||||
const index = currentSegmentSet.findIndex(
|
||||
(curSegment2: Span) => curSegment2.spanId === curSegment.parentSpanId
|
||||
);
|
||||
if (index !== -1) {
|
||||
if (
|
||||
(currentSegmentSet[index].isBroken &&
|
||||
currentSegmentSet[index].parentSpanId === -1) ||
|
||||
!currentSegmentSet[index].isBroken
|
||||
) {
|
||||
currentSegmentSet[index].children.push(curSegment);
|
||||
currentSegmentSet[index].children.sort(
|
||||
(a: Span, b: Span) => a.spanId - b.spanId
|
||||
);
|
||||
}
|
||||
}
|
||||
if (curSegment.isBroken) {
|
||||
const children = lodash.filter(data, (span: Span) => {
|
||||
return lodash.find(span.refs, {
|
||||
traceId: curSegment.traceId,
|
||||
parentSegmentId: curSegment.segmentId,
|
||||
parentSpanId: curSegment.spanId,
|
||||
});
|
||||
}) as Span[];
|
||||
if (children.length) {
|
||||
curSegment.children = curSegment.children || [];
|
||||
curSegment.children.push(...children);
|
||||
}
|
||||
}
|
||||
});
|
||||
segmentMap.set(
|
||||
segmentId,
|
||||
currentSegmentSet[currentSegmentSet.length - 1]
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
segmentMap,
|
||||
segmentIdGroup,
|
||||
};
|
||||
}
|
||||
|
||||
private static collapse(span: Span) {
|
||||
if (span.children) {
|
||||
let dur = span.endTime - span.startTime;
|
||||
span.children.forEach((chlid: Span) => {
|
||||
dur -= chlid.endTime - chlid.startTime;
|
||||
});
|
||||
span.dur = dur < 0 ? 0 : dur;
|
||||
span.children.forEach((chlid) => this.collapse(chlid));
|
||||
}
|
||||
}
|
||||
|
||||
private static traverseTree(
|
||||
node: Span,
|
||||
spanId: number,
|
||||
segmentId: string,
|
||||
childNode: Span
|
||||
) {
|
||||
if (!node || node.isBroken) {
|
||||
return;
|
||||
}
|
||||
if (node.spanId === spanId && node.segmentId === segmentId) {
|
||||
node.children!.push(childNode);
|
||||
return;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
for (const grandchild of node.children) {
|
||||
this.traverseTree(grandchild, spanId, segmentId, childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static getSpanGroupData(
|
||||
groupspans: Span[],
|
||||
groupRef: StatisticsGroupRef
|
||||
): StatisticsSpan {
|
||||
let maxTime = 0;
|
||||
let minTime = 0;
|
||||
let sumTime = 0;
|
||||
const count = groupspans.length;
|
||||
groupspans.forEach((groupspan: Span) => {
|
||||
const duration = groupspan.dur || 0;
|
||||
if (duration > maxTime) {
|
||||
maxTime = duration;
|
||||
}
|
||||
if (duration < minTime) {
|
||||
minTime = duration;
|
||||
}
|
||||
sumTime = sumTime + duration;
|
||||
});
|
||||
const avgTime = count === 0 ? 0 : sumTime / count;
|
||||
return {
|
||||
groupRef,
|
||||
maxTime,
|
||||
minTime,
|
||||
sumTime,
|
||||
avgTime,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
private static calculationChildren(
|
||||
nodes: Span[],
|
||||
result: Map<string, Span[]>
|
||||
): void {
|
||||
nodes.forEach((node: Span) => {
|
||||
const groupRef = node.endpointName + ":" + node.type;
|
||||
if (node.children && node.children.length > 0) {
|
||||
this.calculationChildren(node.children, result);
|
||||
}
|
||||
if (result.get(groupRef) === undefined) {
|
||||
result.set(groupRef, []);
|
||||
result.get(groupRef)!.push(node);
|
||||
} else {
|
||||
result.get(groupRef)!.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user