How to Use CGraph's Perf Profiling for Performance Bottleneck Analysis
TLDR: CGraph's built-in perf() API injects a GPerfAspect into every pipeline element to record execution timestamps, automatically identifies the longest critical path, and generates a Graphviz DOT file that visualizes exactly where compute time is spent.
CGraph is an open-source C++ DAG framework maintained at chunelfeng/cgraph. Its perf profiling system allows developers to analyze pipeline performance without modifying individual node implementations. By capturing fine-grained timing data across the graph execution, you can pinpoint hot spots and understand end-to-end latency drivers.
Core Profiling Architecture
The profiling system operates through three coordinated components that inject instrumentation, collect metrics, and render results.
GPerfAspect: The Timing Instrumentation
Located in src/GraphCtrl/GraphPipeline/_GPerf/GPerfAspect.h, the GPerfAspect class is a template-aspect that attaches to every GElement. It stores a GPerfInfo struct for each node and updates timing fields—first_start_ts_, last_finish_ts_, accu_cost_ts_, and loop counts—during the beginRun and finishRun lifecycle hooks.
GPerf: The Orchestration Engine
The static helper class GPerf, implemented in src/GraphCtrl/GraphPipeline/_GPerf/GPerf.cpp, manages the full profiling workflow. It handles aspect injection via GPerf::inject, executes the pipeline, marks the longest critical path with GPerf::markLongestPath, dumps the collected data, and finally restores the original pipeline state through GPerf::recover.
Public API Entry Points
For C++ applications, the entry point is GPipeline::perf, defined in src/GraphCtrl/GraphPipeline/GPipeline.cpp (lines 219-225). Python bindings expose the same functionality through GPipeline::__perf_4py in src/python/PyCGraph.cpp (lines 219-447), allowing pipeline.perf() calls from Python scripts.
The Five-Step Profiling Workflow
When you invoke pipeline->perf(oss), the following sequence executes:
-
Inject Aspect –
GPerf::injectallocates aGPerfInfoobject for each element and attachesGPerfAspect<CFMSec, GPerfInfoPtr>, capturing the pipeline start time once. -
Run Pipeline –
pipeline->process()executes the graph normally. The aspect records first start, last finish, loop count, and accumulated cost for every node. -
Identify Longest Path –
GPerf::markLongestPathwalks all execution paths collected byGOptimizer::collectPathsand flags the sequence with the greatest totalaccu_cost_ts_. -
Dump Visualization –
pipeline->dump(oss)emits a Graphviz DOT description. Each node label includes average cost, total cost, and loop count. Nodes on the longest path receive a distinct visual style (red border) as implemented insrc/GraphCtrl/GraphElement/GElement.cpp(lines 426-434). -
Recover State –
GPerf::recoverremoves the injected aspects and frees temporaryGPerfInfoobjects, leaving the pipeline unchanged for subsequent production runs.
Profiling a C++ Pipeline
The following minimal example demonstrates how to profile a three-node pipeline:
// tutorial_perf.cpp
#include "MyGNode/MyNode1.h"
#include "MyGNode/MyNode2.h"
using namespace CGraph;
int main() {
// 1. Build a simple pipeline
GPipelinePtr pipeline = GPipelineFactory::create();
GElementPtr a, b, c = nullptr;
a = pipeline->createGNode<MyNode1>(GNodeInfo({}, "nodeA", 1));
b = pipeline->createGNode<MyNode2>(GNodeInfo({a}, "nodeB", 2)); // runs twice
c = pipeline->createGNode<MyNode1>(GNodeInfo({b}, "nodeC", 1));
// 2. Register the nodes (optional, same as createGNode)
pipeline->registerGElement<MyNode1>(&a, {}, "nodeA", 1);
pipeline->registerGElement<MyNode2>(&b, {a}, "nodeB", 2);
pipeline->registerGElement<MyNode1>(&c, {b}, "nodeC", 1);
// 3. Execute the pipeline normally
CStatus status = pipeline->process();
CGRAPH_ECHO("process status: %d", status.getCode());
// 4. Run the profiler
std::ostringstream oss;
status = pipeline->perf(oss); // ← core profiling call
CGRAPH_ECHO("perf status: %d", status.getCode());
// 5. Show the DOT output (or write to file)
std::cout << oss.str() << std::endl;
// Paste the output into https://dreampuf.github.io/GraphvizOnline/ to view the graph.
GPipelineFactory::remove(pipeline);
return 0;
}
The key invocation is pipeline->perf(oss), which triggers the complete profiling chain and writes the Graphviz description to the output stream.
Python Profiling Usage
For Python workflows, the profiler is accessible through the PyCGraph wrapper:
from pycgraph import GPipelineFactory
pipeline = GPipelineFactory.create()
# … build pipeline with Python wrapper (see pycgraph docs) …
dot = pipeline.perf() # returns a string containing DOT
print(dot) # or write to "graph.dot"
# Visualise with Graphviz online or `dot -Tpng graph.dot -o graph.png`
The perf() method forwards to GPipeline::__perf_4py, executing the same C++ instrumentation layer while returning the DOT string directly to the Python environment.
Interpreting Performance Results
The generated DOT file contains node labels with precise timing statistics. A typical entry appears as:
nodeB [label="nodeB
[start 12.34ms, finish 45.67ms,
per_cost 33.33ms, total_cost 66.66ms, loop 2]"];
Key metrics to analyze:
- per_cost – Average execution time per loop iteration. High values indicate CPU-intensive nodes.
- total_cost – Accumulated wall-clock time across all executions (
accu_cost_ts_). - loop – Number of times the node was invoked.
- longest path highlighting – Nodes with
in_longest_path_=trueare rendered with a red border, revealing which sequential chain dominates end-to-end latency.
Paste the DOT output into a Graphviz renderer such as GraphvizOnline to visualize the critical path overlay.
Summary
- CGraph's perf profiling requires no code changes to individual nodes; it works via aspect injection through
GPerfAspect. - The profiler captures
first_start_ts_,last_finish_ts_, andaccu_cost_ts_for everyGElementduringbeginRun/finishRun. - The
GPerfhelper insrc/GraphCtrl/GraphPipeline/_GPerf/GPerf.cpporchestrates injection, execution, longest-path analysis, and cleanup. - Invoke profiling in C++ via
pipeline->perf(oss)or in Python viapipeline.perf(), both defined in their respectiveGPipelineimplementations. - Output is a standard Graphviz DOT file that highlights the longest critical path and per-node timing metrics for immediate bottleneck identification.
Frequently Asked Questions
How do I enable profiling without modifying my node implementations?
CGraph's aspect-oriented design allows GPerfAspect to attach transparently to any GElement. Simply call pipeline->perf(oss) after building your pipeline; the framework automatically injects timing instrumentation into every node and removes it after the profiling run, leaving your node code untouched.
What does the "longest path" represent in the DOT output?
The longest path is the execution sequence with the maximum cumulative accu_cost_ts_ from start to finish. GPerf::markLongestPath calculates this by walking all paths collected via GOptimizer::collectPaths. Nodes on this path are visually highlighted (typically with red borders) because they represent the bottleneck chain that determines total pipeline latency.
Can I profile a pipeline that runs multiple iterations or has dynamic elements?
Yes. The profiler records first_start_ts_ on initial entry and last_finish_ts_ on final exit, while accumulating total cost and loop counts. This design supports nodes that execute multiple times (e.g., loop > 1) or pipelines with conditional logic, providing accurate average costs (per_cost) across all invocations.
Where can I visualize the generated Graphviz DOT file?
The string returned by pipeline->perf() or pipeline.perf() is a valid Graphviz DOT description. You can paste it directly into online renderers like GraphvizOnline, use the command-line dot -Tpng graph.dot -o graph.png, or import it into any Graphviz-compatible visualization tool.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →