介绍
火焰图常用于性能分析中,显示某个函数堆栈的耗时情况,一般火焰图区块越大,表示耗时越高,越有可能是性能热点,如何读懂火焰图?
绘制原理
echarts官方是没有火焰图类型的图表的,但是可以通过 echarts的custom
类型来绘制火焰图;
火焰图本质上就是一些长方体区块的堆叠,因此我们只需要计算好每个方块的左下角坐标,以及每个方块的宽高,就可以把火焰图绘制出来
主要用到了 custom
系列的
renderItem
函数,用于自定义图形绘制,对series中的每一个数据项都会应用一次api.value
,用于在renderItem中获取设置的series中的数据api,coord函数
,用于在renderItem函数中计算某个方块的坐标以及宽高
详细文档参考官方链接https://echarts.apache.org/zh/option.html#series-custom.renderItem
绘制
以下是关键部分代码,也就是renderItem函数
/*
在我的原始数据中,记录了每个函数的调用起始时间偏移量(可以理解为函数调用相对于基准时间的差值),调用耗时,堆栈深度,以及其他数据
绘制过程中主要就是使用 时间偏移量计算在图表中的x坐标,用深度计算y坐标,用耗时计算区块的宽度,
设置的数据格式是
series:{
data:[
[offset,timeSpan, depth,xxx],
[offset,timeSpan, depth,xxx],
[offset,timeSpan, depth,xxx]
]
}
*/
renderItem(params,api)
{
//整个流程是在vue组件中的,所以这里的this指向的是当前vue组件
//这里使用maxDepth是因为我绘制的火焰图是倒过来的,深度小的在上面,深度大的在下面
const maxDepth = this.maxDepth;
const minDepth = this.minDepth; //minDepth其实没用到,不写也可以
const offset = api.value(0);
const timeSpan = api.value(1);
const depth = api.value(2);
const start = api.coord([offset, maxDepth-depth]); //y坐标倒一下
const end = api.coord([offset+timeSpan, maxDepth-depth]);
const normalSize = api.size([1, 1]); //获取单位长度
const height = normalSize[1];
const width= end[0]-start[0];
//如果数据量太大,可以把一些宽度过小的图元过滤掉,减小压力,当然,也可以在数据源进行过滤
if(width<1)
{
return
}
//这里创建一个长方体图元
let rectShape = echarts.graphic.clipRectByRect({
x: start[0],
y: start[1]-height/2,
width: width,
height: normalSize[1],
}, {
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height
});
//var style={ //这里可以自定义填充颜色
// fill: option.color[parseInt(width%option.color.length)],
//}
//if(depth<nowData.minDepth)
//{
// style.fill= '#808080';
//}
//if(depth === criterion.depth && offset === criterion.offset)
//{
// style.stroke='yellow'; //边框颜色以及线的属性也是可以配置的
// style.lineWidth=2;
//}
return rectShape && {
type: 'rect',
shape: rectShape,
//style: api.style(style),
};
},
缩放
火焰图是可以交互的,点击某个区块可以进行缩放,这个可以使用echarts自带的dispatchAction
来发送dataZoom
事件实现,通过设置 dataZoom的startValue和endValue进行缩放
this.chartInstance.dispatchAction(
{
type : 'dataZoom',
startValue: dataItem.offset,
endValue : dataItem.offset+dataItem.timeSpan,
});
需要注意的是,在缩放后,可能会出现边缘有部分区块是没有被完全过滤掉的,正常情况下不会有问题,但是如果你使用动态计算火焰图区块上名字的宽度的话,就会出现问题,
可以在renderItem中通过不绘制的方法进行手动过滤
if(offset>=endValue || offset+timeSpan<=startValue)
{
return;
}
动态计算区块中函数名的宽度的方法可以查看 js获取字符串像素宽度,通过动态计算字符串宽度,在 图表的 series-label-formatter中动态显示字符串
series:{
label:{
formatter: function(param){}
}
}
效果
除了配色辣眼睛外,其他的功能都实现了,配色算法研究中。。。
有什么问题欢迎评论留言一起探讨!