Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caching #71

Merged
merged 4 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ Usage of go-callvis:
Enable verbose log.
-file string
output filename - omit to use server mode
-cacheDir string
Enable caching to avoid unnecessary re-rendering.
-focus string
Focus specific package using name or import path. (default "main")
-format string
Expand Down
18 changes: 10 additions & 8 deletions analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,18 @@ func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
}

type renderOpts struct {
focus string
group []string
ignore []string
include []string
limit []string
nointer bool
nostd bool
cacheDir string
focus string
group []string
ignore []string
include []string
limit []string
nointer bool
refresh bool
nostd bool
}

func (a *analysis) render(opts renderOpts) ([]byte, error) {
func (a *analysis) render(opts *renderOpts) ([]byte, error) {
var (
err error
ssaPkg *ssa.Package
Expand Down
158 changes: 134 additions & 24 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ package main
import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
)

func analysisSetup() (r renderOpts) {
r = renderOpts{
focus: *focusFlag,
group: []string{*groupFlag},
ignore: []string{*ignoreFlag},
include: []string{*includeFlag},
limit: []string{*limitFlag},
nointer: *nointerFlag,
nostd: *nostdFlag}
cacheDir: *cacheDir,
focus: *focusFlag,
group: []string{*groupFlag},
ignore: []string{*ignoreFlag},
include: []string{*includeFlag},
limit: []string{*limitFlag},
nointer: *nointerFlag,
nostd: *nostdFlag}

return r
}
Expand Down Expand Up @@ -76,6 +80,52 @@ func handler(w http.ResponseWriter, r *http.Request) {
logf(" => handling request: %v", r.URL)
logf("----------------------")

opts := buildOptionsFromRequest(r)

var img string
if img = findCachedImg(opts); img != "" {
log.Println("serving file:", img)
http.ServeFile(w, r, img)
return
}

// Convert list-style args to []string
if e := processListArgs(opts); e != nil {
http.Error(w, "invalid parameters", http.StatusBadRequest)
return
}

output, err := Analysis.render(opts)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

if r.Form.Get("format") == "dot" {
log.Println("writing dot output..")
fmt.Fprint(w, string(output))
return
}

log.Printf("converting dot to %s..\n", *outputFormat)

img, err = dotToImage("", *outputFormat, output)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

err = cacheImg(opts, img)
if err != nil {
http.Error(w, "cache img error: "+err.Error(), http.StatusBadRequest)
return
}

log.Println("serving file:", img)
http.ServeFile(w, r, img)
}

func buildOptionsFromRequest(r *http.Request) *renderOpts {
// get cmdline default for analysis
opts := analysisSetup()

Expand All @@ -91,6 +141,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
if inter := r.FormValue("nointer"); inter != "" {
opts.nointer = true
}
if refresh := r.FormValue("refresh"); refresh != "" {
opts.refresh = true
}
if g := r.FormValue("group"); g != "" {
opts.group[0] = g
}
Expand All @@ -104,32 +157,89 @@ func handler(w http.ResponseWriter, r *http.Request) {
opts.include[0] = inc
}

// Convert list-style args to []string
if e := processListArgs(&opts); e != nil {
http.Error(w, "invalid parameters", http.StatusBadRequest)
return
return &opts
}

func findCachedImg(opts *renderOpts) string {
if opts.cacheDir == "" || opts.refresh {
return ""
}

output, err := Analysis.render(opts)
focus := opts.focus
if focus == "" {
focus = "all"
}
focusFilePath := focus + "." + *outputFormat
absFilePath := filepath.Join(opts.cacheDir, focusFilePath)

if exists, err := pathExists(absFilePath); err != nil || !exists {
log.Println("not cached img:", absFilePath)
return ""
}

log.Println("hit cached img")
return absFilePath
}

func cacheImg(opts *renderOpts, img string) error {
if opts.cacheDir == "" || img == "" {
return nil
}

focus := opts.focus
if focus == "" {
focus = "all"
}
absCacheDirPrefix := filepath.Join(opts.cacheDir, focus)
absCacheDirPath := strings.TrimRightFunc(absCacheDirPrefix, func(r rune) bool {
return r != '\\' && r != '/'
})
err := os.MkdirAll(absCacheDirPath, os.ModePerm)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
return err
}

if r.Form.Get("format") == "dot" {
log.Println("writing dot output..")
fmt.Fprint(w, string(output))
return
absFilePath := absCacheDirPrefix + "." + *outputFormat
_, err = copyFile(img, absFilePath)
if err != nil {
return err
}

log.Printf("converting dot to %s..\n", *outputFormat)
return nil
}

func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

img, err := dotToImage("", *outputFormat, output)
func copyFile(src, dst string) (int64, error) {
sourceFileStat, err := os.Stat(src)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
return 0, err
}

log.Println("serving file:", img)
http.ServeFile(w, r, img)
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}

source, err := os.Open(src)
if err != nil {
return 0, err
}
defer source.Close()

destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
return nBytes, err
}
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var (
skipBrowser = flag.Bool("skipbrowser", false, "Skip opening browser.")
outputFile = flag.String("file", "", "output filename - omit to use server mode")
outputFormat = flag.String("format", "svg", "output file format [svg | png | jpg | ...]")
cacheDir = flag.String("cacheDir", "", "Enable caching to avoid unnecessary re-rendering, you can force rendering by adding 'refresh=true' to the URL query or emptying the cache directory")

debugFlag = flag.Bool("debug", false, "Enable verbose log.")
versionFlag = flag.Bool("version", false, "Show version and exit.")
Expand Down Expand Up @@ -63,7 +64,7 @@ func outputDot(fname string, outputFormat string) {
log.Fatalf("%v\n", e)
}

output, err := Analysis.render(opts)
output, err := Analysis.render(&opts)
if err != nil {
log.Fatalf("%v\n", err)
}
Expand Down