github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/debug/goroutineui/dump.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package goroutineui
    12  
    13  import (
    14  	"bytes"
    15  	"io"
    16  	"io/ioutil"
    17  	"runtime"
    18  	"sort"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/maruel/panicparse/stack"
    23  )
    24  
    25  // stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines.
    26  func stacks() []byte {
    27  	// We don't know how big the traces are, so grow a few times if they don't fit. Start large, though.
    28  	var trace []byte
    29  	for n := 1 << 20; /* 1mb */ n <= (1 << 29); /* 512mb */ n *= 2 {
    30  		trace = make([]byte, n)
    31  		nbytes := runtime.Stack(trace, true /* all */)
    32  		if nbytes < len(trace) {
    33  			return trace[:nbytes]
    34  		}
    35  	}
    36  	return trace
    37  }
    38  
    39  // A Dump wraps a goroutine dump with functionality to output through panicparse.
    40  type Dump struct {
    41  	err error
    42  
    43  	now     time.Time
    44  	buckets []*stack.Bucket
    45  }
    46  
    47  // NewDump grabs a goroutine dump and associates it with the supplied time.
    48  func NewDump(now time.Time) Dump {
    49  	return NewDumpFromBytes(now, stacks())
    50  }
    51  
    52  // NewDumpFromBytes is like NewDump, but treats the supplied bytes as a goroutine
    53  // dump.
    54  func NewDumpFromBytes(now time.Time, b []byte) Dump {
    55  	c, err := stack.ParseDump(bytes.NewReader(b), ioutil.Discard, true /* guesspaths */)
    56  	if err != nil {
    57  		return Dump{err: err}
    58  	}
    59  	return Dump{now: now, buckets: stack.Aggregate(c.Goroutines, stack.AnyValue)}
    60  }
    61  
    62  // SortCountDesc rearranges the goroutine buckets such that higher multiplicities
    63  // appear earlier.
    64  func (d Dump) SortCountDesc() {
    65  	sort.Slice(d.buckets, func(i, j int) bool {
    66  		a, b := d.buckets[i], d.buckets[j]
    67  		return len(a.IDs) > len(b.IDs)
    68  	})
    69  }
    70  
    71  // SortWaitDesc rearranges the goroutine buckets such that goroutines that have
    72  // longer wait times appear earlier.
    73  func (d Dump) SortWaitDesc() {
    74  	sort.Slice(d.buckets, func(i, j int) bool {
    75  		a, b := d.buckets[i], d.buckets[j]
    76  		return a.SleepMax > b.SleepMax
    77  	})
    78  }
    79  
    80  // HTML writes the rendered output of panicparse into the supplied Writer.
    81  func (d Dump) HTML(w io.Writer) error {
    82  	if d.err != nil {
    83  		return d.err
    84  	}
    85  	return writeToHTML(w, d.buckets, d.now)
    86  }
    87  
    88  // HTMLString is like HTML, but returns a string. If an error occurs, its string
    89  // representation is returned.
    90  func (d Dump) HTMLString() string {
    91  	var w strings.Builder
    92  	if err := d.HTML(&w); err != nil {
    93  		return err.Error()
    94  	}
    95  	return w.String()
    96  }