diff --git a/.webpackrc.js b/.webpackrc.js index aba80efd..c3bf5d8d 100755 --- a/.webpackrc.js +++ b/.webpackrc.js @@ -42,7 +42,7 @@ const config = { if (process.env.NO_MOCK) { config.proxy['/api'] = { - target: 'http://localhost:12800', + target: 'http://106.75.237.45:12800', changeOrigin: true, pathRewrite: path => { console.log(path); diff --git a/src/components/TraceTree/d3-trace.js b/src/components/TraceTree/d3-trace.js index 16455818..9c795e70 100644 --- a/src/components/TraceTree/d3-trace.js +++ b/src/components/TraceTree/d3-trace.js @@ -19,7 +19,7 @@ import * as d3 from 'd3'; import d3tip from 'd3-tip'; export default class TraceMap { - constructor(el) { + constructor(el, showSpanModal,smax,smin,cmax,cmin) { this.type = { MQ: '#bf99f8', Http: '#72a5fd', @@ -28,26 +28,34 @@ export default class TraceMap { Cache: '#00bcd4', RPCFramework: '#ee4395', }; + this.smax = smax; + this.smin = smin; + this.cmax = cmax; + this.cmin = cmin; + this.showSpanModal = showSpanModal; this.el = el; + this.i = 0; + this.j = 0; this.width = el.clientWidth; this.height = el.clientHeight; + this.body = d3 + .select(this.el) + .append('svg') + .attr('width', this.width) + .attr('height', this.height); + this.tip = d3tip() + .attr('class', 'd3-tip') + .offset([10, 0]) + .html(d => d.data.label); + this.timeTip = d3tip() + .attr('class', 'd3-tip') + .offset([-8, 0]) + .html(d => d.data.label); + this.body.call(this.timeTip); + this.body.call(this.tip); this.treemap = d3.tree().size([this.height * 0.7, this.width]); - this.svg = ''; - this.timeGroup = ''; - this.root = ''; - this.i = 0; - this.j = 0; } - resize() { - d3.select(this.el) - .select('svg') - .remove(); - this.width = this.el.clientWidth; - this.height = this.el.clientHeight; - this.draw(this.data, this.row); - } - draw(data, row, showSpanModal) { - this.showSpanModal = showSpanModal; + init(data, row) { this.row = row; this.data = data; this.min = d3.min(this.row.map(i => i.startTime)); @@ -57,173 +65,130 @@ export default class TraceMap { .scaleSequential() .domain([0, this.list.length]) .interpolator(d3.interpolateCool); + this.svg = this.body.append('g').attr('transform', d => `translate(0, ${this.row.length * 10})`).append('g'); + this.timeGroup = this.body.append('g').attr('class','timeGroup').attr('transform', d => 'translate(5,30)'); + this.body.call(this.getZoomBehavior(this.svg)); + this.root = d3.hierarchy(this.data, d => d.children); + this.root.x0 = this.height / 2; + this.root.y0 = 0; + } + resize() { + this.body + .select('.xAxis') + .remove(); + this.body + .select('.timeGroup') + .remove(); + this.width = this.el.clientWidth; + this.body.attr('width', this.width); + this.xScale = d3 + .scaleLinear() + .domain([0, this.max]) + .range([0, this.width - 10]); + this.xAxis = d3.axisTop(this.xScale).tickFormat(d => { + if (d === 0) return 0; + if (d >= 1000) return d / 1000 + 's'; + return d + ' ms'; + }); + this.body + .append('g') + .attr('class', 'xAxis') + .attr('transform', `translate(5,20)`) + .call(this.xAxis); + this.timeGroup = this.body.append('g').attr('class','timeGroup').attr('transform', d => 'translate(5,30)'); + this.updatexAxis(this.root); + } + draw() { this.xScale = d3 .scaleLinear() - .range([0, this.width - 10]) - .domain([0, this.max]); + .domain([0, this.max]) + .range([0, this.width - 10]); + this.xAxis = d3.axisTop(this.xScale).tickFormat(d => { if (d === 0) return 0; if (d >= 1000) return d / 1000 + 's'; return d + ' ms'; }); - - this.body = d3 - .select(this.el) - .append('svg') - .attr('width', this.width) - .attr('height', this.height); - this.timeGroup = this.body - .append('g') - .attr('transform', d => 'translate(5,30)'); - const main = this.body - .append('g') - .attr('transform', d => 'translate(0,' + this.row.length * 9 + ')'); - this.svg = main.append('g'); - this.root = d3.hierarchy(this.data, d => d.children); - this.root.x0 = this.height / 2; - this.root.y0 = 0; this.body - .append('g') - .attr('transform', `translate(5,20)`) - .call(this.xAxis); - + .append('g') + .attr('class', 'xAxis') + .attr('transform', `translate(5,20)`) + .call(this.xAxis); this.updatexAxis(this.root); this.update(this.root); } - updatexAxis(source) { - const treeData = this.treemap(this.root); - const nodes = treeData.descendants(), - links = treeData.descendants().slice(1); - let index = -1; - nodes.forEach(function(d) { - d.y = d.depth * 200; - d.timeX = ++index * 12; - }); - // time - const timeNode = this.timeGroup.selectAll('g.time').data(nodes, d => { - return d.id; - }); - this.timeTip = d3tip() - .attr('class', 'd3-tip') - .offset([-10, 0]) - .html(function(d) { - return d.data.label; - }); - this.body.call(this.timeTip); - + update(source) { const that = this; - const timeEnter = timeNode + const links = this.nodes.slice(1); + const node = this.svg.selectAll('g.node').data(this.nodes, d => { + return d.id|| (d.id = ++this.i); + }); + // node + const nodeEnter = node .enter() .append('g') - .attr('class', 'time') - .attr('transform', d => 'translate(' + 0 + ',' + d.timeX + ')') + .attr('class', 'node') + .attr('transform', `translate(${source.y0},${source.x0})`) .on('mouseover', function(d, i) { - that.timeTip.show(d, that.timeUpdate._groups[0][i].children[1]); - that.tip.show(d, that.nodeUpdate._groups[0][i]); + that.tip.show(d, this); + const _node = that.timeUpdate._groups[0].filter(group => group.__data__.id === (i+1)); + if(_node.length){ + that.timeTip.show(d, _node[0].children[1]); + } }) .on('mouseout', function(d, i) { - that.timeTip.hide(d, that.timeUpdate._groups[0][i].children[1]); - that.tip.hide(d, that.nodeUpdate._groups[0][i]); + that.tip.hide(d, this); + const _node = that.timeUpdate._groups[0].filter(group => group.__data__.id === (i+1)); + if(_node.length){ + that.timeTip.hide(d, _node[0].children[1]); + } }) .on('click', (d, i) => { this.showSpanModal( d.data, { width: '100%', top: -10, left: '0' }, - d3.select(that.timeUpdate._groups[0][i]).append('rect') + d3.select(nodeEnter._groups[0][i]).append('rect') ); d3.event.stopPropagation(); }); - this.timeEnter = timeEnter; - timeEnter - .append('rect') - .attr('height', 10) - .attr('width', this.width) - .attr('y', -4) - .attr('class', 'time-bg'); - timeEnter + const nodeSelfDur = nodeEnter + .append('g') + .style('opacity', 0) + .attr('class','trace-tree-node-selfdur') + .attr('transform', 'translate(0,-39)') + nodeSelfDur .append('rect') - .attr('class', 'time-inner') - .attr('height', 8) - .attr('width', d => { - if (!d.data.endTime || !d.data.startTime) return 0; - return this.xScale(d.data.endTime - d.data.startTime) + 1; - }) - .attr('rx', 2) - .attr('ry', 2) - .attr('x', d => - !d.data.endTime || !d.data.startTime - ? 0 - : this.xScale(d.data.startTime - this.min) - ) - .attr('y', -3) - .style( - 'fill', - d => - `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}` - ); - this.timeUpdate = timeEnter.merge(timeNode); - this.timeUpdate - .transition() - .duration(600) - .attr('transform', function(d) { - return 'translate(' + 0 + ',' + d.timeX + ')'; - }); - - const timeExit = timeNode - .exit() - .transition() - .duration(600) - .attr('transform', function(d) { - return 'translate(' + 0 + ',' + 10 + ')'; + .attr('width', 65) + .attr('height', 16) + .attr('rx', 3) + .attr('ry', 3) + .attr('fill', '#333') + nodeSelfDur + .append('text') + .attr('dx', 5) + .attr('dy', 11) + .text(d=> { + return d.data.dur + ' ms' }) - .remove(); - } - update(source) { - const treeData = this.treemap(this.root); - const nodes = treeData.descendants(), - links = treeData.descendants().slice(1); - let index = -1; - nodes.forEach(function(d) { - d.y = d.depth * 200; - d.timeX = ++index * 10; - }); - this.tip = d3tip() - .attr('class', 'd3-tip') - .offset([-10, 0]) - .html(function(d) { - return d.data.label; - }); - - this.body.call(this.getZoomBehavior(this.svg)); - this.body.call(this.tip); - const node = this.svg.selectAll('g.node').data(nodes, d => { - return d.id || (d.id = ++this.i); - }); - // node - const that = this; - const nodeEnter = node - .enter() + .attr('fill', '#fff'); + const nodeSelfChild = nodeEnter .append('g') - .attr('class', 'node') - .attr('transform', function(d) { - return 'translate(' + source.y0 + ',' + source.x0 + ')'; - }) - .on('mouseover', function(d, i) { - that.tip.show(d, this); - that.timeTip.show(d, that.timeUpdate._groups[0][i].children[1]); - }) - .on('mouseout', function(d, i) { - that.tip.hide(d, this); - that.timeTip.hide(d, that.timeUpdate._groups[0][i].children[1]); - }) - .on('click', (d, i) => { - this.showSpanModal( - d.data, - { width: '100%', top: -10, left: '0' }, - d3.select(nodeEnter._groups[0][i]).append('rect') - ); - d3.event.stopPropagation(); - }); + .style('opacity', 0) + .attr('class','trace-tree-node-selfchild') + .attr('transform', 'translate(0,-39)') + nodeSelfChild + .append('rect') + .attr('width', 110) + .attr('height', 16) + .attr('rx', 3) + .attr('ry', 3) + .attr('fill', '#333') + nodeSelfChild + .append('text') + .attr('dx', 5) + .attr('dy', 11) + .text(d=> `children: ${d.data.childrenLength}`) + .attr('fill', '#fff') nodeEnter .append('rect') .attr('class', 'block') @@ -231,78 +196,47 @@ export default class TraceMap { .attr('y', '-20') .attr('fill', d => (d.data.isError ? '#ff57221a' : '#f7f7f7')) .attr('stroke', d => (d.data.isError ? '#ff5722aa' : '#e4e4e4')); - nodeEnter .append('rect') .attr('class', 'content') - .style('fill', '#fff') - .attr('stroke', d => { - console.log(d); - return d.data.isError ? '#ff5722aa' : '#e4e4e4'; - }); + .attr('stroke', d => d.data.isError ? '#ff5722aa' : '#e4e4e4'); nodeEnter .append('rect') .attr('class', 'service') .attr('x', '-0.5') .attr('y', '-20.5') - .style( - 'fill', - d => - `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}` - ); + .style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`); nodeEnter .append('text') .attr('dy', 13) .attr('dx', 10) .attr('stroke', '#333') - .attr('text-anchor', function(d) { - return 'start'; - }) - .text(function(d) { - return d.data.label.length > 19 - ? d.data.label.slice(0, 19) - : d.data.label; - }); + .attr('text-anchor', 'start') + .text(d => d.data.label.length > 19 ? d.data.label.slice(0, 19) : d.data.label); nodeEnter .append('text') .attr('dy', -7) .attr('dx', 12) - .attr('text-anchor', function(d) { - return 'start'; - }) - .attr('fill', d => { - return this.type[d.data.layer]; - }) - .attr('stroke', d => { - return this.type[d.data.layer]; - }) - .text(function(d) { - return d.data.layer; - }); + .attr('text-anchor', 'start') + .attr('fill', d => this.type[d.data.layer]) + .attr('stroke', d => this.type[d.data.layer]) + .text(d => d.data.layer); nodeEnter .append('text') .attr('dy', -7) .attr('x', 95) .attr('stroke', '#333') - .attr('text-anchor', function(d) { - return 'start'; - }) - .text(function(d) { - return d.data.endTime - ? d.data.endTime - d.data.startTime + ' ms' - : d.data.traceId; - }); + .attr('text-anchor', 'start') + .text(d => d.data.endTime ? d.data.endTime - d.data.startTime + ' ms' : d.data.traceId); nodeEnter .append('circle') .attr('class', 'node') .attr('r', 4) .attr('cx', '158') - .style('fill', function(d) { - return d._children ? '#8543e0aa' : '#fff'; - }) + .style('fill', d => d._children ? '#8543e0aa' : '#fff') .on('click', click); this.nodeUpdate = nodeEnter.merge(node); @@ -318,12 +252,10 @@ export default class TraceMap { .select('circle.node') .attr('r', 4) .attr('cx', '158') - .style('fill', function(d) { - return d._children ? '#8543e0aa' : '#fff'; - }) + .style('fill', d => d._children ? '#8543e0aa' : '#fff') .attr('cursor', 'pointer'); - var nodeExit = node + const nodeExit = node .exit() .transition() .duration(600) @@ -332,13 +264,8 @@ export default class TraceMap { }) .remove(); - nodeExit.select('circle').attr('r', 0); - - nodeExit.select('text').style('fill-opacity', 0); - - const link = this.svg.selectAll('path.link').data(links, function(d) { - return d.id; - }); + // link + const link = this.svg.selectAll('path.link').data(links, d => d.id); const linkEnter = link .enter() @@ -368,19 +295,14 @@ export default class TraceMap { }) .remove(); - nodes.forEach(function(d) { - d.x0 = d.x; - d.y0 = d.y; - }); - function diagonal(s, d) { return `M ${s.y} ${s.x} C ${s.y - 30} ${s.x}, ${d.y + 188} ${d.x}, ${d.y + 158} ${d.x}`; } function click(d, i) { - that.tip.hide(d, this); - that.timeTip.hide(d, that.timeUpdate._groups[0][i]); + // that.tip.hide(d, this); + // that.timeTip.hide(d, that.timeUpdate._groups[0][i]); if (d.children) { d._children = d.children; d.children = null; @@ -393,6 +315,134 @@ export default class TraceMap { d3.event.stopPropagation(); } } + updatexAxis(source) { + // time + const that = this; + this.nodes = this.treemap(this.root).descendants(); + let index = -1; + this.nodes.forEach(function(d) { + d.y = d.depth * 200; + d.timeX = ++index * 12; + d.x0 = d.x; + d.y0 = d.y; + }); + const timeNode = this.timeGroup.selectAll('g.time').data(this.nodes, d => { + return d.id|| (d.id = ++this.j); + }); + this.timeNode = timeNode; + const timeEnter = timeNode + .enter() + .append('g') + .attr('class', 'time') + .attr('transform', d => `translate(0,${d.timeX})`) + .on('mouseover', function(d, i) { + that.timeTip.show(d, this); + const _node = that.nodeUpdate._groups[0].filter(group => group.__data__.id === (i+1)); + if(_node.length){ + that.tip.show(d, _node[0]); + } + }) + .on('mouseout', function(d, i) { + that.timeTip.hide(d, this); + const _node = that.nodeUpdate._groups[0].filter(group => group.__data__.id === (i+1)); + if(_node.length){ + that.tip.hide(d, _node[0]); + } + }) + .on('click', (d, i) => { + this.showSpanModal( + d.data, + { width: '100%', top: -10, left: '0' }, + d3.select(that.timeUpdate._groups[0][i]).append('rect') + ); + d3.event.stopPropagation(); + }); + timeEnter + .append('rect') + .attr('height', 10) + .attr('width', this.width) + .attr('y', -4) + .attr('class', 'time-bg'); + timeEnter + .append('rect') + .attr('class', 'time-inner') + .attr('height', 8) + .attr('width', d => { + if (!d.data.endTime || !d.data.startTime) return 0; + return this.xScale(d.data.endTime - d.data.startTime) + 1; + }) + .attr('rx', 2) + .attr('ry', 2) + .attr( + 'x', + d => (!d.data.endTime || !d.data.startTime ? 0 : this.xScale(d.data.startTime - this.min)) + ) + .attr('y', -3) + .style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`); + timeEnter + .append('rect') + .style('opacity',0) + .attr('class', 'time-inner-duration') + .attr('height', 8) + .attr('width', d => { + if (!d.data.dur) return 1; + return this.xScale(d.data.dur) + 1; + }) + .attr('rx', 2) + .attr('ry', 2) + .attr( + 'x', + d => (!d.data.endTime || !d.data.startTime ? 0 : this.xScale(d.data.startTime - this.min)) + ) + .attr('y', -3) + .style('fill', d => `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`); + + this.timeUpdate = timeEnter.merge(timeNode); + this.timeUpdate + .transition() + .duration(600) + .attr('transform', d => `translate(0,${d.timeX})`); + + const timeExit = timeNode + .exit() + .transition() + .duration(600) + .attr('transform', 'translate(0 ,10)') + .remove(); + } + setDefault() { + d3.selectAll('.time-inner').style('opacity', 1); + d3.selectAll('.time-inner-duration').style('opacity', 0); + d3.selectAll('.trace-tree-node-selfdur').style('opacity', 0); + d3.selectAll('.trace-tree-node-selfchild').style('opacity', 0); + this.nodeUpdate._groups[0].forEach(i => { + d3.select(i).style('opacity', 1); + }) + } + topChild() { + d3.selectAll('.time-inner').style('opacity', 1); + d3.selectAll('.time-inner-duration').style('opacity', 0); + d3.selectAll('.trace-tree-node-selfdur').style('opacity', 0); + d3.selectAll('.trace-tree-node-selfchild').style('opacity', 1); + this.nodeUpdate._groups[0].forEach(i => { + d3.select(i).style('opacity', .2); + if(i.__data__.data.childrenLength >= this.cmin && i.__data__.data.childrenLength <= this.cmax){ + d3.select(i).style('opacity', 1); + } + }) + } + topSlow() { + d3.selectAll('.time-inner').style('opacity', 0); + d3.selectAll('.time-inner-duration').style('opacity', 1); + d3.selectAll('.trace-tree-node-selfchild').style('opacity', 0); + d3.selectAll('.trace-tree-node-selfdur').style('opacity', 1); + this.nodeUpdate._groups[0].forEach(i => { + d3.select(i).style('opacity', .2); + if(i.__data__.data.dur >= this.smin && i.__data__.data.dur <= this.smax){ + d3.select(i).style('opacity', 1); + } + }) + } getZoomBehavior(g) { return d3 .zoom() @@ -400,9 +450,7 @@ export default class TraceMap { .on('zoom', () => { g.attr( 'transform', - `translate(${d3.event.transform.x},${d3.event.transform.y})scale(${ - d3.event.transform.k - })` + `translate(${d3.event.transform.x},${d3.event.transform.y})scale(${d3.event.transform.k})` ); }); } diff --git a/src/components/TraceTree/index.js b/src/components/TraceTree/index.js index 2a495c51..6e740ea2 100644 --- a/src/components/TraceTree/index.js +++ b/src/components/TraceTree/index.js @@ -16,13 +16,27 @@ */ import React, { Component } from 'react'; +import { Button } from 'antd'; import './style.less'; import Tree from './d3-trace'; +const ButtonGroup = Button.Group; + export default class Trace extends Component { constructor(props) { super(props); - this.state = {} + this.cache = 0; + this.db = 0; + this.http = 0; + this.mq = 0; + this.rpc = 0; + this.state = { + cache: 0, + db: 0, + http: 0, + mq: 0, + rpc: 0, + }; } componentDidMount() { @@ -94,23 +108,80 @@ export default class Trace extends Component { if(segmentGroup[i].refs.length ===0 ) this.segmentId.push(segmentGroup[i]); } - this.tree = new Tree(this.echartsElement) - this.tree.draw({label:`${this.traceId}`, children: this.segmentId}, rowData, propsData.showSpanModal); + this.topSlow = []; + this.topChild = []; + this.segmentId.forEach((_, i) => { + this.collapse(this.segmentId[i]); + }) + this.topSlowMax = this.topSlow.sort((a,b) => b - a)[0]; + this.topSlowMin = this.topSlow.sort((a,b) => b - a)[4]; + + this.topChildMax = this.topChild.sort((a,b) => b - a)[0]; + this.topChildMin = this.topChild.sort((a,b) => b - a)[4]; + this.tree = new Tree(this.echartsElement, propsData.showSpanModal, this.topSlowMax,this.topSlowMin,this.topChildMax,this.topChildMin) + this.tree.init({label:`${this.traceId}`, children: this.segmentId}, rowData); + this.tree.draw(); this.resize = this.tree.resize.bind(this.tree); } - + collapse(d) { + if(d.children){ + let dur = d.endTime - d.startTime; + d.children.forEach(i => { + dur -= (i.endTime - i.startTime); + }) + if(d.layer === "Http"){ + this.http += dur + this.setState({http: this.http}); + } + if(d.layer === "RPCFramework"){ + this.rpc += dur + this.setState({rpc: this.rpc}); + } + if(d.layer === "Database"){ + this.db += dur + this.setState({db: this.db}); + } + if(d.layer === "Cache"){ + this.cache += dur + this.setState({cache: this.cache}); + } + if(d.layer === "MQ"){ + this.mq += dur + this.setState({mq: this.mq}); + } + d.dur = dur < 0 ? 0 : dur; + this.topSlow.push(dur); + this.topChild.push(d.children.length); + d.childrenLength = d.children.length + d.children.forEach((i) => this.collapse(i)); + } + } render() { const newStyle = { - height: 700, + height: 800, // ...style, }; return ( -
{ this.echartsElement = e; }} - style={newStyle} - className="trace-tree" - /> +
+ + + + + +
+ {this.state.cache ? (Cache: {this.state.cache} ms): null} + {this.state.db ? (DB: {this.state.db} ms): null} + {this.state.mq ? (MQ: {this.state.mq} ms): null} + {this.state.http ? (Http: {this.state.http} ms): null} + {this.state.rpc ? (RPCFramework: {this.state.rpc} ms): null} +
+
{ this.echartsElement = e; }} + style={newStyle} + className="trace-tree" + /> +
) } } diff --git a/src/components/TraceTree/style.less b/src/components/TraceTree/style.less index 90093ff6..5924b85b 100644 --- a/src/components/TraceTree/style.less +++ b/src/components/TraceTree/style.less @@ -29,7 +29,7 @@ cursor: pointer; } :global(.trace-tree .time-bg) { - fill: #fff; + fill: rgba(0, 0, 0, 0); cursor: pointer; } :global(.trace-tree .time:hover) { @@ -49,6 +49,7 @@ :global(.trace-tree .node .content) { width: 158px; height: 20px; + fill: #fff; } :global(.trace-tree .node .service) { width: 7px;