ondrajz / go-callvis

Visualize call graph of a Go program using Graphviz
https://ofabry.github.io/go-callvis
MIT License
5.91k stars 408 forks source link

Doesn't work with web servers #9

Closed zacps closed 6 years ago

zacps commented 7 years ago

Is this because it doesn't check calls inside the stdlib?

ondrajz commented 7 years ago

What do you mean by 'web servers'? The net/http package? This tool checks all the calls inside program. By default it focuses only main package. Check the options in README. Or show code you are trying to analyze.

zacps commented 7 years ago

I don't see an option to enable stepping into the stdlib, just the nostdlib option. Yes i was taking about net/http. This shows the call to listen and serve but not calls out to handlers.

ondrajz commented 7 years ago

I think I understand now. ~This is probably related to #4 where the issue was similar.~

Basically, only the calls from and to the focused package (main by default) are shown. That means that it will only display calls that are directly called from your focused package to the net/http and ones that are directly from net/http to your focused package. You could use empty string for the -focus flag to show ALL the calls of the program. However this will generate huge output if you don't use -limit or -ignore.

EDIT: Actually I am not sure now... I could help more if you provided some code examples.

zacps commented 7 years ago

So the entry point is main.go which calls a function to setup the routes in another file (same package). After that it calls listen and serve. The routes call into another package called views which calls into another package and so on.

Does specifying a directory (recursively) work?

ondrajz commented 7 years ago

Could you show the command with the arguments you used to generate the output?

Since you provided no example code. I made small example to show that the calls from net/http to handlers are shown.

Code:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    setupRoutes()
    http.ListenAndServe(":4321", nil)
}

func setupRoutes() {
    http.HandleFunc("/", index)
}

func index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "index")
}

Command:

go-callvis playground/callvis | dot -Tpng -o callvis.png 

Output:

callvis

zacps commented 7 years ago

That code works. However when I took the index method out of the main file and moved it into it's own directory it didn't. Even though I explicitly imported it:

go-callvis . ./api/ | dot -Tpng -o callvis.png

output

api/main.go

package api
import (
    "fmt"
    "net/http"
)
func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "index")
}

main.go

package main

import (
    "github.com/zacps/test/api"
    "net/http"
)

func main() {
    setupRoutes()
    http.ListenAndServe(":4321", nil)
}

func setupRoutes() {
    http.HandleFunc("/", api.Index)
}

Not sure what's going on here...

ondrajz commented 7 years ago

I've played around with different code structure and various flag options. I will try to summarize my findings.

Let's start with the command you used: go-callvis . ./api/ | ...

Maybe it doesn't throw error and probably should, but only one argument is supported currently (which has to be a main package). Other arguments after the first one are ignored, because important is the main package where the program starts, and the code analysis can figure out all other calls from there.

The -focus flag is by default set to main since all the programs have it and keeping focus empty by default would generate gigantic output.

Single package

In my example I posted before, it's pretty straightforward since only single package is used.

Output contains all the calls that:

callvis

Multiple packages

If the program consists of multiple packages like in your case, you can choose another package for -focus or use empty focus with relevant -limit and -ignore flags to constraint the size of output.

Code

main.go

package main

import (
    "playground/callvis/api"
    "net/http"
)

func main() {
    api.SetupRoutes()
    http.ListenAndServe(":4321", nil)
}

api/api.go

package api

import (
    "fmt"
    "net/http"
)

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "index")
}

func SetupRoutes() {
    http.HandleFunc("/", Index)
}

Output variants


Focusing package main

Output doesn't show call to http.HandleFunc() and fmt.Fprintf(), because neither caller nor callee are inside focused package.

callvis_main

go-callvis -focus main -group pkg playground/callvis | ...

Focusing package api

Output doesn't show call to http.ListenAndServe(), because neither caller nor callee are inside focused package. However it still shows api.SetupRoutes() call since it is inside api package.

callvis_api

go-callvis -focus api -group pkg playground/callvis | ...

No focusing

By using -limit we can define import path prefix and packages without this prefix will be ignored. Same way the -ignore flag defines import path for ignoring packages. Both can contain multiple prefix paths.

callvis

go-callvis -focus="" -limit playground/callvis -group pkg playground/callvis | ...

Including net/http

We can add net/http package to the -limit to include it's callgraph. This generates huge output, because it contains internal calls inside std packages.

hugethumb

go-callvis -focus="" -limit "net/http,playground/callvis" -group pkg playground/callvis | ...

Conclusion

To me it seems there is missing a way to show output similar to the last one without the internal calls of the std package.

I think the behaviour of -nostd should be changed so it only omits internal calls inside and between std packages. Or some new flag added with default true since I assume that not many users are interested in internal calls inside std package.

I will look into this further to find the most optimal solution.