blob: a64a997ee9a8dbc023569a3312ca9b3d77efb616 [file] [log] [blame]
/**
* 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-tree-charts scroll_hide flex-v">
<transition-group name="fade" tag="div" style="padding: 10px 30px;">
<span class="time-charts-item mr-10" v-for="(i,index) in list" :key="i" :style="`color:${computedScale(index)}`">
<svg class="icon vm mr-5 sm">
<use xlink:href="#issue-open-m"></use>
</svg>
<span>{{i}}</span>
</span>
</transition-group>
<div style="padding: 0 30px;">
<a class="trace-tree-btn mr-10" @click="tree.setDefault()">Default</a>
<a class="trace-tree-btn mr-10" @click="tree.getTopSlow()">Top 5 of slow</a>
<a class="trace-tree-btn mr-10" @click="tree.getTopChild()">Top 5 of children</a>
</div>
<rk-sidebox :width="'50%'" :show.sync="showDetail" :title="$t('spanInfo')">
<div class="rk-trace-detail">
<h5 class="mb-15">{{$t('tags')}}.</h5>
<div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('endpoint')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.label}}</span></div>
<div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('spanType')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.type}}</span></div>
<div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('component')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.component}}</span></div>
<div class="mb-10 clear"><span class="g-sm-4 grey">Peer:</span><span class="g-sm-8 wba">{{this.currentSpan.peer||'No Peer'}}</span></div>
<div class="mb-10 clear"><span class="g-sm-4 grey">{{$t('error')}}:</span><span class="g-sm-8 wba">{{this.currentSpan.isError}}</span></div>
<div class="mb-10 clear" v-for="i in this.currentSpan.tags" :key="i.key">
<span class="g-sm-4 grey">{{i.key}}:</span>
<span class="g-sm-8 wba">
{{i.value}}
<svg v-if="i.key==='db.statement'" class="icon vm grey link-hover cp ml-5" @click="copy(i.value)">
<use xlink:href="#review-list"></use>
</svg>
</span>
</div>
<h5 class="mb-10" v-if="this.currentSpan.logs" v-show="this.currentSpan.logs.length">{{$t('logs')}}.</h5>
<div v-for="(i, index) in this.currentSpan.logs" :key="index">
<div class="mb-10 sm"><span class="mr-10">{{$t('time')}}:</span><span class="grey">{{i.time | dateformat}}</span></div>
<div class="mb-15 clear" v-for="(_i, _index) in i.data" :key="_index">
<div class="mb-10">{{_i.key}}:<span v-if="_i.key==='stack'" class="r rk-sidebox-magnify"
@click="showCurrentSpanDetail(_i.key, _i.value)">
<svg class="icon">
<use xlink:href="#magnify"></use>
</svg>
</span></div><pre class="pl-15 mt-0 mb-0 sm oa">{{_i.value}}</pre>
</div>
</div>
</div>
</rk-sidebox>
<v-dialog width="90%"/>
<div class="trace-tree" style="height:100%">
<div class="trace-tree-inner" ref="traceTree"></div>
</div>
</div>
</template>
<script lang="js">
import copy from '@/utils/copy';
import * as d3 from 'd3';
import Tree from './d3-trace-tree';
import _ from 'lodash';
/* eslint-disable */
/* tslint:disable */
export default {
props: ['data', 'traceId'],
data(){
return {
segmentId:[],
showDetail: false,
list: [],
currentSpan: [],
};
},
watch: {
data() {
if(!this.data.length) {return;}
d3.select('.trace-tree-inner').selectAll('svg').selectAll('svg').remove();
this.changeTree();
this.tree.init({label:`${this.traceId}`, children: this.segmentId}, this.data);
}
},
mounted() {
window.addEventListener('resize', this.resize);
this.changeTree();
this.tree = new Tree(this.$refs.traceTree, this);
this.tree.init({label:`${this.traceId}`, children: this.segmentId}, this.data);
},
beforeDestroy() {
window.removeEventListener('resize', this.resize);
},
methods: {
copy,
handleSelectSpan(i) {
this.currentSpan = i.data;
this.showDetail = true;
},
traverseTree(node, spanId, segmentId, data){
if (!node) return;
if(node.spanId == spanId && node.segmentId == segmentId) {node.children.push(data);return;}
if (node.children && node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
this.traverseTree(node.children[i],spanId,segmentId,data);
}
}
},
computedScale(i) {
// Rainbow map
const sequentialScale = d3.scaleSequential()
.domain([0, this.list.length + 1])
.interpolator(d3.interpolateCool);
return sequentialScale(i);
},
changeTree(){
if (this.data.length === 0) return [];
this.list = Array.from(new Set(this.data.map(i => i.serviceCode)));
this.segmentId = [];
const segmentGroup = {}
const segmentIdGroup = []
const fixSpans = [];
const segmentHeaders = [];
this.data.forEach((span) => {
if (span.parentSpanId === -1) {
segmentHeaders.push(span);
} else {
const index = this.data.findIndex(i => (i.segmentId === span.segmentId && i.spanId === (span.spanId - 1)));
const fixSpanKeyContent = {
traceId: span.traceId,
segmentId: span.segmentId,
spanId: span.spanId - 1,
parentSpanId: span.spanId - 2,
};
if (index === -1 && !_.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: [],
},
);
}
}
});
segmentHeaders.forEach((span) => {
if (span.refs.length) {
span.refs.forEach((ref) => {
const index = this.data.findIndex(i => (ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId));
if (index === -1) {
// create a known broken node.
const i = ref.parentSpanId;
const fixSpanKeyContent = {
traceId: ref.traceId,
segmentId: ref.parentSegmentId,
spanId: i,
parentSpanId: i > -1 ? 0 : -1,
};
!_.find(fixSpans, fixSpanKeyContent) && fixSpans.push(
{
...fixSpanKeyContent, refs: [], endpointName: `VNode: ${ref.parentSegmentId}`, serviceCode: 'VirtualNode', type: `[Broken] ${ref.type}`, peer: '', component: `VirtualNode: #${i}`, isError: true, isBroken: true, layer: 'Broken', tags: [], logs: [],
},
);
// 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,
};
!_.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: [],
},
);
}
}
});
}
});
[...fixSpans, ...this.data].forEach(i => {
i.label=i.endpointName || 'no operation name';
i.children = [];
if(segmentGroup[i.segmentId] === undefined){
segmentIdGroup.push(i.segmentId);
segmentGroup[i.segmentId] = [];
segmentGroup[i.segmentId].push(i);
}else{
segmentGroup[i.segmentId].push(i);
}
});
segmentIdGroup.forEach(id => {
let currentSegment = segmentGroup[id].sort((a,b) => b.parentSpanId-a.parentSpanId);
currentSegment.forEach(s =>{
let index = currentSegment.findIndex(i => i.spanId === s.parentSpanId);
if (index !== -1) {
if ((currentSegment[index].isBroken && currentSegment[index].parentSpanId === -1) || !currentSegment[index].isBroken) {
currentSegment[index].children.push(s);
currentSegment[index].children.sort((a, b) => a.spanId - b.spanId);
}
}
if (s.isBroken) {
const children = _.filter(this.data, (span) => {
return _.find(span.refs, {traceId: s.traceId, parentSegmentId: s.segmentId, parentSpanId: s.spanId});
});
children.length > 0 && s.children.push(...children);
}
})
segmentGroup[id] = currentSegment[currentSegment.length-1]
})
segmentIdGroup.forEach(id => {
segmentGroup[id].refs.forEach(ref => {
if(ref.traceId === this.traceId) {
this.traverseTree(segmentGroup[ref.parentSegmentId],ref.parentSpanId,ref.parentSegmentId,segmentGroup[id])
};
})
// if(segmentGroup[id].refs.length !==0 ) delete segmentGroup[id];
})
for (let i in segmentGroup) {
if(segmentGroup[i].refs.length ===0 )
this.segmentId.push(segmentGroup[i]);
}
this.segmentId.forEach((_, i) => {
this.collapse(this.segmentId[i]);
})
},
collapse(d) {
if(d.children){
let dur = d.endTime - d.startTime;
d.children.forEach(i => {
dur -= (i.endTime - i.startTime);
})
d.dur = dur < 0 ? 0 : dur;
d.children.forEach((i) => this.collapse(i));
}
},
resize() {
this.tree.resize();
},
showCurrentSpanDetail(title, text) {
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',
},
],
})
},
}
};
</script>
<style lang="scss">
.trace-tree-btn{
display: inline-block;
border-radius: 4px;
padding: 0px 7px;
background-color: #40454e;
color: #eee;
font-size: 11px;
}
.trace-tree-charts{
overflow: auto;
flex-grow: 1;
height: 100%;
}
.trace-node .group {
cursor: pointer;
fill-opacity: 0;
}
.trace-tree-inner{
height: 100%;
}
.trace-node-container{
fill: rgba(0, 0, 0, 0);
stroke-width: 5px;
cursor: pointer;
&:hover{
fill: rgba(0,0,0,0.05)
}
}
.trace-node .node-text {
font: 12.5px sans-serif;
pointer-events: none;
}
.domain{display: none;}
.tree-link {
fill: none;
stroke: rgba(0,0,0,0.1);
stroke-width: 2px;
}
.time-charts-item{
display: inline-block;
padding: 2px 8px;
border: 1px solid;
font-size: 11px;
border-radius: 4px;
}
.trace-tree{
fill: rgba(0,0,0,0);
flex-grow: 1;
}
.trace-tree .trace-node rect{
&:hover{
fill: rgba(0,0,0,0.05)
}
}
.dialog-c-text {
white-space: pre;
overflow: auto;
font-family: monospace;
}
</style>