Skip to main content

Symflower trace

symflower trace is a task visualization tool that provides insights into application execution behavior. The output of the command is an HTML file with a tree visualization of a trace file.

Usage:

symflower trace -trace-file=${trace file} -output-file=${output file}

The command takes two arguments:

  • --trace-file: path to a trace file
  • --output-file: path to where the HTML tree visualization will be stored.

Use this feature to overcome some limitations of the built-in Go tool for tracing applications with go tool trace:

  • Lack of clear parent-child relationships: symflower trace provides a hierarchical tree visualization over tasks, making it easier to visually identify parent-child relationships.
  • Scalability issues with large task counts: symflower trace can handle many tasks without sacrificing performance or usability.
  • Difficulty in identifying performance outliers: symflower trace color-codes task execution times on a linear scale: green for fast execution times, transitioning through yellow to red for slowest ones.

Tutorial: symflower trace

Let's take the following example of making a sandwich:

package main

import (
"context"
"os"
"runtime/trace"
"sync"
"time"
)

func main() {
traceFile, err := os.Create("sandwich.trace")
if err != nil {
panic(err)
}
if err = trace.Start(traceFile); err != nil {
panic(err)
}
defer func() {
trace.Stop()
if err := traceFile.Close(); err != nil {
panic(err)
}
}()

ctx := context.Background()
ctx, task := trace.NewTask(ctx, "main")
defer task.End()

numOfWorkers := 3
var wg sync.WaitGroup
wg.Add(numOfWorkers)

for i := 0; i < numOfWorkers; i++ {
go func() {
defer wg.Done()
makeSandwich(ctx)
}()
}

wg.Wait()
}

func makeSandwich(ctx context.Context) {
ctx, task := trace.NewTask(ctx, "makeSandwich")
defer task.End()

region := trace.StartRegion(ctx, "Preparation")
prepareIngredients(ctx)
assembleSandwich(ctx)
region.End()
}

func prepareIngredients(ctx context.Context) {
ctx, task := trace.NewTask(ctx, "prepareIngredients")
defer task.End()

time.Sleep(200 * time.Millisecond)
}

func assembleSandwich(ctx context.Context) {
ctx, task := trace.NewTask(ctx, "assembleSandwich")
defer task.End()

time.Sleep(100 * time.Millisecond)
}

At the beginning of the main function, the tracing is initialized with trace.Start and the file sandwich.trace is passed to store tracing data. Three tasks are kept track of:

  • making sandwiches
  • preparing ingredients
  • assembling sandwiches.

context.Context is passed along the functions to establish a relation between tasks and sub-tasks. A region inside makeSandwich is created to understand how long it takes to prepare and assemble a sandwich.

When running the main function go run main.go, the trace file sandwich.trace is created in the root of the project. Use the generated trace file as input to the symflower trace command so it generates a tree visualization of the trace file:

symflower trace --trace-file=sandwich.trace --output-file=sandwich.html

Opening the sandwich.html file in your browser, you will see the following tree structure:

Analyzing application execution behavior with `symflower trace`

Tree analysis:

  • The tasks for making the sandwiches in parallel take over 99% of the main task since they are executed concurrently.
  • Color-coding allows us to identify performance outliers. For example, the makeSandwich node in the middle of the tree is red since it took a longer time to execute compared to the other nodes of the same task.