| // Copyright 2016 Marc-Antoine Ruel. All rights reserved. |
| // Use of this source code is governed under the Apache License, Version 2.0 |
| // that can be found in the LICENSE file. |
| |
| package internal |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/maruel/panicparse/v2/stack" |
| ) |
| |
| // Palette defines the color used. |
| // |
| // An empty object Palette{} can be used to disable coloring. |
| type Palette struct { |
| EOLReset string |
| |
| // Routine header. |
| RoutineFirst string // The first routine printed. |
| Routine string // Following routines. |
| CreatedBy string |
| Race string |
| |
| // Call line. |
| Package string |
| SrcFile string |
| FuncMain string |
| FuncLocationUnknown string |
| FuncLocationUnknownExported string |
| FuncGoMod string |
| FuncGoModExported string |
| FuncGOPATH string |
| FuncGOPATHExported string |
| FuncGoPkg string |
| FuncGoPkgExported string |
| FuncStdLib string |
| FuncStdLibExported string |
| Arguments string |
| } |
| |
| // pathFormat determines how much to show. |
| type pathFormat int |
| |
| const ( |
| fullPath pathFormat = iota |
| relPath |
| basePath |
| ) |
| |
| func (pf pathFormat) formatCall(c *stack.Call) string { |
| switch pf { |
| case relPath: |
| if c.RelSrcPath != "" { |
| return fmt.Sprintf("%s:%d", c.RelSrcPath, c.Line) |
| } |
| fallthrough |
| case fullPath: |
| if c.LocalSrcPath != "" { |
| return fmt.Sprintf("%s:%d", c.LocalSrcPath, c.Line) |
| } |
| return fmt.Sprintf("%s:%d", c.RemoteSrcPath, c.Line) |
| default: |
| return fmt.Sprintf("%s:%d", c.SrcName, c.Line) |
| } |
| } |
| |
| func (pf pathFormat) createdByString(s *stack.Signature) string { |
| if len(s.CreatedBy.Calls) == 0 { |
| return "" |
| } |
| return s.CreatedBy.Calls[0].Func.DirName + "." + s.CreatedBy.Calls[0].Func.Name + " @ " + pf.formatCall(&s.CreatedBy.Calls[0]) |
| } |
| |
| // calcBucketsLengths returns the maximum length of the source lines and |
| // package names. |
| func calcBucketsLengths(a *stack.Aggregated, pf pathFormat) (int, int) { |
| srcLen := 0 |
| pkgLen := 0 |
| for _, e := range a.Buckets { |
| for i := range e.Signature.Stack.Calls { |
| if l := len(pf.formatCall(&e.Signature.Stack.Calls[i])); l > srcLen { |
| srcLen = l |
| } |
| if l := len(e.Signature.Stack.Calls[i].Func.DirName); l > pkgLen { |
| pkgLen = l |
| } |
| } |
| } |
| return srcLen, pkgLen |
| } |
| |
| // calcGoroutinesLengths returns the maximum length of the source lines and |
| // package names. |
| func calcGoroutinesLengths(s *stack.Snapshot, pf pathFormat) (int, int) { |
| srcLen := 0 |
| pkgLen := 0 |
| for _, e := range s.Goroutines { |
| for i := range e.Signature.Stack.Calls { |
| if l := len(pf.formatCall(&e.Signature.Stack.Calls[i])); l > srcLen { |
| srcLen = l |
| } |
| if l := len(e.Signature.Stack.Calls[i].Func.DirName); l > pkgLen { |
| pkgLen = l |
| } |
| } |
| } |
| return srcLen, pkgLen |
| } |
| |
| // functionColor returns the color to be used for the function name based on |
| // the type of package the function is in. |
| func (p *Palette) functionColor(c *stack.Call) string { |
| return p.funcColor(c.Location, c.Func.IsPkgMain, c.Func.IsExported) |
| } |
| |
| func (p *Palette) funcColor(l stack.Location, main, exported bool) string { |
| if main { |
| return p.FuncMain |
| } |
| switch l { |
| default: |
| fallthrough |
| case stack.LocationUnknown: |
| if exported { |
| return p.FuncLocationUnknownExported |
| } |
| return p.FuncLocationUnknown |
| case stack.GoMod: |
| if exported { |
| return p.FuncGoModExported |
| } |
| return p.FuncGoMod |
| case stack.GOPATH: |
| if exported { |
| return p.FuncGOPATHExported |
| } |
| return p.FuncGOPATH |
| case stack.GoPkg: |
| if exported { |
| return p.FuncGoPkgExported |
| } |
| return p.FuncGoPkg |
| case stack.Stdlib: |
| if exported { |
| return p.FuncStdLibExported |
| } |
| return p.FuncStdLib |
| } |
| } |
| |
| // routineColor returns the color for the header of the goroutines bucket. |
| func (p *Palette) routineColor(first, multipleBuckets bool) string { |
| if first && multipleBuckets { |
| return p.RoutineFirst |
| } |
| return p.Routine |
| } |
| |
| // BucketHeader prints the header of a goroutine signature. |
| func (p *Palette) BucketHeader(b *stack.Bucket, pf pathFormat, multipleBuckets bool) string { |
| extra := "" |
| if s := b.SleepString(); s != "" { |
| extra += " [" + s + "]" |
| } |
| if b.Locked { |
| extra += " [locked]" |
| } |
| if c := pf.createdByString(&b.Signature); c != "" { |
| extra += p.CreatedBy + " [Created by " + c + "]" |
| } |
| return fmt.Sprintf( |
| "%s%d: %s%s%s\n", |
| p.routineColor(b.First, multipleBuckets), len(b.IDs), |
| b.State, extra, |
| p.EOLReset) |
| } |
| |
| // GoroutineHeader prints the header of a goroutine. |
| func (p *Palette) GoroutineHeader(g *stack.Goroutine, pf pathFormat, multipleGoroutines bool) string { |
| extra := "" |
| if s := g.SleepString(); s != "" { |
| extra += " [" + s + "]" |
| } |
| if g.Locked { |
| extra += " [locked]" |
| } |
| if c := pf.createdByString(&g.Signature); c != "" { |
| extra += p.CreatedBy + " [Created by " + c + "]" |
| } |
| if g.RaceAddr != 0 { |
| r := "read" |
| if g.RaceWrite { |
| r = "write" |
| } |
| extra += fmt.Sprintf("%s%s Race %s @ 0x%08x", p.EOLReset, p.Race, r, g.RaceAddr) |
| } |
| return fmt.Sprintf( |
| "%s%d: %s%s%s\n", |
| p.routineColor(g.First, multipleGoroutines), g.ID, |
| g.State, extra, |
| p.EOLReset) |
| } |
| |
| // callLine prints one stack line. |
| func (p *Palette) callLine(line *stack.Call, srcLen, pkgLen int, pf pathFormat) string { |
| return fmt.Sprintf( |
| " %s%-*s %s%-*s %s%s%s(%s)%s", |
| p.Package, pkgLen, line.Func.DirName, |
| p.SrcFile, srcLen, pf.formatCall(line), |
| p.functionColor(line), line.Func.Name, |
| p.Arguments, &line.Args, |
| p.EOLReset) |
| } |
| |
| // StackLines prints one complete stack trace, without the header. |
| func (p *Palette) StackLines(signature *stack.Signature, srcLen, pkgLen int, pf pathFormat) string { |
| out := make([]string, len(signature.Stack.Calls)) |
| for i := range signature.Stack.Calls { |
| out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, pf) |
| } |
| if signature.Stack.Elided { |
| out = append(out, " (...)") |
| } |
| return strings.Join(out, "\n") + "\n" |
| } |