functiongotreecall-graph

Print Go call tree


Given a file like this:

package main

func A() {}

func B() {
   A()
}

func C() {
   A()
}

func D() {
   B()
}

func E() {
   B()
}

func F() {
   C()
}

func G() {
   C()
}

func main() {
   D()
   E()
   F()
   G()
}

I would like to print a call tree of the program, something like this:

main
   D
      B
         A
   E
      B
         A
   F
      C
         A
   G
      C
         A

I found the callgraph program [1], but it does not create a tree:

PS C:\prog> callgraph .
prog.A  --static-4:5--> prog.C
prog.A  --static-5:5--> prog.D
prog.main       --static-19:5-->        prog.A
prog.B  --static-9:5--> prog.E
prog.B  --static-10:5-->        prog.F
prog.main       --static-20:5-->        prog.B

Is some method available to do this?

  1. https://github.com/golang/tools/blob/master/cmd/callgraph

Solution

  • So I did find a package that seems to handle printing a tree from a graph on the command line [1]. However I thought about it some more, and a printed tree might not be the best solution to my issue. What I want to do, is return an error from one of my functions. However to do that, I need to propagate the error all the way up to main. As this can be several layers, I thought it would be best if I start from main, and work my way down to the desired function. That way, I can the work in stages if need be. The issue is, how do I get an ordered list of these functions? I found a solution with tsort [2]:

    PS C:\> callgraph -format digraph . | coreutils tsort
    "init/test.main"
    "init/test.D"
    "init/test.E"
    "init/test.F"
    "init/test.G"
    "init/test.B"
    "init/test.C"
    "init/test.A"
    

    but I may not always want the entire call graph. Next I thought about just adding a panic:

    func A() {
       panic(1)
    }
    

    but this will not give you all branches, only the first path to the target function:

    main.A(...)
            C:/test.go:4
    main.B(...)
            C:/test.go:8
    main.D(...)
            C:/test.go:16
    main.main()
            C:/test.go:32 +0x45
    

    Finally I wrote my own sort function, that takes arbitrary destination as input, and prints all paths in order from main to the target function:

    package main
    
    func tsort(graph map[string][]string, end string) []string {
       var (
          b = make(map[string]bool)
          l []string
          s = []string{end}
       )
       for len(s) > 0 {
          n := s[len(s) - 1]
          b[n] = true
          for _, m := range graph[n] {
             if ! b[m] {
                s = append(s, m)
             }
          }
          if s[len(s) - 1] == n {
             s = s[:len(s) - 1]
             l = append(l, n)
          }
       }
       return l
    }
    

    Example:

    package main
    
    import (
       "bytes"
       "fmt"
       "os/exec"
    )
    
    func main() {
       b := new(bytes.Buffer)
       c := exec.Command("callgraph", "-format", "digraph", ".")
       c.Stdout = b
       c.Run()
       m := make(map[string][]string)
       for {
          var parent, child string
          _, e := fmt.Fscanln(b, &parent, &child)
          if e != nil { break }
          m[child] = append(m[child], parent)
       }
       for n, s := range tsort(m, `"init/test.A"`) {
          fmt.Print(n+1, ". ", s, "\n")
       }
    }
    

    Result:

    1. "init/test.main"
    2. "init/test.G"
    3. "init/test.F"
    4. "init/test.C"
    5. "init/test.D"
    6. "init/test.E"
    7. "init/test.B"
    8. "init/test.A"
    
    1. https://github.com/soniakeys/graph/blob/master/treevis/treevis.go
    2. https://github.com/uutils/coreutils