开源 C 语言库 Melon:用户态动态追踪与控制反馈

2023-02-03 20:13:14 +08:00
 monkeyNik

前言

本文根据开源 C 语言库 Melon 的最新特性,讲述使用该库做用户态动态追踪,以及根据追踪内容进行计算,并将结果用于反馈给程序,同时对程序的处理流程进行影响。

说到动态追踪,大家可能第一印象是 bpf 、dtrace 、systemtap 等等,但是本文介绍的动态追踪不依赖于这些内容。Melon 中提供的功能更加倾向于让程序在用户态内完成对自身的动态追踪,而不依赖于内核态,也不依赖于 uprobe 和 usdt 等内容

关于 Melon 库,这是一个开源的 C 语言库,它具有:开箱即用、无第三方依赖、安装部署简单、中英文文档齐全等优势。

Github: https://github.com/Water-Melon/Melon

原理

简单来说,Melon 库的动态追踪也是在程序中加入跟踪点。只是在 Melon 中,一个应用程序被划分为两个层面(但都运行在同一个进程中):

两个代码层面可以运行在同一线程,也可以运行在不同线程中,但需要运行于同一进程下。

换言之:跟踪点信息会在 C 层被抛出,传入给指定的脚本任务,然后脚本任务接收信息并进行处理。

这么看,似乎和在程序中记录日志,然后额外写一个程序来读取日志进行处理没有什么区别,那么这么做的好处是什么呢?

优势

示例

下面来看一个非常简单的例子:

#include <stdio.h>
#include "mln_log.h"
#include "mln_core.h"
#include "mln_trace.h"
#include "mln_conf.h"
#include "mln_event.h"

int timeout = 100;

static void timeout_handler(mln_event_t *ev, void *data)
{
    mln_trace("sir", "Hello", getpid(), 3.1);
    mln_event_timer_set(ev, timeout, NULL, timeout_handler);
}

static int recv_handler(mln_lang_ctx_t *ctx, mln_lang_val_t *val)
{
    timeout += val->data.i;
    return 0;
}

int main(int argc, char *argv[])
{
    mln_event_t *ev;
    struct mln_core_attr cattr;

    cattr.argc = argc;
    cattr.argv = argv;
    cattr.global_init = NULL;
    cattr.main_thread = NULL;
    cattr.master_process = NULL;
    cattr.worker_process = NULL;

    if (mln_core_init(&cattr) < 0) {
       fprintf(stderr, "Melon init failed.\n");
       return -1;
    }

    if ((ev = mln_event_new()) == NULL) {
        mln_log(error, "event new error\n");
        return -1;
    }

    if (mln_trace_init(ev, mln_trace_path()) < 0) {
        mln_log(error, "trace init error\n");
        return -1;
    }
    mln_trace_recv_handler_set(recv_handler);

    mln_event_timer_set(ev, 1000, NULL, timeout_handler);

    mln_event_dispatch(ev);

    return 0;
}

简单来描述下程序流程:

  1. 对 Melon 库进行全局初始化(mln_core_init)
  2. 初始化事件对象
  3. 初始化跟踪脚本
  4. 设置用于处理脚本层发来数据的函数
  5. 设置超时事件
  6. 事件分发,超时事件会被触发

在超时处理函数timeout_handler中,我们利用mln_trace向脚本任务发送了三个不同类型的数据,然后继续设置超时事件。

超时时长是一个全局变量timeout,初始为 100 ,即 100 毫秒。

当脚本层发来数据时,这里我们约定脚本层一定发来的是一个整数,那么在接收函数recv_handler中,我们将这一数值与timeout进行累加,作为随后的超时时长。

由此,可以猜测,程序中每秒向脚本层投递的数据量会越来越少。

下面给出脚本层代码:

sys = Import('sys');
if (MASTER)
    sys.print('master process');
else
    sys.print('worker process');

Pipe('subscribe');
while (1) {
    ret = Pipe('recv');
    if (ret) {
        for (i = 0; i < sys.size(ret); ++i) {
            sys.print(ret[i]);
        }
        Pipe('send', 100);
    } fi
    sys.msleep(1000);
}
Pipe('unsubscribe');

简单描述下脚本层逻辑就是,每秒钟从 C 层接收一批数据,然后向终端输出,且在输出后,向 C 层发送一个整数100

下面来看下程序运行结果:

...
[Hello, 72173, 3.100000, ]
[Hello, 72173, 3.100000, ]
[Hello, 72173, 3.100000, ]
...

会看到很多上述输出,但是读者若自己运行则会发现,每秒钟的输出行数会越来越少,这与我们的程序逻辑是相符合的。

结语

从这一例子中,我们可以看到,我们既可以在脚本侧做跟踪统计,也可以向 C 层施加影响。而最关键的三点是:

  1. C 层中不需要增加额外的统计变量和结构
  2. C 层与脚本层在代码管理层面上是分离的,互不干扰
  3. 两个层面的代码运行在同一个线程下

为了简化演示代码,上面的例子中没有给出在脚本层做网络通信和数据入库,但这些功能 Melang 脚本全部支持,感兴趣的读者可以参考Melang 官网

最后,对于 Melon 库感兴趣的读者可以访问其Github 仓库获取更多信息。

感谢阅读!

857 次点击
所在节点    C
1 条回复
tairan2006
2023-02-04 09:03:29 +08:00
这种现在不是都用 OpenTelemetry 么

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/913004

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX