mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 12:05:24 +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"
|
: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"
|
||||||
>
|
>
|
||||||
<component :is="item.type" :data="item" />
|
<component :is="item.type" :data="item" />
|
||||||
</grid-item>
|
</grid-item>
|
||||||
|
@ -100,6 +100,8 @@ limitations under the License. -->
|
|||||||
:is="displayMode"
|
:is="displayMode"
|
||||||
:data="traceStore.traceSpans"
|
:data="traceStore.traceSpans"
|
||||||
:traceId="traceStore.currentTrace.traceIds[0].value"
|
:traceId="traceStore.currentTrace.traceIds[0].value"
|
||||||
|
:showBtnDetail="false"
|
||||||
|
HeaderType="trace"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,7 +25,7 @@ import ListGraph from "../utils/d3-trace-list";
|
|||||||
import TreeGraph from "../utils/d3-trace-tree";
|
import TreeGraph from "../utils/d3-trace-tree";
|
||||||
import { Span } from "@/types/trace";
|
import { Span } from "@/types/trace";
|
||||||
|
|
||||||
/* global defineProps, Nullable*/
|
/* global defineProps, Nullable, defineExpose*/
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: { type: Array as PropType<Span[]>, default: () => [] },
|
data: { type: Array as PropType<Span[]>, default: () => [] },
|
||||||
traceId: { type: String, default: "" },
|
traceId: { type: String, default: "" },
|
||||||
@ -38,7 +38,9 @@ const segmentId = ref<any>([]);
|
|||||||
const currentSpan = ref<Array<Span>>([]);
|
const currentSpan = ref<Array<Span>>([]);
|
||||||
const tree = ref<any>(null);
|
const tree = ref<any>(null);
|
||||||
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
defineExpose({
|
||||||
|
tree,
|
||||||
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
changeTree();
|
changeTree();
|
||||||
|
@ -24,18 +24,18 @@ limitations under the License. -->
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding: 10px 0">
|
<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") }}
|
{{ t("default") }}
|
||||||
</a>
|
</a>
|
||||||
<a class="trace-tree-btn mr-10" @click="tree.getTopSlow()">
|
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopSlow()">
|
||||||
{{ t("topSlow") }}
|
{{ t("topSlow") }}
|
||||||
</a>
|
</a>
|
||||||
<a class="trace-tree-btn mr-10" @click="tree.getTopChild()">
|
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopChild()">
|
||||||
{{ t("topChildren") }}
|
{{ t("topChildren") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="trace-tree">
|
<div class="trace-tree">
|
||||||
<Graph :data="data" :traceId="traceId" type="Tree" />
|
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -54,7 +54,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const list = ref<string[]>([]);
|
const list = ref<string[]>([]);
|
||||||
const tree = ref<any>(null);
|
const charts = ref<any>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
list.value = Array.from(new Set(props.data.map((i: Span) => i.serviceCode)));
|
list.value = Array.from(new Set(props.data.map((i: Span) => i.serviceCode)));
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
import List from "./List.vue";
|
import List from "./List.vue";
|
||||||
import Tree from "./Tree.vue";
|
import Tree from "./Tree.vue";
|
||||||
|
import Table from "./table/Index.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
List,
|
List,
|
||||||
Tree,
|
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) => {
|
.on("click", (d: any) => {
|
||||||
this.click(d, this);
|
this.click(d, this);
|
||||||
// (d3 as any).event.stopPropagation();
|
|
||||||
});
|
});
|
||||||
node
|
node
|
||||||
.transition()
|
.transition()
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import d3tip from "d3-tip";
|
import d3tip from "d3-tip";
|
||||||
import { Trace, Span } from "@/types/trace";
|
import { Trace, Span } from "@/types/trace";
|
||||||
import { style } from "d3";
|
|
||||||
|
|
||||||
export default class TraceMap {
|
export default class TraceMap {
|
||||||
private i = 0;
|
private i = 0;
|
||||||
@ -53,10 +52,11 @@ export default class TraceMap {
|
|||||||
this.topSlow = [];
|
this.topSlow = [];
|
||||||
this.topChild = [];
|
this.topChild = [];
|
||||||
this.width = el.clientWidth - 20;
|
this.width = el.clientWidth - 20;
|
||||||
this.height = el.clientHeight;
|
this.height = el.clientHeight - 30;
|
||||||
this.body = d3
|
this.body = d3
|
||||||
.select(this.el)
|
.select(this.el)
|
||||||
.append("svg")
|
.append("svg")
|
||||||
|
.attr("class", "d3-trace-tree")
|
||||||
.attr("width", this.width)
|
.attr("width", this.width)
|
||||||
.attr("height", this.height);
|
.attr("height", this.height);
|
||||||
this.tip = (d3tip as any)()
|
this.tip = (d3tip as any)()
|
||||||
@ -186,7 +186,6 @@ export default class TraceMap {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("click", function (d: any) {
|
.on("click", function (d: any) {
|
||||||
(d3 as any).event.stopPropagation();
|
|
||||||
that.handleSelectSpan(d);
|
that.handleSelectSpan(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -289,7 +288,6 @@ export default class TraceMap {
|
|||||||
)
|
)
|
||||||
.attr("cursor", "pointer")
|
.attr("cursor", "pointer")
|
||||||
.on("click", (d: any) => {
|
.on("click", (d: any) => {
|
||||||
(d3 as any).event.stopPropagation();
|
|
||||||
click(d);
|
click(d);
|
||||||
});
|
});
|
||||||
const nodeExit = node
|
const nodeExit = node
|
||||||
@ -320,9 +318,9 @@ export default class TraceMap {
|
|||||||
const o = { x: source.x0, y: source.y0 };
|
const o = { x: source.x0, y: source.y0 };
|
||||||
return diagonal(o, o);
|
return diagonal(o, o);
|
||||||
})
|
})
|
||||||
|
.attr("stroke", "rgba(0, 0, 0, 0.1)")
|
||||||
.style("stroke-width", 1.5)
|
.style("stroke-width", 1.5)
|
||||||
.style("fill", "none")
|
.style("fill", "none");
|
||||||
.attr("stroke", "rgba(0, 0, 0, 0.1)");
|
|
||||||
|
|
||||||
const linkUpdate = linkEnter.merge(link);
|
const linkUpdate = linkEnter.merge(link);
|
||||||
linkUpdate
|
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