C语言用户态函数可观测性

本文不是介绍eBPF相关的用户态Probe的内容,而是如何利用开源C语言库Melon的函数模板来轻松实现函数的可观测性需求,例如:测量耗时等。

本文主要介绍的是Melon库中的func模块,之所以没有给这个模块起名叫可观测性或者span,原因是这是一个更为通用的模块,不仅限于可观测性的需求。

func模块实现的功能与GCC的constructor和destructor特性十分相似,就是在C语言函数的入口和出口增加用户自定义回调函数,在调用函数时自行调用这些函数。

我们先看一个简单的例子:

// a.c#include "mln_func.h"MLN_FUNC(int, abc, (int a, int b), (a, b), {printf("in %s\n", __FUNCTION__);return a + b;
})MLN_FUNC(static int, bcd, (int a, int b), (a, b), {printf("in %s\n", __FUNCTION__);return abc(a, b) + abc(a, b);
})static void my_entry(const char *file, const char *func, int line)
{printf("entry %s %s %d\n", file, func, line);
}static void my_exit(const char *file, const char *func, int line)
{printf("exit %s %s %d\n", file, func, line);
}int main(void)
{mln_func_entry_callback_set(my_entry);mln_func_exit_callback_set(my_exit);printf("%d\n", bcd(1, 2));return 0;
}

这段代码中,使用MLN_FUNC定义了两个函数,分别为abcbcd,且在bcd中会调用abc。其实这个模板宏相对比较容易理解,其宏函数参数顺序如下:

  • 返回值类型(涵盖函数作用域,如static
  • 函数名
  • 函数形参列表(需要用()括住)
  • 函数实参列表(需要用()括住)
  • 函数体

这里唯一有些困惑的是实参列表,这与宏的实现有关。我们以abc为例,简述一下实现原理。

**原理:**这个宏会定义两个函数,一个名为abc,一个名为__abc。函数体其实对应的是__abc,也就是说__abc才是真正我们期望调用的那个函数,而abc是对__abc的一个封装,会在__abc的调用前后调用自定义回调函数。

而实参列表就是在函数abc中调用__abc时需要给__abc传递的参数,所以这个参数列表其实就是形参列表去掉类型之后的名字和顺序。

这个实参列表无法忽略,是因为__abc不能省略,而__abc不能省略是因为函数体中可能包含return语句,因此我们无法完全隐式地在return前,甚至是在return的表达式计算后真正的返回前调用回调函数。所以必须单独定义成一个函数也就是__abc

下面我们来编译这个程序:

cc -o a a.c -I /path/to/melon/include -L /path/to/melon/lib -lmelon

其中/path/to/melon的部分是Melon的安装路径,默认一般是/usr/local/melon

然后运行一下

./ain bcd
in abc
in abc
6

你会发现回调函数完全没被调用。这不是我们的代码有问题,而是我们并未启用模板功能。模板启用需要编译时存在MLN_FUNC_FLAG的宏定义,我们既可以将它定义在源文件中,也可以在编译时作为命令行参数给出。下面我以后者为例展示:

cc -o a a.c -I /path/to/melon/include -L /path/to/melon/lib -lmelon -DMLN_FUNC_FLAG

再次运行

./aentry a.c bcd 10
in __bcd
entry a.c abc 5
in __abc
exit a.c abc 5
entry a.c abc 5
in __abc
exit a.c abc 5
exit a.c bcd 10
6

可以看到,回调函数都被正常调用了。

利用这个开关宏,我们可以在不修改任何代码的情况下,轻松切换是否需要开启这项功能。

综合示例

前面给出的例子比较简单,那么下面就来看一个实现测量函数调用耗时的例子吧。

这里我将给出三个文件:

  • span.h:这是为测量耗时所定义的数据结构和函数声明等内容。
  • span.c:这是为测量耗时定义的相关函数。
  • a.c:这是我们自定义的一些函数以及在main函数中调用这些函数。

其中,span.hspan.c可以随意复制粘贴使用,这是一个独立的模块,当然,你还需要先安装好Melon库。

span.h
#include <sys/time.h>
#include "mln_array.h"typedef struct mln_span_s {struct timeval     begin;struct timeval     end;const char        *file;const char        *func;int                line;mln_array_t        subspans;struct mln_span_s *parent;
} mln_span_t;extern int mln_span_start(void);
extern void mln_span_stop(void);
extern void mln_span_dump(void);
extern void mln_span_release(void);

这里定义了一个数据结构mln_span_t,用来存放函数调用的起始和结束时的时间戳,以及函数所在源文件的信息。还包含了这个函数中调用的其他函数的调用时长信息,以及一个指向上一级调用(也就是调用当前函数的函数)信息的指针。

也就是说,当我们的函数执行完毕后,我们遍历这个结构就能拿到完整的调用关系及其调用细节。

span.c
#include <stdlib.h>
#include <string.h>
#include "span.h"
#include "mln_stack.h"
#include "mln_func.h"static mln_stack_t *callstack = NULL;
static mln_span_t *root = NULL;static void mln_span_entry(const char *file, const char *func, int line);
static void mln_span_exit(const char *file, const char *func, int line);
static mln_span_t *mln_span_new(mln_span_t *parent, const char *file, const char *func, int line);
static void mln_span_free(mln_span_t *s);static mln_span_t *mln_span_new(mln_span_t *parent, const char *file, const char *func, int line)
{mln_span_t *s;struct mln_array_attr attr;if (parent != NULL) {s = (mln_span_t *)mln_array_push(&parent->subspans);} else {s = (mln_span_t *)malloc(sizeof(mln_span_t));}if (s == NULL) return NULL;memset(&s->begin, 0, sizeof(struct timeval));memset(&s->end, 0, sizeof(struct timeval));s->file = file;s->func = func;s->line = line;attr.pool = NULL;attr.pool_alloc = NULL;attr.pool_free = NULL;attr.free = (array_free)mln_span_free;attr.size = sizeof(mln_span_t);attr.nalloc = 7;if (mln_array_init(&s->subspans, &attr) < 0) {if (parent == NULL) free(s);return NULL;}s->parent = parent;return s;
}static void mln_span_free(mln_span_t *s)
{if (s == NULL) return;mln_array_destroy(&s->subspans);if (s->parent == NULL) free(s);
}int mln_span_start(void)
{struct mln_stack_attr sattr;mln_func_entry_callback_set(mln_span_entry);mln_func_exit_callback_set(mln_span_exit);sattr.free_handler = NULL;sattr.copy_handler = NULL;if ((callstack = mln_stack_init(&sattr)) == NULL)return -1;return 0;
}void mln_span_stop(void)
{mln_func_entry_callback_set(NULL);mln_func_exit_callback_set(NULL);mln_stack_destroy(callstack);
}void mln_span_release(void)
{mln_span_free(root);
}static void mln_span_format_dump(mln_span_t *span, int blanks)
{int i;mln_span_t *sub;for (i = 0; i < blanks; ++i)printf(" ");printf("| %s at %s:%d takes %lu (us)\n", \span->func, span->file, span->line, \(span->end.tv_sec * 1000000 + span->end.tv_usec) - (span->begin.tv_sec * 1000000 + span->begin.tv_usec));for (i = 0; i < mln_array_nelts(&(span->subspans)); ++i) {sub = ((mln_span_t *)mln_array_elts(&(span->subspans))) + i;mln_span_format_dump(sub, blanks + 2);}
}void mln_span_dump(void)
{if (root != NULL)mln_span_format_dump(root, 0);
}static void mln_span_entry(const char *file, const char *func, int line)
{mln_span_t *span;if ((span = mln_span_new(mln_stack_top(callstack), file, func, line)) == NULL) {fprintf(stderr, "new span failed\n");exit(1);}if (mln_stack_push(callstack, span) < 0) {fprintf(stderr, "push span failed\n");exit(1);}if (root == NULL) root = span;gettimeofday(&span->begin, NULL);
}static void mln_span_exit(const char *file, const char *func, int line)
{mln_span_t *span = mln_stack_pop(callstack);if (span == NULL) {fprintf(stderr, "call stack crashed\n");exit(1);}gettimeofday(&span->end, NULL);
}

这里就是耗时统计所需要的所有函数定义。利用一个栈数据结构来保证函数的调用关系,然后在函数的入口回调处创建mln_span_t结点记录起始时间和函数信息并入栈,在出口回调处记录结束时间并出栈。

a.c
#include "span.h"
#include "mln_func.h"MLN_FUNC(int, abc, (int a, int b), (a, b), {return a + b;
})MLN_FUNC(static int, bcd, (int a, int b), (a, b), {return abc(a, b) + abc(a, b);
})int main(void)
{mln_span_start();bcd(1, 2);mln_span_stop();mln_span_dump();mln_span_release();return 0;
}

这里还是那个配方,就是调用bcd,然后bcd调用abc。我们这次在main函数中使用span.h中声明的函数。

一起来简单编译一下:

cc -o a span.c a.c -I /usr/local/melon/include -L /usr/local/melon/lib -lmelon -DMLN_FUNC_FLAG

然后运行一下:

./a| bcd at a.c:8 takes 2 (us)| abc at a.c:4 takes 0 (us)| abc at a.c:4 takes 0 (us)

小结

Melon的函数模板其实设计之初也是为了可观测性,因为GCC仅支持了constructor和destructor。如果显式地在代码中加入各种跟踪函数调用,就会让整个函数定义看着非常不连贯和杂乱。因此选择了当前的这个使用方式,但也不可避免的引入了看似没什么用途的实参部分。

另外,Melon库支持模块选择性编译,因此函数模版模块可以单独编译成库,换言之,这个模块是完全无操作系统依赖的,单片机的小伙伴们可以随意取用。

感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/648018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot(ssm成都旅游网 旅游管理系统 旅游规划系统Java系统

springboot(ssm成都旅游网 旅游管理系统 旅游规划系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.…

如何利用监管工具有效防止员工私单飞单?

在今天的商业社会中&#xff0c;企业必须保持高度的监管和控制力度&#xff0c;以确保员工遵守公司的规章制度和道德准则。尤其在微信成为了日常工作中不可或缺的沟通工具的情况下&#xff0c;如何有效防止员工进行私单飞单成为了每个企业亟需解决的问题。 而微信管理系统通过…

【CCF】JCR3区,SCIEI双检,征稿领域广,来稿不拒!

一、期刊简介 3区计算机类SCI&EI 【期刊概况】IF&#xff1a;2.0-3.0&#xff0c;JCR3区&#xff0c;中科院4区&#xff1b; 【终审周期】走期刊部系统&#xff0c;3个月左右录用&#xff1b; 【检索情况】SCI&EI双检&#xff1b; 【WOS收录年份】2001年&#xff…

应急响应-Windows-进程排查

进程&#xff08;process&#xff09;是计算机中的程序关于某数据集合上的一次运动活动&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;是操作系统结果的基础。在早期面向进程结构中&#xff0c;进程是线程的容器。无论是在Windows系统还是Linux系统中&#xff0c…

专业远程控制软件有哪些品牌

远程办公、远程控制类的软件很多&#xff0c;主打方向和面向的客户人群也不一样。个人用户可能更在意便捷、免费等因素&#xff1b;专业用户会更注重安全性、管理功能等。今天我们介绍几个在全球知名的专业商业远程软件。 1、TeamViewer 简介&#xff1a;TeamViewer &#xf…

2016年认证杯SPSSPRO杯数学建模B题(第一阶段)低分辨率下看世界全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 B题 低分辨率下看世界 原题再现&#xff1a; 数码摄像技术被广泛使用于多种场合中。有时由于客观条件的限制&#xff0c;拍摄设备只能在较低的分辨率下成像。为简单起见&#xff0c;我们只考虑单色成像。假设成像的分辨率为 32 64&#xff0c…

c#中Image<Rgba32>转Bitmap

private Bitmap ImageToBitmap(Image<Rgba32> image){using var memoryStream new MemoryStream();image.SaveAsBmp(memoryStream);memoryStream.Seek(0, SeekOrigin.Begin);return new Bitmap(memoryStream);} c#中Image<Rgba32>转Bitmap 当然也可以直接使用EM…

持续集成工具Jenkins的使用之安装篇(一)

Jenkins是一个基于Java开发的开源的一种持续集成工具&#xff0c;主要用于环境部署&#xff0c;监控重复性的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。要想使用它&#xff0c;你就必须的先安装&#xff0c;接下来我们就介绍下J…

win10重启以后才能识别有线耳机

最近遇到一件让人很无奈的事情&#xff0c;win10重启以后才能识别有限耳机。奇怪的是无线耳机没有问题。 bing搜索了很多&#xff0c;就是解决不了问题。 偶然之间&#xff0c;文后的参考文献被搜索到&#xff0c;该文献提供的方法比较简单&#xff0c;经过验证&#xff0c;方…

恒创科技:云服务器配置中的vCPU与物理CPU有啥区别?

​  说到云服务器&#xff0c;您可能经常会遇到vCPU这个词&#xff0c;而且它和物理CPU经常被拿来谈论。尽管它们听起来相似&#xff0c;但两者之间存在显著差异。在本文中&#xff0c;我们将详细讨论云vCPU和物理CPU之间的差异。 物理与虚拟 CPU 和 vCPU 之间最显著的区别在…

降本增效及大模型优化调研总结[小工蚁视频调研]

可用需求1&#xff1a;可用于大模型优化的技术 最强长上下文Text Embedding 开源模型M2-BERT-小工蚁创始人-小工蚁创始人-哔哩哔哩视频 (bilibili.com) 疑问&#xff1a;和Text2vec或sentence2vec的区别&#xff0c;谁更好&#xff1f; 智谱AI GLM4和InternLM2国产大语言模型…

H5112B 48V 60V 80V 100V 多路共阳 RGB调光 PWM调光芯片

多路共阳恒流芯片是一种常用于LED驱动等应用的电子元件&#xff0c;它可以实现多个LED灯共享一个电流源&#xff0c;并且保持每个LED灯的亮度稳定。其工作原理如下&#xff1a; 多路输入&#xff1a;多路共阳恒流芯片通常有多个输入引脚&#xff0c;每个引脚对应一个LED灯。这…

kafka集群和Filebeat+Kafka+ELK

一、Kafka 概述 1.1 为什么需要消息队列&#xff08;MQ&#xff09; 主要原因是由于在高并发环境下&#xff0c;同步请求来不及处理&#xff0c;请求往往会发生阻塞。比如大量的请求并发访问数据库&#xff0c;导致行锁表锁&#xff0c;最后请求线程会堆积过多&#xff0c;从…

攻防世界WEB新手训练区

view_source 此题我愿称之为网安领域的hello world 查看网页源代码的方式一般有—— 右键->查看网页源代码F12->源代码/来源Ctrlu 随后可以再代码第17行处找到flag&#xff0c;至此迈入网安第一步。可喜可贺&#xff0c;可喜可贺... get_post 考察http的两种请求方式&…

“JavaScript 循环中的 ‘await‘

目录 前言 for使用await -- 有效的 forEach使用await -- 无效的 for of 使用await 有效的 for await of 使用await 有效的 总结 前言 在JavaScript的forEach方法中使用await是无效的&#xff0c;因为forEach方法不支持异步操作的等待。 forEach是一个数组的遍历方法&…

精要图示:园区金融数字化服务蓝图,以园区为支点推动信贷业务增长

作为企业集聚地&#xff0c;园区已然成为银行业夯实客群基础的重要切口&#xff0c;各大行陆续围绕园区场景创新金融产品&#xff0c;以期抢跑园区金融新赛道、把握新增量。 启信慧眼首推一站式【园区金融】数字化服务方案&#xff0c;该方案同时支持启信天元私有化部署&#x…

uniapp map自定义气泡窗

uniapp map自定义气泡窗 1、map <template><view><map class"map" :latitude"mapCenter.lat" :longitude"mapCenter.lng" :scale"5" :markers"mapData"><!--自定义冒泡--><cover-view slot&qu…

windows下git pull超时,ping不通github

报错 ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. 解决办法 修改hosts 最后加一行&#xff0c;文件位置&#xff1a;…

Kotlin Multiplatform项目推荐 | 太空人分布图

Kotlin Multiplatform项目推荐 | 太空人分布图 项目简介 Kotlin Multiplatform项目是一种跨平台开发技术&#xff0c;它可以同时使用SwiftUI、Jetpack Compose、Compose for Wear OS、Compose for Desktop、Compose for Web、Kotlin/JS React等客户端框架&#xff0c;并且使…

分表过多引起的问题/Apache ShardingSphere元数据加载慢

目录 环境 背景 探寻 元数据的加载策略 如何解决 升级版本到5.x 调大max.connections.size.per.query max.connections.size.per.query分析 服务启动阶段相关源码 服务运行阶段相关源码 受到的影响 注意事项&#xff08;重要&#xff09; 其他 环境 Spring Boot 2…