blob: a1da632e0a4ead105cf39c8932539f9e23b363ff [file]
// Copyright 2018 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.
// Output is currently different on 32 bits on go1.17 so skip the examples for
// now. It's a gross hack.
//go:build go1.18 || !386
package stack_test
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime/debug"
"github.com/maruel/panicparse/v2/stack"
)
// Runs a crashing program and converts it to a dense text format like pp does.
func Example_text() {
source := `package main
import "time"
func main() {
c := crashy{}
go func() {
c.die(42.)
}()
select {}
}
type crashy struct{}
func (c crashy) die(f float32) {
time.Sleep(time.Millisecond)
panic(int(f))
}`
// Skipped error handling to make the example shorter.
root, _ := ioutil.TempDir("", "stack")
defer os.RemoveAll(root)
p := filepath.Join(root, "main.go")
ioutil.WriteFile(p, []byte(source), 0600)
// Disable both optimization (-N) and inlining (-l).
c := exec.Command("go", "run", "-gcflags", "-N -l", p)
// This is important, otherwise only the panicking goroutine will be printed.
c.Env = append(os.Environ(), "GOTRACEBACK=1")
raw, _ := c.CombinedOutput()
stream := bytes.NewReader(raw)
s, suffix, err := stack.ScanSnapshot(stream, os.Stdout, stack.DefaultOpts())
if err != nil && err != io.EOF {
log.Fatal(err)
}
// Find out similar goroutine traces and group them into buckets.
buckets := s.Aggregate(stack.AnyValue).Buckets
// Calculate alignment.
srcLen := 0
pkgLen := 0
for _, bucket := range buckets {
for _, line := range bucket.Signature.Stack.Calls {
if l := len(fmt.Sprintf("%s:%d", line.SrcName, line.Line)); l > srcLen {
srcLen = l
}
if l := len(filepath.Base(line.Func.ImportPath)); l > pkgLen {
pkgLen = l
}
}
}
for _, bucket := range buckets {
// Print the goroutine header.
extra := ""
if s := bucket.SleepString(); s != "" {
extra += " [" + s + "]"
}
if bucket.Locked {
extra += " [locked]"
}
if len(bucket.CreatedBy.Calls) != 0 {
extra += fmt.Sprintf(" [Created by %s.%s @ %s:%d]", bucket.CreatedBy.Calls[0].Func.DirName, bucket.CreatedBy.Calls[0].Func.Name, bucket.CreatedBy.Calls[0].SrcName, bucket.CreatedBy.Calls[0].Line)
}
fmt.Printf("%d: %s%s\n", len(bucket.IDs), bucket.State, extra)
// Print the stack lines.
for _, line := range bucket.Stack.Calls {
fmt.Printf(
" %-*s %-*s %s(%s)\n",
pkgLen, line.Func.DirName, srcLen,
fmt.Sprintf("%s:%d", line.SrcName, line.Line),
line.Func.Name, &line.Args)
}
if bucket.Stack.Elided {
io.WriteString(os.Stdout, " (...)\n")
}
}
// If there was any remaining data in the pipe, dump it now.
if len(suffix) != 0 {
os.Stdout.Write(suffix)
}
if err == nil {
io.Copy(os.Stdout, stream)
}
// Output:
// panic: 42
//
// 1: running [Created by main.main @ main.go:7]
// main main.go:17 crashy.die(42)
// main main.go:8 main.func1()
// 1: select (no cases)
// main main.go:10 main()
// exit status 2
}
// Process multiple consecutive goroutine snapshots.
func Example_stream() {
// Stream of stack traces:
var r io.Reader
var w io.Writer
opts := stack.DefaultOpts()
for {
s, suffix, err := stack.ScanSnapshot(r, w, opts)
if s != nil {
// Process the snapshot...
}
if err != nil && err != io.EOF {
if len(suffix) != 0 {
w.Write(suffix)
}
log.Fatal(err)
}
// Prepend the suffix that was read to the rest of the input stream to
// catch the next snapshot signature:
r = io.MultiReader(bytes.NewReader(suffix), r)
}
}
// Converts a stack trace from os.Stdin into HTML on os.Stdout, discarding
// everything else.
func Example_hTML() {
s, _, err := stack.ScanSnapshot(os.Stdin, ioutil.Discard, stack.DefaultOpts())
if err != nil && err != io.EOF {
log.Fatal(err)
}
if s != nil {
s.Aggregate(stack.AnyValue).ToHTML(os.Stdout, "")
}
}
// A sample parseStack function expects a stdlib stacktrace from runtime.Stack or debug.Stack and returns
// the parsed stack object.
func Example_simple() {
parseStack := func(rawStack []byte) stack.Stack {
s, _, err := stack.ScanSnapshot(bytes.NewReader(rawStack), ioutil.Discard, stack.DefaultOpts())
if err != nil && err != io.EOF {
panic(err)
}
if len(s.Goroutines) > 1 {
panic(errors.New("provided stacktrace had more than one goroutine"))
}
return s.Goroutines[0].Signature.Stack
}
parsedStack := parseStack(debug.Stack())
fmt.Printf("parsedStack: %#v", parsedStack)
}