如何构建特定函数调用的图表?

How to build a graph of specific function calls?

我有一个项目,我想在其中动态构建特定函数调用的图表。例如,如果我有 2 个模板 类、A 和 B,其中 A 有一个跟踪方法(保存为图形节点)而 B 有 3 个方法(非跟踪方法、跟踪方法和一个调用 A 的跟踪方法的跟踪方法方法),那么我希望能够仅将跟踪的方法调用作为节点注册到图形对象中。图形对象可以是单例。

template <class TA>
class A
{
public:
    void runTracked()
    {
        // do stuff
    }
};

template <class TB>
class B
{
public:
    void runNonTracked()
    {
        // do stuff
    }

    void runTracked()
    {
        // do stuff
    }

    void callATracked()
    {
        auto a = A<TB>();
        a.runTracked();
        // do stuff
    }
};

void root()
{
    auto b1 = B<int>();
    auto b2 = B<double>();
    b1.runTracked();
    b2.runNonTracked();
    b2.callATracked();
    
}

int main()
{
    auto b = B<int>();
    b.runTracked()
    root();
    return 0;
}

这应该输出一个类似于下面的图形对象:

root()
\-- B<int>::runTracked()
\-- B<double>::callATracked()
    \-- A<double>::runTracked()

跟踪的功能应该是可调的。如果根是可调整的(如上例所示),那将是最好的。 有没有简单的方法可以做到这一点?

我正在考虑引入一个用于跟踪功能的宏和一个将跟踪功能注册为节点的单例图对象。但是,我不确定如何确定哪个是调用堆栈中最后跟踪的函数,或者(从图形的角度来看)当我想添加一个新节点时哪个图形节点应该是父节点。

一般来说,你有两种策略:

  1. 使用某种 logging/tracing 框架来检测您的应用程序,然后尝试复制某种类似跟踪混合的功能以应用 global/local 跟踪,具体取决于应用 mixin 的代码。

  2. 使用为您的编译器或运行时启用的某种跟踪检测功能重新编译您的代码,然后使用关联的跟踪 compiler/runtime-specific tools/frameworks 到 transform/sift 通过数据。

对于 1,这将需要您手动插入更多代码或类似的东西 _penter/_pexit for MSVC manually or create some sort of ScopedLogger that would (hopefully!) log async to some external file/stream/process. This is not necessarily a bad thing, as having a separate process control the trace tracking would probably be better in the case where the traced process crashes. Regardless, you'd probably have to refactor your code since C++ does not have great first-class support for metaprogramming to refactor/instrument code at a module/global level. However, this is not an uncommon pattern anyways for larger applications; for example, AWS X-Ray 是商业跟踪服务的示例(不过,通常情况下,我认为它适合跟踪网络调用的用例和RPC 调用而不是进程内函数调用。

对于 2,您可以尝试 utrace or something compiler-specific: MSVC has various tools like Performance Explorer, LLVM has XRay, GCC has gprof。您基本上是在某种“调试 ++”模式下编译,或者有一些特殊的 OS/hardware/compiler 魔法可以自动插入跟踪指令或标记,帮助运行时跟踪您所需的代码。这些启用跟踪的 programs/runtimes 通常会发出某种独特的跟踪格式,然后必须由独特的跟踪格式 reader.

读取

最后,在内存中动态构建图形也是一个类似的故事。与上述跟踪策略一样,有多种应用程序和运行时级别的库可帮助跟踪您可以以编程方式与之交互的代码。即使是创建记录到跟踪文件的 ScopedTracer 对象的最简单版本,也可以配备一个消费者线程,该线程拥有并根据您拥有的任何所需的延迟和数据持久性要求更新跟踪图。