C语言用户态函数可观测性-综合示例

时间:2024-01-26 12:04:42

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

这里我将给出三个文件:

  • 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)