Following our journey through flame graphs and icicle graphs, we now explore flame charts, a complementary visualization that reintroduces what flame graphs deliberately left behind: the dimension of time. While flame graphs excel at highlighting which functions consume resources, flame charts go a step further by also showing when those resources are consumed.
Try It Now
Before we dive into the details, here's the exciting news: flame charts are now available as an alpha feature in Polar Signals Cloud! Here's how to get started:
Step 1: Enable the Feature Navigate to the Preferences in the visualization section and enable flame charts (currently in alpha).
Step 2: Select Flame Chart Visualization In your profiling view, select "Flame Chart" from the visualization type dropdown:
For complete setup instructions including agent configuration, see our detailed flame charts documentation.
Now, let's dive into what makes flame charts stand out.
Understanding the Distinction
Remember when we discussed how flame graphs sort samples alphabetically to maximize merging? This design choice, while brilliant for identifying bottlenecks, deliberately discards temporal information. Flame charts restore this temporal dimension, providing a complementary view that reveals performance patterns over time.
Key Differences
Aspect | Flame Graphs | Flame Charts |
---|---|---|
X-axis | Alphabetically/Cumulative value sorted samples | Time (chronological order) |
Primary Use | Identifying hotspots | Understanding execution flow |
Merging | Aggressive merging of identical stacks | Temporal order prioritized over merging |
Width | Proportional to total time spent | Proportional to wall clock time |
Anatomy of a Flame Chart
Like flame graphs, flame charts use stacked rectangles to represent function calls. However, the interpretation differs quite a bit, below image gives a high-level idea on how a flame chart is laid out:
Reading a Flame Chart
Prerequisite
To generate meaningful flame charts, you must filter your profiling data to show samples from a single CPU core or thread. This is essential because flame charts display execution over time on a single timeline, threads or vCPUs can't execute two things simultaneously, so filtering by these dimensions ensures each moment in time corresponds to exactly one function in the call stack. Note: Hyperthreading is considered as a separate vCPU in both Parca and Polar Signals Cloud.
When querying profiling data for flame chart visualization, ensure you group by any label that guarantees non-overlapping samples, such as thread_id
or cpu
. For more information on configuring these labels, see the profiling agent documentation.
Interpreting the Flame Chart
Unlike flamegraphs, which aggregate similar function calls into a single stack, flame charts preserve every individual function call in its temporal context. This means you'll see the same function appearing multiple times across the timeline, each instance representing a separate invocation.
Three key points guide flame chart interpretation:
-
Timeline Guide: The top of the flame chart displays a timeline ruler showing timestamps. This provides the temporal reference for all stacks below, allowing you to pinpoint exactly when each function executed.
-
Stack Duration: Each stack rectangle starts at the precise timeline position where execution begins and extends horizontally to where it ends. The width directly represents the function's execution duration.
-
Timeline Gaps: Empty spaces along the timeline indicate periods when the thread wasn't executing on the CPU core, typically due to I/O wait, blocking operations, or CPU scheduling.
An Example
Consider a food delivery app processing multiple orders:
func OrderProcessorMain() {
for {
orders := pollNewOrders()
if len(orders) > 0 {
processOrders(orders)
} else {
// No new orders, sleep for 100ms
time.Sleep(100 * time.Millisecond)
}
}
}
func processOrders(orders []Order) error {
for _, order := range orders {
err := processOrder(order)
if err != nil {
// Log error and continue with next order
continue
}
}
return nil
}
func processOrder(order Order) error {
// Check if restaurant is open and has items
available, err := checkRestaurant(order.RestaurantID, order.Items)
if err != nil || !available {
return err
}
// Find and assign nearest available driver
driver, err := assignDriver(order.RestaurantLocation)
if err != nil {
return err
}
// Send push notification to customer
notifyCustomer(order.CustomerID, driver.ID)
return nil
}
The corresponding flame chart showing the polling loop with order processing:
Key observations from the flame chart:
- 1st iteration (0-350ms): Processes 2 orders
- 2nd iteration (350-380ms): No new orders, goes to sleep for 100ms
- 3rd iteration (480-510ms): No new orders, sleeps again for 100ms
- 4th iteration (610-850ms): Processes 1 order
- 5th iteration (850-880ms): No new orders, sleeps for 100ms
- 6th iteration (980-1010ms): No new orders
This example perfectly demonstrates the power of flame charts: iterations 2 and 5 start immediately after order processing (no gap), while iterations 3, 4, and 6 show 100ms gaps after no-order polls. These timing patterns make control flow decisions visible, something impossible to see in traditional flame graphs. Where flame graphs would merge all pollNewOrders
calls into one stack, flame charts preserve each instance in time, revealing the application's actual behavior and rhythm.
Conclusion
With flame charts now in alpha on Polar Signals Cloud, you have both dimensions of performance analysis at your fingertips, what's consuming resources and when it's happening, a powerful combination for understanding your application's true behavior. Are you now ready to see your application's execution timeline? Start your 14-day free trial and discover what your flame graphs have been hiding!