1.systemtap从入门到放弃(一)
2.TiFlash 源码阅读(一) TiFlash 存储层概览
3.手写webpacktapable源码,理源理官方tapable的戏代性能真的就一定是好的吗?
4.带tap是什么意思?
5.systemtap使用指南
6.systemtap安装SystemTap
systemtap从入门到放弃(一)
内核调试利器:systemtap从入门到放弃(一)
systemtap,一个用于简化Linux系统运行形态信息收集的理源理开源工具,立足于性能诊断与bug调试。戏代相较于繁琐的理源理工具、耗时的戏代源码燕窝礼盒包装重新编译与引导过程,systemtap以动态hook内核代码的理源理特性,提供了便捷的戏代解决方案。其工作原理与底层kprobe接口紧密相连,理源理通过在kprobe基础上引入脚本解析与内核模块编译运行单元,戏代使开发人员得以在应用层实现对内核的理源理hook,简化了开发流程。戏代
相比传统的理源理kernel API与debugfs接口,systemtap提供了更加简洁的戏代命令行接口与内核指令脚本语言,对于开发者而言,理源理它成为了一款极其实用的工具,尤其在bug调试、性能分析与源码学习方面。
入门systemtap,首先需了解其独特的脚本语法。脚本的编写,就是找到所需事件并设计事件处理流程的过程。系统中常用的语法元素包括脚本命名、注释、变量、数组、条件语句与循环等。其中,变量作用域默认为函数或括号内,全局变量则需在函数外显式声明。数组与关联数组则提供了数据存储与访问的灵活性。
在systemtap中,通过探针(probe)来触发事件处理,支持对特定内核函数、模块函数进行探测。此外,systemtap还提供了一系列内置探针(tapset),开发者可直接调用,oschina源码分析加速调试与分析工作。
系统中常见的可探测事件包括函数调用、异常处理、系统调用等,这些事件为开发者提供了丰富的调试切入点。通过精心设计的脚本,开发者能够精准捕捉并分析系统行为,优化性能,定位并解决bug。
systemtap的脚本语言简洁易懂,对于底层开发人员而言,学习曲线相对平缓。然而,掌握其脚本语法与内置探针的使用,对于深入应用systemtap,实现高效、精确的调试至关重要。
综上所述,systemtap凭借其独特的功能与简洁的语法,为Linux内核调试提供了一种高效、便捷的工具。从入门到掌握,开发者需深入理解其工作原理与脚本语言,灵活应用内置探针,以实现精准的系统调试与优化。
TiFlash 源码阅读(一) TiFlash 存储层概览
本系列文章聚焦于 TiFlash,读者需具备基本的 TiDB 知识。TiFlash 是 TiDB HTAP 模式的关键组件,作为 TiKV 的列存扩展,通过 Raft Learner 协议实现异步复制,并提供与 TiKV 相同的快照隔离支持。自 5.0 引入 MPP 后,TiDB 的实时分析场景下计算加速能力得到了增强。
TiFlash 整体逻辑模块划分如下:通过 Raft Learner Proxy 接入多 Raft 体系,计算层 MPP 在 TiFlash 间进行数据交换,提供更强的分析计算能力。Schema 模块与 TiDB 表结构同步,html头像源码将 TiKV 同步数据转换为列形式,并写入列存引擎。底层为 DeltaTree 引擎。
TiFlash 基于 ClickHouse fork,沿用了 ClickHouse 的向量化执行引擎,并加入针对 TiDB 的对接、MySQL 兼容、Raft 协议、集群模式、实时更新列存引擎、MPP 架构等特性。DeltaTree 引擎解决了高频率数据写入、实时更新读性能优化、符合 TiDB 事务模型、支持 MVCC 过滤、数据分片便于分析场景等需求。
DeltaTree 引擎不同于 MergeTree,具备原生支持高频率写入、列存实时更新下读性能优化、支持 TiDB 事务模型、数据分片便于提供分析特性等优势。MergeTree 引擎存在写入碎片、Scan 时 CPU cache miss 严重、清理过期数据时 compaction 导致性能波动等问题,而 DeltaTree 通过横向分割数据管理、delta-stable 数据组织、PageStorage 存储等设计优化了性能。
DeltaTree 引擎通过在表内按 handle 列分段管理数据,采用 delta-stable 数据组织,PageStorage 存储小数据块,构建 DeltaIndex 和 Rough Set Index 等组件优化读性能。DeltaIndex 帮助减少 CPU bound 的 merge 操作,Rough Set Index 用于过滤数据块,减少不必要的 IO 操作。
TiFlash 存储层 DeltaTree 引擎在不同数据量和更新 TPS 下读性能表现优于基于 MergeTree 的实现,提供更稳定、高效的源码安装emqttd读、写性能。TiFlash 中的 PageStorage、DeltaIndex、Rough Set Index 等组件协同作用,优化数据管理和查询性能。
DeltaTree 引擎在 TiFlash 内部实现中,通过 PageStorage 存储数据,DeltaIndex 提高读性能,Rough Set Index 优化查询效率,提供了对 HTAP 场景的优化和支持。TiFlash 存储层 DeltaTree 引擎的设计和实现细节将在后续章节中详细展开。
手写webpacktapable源码,官方tapable的性能真的就一定是好的吗?
完整的手写源码仓库tapable是Webpack?插件机制核心。?mini-tapable?不仅解读官方?tapable?的源码,还用自己的思路去实现一遍,并且和官方的运行时间做了个比较,我和webpack作者相关的讨论可以点击查看。webpacktapable源码内部根据newFunction动态生成函数执行体这种优化方式不一定是好的。当我们熟悉了tapable后,就基本搞懂了webpackplugin的底层逻辑,再回头看webpack源码就轻松很多
目录src目录。这个目录下是手写所有的tapablehook的源码,每个hook都用自己的思路实现一遍,并且和官方的hook执行时间做个对比。
tapable的设计理念:单态、多态及内联缓存由于在webpack打包构建的过程中,会有上千(数量其实是取决于自身业务复杂度)个插件钩子执行,同时同类型的钩子在执行时,函数参数固定,函数体相同,因此tapable针对这些业务场景进行了相应的优化。这其中最重要的是运用了单态性及多态性概念,内联缓存的原理,也可以看这个issue。为了达到这个目标,tapable采用newFunction动态生成函数执行体的方式,主要逻辑在源码的云顶娱乐源码HookCodeFactory.js文件中。
如何理解tapable的设计理念思考下面两种实现方法,哪一种执行效率高,哪一种实现方式简洁?
//方法一:constcallFn=(...tasks)=>(...args)=>{ for(constfnoftasks){ fn(...args)}}//方法二:constcallFn2=(a,b,c)=>(x,y)=>{ a(x,y);b(x,y);c(x,y);}callFn及callFn2的目的都是为了实现将一组方法以相同的参数调用,依次执行。很显然,方法一效率明显更高,并且容易扩展,能支持传入数量不固定的一组方法。但是,如果根据单态性以及内联缓存的说法,很明显方法二的执行效率更高,同时也存在一个问题,即只支持传入a,b,c三个方法,参数形态也固定,这种方式显然没有方法一灵活,那能不能同时兼顾效率以及灵活性呢?答案是可以的。我们可以借助newFunction动态生成函数体的方式。
classHookCodeFactory{ constructor(args){ this._argNames=args;this.tasks=[];}tap(task){ this.tasks.push(task);}createCall(){ letcode="";//注意思考这里是如何拼接参数已经函数执行体的constparams=this._argNames.join(",");for(leti=0;i<this.tasks.length;i++){ code+=`varcallback${ i}=this.tasks[${ i}];callback${ i}(${ params})`;}returnnewFunction(params,code);}call(...args){ constfinalCall=this.createCall();//将函数打印出来,方便观察最终拼接后的结果console.log(finalCall);returnfinalCall.apply(this,args);}}//构造函数接收的arg数组里面的参数,就是taska、b、c三个函数的参数constcallFn=newHookCodeFactory(["x","y","z"]);consta=(x,y,z)=>{ console.log("taska:",x,y,z);};constb=(x,y,z)=>{ console.log("taskb:",x,y,z);};constc=(x,y,z)=>{ console.log("taskc:",x,y,z);};callFn.tap(a);callFn.tap(b);callFn.tap(c);callFn.call(4,5,6);当我们在浏览器控制台执行上述代码时:
拼接后的完整函数执行体:
可以看到,通过这种动态生成函数执行体的方式,我们能够同时兼顾性能及灵活性。我们可以通过tap方法添加任意数量的任务,同时通过在初始化构造函数时newHookCodeFactory(['x','y',...,'n'])传入任意参数。
实际上,这正是官方tapable的HookCodeFactory.js的简化版本。这是tapable的精华所在。
tapable源码解读tapable最主要的源码在Hook.js以及HookCodeFactory.js中。Hook.js主要是提供了tap、tapAsync、tapPromise等方法,每个Hook都在构造函数内部调用consthook=newHook()初始化hook实例。HookCodeFactory.js主要是根据newFunction动态生成函数执行体。
demo以SyncHook.js为例,SyncHook钩子使用如下:
const{ SyncHook}=require("tapable");debugger;consttesthook=newSyncHook(["compilation","name"]);//注册plugin1testhook.tap("plugin1",(compilation,name)=>{ console.log("plugin1",name);compilation.sum=compilation.sum+1;});//注册plugin2testhook.tap("plugin2",(compilation,name)=>{ console.log("plugin2..",name);compilation.sum=compilation.sum+2;});//注册plugin3testhook.tap("plugin3",(compilation,name)=>{ console.log("plugin3",compilation,name);compilation.sum=compilation.sum+3;});constcompilation={ sum:0};//第一次调用testhook.call(compilation,"mytest1");//第二次调用testhook.call(compilation,"mytest2");//第三次调用testhook.call(compilation,"mytest3");...//第n次调用testhook.call(compilation,"mytestn");我们用这个demo做为用例,一步步debug。
SyncHook.js源码主要逻辑如下:
constHook=require("./Hook");constHookCodeFactory=require("./HookCodeFactory");//继承HookCodeFactoryclassSyncHookCodeFactoryextendsHookCodeFactory{ }constfactory=newSyncHookCodeFactory();constCOMPILE=function(options){ factory.setup(this,options);returnfactory.create(options);};functionSyncHook(args=[],name=undefined){ //初始化Hookconsthook=newHook(args,name);//注意这里修改了hook的constructorhook.constructor=SyncHook;...//每个钩子都必须自行实现自己的compile方法!!!hook.compile=COMPILE;returnhook;}Hook.js源码主要逻辑如下:
//问题一:思考一下为什么需要CALL_DELEGATEconstCALL_DELEGATE=function(...args){ //当第一次调用时,实际上执行的是CALL_DELEGATE方法this.call=this._createCall("sync");//当第二次或者第n次调用时,此时this.call方法已经被设置成this._createCall的返回值returnthis.call(...args);};...classHook{ constructor(args=[],name=undefined){ this._args=args;this.name=name;this.taps=[];//存储我们通过hook.tap注册的插件this.interceptors=[];this._call=CALL_DELEGATE;//初始化时,this.call被设置成CALL_DELEGATEthis.call=CALL_DELEGATE;...//问题三:this._x=undefined是什么this._x=undefined;//this._x实际上就是this.taps中每个插件的回调//问题四:为什么需要在构造函数中绑定这些函数this.compile=this.compile;this.tap=this.tap;this.tapAsync=this.tapAsync;this.tapPromise=this.tapPromise;}//每个钩子必须自行实现自己的compile方法。compile方法根据this.taps以及this._args动态生成函数执行体compile(options){ thrownewError("Abstract:shouldbeoverridden");}//生成函数执行体_createCall(type){ returnthis.compile({ taps:this.taps,interceptors:this.interceptors,args:this._args,type:type});}..._tap(type,options,fn){ ...this._insert(options);}tap(options,fn){ this._tap("sync",options,fn);}_resetCompilation(){ this.call=this._call;this.callAsync=this._callAsync;this.promise=this._promise;}_insert(item){ //问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法?this._resetCompilation();...}}思考Hook.js源码中的几个问题问题一:为什么需要CALL_DELEGATE
问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法?
问题三:this._x=undefined是什么
问题四:为什么需要在构造函数中绑定this.compile、this.tap、this.tapAsync以及this.tapPromise等方法
当我们每次调用testhook.tap方法注册插件时,流程如下:
方法往this.taps数组中添加一个插件。this.__insert方法逻辑比较简单,但这里有一个细节需要注意一下,为什么每次注册插件时,都需要调用this._resetCompilation()重置this.call等方法?我们稍后再看下这个问题。先继续debug。
当我们第一次(注意是第一次)调用testhook.call时,实际上调用的是CALL_DELEGATE方法
constCALL_DELEGATE=function(...args){ //当第一次调用时,实际上执行的是CALL_DELEGATE方法this.call=this._createCall("sync");//当第二次或者第n次调用时,此时this.call方法已经被缓存成this._createCall的返回值returnthis.call(...args);};CALL_DELEGATE调用this._createCall函数根据注册的this.taps动态生成函数执行体。并且this.call被设置成this._createCall的返回值缓存起来,如果this.taps改变了,则需要重新生成。
此时如果我们第二次调用testhook.call时,就不需要再重新动态生成一遍函数执行体。这也是tapable的优化技巧之一。这也回答了问题一:为什么需要CALL_DELEGATE。
如果我们调用了n次testhook.call,然后又调用testhook.tap注册插件,此时this.call已经不能重用了,需要再根据CALL_DELEGATE重新生成一次函数执行体,这也回答了问题二:为什么每次调用testhook.tap()注册插件时,都需要重置this.call等方法。可想而知重新生成的过程是很耗时的。因此我们在使用tapable时,最好一次性注册完所有插件,再调用call
testhook.tap("plugin1");testhook.tap("plugin2");testhook.tap("plugin3");testhook.call(compilation,"mytest1");//第一次调用call时,会调用CALL_DELEGATE动态生成函数执行体并缓存起来testhook.call(compilation,"mytest2");//不会重新生成函数执行体,使用第一次的testhook.call(compilation,"mytest3");//不会重新生成函数执行体,使用第一次的避免下面的调用方式:
testhook.tap("plugin1");testhook.call(compilation,"mytest1");//第一次调用call时,会调用CALL_DELEGATE动态生成函数执行体并缓存起来testhook.tap("plugin2");testhook.call(compilation,"mytest2");//重新调用CALL_DELEGATE生成函数执行体testhook.tap("plugin3");testhook.call(compilation,"mytest3");//重新调用CALL_DELEGATE生成函数执行体现在让我们看看第三个问题,调用this.compile方法时,实际上会调用HookCodeFacotry.js中的setup方法:
setup(instance,options){ instance._x=options.taps.map(t=>t.fn);}对于问题四,实际上这和V8引擎的HiddenClass有关,通过在构造函数中绑定这些方法,类中的属性形态固定,这样在查找这些方法时就能利用V8引擎中HiddenClass属性查找机制,提高性能。
HookCodeFactory.js主要逻辑:
classHookCodeFactory{ constructor(config){ this.config=config;this.options=undefined;this._args=undefined;}create(options){ this.init(options);letfn;switch(this.options.type){ case'sync':fn=newFunction(...)breakcase'async':fn=newFunction(...)breakcase'promise':fn=newFunction(...)break}this.deinit();returnfn;}setup(instance,options){ instance._x=options.taps.map(t=>t.fn);}...}手写tapable每个Hook手写tapable中所有的hook,并比较我们自己实现的hook和官方的执行时间
这里面每个文件都会实现一遍官方的hook,并比较执行时间,以SyncHook为例,批量注册个插件时,我们自己手写的MySyncHook执行时间0.ms,而官方的需要6ms,这中间整整倍的差距!!!
具体可以看我的仓库
原文:/post/带tap是什么意思?
在技术领域,tap代表着一种调试协议的名称。tap调试协议可以使调试者连接到目标设备上的调试接口,进而进行源码级别的调试和管理。它是一种简单、高效的调试协议,被广泛应用于软件调试和开发中。
在软件调试中,tap的主要意义是提供了一种可视化的、实时的调试手段。通过连接调试器和目标设备,调试者可以随时随地对目标设备中的程序进行调试、查看、修改,从而更好地发现问题并解决它们。同时,tap还能简化调试工作流程,提升开发效率。
tap调试协议可适用于不同的操作系统和开发环境。例如,在嵌入式系统开发中,tap常被应用于ARM架构的芯片和微控制器上,为嵌入式程序的调试和开发提供支持。而在PC端应用开发中,tap则主要应用于C/C++编程语言,可以与各种不同的集成开发环境(IDE)相兼容。总而言之,tap在软件开发、调试领域的应用非常广泛,对提升程序质量和开发效率都起到了积极的作用。
systemtap使用指南
Systemtap 是一种工具,用于开发人员和管理员编写和复用简单脚本以深入检查 Linux 系统活动。它允许快速提取、过滤和汇总数据,安全地诊断复杂性能或功能问题。
使用 Systemtap,最简单的探测类型是跟踪一个事件,例如在 open 系统调用执行时打印特定进程信息及参数。
Systemtap 支持多种内置事件,并可自定义额外事件,这些事件通过统一命名的点分隔参数化标识符语法定义。编写脚本时,需指定探测位置及打印内容。
Systemtap 的脚本通过翻译成 C 代码并运行系统 C 编译器来创建内核模块,模块在内核中激活所有探测事件。模块加载时,探测到的事件触发编译的处理程序运行。
跟踪事件的处理程序可以使用丰富控制结构,类似于 awk 语法,用于描述算法。脚本支持条件语句,允许限制跟踪或逻辑特定于进程或感兴趣的地方。
Systemtap 脚本可以使用变量、表达式和函数,支持 C 和 awk 风格的语法,以及字符串和数字运算。全局变量和特殊“目标变量”用于访问探测点上下文。
目标变量允许访问内核源代码中的值,并提供如地址、结构打印、变量范围和类型转换功能。全局变量用于在探测程序之间共享数据。
脚本还可以包含自定义函数,定义在脚本中的任何位置,并接受字符串或数字参数,返回单个字符串或数字。
Systemtap 提供关联数组,用于在不同探测程序之间共享数据,数组实现为哈希表,并且必须声明为全局变量。数组支持设置、查找、迭代和测试元素。
探测程序执行受到时间限制,避免了内存动态分配,且具有安全机制,如限制函数调用嵌套深度和检测潜在危险操作。
Systemtap 通过 tapset 脚本库支持共享,允许建立相互构建的脚本库,这些库可以自动解析未定义的全局符号以在脚本中进行搜索。
嵌入式 C 代码功能允许使用安全且正确的 C 代码,以补充探测功能,如遍历全局链表。此代码在内核模块中转录,并需遵守安全约束。
为了防止名称冲突,建议遵循 Systemtap tapset 开发人员的命名约定,以避免翻译或运行时错误。
Systemtap 的安装方式包括默认目录和额外目录选项,以及使用 /usr/share/systemtap/tapset/ 和 sourceware.org 等资源。
总结,Systemtap 提供了一种实用的工具,让开发者和管理员能够深入分析 Linux 系统活动,诊断性能和功能问题。尽管存在安全风险,对于内核程序员而言,它提供了访问内核信息和增强理解的有效途径。
systemtap安装SystemTap
在开始安装SystemTap之前,确保你的系统已经安装了两个关键软件包:kernel-debuginfo RPM和elfutils RPM。kernel-debuginfo是SystemTap依赖于内核调试信息的工具,大多数发行版并未预装,需要从相应的下载站点获取。 elfutils RPM则为SystemTap提供了分析调试信息所需的库函数。推荐安装elfutils-0.或更高版本,目前最新版本为0.-0.1。如果系统中没有,可以访问SystemTap的官方网站下载RPM包或源代码进行升级。 安装SystemTap有RPM包和源码编译两种途径。对于Fedora Core 6用户,系统自带的systemtap可能已经足够,无需额外安装。而对于其他需要源码编译的情况,步骤如下:首先,从SystemTap FTP站点下载最新的源码包:/root > tar -jxf SystemTap
然后,切换到源码目录:/root > cd src
执行配置步骤:/root/src> ./configure
开始编译:/root/src> make
最后,安装SystemTap:/root/src> make install
请根据你的系统需求和环境,选择合适的安装方法。确保所有依赖已准备就绪,以顺利完成SystemTap的安装过程。