go mod tidy

develop^2
Miguel Mota 3 years ago
parent 9a906c3a68
commit 55ab27095d
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9

@ -13,7 +13,6 @@ require (
github.com/gliderlabs/ssh v0.3.3
github.com/goodsign/monday v1.0.0
github.com/jeandeaual/go-locale v0.0.0-20210323163322-5cf4ff553a8d
github.com/maruel/panicparse v1.6.1
github.com/mattn/go-runewidth v0.0.13
github.com/miguelmota/go-coinmarketcap v0.1.8
github.com/mitchellh/go-wordwrap v1.0.1

@ -155,7 +155,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -225,11 +224,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/maruel/panicparse v1.6.1 h1:803MjBzGcUgE1vYgg3UMNq3G1oyYeKkMu3t6hBS97x0=
github.com/maruel/panicparse v1.6.1/go.mod h1:uoxI4w9gJL6XahaYPMq/z9uadrdr1SyHuQwV2q80Mm0=
github.com/maruel/panicparse/v2 v2.1.1/go.mod h1:AeTWdCE4lcq8OKsLb6cHSj1RWHVSnV9HBCk7sKLF4Jg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -242,7 +237,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miguelmota/go-coinmarketcap v0.1.8 h1:rZhB7xs1j7qxxd1zftjADhAv6ECJQVhBom1dh3zURKY=
github.com/miguelmota/go-coinmarketcap v0.1.8/go.mod h1:hBjej1IiB5+pfj+0cZhnxRkAc2bgky8qWLhCJTQ3zjw=
@ -476,7 +470,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Marc-Antoine Ruel
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,106 +0,0 @@
// Copyright 2015 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 stack
import (
"sort"
)
// Similarity is the level at which two call lines arguments must match to be
// considered similar enough to coalesce them.
type Similarity int
const (
// ExactFlags requires same bits (e.g. Locked).
ExactFlags Similarity = iota
// ExactLines requests the exact same arguments on the call line.
ExactLines
// AnyPointer considers different pointers a similar call line.
AnyPointer
// AnyValue accepts any value as similar call line.
AnyValue
)
// Aggregate merges similar goroutines into buckets.
//
// The buckets are ordered in library provided order of relevancy. You can
// reorder at your choosing.
func Aggregate(goroutines []*Goroutine, similar Similarity) []*Bucket {
type count struct {
ids []int
first bool
}
b := map[*Signature]*count{}
// O(n²). Fix eventually.
for _, routine := range goroutines {
found := false
for key, c := range b {
// When a match is found, this effectively drops the other goroutine ID.
if key.similar(&routine.Signature, similar) {
found = true
c.ids = append(c.ids, routine.ID)
c.first = c.first || routine.First
if !key.equal(&routine.Signature) {
// Almost but not quite equal. There's different pointers passed
// around but the same values. Zap out the different values.
newKey := key.merge(&routine.Signature)
b[newKey] = c
delete(b, key)
}
break
}
}
if !found {
// Create a copy of the Signature, since it will be mutated.
key := &Signature{}
*key = routine.Signature
b[key] = &count{ids: []int{routine.ID}, first: routine.First}
}
}
out := make(buckets, 0, len(b))
for signature, c := range b {
sort.Ints(c.ids)
out = append(out, &Bucket{Signature: *signature, IDs: c.ids, First: c.first})
}
sort.Sort(out)
return out
}
// Bucket is a stack trace signature and the list of goroutines that fits this
// signature.
type Bucket struct {
// Signature is the generalized signature for this bucket.
Signature
// IDs is the ID of each Goroutine with this Signature.
IDs []int
// First is true if this Bucket contains the first goroutine, e.g. the one
// Signature that likely generated the panic() call, if any.
First bool
}
// less does reverse sort.
func (b *Bucket) less(r *Bucket) bool {
if b.First || r.First {
return b.First
}
return b.Signature.less(&r.Signature)
}
//
// buckets is a list of Bucket sorted by repeation count.
type buckets []*Bucket
func (b buckets) Len() int {
return len(b)
}
func (b buckets) Less(i, j int) bool {
return b[i].less(b[j])
}
func (b buckets) Swap(i, j int) {
b[j], b[i] = b[i], b[j]
}

@ -1,935 +0,0 @@
// 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.
//go:generate go get golang.org/x/tools/cmd/stringer
//go:generate stringer -type state
package stack
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
)
// Context is a parsing context.
//
// It contains the deduced GOROOT and GOPATH, if guesspaths is true.
type Context struct {
// Goroutines is the Goroutines found.
//
// They are in the order that they were printed.
Goroutines []*Goroutine
// GOROOT is the GOROOT as detected in the traceback, not the on the host.
//
// It can be empty if no root was determined, for example the traceback
// contains only non-stdlib source references.
//
// Empty is guesspaths was false.
GOROOT string
// GOPATHs is the GOPATH as detected in the traceback, with the value being
// the corresponding path mapped to the host.
//
// It can be empty if only stdlib code is in the traceback or if no local
// sources were matched up. In the general case there is only one entry in
// the map.
//
// Nil is guesspaths was false.
GOPATHs map[string]string
// localGomoduleRoot is the root directory containing go.mod. It is
// considered to be the primary project containing the main executable. It is
// initialized by findRoots().
//
// It only works with stack traces created in the local file system.
localGomoduleRoot string
// gomodImportPath is set to the relative import path that localGomoduleRoot
// represents.
gomodImportPath string
// localgoroot is GOROOT with "/" as path separator. No trailing "/".
localgoroot string
// localgopaths is GOPATH with "/" as path separator. No trailing "/".
localgopaths []string
}
// ParseDump processes the output from runtime.Stack().
//
// Returns nil *Context if no stack trace was detected.
//
// It pipes anything not detected as a panic stack trace from r into out. It
// assumes there is junk before the actual stack trace. The junk is streamed to
// out.
//
// If guesspaths is false, no guessing of GOROOT and GOPATH is done, and Call
// entites do not have LocalSrcPath and IsStdlib filled in. If true, be warned
// that file presence is done, which means some level of disk I/O.
func ParseDump(r io.Reader, out io.Writer, guesspaths bool) (*Context, error) {
goroutines, err := parseDump(r, out)
if len(goroutines) == 0 {
return nil, err
}
c := &Context{
Goroutines: goroutines,
localgoroot: strings.Replace(runtime.GOROOT(), "\\", "/", -1),
localgopaths: getGOPATHs(),
}
nameArguments(goroutines)
// Corresponding local values on the host for Context.
if guesspaths {
c.findRoots()
for _, r := range c.Goroutines {
// Note that this is important to call it even if
// c.GOROOT == c.localgoroot.
r.updateLocations(c.GOROOT, c.localgoroot, c.localGomoduleRoot, c.gomodImportPath, c.GOPATHs)
}
}
return c, err
}
// Private stuff.
func parseDump(r io.Reader, out io.Writer) ([]*Goroutine, error) {
scanner := bufio.NewScanner(r)
scanner.Split(scanLines)
// Do not enable race detection parsing yet, since it cannot be returned in
// Context at the moment.
s := scanningState{}
for scanner.Scan() {
line, err := s.scan(scanner.Text())
if line != "" {
_, _ = io.WriteString(out, line)
}
if err != nil {
return s.goroutines, err
}
}
return s.goroutines, scanner.Err()
}
// scanLines is similar to bufio.ScanLines except that it:
// - doesn't drop '\n'
// - doesn't strip '\r'
// - returns when the data is bufio.MaxScanTokenSize bytes
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
return i + 1, data[0 : i+1], nil
}
if atEOF {
return len(data), data, nil
}
if len(data) >= bufio.MaxScanTokenSize {
// Returns the line even if it is not at EOF nor has a '\n', otherwise the
// scanner will return bufio.ErrTooLong which is definitely not what we
// want.
return len(data), data, nil
}
return 0, nil, nil
}
const (
lockedToThread = "locked to thread"
elided = "...additional frames elided..."
// gotRaceHeader1, normal
raceHeaderFooter = "=================="
// gotRaceHeader2
raceHeader = "WARNING: DATA RACE"
)
// These are effectively constants.
var (
// gotRoutineHeader
reRoutineHeader = regexp.MustCompile("^([ \t]*)goroutine (\\d+) \\[([^\\]]+)\\]\\:$")
reMinutes = regexp.MustCompile(`^(\d+) minutes$`)
// gotUnavail
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
// gotFileFunc, gotRaceOperationFile, gotRaceGoroutineFile
// See gentraceback() in src/runtime/traceback.go for more information.
// - Sometimes the source file comes up as "<autogenerated>". It is the
// compiler than generated these, not the runtime.
// - The tab may be replaced with spaces when a user copy-paste it, handle
// this transparently.
// - "runtime.gopanic" is explicitly replaced with "panic" by gentraceback().
// - The +0x123 byte offset is printed when frame.pc > _func.entry. _func is
// generated by the linker.
// - The +0x123 byte offset is not included with generated code, e.g. unnamed
// functions "func·006()" which is generally go func() { ... }()
// statements. Since the _func is generated at runtime, it's probably why
// _func.entry is not set.
// - C calls may have fp=0x123 sp=0x123 appended. I think it normally happens
// when a signal is not correctly handled. It is printed with m.throwing>0.
// These are discarded.
// - For cgo, the source file may be "??".
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+(?:| pc=0x[0-9a-f]+))$")
// gotCreated
// Sadly, it doesn't note the goroutine number so we could cascade them per
// parenthood.
reCreated = regexp.MustCompile("^created by (.+)$")
// gotFunc, gotRaceOperationFunc, gotRaceGoroutineFunc
reFunc = regexp.MustCompile(`^(.+)\((.*)\)$`)
// Race:
// See https://github.com/llvm/llvm-project/blob/master/compiler-rt/lib/tsan/rtl/tsan_report.cpp
// for the code generating these messages. Please note only the block in
// #else // #if !SANITIZER_GO
// is used.
// TODO(maruel): " [failed to restore the stack]\n\n"
// TODO(maruel): "Global var %s of size %zu at %p declared at %s:%zu\n"
// gotRaceOperationHeader
reRaceOperationHeader = regexp.MustCompile(`^(Read|Write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
// gotRaceOperationHeader
reRacePreviousOperationHeader = regexp.MustCompile(`^Previous (read|write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
// gotRaceGoroutineHeader
reRaceGoroutine = regexp.MustCompile(`^Goroutine (\d+) \((running|finished)\) created at:$`)
// TODO(maruel): Use it.
//reRacePreviousOperationMainHeader = regexp.MustCompile("^Previous (read|write) at (0x[0-9a-f]+) by main goroutine:$")
)
// state is the state of the scan to detect and process a stack trace.
type state int
// Initial state is normal. Other states are when a stack trace is detected.
const (
// Outside a stack trace.
// to: gotRoutineHeader, raceHeader1
normal state = iota
// Panic stack trace:
// Signature: ""
// An empty line between goroutines.
// from: gotFileCreated, gotFileFunc
// to: gotRoutineHeader, normal
betweenRoutine
// Regexp: reRoutineHeader
// Signature: "goroutine 1 [running]:"
// Goroutine header was found.
// from: normal
// to: gotUnavail, gotFunc
gotRoutineHeader
// Regexp: reFunc
// Signature: "main.main()"
// Function call line was found.
// from: gotRoutineHeader
// to: gotFileFunc
gotFunc
// Regexp: reCreated
// Signature: "created by main.glob..func4"
// Goroutine creation line was found.
// from: gotFileFunc
// to: gotFileCreated
gotCreated
// Regexp: reFile
// Signature: "\t/foo/bar/baz.go:116 +0x35"
// File header was found.
// from: gotFunc
// to: gotFunc, gotCreated, betweenRoutine, normal
gotFileFunc
// Regexp: reFile
// Signature: "\t/foo/bar/baz.go:116 +0x35"
// File header was found.
// from: gotCreated
// to: betweenRoutine, normal
gotFileCreated
// Regexp: reUnavail
// Signature: "goroutine running on other thread; stack unavailable"
// State when the goroutine stack is instead is reUnavail.
// from: gotRoutineHeader
// to: betweenRoutine, gotCreated
gotUnavail
// Race detector:
// Constant: raceHeaderFooter
// Signature: "=================="
// from: normal
// to: normal, gotRaceHeader2
gotRaceHeader1
// Constant: raceHeader
// Signature: "WARNING: DATA RACE"
// from: gotRaceHeader1
// to: normal, gotRaceOperationHeader
gotRaceHeader2
// Regexp: reRaceOperationHeader, reRacePreviousOperationHeader
// Signature: "Read at 0x00c0000e4030 by goroutine 7:"
// A race operation was found.
// from: gotRaceHeader2
// to: normal, gotRaceOperationFunc
gotRaceOperationHeader
// Regexp: reFunc
// Signature: " main.panicRace.func1()"
// Function that caused the race.
// from: gotRaceOperationHeader
// to: normal, gotRaceOperationFile
gotRaceOperationFunc
// Regexp: reFile
// Signature: "\t/foo/bar/baz.go:116 +0x35"
// File header that caused the race.
// from: gotRaceOperationFunc
// to: normal, betweenRaceOperations, gotRaceOperationFunc
gotRaceOperationFile
// Signature: ""
// Empty line between race operations or just after.
// from: gotRaceOperationFile
// to: normal, gotRaceOperationHeader, gotRaceGoroutineHeader
betweenRaceOperations
// Regexp: reRaceGoroutine
// Signature: "Goroutine 7 (running) created at:"
// Goroutine header.
// from: betweenRaceOperations, betweenRaceGoroutines
// to: normal, gotRaceOperationHeader
gotRaceGoroutineHeader
// Regexp: reFunc
// Signature: " main.panicRace.func1()"
// Function that caused the race.
// from: gotRaceGoroutineHeader
// to: normal, gotRaceGoroutineFile
gotRaceGoroutineFunc
// Regexp: reFile
// Signature: "\t/foo/bar/baz.go:116 +0x35"
// File header that caused the race.
// from: gotRaceGoroutineFunc
// to: normal, betweenRaceGoroutines
gotRaceGoroutineFile
// Signature: ""
// Empty line between race stack traces.
// from: gotRaceGoroutineFile
// to: normal, gotRaceGoroutineHeader
betweenRaceGoroutines
)
// raceOp is one of the detected data race operation as detected by the race
// detector.
type raceOp struct {
write bool
addr uint64
id int
create Stack
}
// scanningState is the state of the scan to detect and process a stack trace
// and stores the traces found.
type scanningState struct {
// Determines if race detection is enabled. Currently false since scan()
// would swallow the race detector output, but the data is not part of
// Context yet.
raceDetectionEnabled bool
// goroutines contains all the goroutines found.
goroutines []*Goroutine
state state
prefix string
races map[int]*raceOp
goroutineID int
}
// scan scans one line, updates goroutines and move to the next state.
//
// TODO(maruel): Handle corrupted stack cases:
// - missed stack barrier
// - found next stack barrier at 0x123; expected
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
func (s *scanningState) scan(line string) (string, error) {
/* This is very useful to debug issues in the state machine.
defer func() {
log.Printf("scan(%q) -> %s", line, s.state)
}()
//*/
var cur *Goroutine
if len(s.goroutines) != 0 {
cur = s.goroutines[len(s.goroutines)-1]
}
trimmed := line
if strings.HasSuffix(line, "\r\n") {
trimmed = line[:len(line)-2]
} else if strings.HasSuffix(line, "\n") {
trimmed = line[:len(line)-1]
} else {
// There's two cases:
// - It's the end of the stream and it's not terminating with EOL character.
// - The line is longer than bufio.MaxScanTokenSize
if s.state == normal {
return line, nil
}
// Let it flow. It's possible the last line was trimmed and we still want to parse it.
}
if trimmed != "" && s.prefix != "" {
// This can only be the case if s.state != normal or the line is empty.
if !strings.HasPrefix(trimmed, s.prefix) {
prefix := s.prefix
s.state = normal
s.prefix = ""
return "", fmt.Errorf("inconsistent indentation: %q, expected %q", trimmed, prefix)
}
trimmed = trimmed[len(s.prefix):]
}
switch s.state {
case normal:
// We could look for '^panic:' but this is more risky, there can be a lot
// of junk between this and the stack dump.
fallthrough
case betweenRoutine:
// Look for a goroutine header.
if match := reRoutineHeader.FindStringSubmatch(trimmed); match != nil {
if id, err := strconv.Atoi(match[2]); err == nil {
// See runtime/traceback.go.
// "<state>, \d+ minutes, locked to thread"
items := strings.Split(match[3], ", ")
sleep := 0
locked := false
for i := 1; i < len(items); i++ {
if items[i] == lockedToThread {
locked = true
continue
}
// Look for duration, if any.
if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil {
sleep, _ = strconv.Atoi(match2[1])
}
}
g := &Goroutine{
Signature: Signature{
State: items[0],
SleepMin: sleep,
SleepMax: sleep,
Locked: locked,
},
ID: id,
First: len(s.goroutines) == 0,
}
// Increase performance by always allocating 4 goroutines minimally.
if s.goroutines == nil {
s.goroutines = make([]*Goroutine, 0, 4)
}
s.goroutines = append(s.goroutines, g)
s.state = gotRoutineHeader
s.prefix = match[1]
return "", nil
}
}
// Switch to race detection mode.
if s.raceDetectionEnabled && trimmed == raceHeaderFooter {
// TODO(maruel): We should buffer it in case the next line is not a
// WARNING so we can output it back.
s.state = gotRaceHeader1
return "", nil
}
// Fallthrough.
s.state = normal
s.prefix = ""
return line, nil
case gotRoutineHeader:
if reUnavail.MatchString(trimmed) {
// Generate a fake stack entry.
cur.Stack.Calls = []Call{{SrcPath: "<unavailable>"}}
// Next line is expected to be an empty line.
s.state = gotUnavail
return "", nil
}
c := Call{}
if found, err := parseFunc(&c, trimmed); found {
// Increase performance by always allocating 4 calls minimally.
if cur.Stack.Calls == nil {
cur.Stack.Calls = make([]Call, 0, 4)
}
cur.Stack.Calls = append(cur.Stack.Calls, c)
s.state = gotFunc
return "", err
}
return "", fmt.Errorf("expected a function after a goroutine header, got: %q", strings.TrimSpace(trimmed))
case gotFunc:
// cur.Stack.Calls is guaranteed to have at least one item.
if found, err := parseFile(&cur.Stack.Calls[len(cur.Stack.Calls)-1], trimmed); err != nil {
return "", err
} else if !found {
return "", fmt.Errorf("expected a file after a function, got: %q", strings.TrimSpace(trimmed))
}
s.state = gotFileFunc
return "", nil
case gotCreated:
if found, err := parseFile(&cur.CreatedBy, trimmed); err != nil {
return "", err
} else if !found {
return "", fmt.Errorf("expected a file after a created line, got: %q", trimmed)
}
s.state = gotFileCreated
return "", nil
case gotFileFunc:
if match := reCreated.FindStringSubmatch(trimmed); match != nil {
cur.CreatedBy.Func.Raw = match[1]
s.state = gotCreated
return "", nil
}
if elided == trimmed {
cur.Stack.Elided = true
// TODO(maruel): New state.
return "", nil
}
c := Call{}
if found, err := parseFunc(&c, trimmed); found {
// Increase performance by always allocating 4 calls minimally.
if cur.Stack.Calls == nil {
cur.Stack.Calls = make([]Call, 0, 4)
}
cur.Stack.Calls = append(cur.Stack.Calls, c)
s.state = gotFunc
return "", err
}
if trimmed == "" {
s.state = betweenRoutine
return "", nil
}
// Back to normal state.
s.state = normal
s.prefix = ""
return line, nil
case gotFileCreated:
if trimmed == "" {
s.state = betweenRoutine
return "", nil
}
s.state = normal
s.prefix = ""
return line, nil
case gotUnavail:
if trimmed == "" {
s.state = betweenRoutine
return "", nil
}
if match := reCreated.FindStringSubmatch(trimmed); match != nil {
cur.CreatedBy.Func.Raw = match[1]
s.state = gotCreated
return "", nil
}
return "", fmt.Errorf("expected empty line after unavailable stack, got: %q", strings.TrimSpace(trimmed))
// Race detector.
case gotRaceHeader1:
if raceHeader == trimmed {
// TODO(maruel): We should buffer it in case the next line is not a
// WARNING so we can output it back.
s.state = gotRaceHeader2
return "", nil
}
s.state = normal
return line, nil
case gotRaceHeader2:
if match := reRaceOperationHeader.FindStringSubmatch(trimmed); match != nil {
w := match[1] == "Write"
addr, err := strconv.ParseUint(match[2], 0, 64)
if err != nil {
return "", fmt.Errorf("failed to parse address on line: %q", strings.TrimSpace(trimmed))
}
id, err := strconv.Atoi(match[3])
if err != nil {
return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
}
if s.races != nil {
panic("internal failure; expected s.races to be nil")
}
if s.goroutines != nil {
panic("internal failure; expected s.goroutines to be nil")
}
s.races = make(map[int]*raceOp, 4)
s.races[id] = &raceOp{write: w, addr: addr, id: id}
s.goroutines = append(make([]*Goroutine, 0, 4), &Goroutine{ID: id, First: true})
s.goroutineID = id
s.state = gotRaceOperationHeader
return "", nil
}
s.state = normal
return line, nil
case gotRaceOperationHeader:
c := Call{}
if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
// Increase performance by always allocating 4 calls minimally.
if cur.Stack.Calls == nil {
cur.Stack.Calls = make([]Call, 0, 4)
}
cur.Stack.Calls = append(cur.Stack.Calls, c)
s.state = gotRaceOperationFunc
return "", err
}
return "", fmt.Errorf("expected a function after a race operation, got: %q", trimmed)
case gotRaceOperationFunc:
if found, err := parseFile(&cur.Stack.Calls[len(cur.Stack.Calls)-1], trimmed); err != nil {
return "", err
} else if !found {
return "", fmt.Errorf("expected a file after a race function, got: %q", trimmed)
}
s.state = gotRaceOperationFile
return "", nil
case gotRaceOperationFile:
if trimmed == "" {
s.state = betweenRaceOperations
return "", nil
}
c := Call{}
if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
cur.Stack.Calls = append(cur.Stack.Calls, c)
s.state = gotRaceOperationFunc
return "", err
}
return "", fmt.Errorf("expected an empty line after a race file, got: %q", trimmed)
case betweenRaceOperations:
// Look for other previous race data operations.
if match := reRacePreviousOperationHeader.FindStringSubmatch(trimmed); match != nil {
w := match[1] == "write"
addr, err := strconv.ParseUint(match[2], 0, 64)
if err != nil {
return "", fmt.Errorf("failed to parse address on line: %q", strings.TrimSpace(trimmed))
}
id, err := strconv.Atoi(match[3])
if err != nil {
return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
}
s.goroutineID = id
s.races[s.goroutineID] = &raceOp{write: w, addr: addr, id: id}
s.goroutines = append(s.goroutines, &Goroutine{ID: id})
s.state = gotRaceOperationHeader
return "", nil
}
fallthrough
case betweenRaceGoroutines:
if match := reRaceGoroutine.FindStringSubmatch(trimmed); match != nil {
id, err := strconv.Atoi(match[1])
if err != nil {
return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
}
found := false
for _, g := range s.goroutines {
if g.ID == id {
g.State = match[2]
found = true
break
}
}
if !found {
return "", fmt.Errorf("unexpected goroutine ID on line: %q", strings.TrimSpace(trimmed))
}
s.goroutineID = id
s.state = gotRaceGoroutineHeader
return "", nil
}
return "", fmt.Errorf("expected an operator or goroutine, got: %q", trimmed)
// Race stack traces
case gotRaceGoroutineFunc:
c := s.races[s.goroutineID].create.Calls
if found, err := parseFile(&c[len(c)-1], trimmed); err != nil {
return "", err
} else if !found {
return "", fmt.Errorf("expected a file after a race function, got: %q", trimmed)
}
// TODO(maruel): Set s.goroutines[].CreatedBy.
s.state = gotRaceGoroutineFile
return "", nil
case gotRaceGoroutineFile:
if trimmed == "" {
s.state = betweenRaceGoroutines
return "", nil
}
if trimmed == raceHeaderFooter {
// Done.
s.state = normal
return "", nil
}
fallthrough
case gotRaceGoroutineHeader:
c := Call{}
if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
// TODO(maruel): Set s.goroutines[].CreatedBy.
s.races[s.goroutineID].create.Calls = append(s.races[s.goroutineID].create.Calls, c)
s.state = gotRaceGoroutineFunc
return "", err
}
return "", fmt.Errorf("expected a function after a race operation or a race file, got: %q", trimmed)
default:
return "", errors.New("internal error")
}
}
// parseFunc only return an error if also returning a Call.
//
// Uses reFunc.
func parseFunc(c *Call, line string) (bool, error) {
if match := reFunc.FindStringSubmatch(line); match != nil {
c.Func.Raw = match[1]
for _, a := range strings.Split(match[2], ", ") {
if a == "..." {
c.Args.Elided = true
continue
}
if a == "" {
// Remaining values were dropped.
break
}
v, err := strconv.ParseUint(a, 0, 64)
if err != nil {
return true, fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
}
// Increase performance by always allocating 4 values minimally.
if c.Args.Values == nil {
c.Args.Values = make([]Arg, 0, 4)
}
c.Args.Values = append(c.Args.Values, Arg{Value: v})
}
return true, nil
}
return false, nil
}
// parseFile only return an error if also processing a Call.
//
// Uses reFile.
func parseFile(c *Call, line string) (bool, error) {
if match := reFile.FindStringSubmatch(line); match != nil {
num, err := strconv.Atoi(match[2])
if err != nil {
return true, fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
}
c.SrcPath = match[1]
c.Line = num
return true, nil
}
return false, nil
}
// hasSrcPrefix returns true if any of s is the prefix of p.
func hasSrcPrefix(p string, s map[string]string) bool {
for prefix := range s {
if strings.HasPrefix(p, prefix+"/src/") || strings.HasPrefix(p, prefix+"/pkg/mod/") {
return true
}
}
return false
}
// getFiles returns all the source files deduped and ordered.
func getFiles(goroutines []*Goroutine) []string {
files := map[string]struct{}{}
for _, g := range goroutines {
for _, c := range g.Stack.Calls {
files[c.SrcPath] = struct{}{}
}
}
if len(files) == 0 {
return nil
}
out := make([]string, 0, len(files))
for f := range files {
out = append(out, f)
}
sort.Strings(out)
return out
}
// splitPath splits a path using "/" as separator into its components.
//
// The first item has its initial path separator kept.
func splitPath(p string) []string {
if p == "" {
return nil
}
var out []string
s := ""
for _, c := range p {
if c != '/' || (len(out) == 0 && strings.Count(s, "/") == len(s)) {
s += string(c)
} else if s != "" {
out = append(out, s)
s = ""
}
}
if s != "" {
out = append(out, s)
}
return out
}
// isFile returns true if the path is a valid file.
func isFile(p string) bool {
// TODO(maruel): Is it faster to open the file or to stat it? Worth a perf
// test on Windows.
i, err := os.Stat(p)
return err == nil && !i.IsDir()
}
// rootedIn returns a root if the file split in parts is rooted in root.
//
// Uses "/" as path separator.
func rootedIn(root string, parts []string) string {
//log.Printf("rootIn(%s, %v)", root, parts)
for i := 1; i < len(parts); i++ {
suffix := pathJoin(parts[i:]...)
if isFile(pathJoin(root, suffix)) {
return pathJoin(parts[:i]...)
}
}
return ""
}
// reModule find the module line in a go.mod file. It works even on CRLF file.
var reModule = regexp.MustCompile(`(?m)^module\s+([^\n\r]+)\r?$`)
// isGoModule returns the string to the directory containing a go.mod/go.sum
// files pair, and the go import path it represents, if found.
func isGoModule(parts []string) (string, string) {
for i := len(parts); i > 0; i-- {
prefix := pathJoin(parts[:i]...)
if isFile(pathJoin(prefix, "go.sum")) {
b, err := ioutil.ReadFile(pathJoin(prefix, "go.mod"))
if err != nil {
continue
}
if match := reModule.FindSubmatch(b); match != nil {
return prefix, string(match[1])
}
}
}
return "", ""
}
// findRoots sets member GOROOT, GOPATHs and localGomoduleRoot.
//
// This causes disk I/O as it checks for file presence.
//
// Returns the number of missing files.
func (c *Context) findRoots() int {
c.GOPATHs = map[string]string{}
missing := 0
for _, f := range getFiles(c.Goroutines) {
// TODO(maruel): Could a stack dump have mixed cases? I think it's
// possible, need to confirm and handle.
//log.Printf(" Analyzing %s", f)
// First checks skip file I/O.
if c.GOROOT != "" && strings.HasPrefix(f, c.GOROOT+"/src/") {
// stdlib.
continue
}
if hasSrcPrefix(f, c.GOPATHs) {
// $GOPATH/src or go.mod dependency in $GOPATH/pkg/mod.
continue
}
// At this point, disk will be looked up.
parts := splitPath(f)
if c.GOROOT == "" {
if r := rootedIn(c.localgoroot+"/src", parts); r != "" {
c.GOROOT = r[:len(r)-4]
//log.Printf("Found GOROOT=%s", c.GOROOT)
continue
}
}
found := false
for _, l := range c.localgopaths {
if r := rootedIn(l+"/src", parts); r != "" {
//log.Printf("Found GOPATH=%s", r[:len(r)-4])
c.GOPATHs[r[:len(r)-4]] = l
found = true
break
}
if r := rootedIn(l+"/pkg/mod", parts); r != "" {
//log.Printf("Found GOPATH=%s", r[:len(r)-8])
c.GOPATHs[r[:len(r)-8]] = l
found = true
break
}
}
// If the source is not found, it's probably a go module.
if !found {
if c.localGomoduleRoot == "" && len(parts) > 1 {
// Search upward looking for a go.mod/go.sum pair.
c.localGomoduleRoot, c.gomodImportPath = isGoModule(parts[:len(parts)-1])
}
if c.localGomoduleRoot != "" && strings.HasPrefix(f, c.localGomoduleRoot+"/") {
continue
}
}
if !found {
// If the source is not found, just too bad.
//log.Printf("Failed to find locally: %s", f)
missing++
}
}
return missing
}
// getGOPATHs returns parsed GOPATH or its default, using "/" as path separator.
func getGOPATHs() []string {
var out []string
if gp := os.Getenv("GOPATH"); gp != "" {
for _, v := range filepath.SplitList(gp) {
// Disallow non-absolute paths?
if v != "" {
v = strings.Replace(v, "\\", "/", -1)
// Trim trailing "/".
if l := len(v); v[l-1] == '/' {
v = v[:l-1]
}
out = append(out, v)
}
}
}
if len(out) == 0 {
homeDir := ""
u, err := user.Current()
if err != nil {
homeDir = os.Getenv("HOME")
if homeDir == "" {
panic(fmt.Sprintf("Could not get current user or $HOME: %s\n", err.Error()))
}
} else {
homeDir = u.HomeDir
}
out = []string{strings.Replace(homeDir+"/go", "\\", "/", -1)}
}
return out
}

@ -1,302 +0,0 @@
// Copyright 2015 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.
// This file contains the code to process sources, to be able to deduct the
// original types.
package stack
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"math"
"strings"
)
// cache is a cache of sources on the file system.
type cache struct {
files map[string][]byte
parsed map[string]*parsedFile
}
// Augment processes source files to improve calls to be more descriptive.
//
// It modifies goroutines in place. It requires calling ParseDump() with
// guesspaths set to true to work properly.
func Augment(goroutines []*Goroutine) {
c := &cache{}
for _, g := range goroutines {
c.augmentGoroutine(g)
}
}
// augmentGoroutine processes source files to improve call to be more
// descriptive.
//
// It modifies the routine.
func (c *cache) augmentGoroutine(goroutine *Goroutine) {
if c.files == nil {
c.files = map[string][]byte{}
}
if c.parsed == nil {
c.parsed = map[string]*parsedFile{}
}
// For each call site, look at the next call and populate it. Then we can
// walk back and reformat things.
for i := range goroutine.Stack.Calls {
c.load(goroutine.Stack.Calls[i].LocalSrcPath)
}
// Once all loaded, we can look at the next call when available.
for i := 0; i < len(goroutine.Stack.Calls)-1; i++ {
// Get the AST from the previous call and process the call line with it.
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
processCall(&goroutine.Stack.Calls[i], f)
}
}
}
// Private stuff.
// load loads a source file and parses the AST tree. Failures are ignored.
func (c *cache) load(fileName string) {
if fileName == "" {
return
}
if _, ok := c.parsed[fileName]; ok {
return
}
c.parsed[fileName] = nil
if !strings.HasSuffix(fileName, ".go") {
// Ignore C and assembly.
c.files[fileName] = nil
return
}
//log.Printf("load(%s)", fileName)
if _, ok := c.files[fileName]; !ok {
var err error
if c.files[fileName], err = ioutil.ReadFile(fileName); err != nil {
log.Printf("Failed to read %s: %s", fileName, err)
c.files[fileName] = nil
return
}
}
fset := token.NewFileSet()
src := c.files[fileName]
parsed, err := parser.ParseFile(fset, fileName, src, 0)
if err != nil {
log.Printf("Failed to parse %s: %s", fileName, err)
return
}
// Convert the line number into raw file offset.
offsets := []int{0, 0}
start := 0
for l := 1; start < len(src); l++ {
start += bytes.IndexByte(src[start:], '\n') + 1
offsets = append(offsets, start)
}
c.parsed[fileName] = &parsedFile{offsets, parsed}
}
func (c *cache) getFuncAST(call *Call) *ast.FuncDecl {
if p := c.parsed[call.LocalSrcPath]; p != nil {
return p.getFuncAST(call.Func.Name(), call.Line)
}
return nil
}
type parsedFile struct {
lineToByteOffset []int
parsed *ast.File
}
// getFuncAST gets the callee site function AST representation for the code
// inside the function f at line l.
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
if len(p.lineToByteOffset) <= l {
// The line number in the stack trace line does not exist in the file. That
// can only mean that the sources on disk do not match the sources used to
// build the binary.
// TODO(maruel): This should be surfaced, so that source parsing is
// completely ignored.
return
}
// Walk the AST to find the lineToByteOffset that fits the line number.
var lastFunc *ast.FuncDecl
// Inspect() goes depth first. This means for example that a function like:
// func a() {
// b := func() {}
// c()
// }
//
// Were we are looking at the c() call can return confused values. It is
// important to look at the actual ast.Node hierarchy.
ast.Inspect(p.parsed, func(n ast.Node) bool {
if d != nil {
return false
}
if n == nil {
return true
}
if int(n.Pos()) >= p.lineToByteOffset[l] {
// We are expecting a ast.CallExpr node. It can be harder to figure out
// when there are multiple calls on a single line, as the stack trace
// doesn't have file byte offset information, only line based.
// gofmt will always format to one function call per line but there can
// be edge cases, like:
// a = A{Foo(), Bar()}
d = lastFunc
//p.processNode(call, n)
return false
} else if f, ok := n.(*ast.FuncDecl); ok {
lastFunc = f
}
return true
})
return
}
func name(n ast.Node) string {
switch t := n.(type) {
case *ast.InterfaceType:
return "interface{}"
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
return t.Sel.Name
case *ast.StarExpr:
return "*" + name(t.X)
default:
return "<unknown>"
}
}
// fieldToType returns the type name and whether if it's an ellipsis.
func fieldToType(f *ast.Field) (string, bool) {
switch arg := f.Type.(type) {
case *ast.ArrayType:
return "[]" + name(arg.Elt), false
case *ast.Ellipsis:
return name(arg.Elt), true
case *ast.FuncType:
// Do not print the function signature to not overload the trace.
return "func", false
case *ast.Ident:
return arg.Name, false
case *ast.InterfaceType:
return "interface{}", false
case *ast.SelectorExpr:
return arg.Sel.Name, false
case *ast.StarExpr:
return "*" + name(arg.X), false
case *ast.MapType:
return fmt.Sprintf("map[%s]%s", name(arg.Key), name(arg.Value)), false
case *ast.ChanType:
return fmt.Sprintf("chan %s", name(arg.Value)), false
default:
// TODO(maruel): Implement anything missing.
return "<unknown>", false
}
}
// extractArgumentsType returns the name of the type of each input argument.
func extractArgumentsType(f *ast.FuncDecl) ([]string, bool) {
var fields []*ast.Field
if f.Recv != nil {
if len(f.Recv.List) != 1 {
panic("Expect only one receiver; please fix panicparse's code")
}
// If it is an object receiver (vs a pointer receiver), its address is not
// printed in the stack trace so it needs to be ignored.
if _, ok := f.Recv.List[0].Type.(*ast.StarExpr); ok {
fields = append(fields, f.Recv.List[0])
}
}
var types []string
extra := false
for _, arg := range append(fields, f.Type.Params.List...) {
// Assert that extra is only set on the last item of fields?
var t string
t, extra = fieldToType(arg)
mult := len(arg.Names)
if mult == 0 {
mult = 1
}
for i := 0; i < mult; i++ {
types = append(types, t)
}
}
return types, extra
}
// processCall walks the function and populate call accordingly.
func processCall(call *Call, f *ast.FuncDecl) {
values := make([]uint64, len(call.Args.Values))
for i := range call.Args.Values {
values[i] = call.Args.Values[i].Value
}
index := 0
pop := func() uint64 {
if len(values) != 0 {
x := values[0]
values = values[1:]
index++
return x
}
return 0
}
popName := func() string {
n := call.Args.Values[index].Name
v := pop()
if len(n) == 0 {
return fmt.Sprintf("0x%x", v)
}
return n
}
types, extra := extractArgumentsType(f)
for i := 0; len(values) != 0; i++ {
var t string
if i >= len(types) {
if !extra {
// These are unexpected value! Print them as hex.
call.Args.Processed = append(call.Args.Processed, popName())
continue
}
t = types[len(types)-1]
} else {
t = types[i]
}
switch t {
case "float32":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float32frombits(uint32(pop()))))
case "float64":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float64frombits(pop())))
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%d", pop()))
case "string":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s, len=%d)", t, popName(), pop()))
default:
if strings.HasPrefix(t, "*") {
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
} else if strings.HasPrefix(t, "[]") {
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s len=%d cap=%d)", t, popName(), pop(), pop()))
} else {
// Assumes it's an interface. For now, discard the object value, which
// is probably not a good idea.
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
pop()
}
}
if len(values) == 0 && call.Args.Elided {
return
}
}
}

@ -1,733 +0,0 @@
// Copyright 2015 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 stack analyzes stack dump of Go processes and simplifies it.
//
// It is mostly useful on servers will large number of identical goroutines,
// making the crash dump harder to read than strictly necessary.
package stack
import (
"fmt"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
"unicode/utf8"
)
// Func is a function call as read in a goroutine stack trace.
//
// Go stack traces print a mangled function call, this wrapper unmangle the
// string before printing and adds other filtering methods.
//
// The main caveat is that for calls in package main, the package import URL is
// left out.
type Func struct {
Raw string
}
// String return the fully qualified package import path dot function/method
// name.
//
// It returns the unmangled form of .Raw.
func (f *Func) String() string {
s, _ := url.QueryUnescape(f.Raw)
return s
}
// Name returns the function name.
//
// Methods are fully qualified, including the struct type.
func (f *Func) Name() string {
// This works even on Windows as filepath.Base() splits also on "/".
// TODO(maruel): This code will fail on a source file with a dot in its name.
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
if len(parts) == 1 {
return parts[0]
}
return parts[1]
}
// importPath returns the fully qualified package import URL as a guess from
// the function signature.
//
// Not exported because Call.ImportPath() should be called instead, as this
// function can't return the import path for package main.
func (f *Func) importPath() string {
i := strings.LastIndexByte(f.Raw, '/')
if i == -1 {
return ""
}
j := strings.IndexByte(f.Raw[i:], '.')
if j == -1 {
return ""
}
s, _ := url.QueryUnescape(f.Raw[:i+j])
return s
}
// PkgName returns the guessed package name for this function reference.
//
// Since the package name can differ from the package import path, the result
// is incorrect when there's a mismatch between the directory name containing
// the package and the package name.
func (f *Func) PkgName() string {
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
if len(parts) == 1 {
return ""
}
s, _ := url.QueryUnescape(parts[0])
return s
}
// PkgDotName returns "<package>.<func>" format.
//
// Since the package name can differ from the package import path, the result
// is incorrect when there's a mismatch between the directory name containing
// the package and the package name.
func (f *Func) PkgDotName() string {
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
s, _ := url.QueryUnescape(parts[0])
if len(parts) == 1 {
return parts[0]
}
if s != "" || parts[1] != "" {
return s + "." + parts[1]
}
return ""
}
// IsExported returns true if the function is exported.
func (f *Func) IsExported() bool {
name := f.Name()
// TODO(maruel): Something like serverHandler.ServeHTTP in package net/host
// should not be considered exported. We need something similar to the
// decoding done in symbol() in internal/htmlstack.
parts := strings.Split(name, ".")
r, _ := utf8.DecodeRuneInString(parts[len(parts)-1])
if unicode.ToUpper(r) == r {
return true
}
return f.PkgName() == "main" && name == "main"
}
// Arg is an argument on a Call.
type Arg struct {
Value uint64 // Value is the raw value as found in the stack trace
Name string // Name is a pseudo name given to the argument
}
const (
// Assumes all values are above 4MiB and positive are pointers; assuming that
// above half the memory is kernel memory.
//
// This is not always true but this should be good enough to help
// implementing AnyPointer.
pointerFloor = 4 * 1024 * 1024
// Assume the stack was generated with the same bitness (32 vs 64) than the
// code processing it.
pointerCeiling = uint64((^uint(0)) >> 1)
)
// IsPtr returns true if we guess it's a pointer. It's only a guess, it can be
// easily be confused by a bitmask.
func (a *Arg) IsPtr() bool {
return a.Value > pointerFloor && a.Value < pointerCeiling
}
const zeroToNine = "0123456789"
// String prints the argument as the name if present, otherwise as the value.
func (a *Arg) String() string {
if a.Name != "" {
return a.Name
}
if a.Value < uint64(len(zeroToNine)) {
return zeroToNine[a.Value : a.Value+1]
}
return fmt.Sprintf("0x%x", a.Value)
}
// similar returns true if the two Arg are equal or almost but not quite equal.
func (a *Arg) similar(r *Arg, similar Similarity) bool {
switch similar {
case ExactFlags, ExactLines:
return *a == *r
case AnyValue:
return true
case AnyPointer:
if a.IsPtr() != r.IsPtr() {
return false
}
return a.IsPtr() || a.Value == r.Value
default:
return false
}
}
// Args is a series of function call arguments.
type Args struct {
// Values is the arguments as shown on the stack trace. They are mangled via
// simplification.
Values []Arg
// Processed is the arguments generated from processing the source files. It
// can have a length lower than Values.
Processed []string
// Elided when set means there was a trailing ", ...".
Elided bool
}
func (a *Args) String() string {
var v []string
if len(a.Processed) != 0 {
v = a.Processed
} else {
v = make([]string, 0, len(a.Values))
for _, item := range a.Values {
v = append(v, item.String())
}
}
if a.Elided {
v = append(v, "...")
}
return strings.Join(v, ", ")
}
// equal returns true only if both arguments are exactly equal.
func (a *Args) equal(r *Args) bool {
if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
return false
}
for i, l := range a.Values {
if l != r.Values[i] {
return false
}
}
return true
}
// similar returns true if the two Args are equal or almost but not quite
// equal.
func (a *Args) similar(r *Args, similar Similarity) bool {
if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
return false
}
for i := range a.Values {
if !a.Values[i].similar(&r.Values[i], similar) {
return false
}
}
return true
}
// merge merges two similar Args, zapping out differences.
func (a *Args) merge(r *Args) Args {
out := Args{
Values: make([]Arg, len(a.Values)),
Elided: a.Elided,
}
for i, l := range a.Values {
if l != r.Values[i] {
out.Values[i].Name = "*"
out.Values[i].Value = l.Value
} else {
out.Values[i] = l
}
}
return out
}
// Call is an item in the stack trace.
type Call struct {
// SrcPath is the full path name of the source file as seen in the trace.
SrcPath string
// LocalSrcPath is the full path name of the source file as seen in the host,
// if found.
LocalSrcPath string
// Line is the line number.
Line int
// Func is the fully qualified function name (encoded).
Func Func
// Args is the call arguments.
Args Args
// The following are only set if guesspaths is set to true in ParseDump().
// IsStdlib is true if it is a Go standard library function. This includes
// the 'go test' generated main executable.
IsStdlib bool
// RelSrcPath is the relative path to GOROOT or GOPATH. Only set when
// Augment() is called.
RelSrcPath string
}
// equal returns true only if both calls are exactly equal.
func (c *Call) equal(r *Call) bool {
return c.SrcPath == r.SrcPath && c.Line == r.Line && c.Func == r.Func && c.Args.equal(&r.Args)
}
// similar returns true if the two Call are equal or almost but not quite
// equal.
func (c *Call) similar(r *Call, similar Similarity) bool {
return c.SrcPath == r.SrcPath && c.Line == r.Line && c.Func == r.Func && c.Args.similar(&r.Args, similar)
}
// merge merges two similar Call, zapping out differences.
func (c *Call) merge(r *Call) Call {
return Call{
SrcPath: c.SrcPath,
LocalSrcPath: c.LocalSrcPath,
Line: c.Line,
Func: c.Func,
Args: c.Args.merge(&r.Args),
IsStdlib: c.IsStdlib,
RelSrcPath: c.RelSrcPath,
}
}
// SrcName returns the base file name of the source file.
func (c *Call) SrcName() string {
return filepath.Base(c.SrcPath)
}
// SrcLine returns "source.go:line", including only the base file name.
//
// Deprecated: Format it yourself, will be removed in v2.
func (c *Call) SrcLine() string {
return fmt.Sprintf("%s:%d", c.SrcName(), c.Line)
}
// FullSrcLine returns "/path/to/source.go:line".
//
// This file path is mutated to look like the local path.
//
// Deprecated: Format it yourself, will be removed in v2.
func (c *Call) FullSrcLine() string {
return fmt.Sprintf("%s:%d", c.SrcPath, c.Line)
}
// PkgSrc returns one directory plus the file name of the source file.
//
// Since the package name can differ from the package import path, the result
// is incorrect when there's a mismatch between the directory name containing
// the package and the package name.
func (c *Call) PkgSrc() string {
return pathJoin(filepath.Base(filepath.Dir(c.SrcPath)), c.SrcName())
}
// IsPkgMain returns true if it is in the main package.
func (c *Call) IsPkgMain() bool {
return c.Func.PkgName() == "main"
}
// ImportPath returns the fully qualified package import path.
//
// In the case of package "main", it returns the underlying path to the main
// package instead of "main" if guesspaths=true was specified to ParseDump().
func (c *Call) ImportPath() string {
// In case guesspath=true was passed to ParseDump().
if c.RelSrcPath != "" {
if i := strings.LastIndexByte(c.RelSrcPath, '/'); i != -1 {
return c.RelSrcPath[:i]
}
}
// Fallback to best effort.
if !c.IsPkgMain() {
return c.Func.importPath()
}
// In package main, it can only work well if guesspath=true was used. Return
// an empty string instead of garbagge.
return ""
}
const testMainSrc = "_test" + string(os.PathSeparator) + "_testmain.go"
// updateLocations initializes LocalSrcPath, RelSrcPath and IsStdlib.
//
// goroot, localgoroot, localgomod, gomodImportPath and gopaths are expected to
// be in "/" format even on Windows. They must not have a trailing "/".
func (c *Call) updateLocations(goroot, localgoroot, localgomod, gomodImportPath string, gopaths map[string]string) {
if c.SrcPath == "" {
return
}
// Check GOROOT first.
if goroot != "" {
if prefix := goroot + "/src/"; strings.HasPrefix(c.SrcPath, prefix) {
// Replace remote GOROOT with local GOROOT.
c.RelSrcPath = c.SrcPath[len(prefix):]
c.LocalSrcPath = pathJoin(localgoroot, "src", c.RelSrcPath)
c.IsStdlib = true
goto done
}
}
// Check GOPATH.
// TODO(maruel): Sort for deterministic behavior?
for prefix, dest := range gopaths {
if p := prefix + "/src/"; strings.HasPrefix(c.SrcPath, p) {
c.RelSrcPath = c.SrcPath[len(p):]
c.LocalSrcPath = pathJoin(dest, "src", c.RelSrcPath)
goto done
}
// For modules, the path has to be altered, as it contains the version.
if p := prefix + "/pkg/mod/"; strings.HasPrefix(c.SrcPath, p) {
c.RelSrcPath = c.SrcPath[len(p):]
c.LocalSrcPath = pathJoin(dest, "pkg/mod", c.RelSrcPath)
goto done
}
}
// Go module path detection only works with stack traces created on the local
// file system.
if localgomod != "" {
if prefix := localgomod + "/"; strings.HasPrefix(c.SrcPath, prefix) {
c.RelSrcPath = gomodImportPath + "/" + c.SrcPath[len(prefix):]
c.LocalSrcPath = c.SrcPath
goto done
}
}
done:
if !c.IsStdlib {
// Consider _test/_testmain.go as stdlib since it's injected by "go test".
c.IsStdlib = c.PkgSrc() == testMainSrc
}
}
// Stack is a call stack.
type Stack struct {
// Calls is the call stack. First is original function, last is leaf
// function.
Calls []Call
// Elided is set when there's >100 items in Stack, currently hardcoded in
// package runtime.
Elided bool
}
// equal returns true on if both call stacks are exactly equal.
func (s *Stack) equal(r *Stack) bool {
if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
return false
}
for i := range s.Calls {
if !s.Calls[i].equal(&r.Calls[i]) {
return false
}
}
return true
}
// similar returns true if the two Stack are equal or almost but not quite
// equal.
func (s *Stack) similar(r *Stack, similar Similarity) bool {
if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
return false
}
for i := range s.Calls {
if !s.Calls[i].similar(&r.Calls[i], similar) {
return false
}
}
return true
}
// merge merges two similar Stack, zapping out differences.
func (s *Stack) merge(r *Stack) *Stack {
// Assumes similar stacks have the same length.
out := &Stack{
Calls: make([]Call, len(s.Calls)),
Elided: s.Elided,
}
for i := range s.Calls {
out.Calls[i] = s.Calls[i].merge(&r.Calls[i])
}
return out
}
// less compares two Stack, where the ones that are less are more
// important, so they come up front.
//
// A Stack with more private functions is 'less' so it is at the top.
// Inversely, a Stack with only public functions is 'more' so it is at the
// bottom.
func (s *Stack) less(r *Stack) bool {
lStdlib := 0
lPrivate := 0
for _, c := range s.Calls {
if c.IsStdlib {
lStdlib++
} else {
lPrivate++
}
}
rStdlib := 0
rPrivate := 0
for _, s := range r.Calls {
if s.IsStdlib {
rStdlib++
} else {
rPrivate++
}
}
if lPrivate > rPrivate {
return true
}
if lPrivate < rPrivate {
return false
}
if lStdlib > rStdlib {
return false
}
if lStdlib < rStdlib {
return true
}
// Stack lengths are the same.
for x := range s.Calls {
if s.Calls[x].Func.Raw < r.Calls[x].Func.Raw {
return true
}
if s.Calls[x].Func.Raw > r.Calls[x].Func.Raw {
return true
}
if s.Calls[x].PkgSrc() < r.Calls[x].PkgSrc() {
return true
}
if s.Calls[x].PkgSrc() > r.Calls[x].PkgSrc() {
return true
}
if s.Calls[x].Line < r.Calls[x].Line {
return true
}
if s.Calls[x].Line > r.Calls[x].Line {
return true
}
}
return false
}
func (s *Stack) updateLocations(goroot, localgoroot, localgomod, gomodImportPath string, gopaths map[string]string) {
for i := range s.Calls {
s.Calls[i].updateLocations(goroot, localgoroot, localgomod, gomodImportPath, gopaths)
}
}
// Signature represents the signature of one or multiple goroutines.
//
// It is effectively the stack trace plus the goroutine internal bits, like
// it's state, if it is thread locked, which call site created this goroutine,
// etc.
type Signature struct {
// State is the goroutine state at the time of the snapshot.
//
// Use git grep 'gopark(|unlock)\(' to find them all plus everything listed
// in runtime/traceback.go. Valid values includes:
// - chan send, chan receive, select
// - finalizer wait, mark wait (idle),
// - Concurrent GC wait, GC sweep wait, force gc (idle)
// - IO wait, panicwait
// - semacquire, semarelease
// - sleep, timer goroutine (idle)
// - trace reader (blocked)
// Stuck cases:
// - chan send (nil chan), chan receive (nil chan), select (no cases)
// Runnable states:
// - idle, runnable, running, syscall, waiting, dead, enqueue, copystack,
// Scan states:
// - scan, scanrunnable, scanrunning, scansyscall, scanwaiting, scandead,
// scanenqueue
State string
// Createdby is the goroutine which created this one, if applicable.
CreatedBy Call
// SleepMin is the wait time in minutes, if applicable.
SleepMin int
// SleepMax is the wait time in minutes, if applicable.
SleepMax int
// Stack is the call stack.
Stack Stack
// Locked is set if the goroutine was locked to an OS thread.
Locked bool
}
// equal returns true only if both signatures are exactly equal.
func (s *Signature) equal(r *Signature) bool {
if s.State != r.State || !s.CreatedBy.equal(&r.CreatedBy) || s.Locked != r.Locked || s.SleepMin != r.SleepMin || s.SleepMax != r.SleepMax {
return false
}
return s.Stack.equal(&r.Stack)
}
// similar returns true if the two Signature are equal or almost but not quite
// equal.
func (s *Signature) similar(r *Signature, similar Similarity) bool {
if s.State != r.State || !s.CreatedBy.similar(&r.CreatedBy, similar) {
return false
}
if similar == ExactFlags && s.Locked != r.Locked {
return false
}
return s.Stack.similar(&r.Stack, similar)
}
// merge merges two similar Signature, zapping out differences.
func (s *Signature) merge(r *Signature) *Signature {
min := s.SleepMin
if r.SleepMin < min {
min = r.SleepMin
}
max := s.SleepMax
if r.SleepMax > max {
max = r.SleepMax
}
return &Signature{
State: s.State, // Drop right side.
CreatedBy: s.CreatedBy, // Drop right side.
SleepMin: min,
SleepMax: max,
Stack: *s.Stack.merge(&r.Stack),
Locked: s.Locked || r.Locked, // TODO(maruel): This is weirdo.
}
}
// less compares two Signature, where the ones that are less are more
// important, so they come up front. A Signature with more private functions is
// 'less' so it is at the top. Inversely, a Signature with only public
// functions is 'more' so it is at the bottom.
func (s *Signature) less(r *Signature) bool {
if s.Stack.less(&r.Stack) {
return true
}
if r.Stack.less(&s.Stack) {
return false
}
if s.Locked && !r.Locked {
return true
}
if r.Locked && !s.Locked {
return false
}
if s.State < r.State {
return true
}
if s.State > r.State {
return false
}
return false
}
// SleepString returns a string "N-M minutes" if the goroutine(s) slept for a
// long time.
//
// Returns an empty string otherwise.
func (s *Signature) SleepString() string {
if s.SleepMax == 0 {
return ""
}
if s.SleepMin != s.SleepMax {
return fmt.Sprintf("%d~%d minutes", s.SleepMin, s.SleepMax)
}
return fmt.Sprintf("%d minutes", s.SleepMax)
}
// CreatedByString return a short context about the origin of this goroutine
// signature.
//
// Deprecated: Format it yourself, will be removed in v2.
func (s *Signature) CreatedByString(fullPath bool) string {
created := s.CreatedBy.Func.PkgDotName()
if created == "" {
return ""
}
created += " @ "
if fullPath {
created += s.CreatedBy.FullSrcLine()
} else {
created += s.CreatedBy.SrcLine()
}
return created
}
func (s *Signature) updateLocations(goroot, localgoroot, localgomod, gomodImportPath string, gopaths map[string]string) {
s.CreatedBy.updateLocations(goroot, localgoroot, localgomod, gomodImportPath, gopaths)
s.Stack.updateLocations(goroot, localgoroot, localgomod, gomodImportPath, gopaths)
}
// Goroutine represents the state of one goroutine, including the stack trace.
type Goroutine struct {
// Signature is the stack trace, internal bits, state, which call site
// created it, etc.
Signature
// ID is the goroutine id.
ID int
// First is the goroutine first printed, normally the one that crashed.
First bool
}
// Private stuff.
// nameArguments is a post-processing step where Args are 'named' with numbers.
func nameArguments(goroutines []*Goroutine) {
// Set a name for any pointer occurring more than once.
type object struct {
args []*Arg
inPrimary bool
}
objects := map[uint64]object{}
// Enumerate all the arguments.
for i := range goroutines {
for j := range goroutines[i].Stack.Calls {
for k := range goroutines[i].Stack.Calls[j].Args.Values {
arg := goroutines[i].Stack.Calls[j].Args.Values[k]
if arg.IsPtr() {
objects[arg.Value] = object{
args: append(objects[arg.Value].args, &goroutines[i].Stack.Calls[j].Args.Values[k]),
inPrimary: objects[arg.Value].inPrimary || i == 0,
}
}
}
}
// CreatedBy.Args is never set.
}
order := make(uint64Slice, 0, len(objects)/2)
for k, obj := range objects {
if len(obj.args) > 1 && obj.inPrimary {
order = append(order, k)
}
}
sort.Sort(order)
nextID := 1
for _, k := range order {
for _, arg := range objects[k].args {
arg.Name = fmt.Sprintf("#%d", nextID)
}
nextID++
}
// Now do the rest. This is done so the output is deterministic.
order = make(uint64Slice, 0, len(objects))
for k := range objects {
order = append(order, k)
}
sort.Sort(order)
for _, k := range order {
// Process the remaining pointers, they were not referenced by primary
// thread so will have higher IDs.
if objects[k].inPrimary {
continue
}
for _, arg := range objects[k].args {
arg.Name = fmt.Sprintf("#%d", nextID)
}
nextID++
}
}
func pathJoin(s ...string) string {
return strings.Join(s, "/")
}
type uint64Slice []uint64
func (a uint64Slice) Len() int { return len(a) }
func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] }

@ -1,40 +0,0 @@
// Code generated by "stringer -type state"; DO NOT EDIT.
package stack
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[normal-0]
_ = x[betweenRoutine-1]
_ = x[gotRoutineHeader-2]
_ = x[gotFunc-3]
_ = x[gotCreated-4]
_ = x[gotFileFunc-5]
_ = x[gotFileCreated-6]
_ = x[gotUnavail-7]
_ = x[gotRaceHeader1-8]
_ = x[gotRaceHeader2-9]
_ = x[gotRaceOperationHeader-10]
_ = x[gotRaceOperationFunc-11]
_ = x[gotRaceOperationFile-12]
_ = x[betweenRaceOperations-13]
_ = x[gotRaceGoroutineHeader-14]
_ = x[gotRaceGoroutineFunc-15]
_ = x[gotRaceGoroutineFile-16]
_ = x[betweenRaceGoroutines-17]
}
const _state_name = "normalbetweenRoutinegotRoutineHeadergotFuncgotCreatedgotFileFuncgotFileCreatedgotUnavailgotRaceHeader1gotRaceHeader2gotRaceOperationHeadergotRaceOperationFuncgotRaceOperationFilebetweenRaceOperationsgotRaceGoroutineHeadergotRaceGoroutineFuncgotRaceGoroutineFilebetweenRaceGoroutines"
var _state_index = [...]uint16{0, 6, 20, 36, 43, 53, 64, 78, 88, 102, 116, 138, 158, 178, 199, 221, 241, 261, 282}
func (i state) String() string {
if i < 0 || i >= state(len(_state_index)-1) {
return "state(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _state_name[_state_index[i]:_state_index[i+1]]
}

@ -105,7 +105,6 @@ github.com/jeandeaual/go-locale
github.com/lucasb-eyer/go-colorful
# github.com/maruel/panicparse v1.6.1
## explicit; go 1.11
github.com/maruel/panicparse/stack
# github.com/mattn/go-colorable v0.1.9
## explicit; go 1.13
github.com/mattn/go-colorable

Loading…
Cancel
Save