feat: add trace table

This commit is contained in:
Qiuxia Fan 2022-02-25 17:24:50 +08:00
parent 8c7d708257
commit 77cfae3149
13 changed files with 1129 additions and 14 deletions

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

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="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>

View File

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

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

View 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",
},
];

View File

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

View File

@ -239,7 +239,6 @@ export default class ListGraph {
)
.on("click", (d: any) => {
this.click(d, this);
// (d3 as any).event.stopPropagation();
});
node
.transition()

View File

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

View 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);
}
});
}
}