Jak zrzucić ślady stosu goroutine?

91

Mam tło w Javie i uwielbiam używać sygnału QUIT do sprawdzania zrzutu wątku Java.

Jak pozwolić Golangowi wydrukować wszystkie ślady stosu gorutyn?

rogerdpack
źródło
Czy to odpowiada na twoje pytanie? Jak uzyskać ślad paniki (i zapisać jako zmienną)
hlin117

Odpowiedzi:

107

Aby wydrukować ślad stosu dla bieżącej gorutyny, użyj PrintStack()fromruntime/debug .

PrintStack drukuje ze standardowym błędem ślad stosu zwrócony przez Stack.

Na przykład:

import(
   "runtime/debug"
)
...    
debug.PrintStack()

Aby wydrukować ślad stosu dla wszystkich gorutyn, użyj Lookupi WriteTood runtime/pprof.

func Lookup(name string) *Profile
// Lookup returns the profile with the given name,
// or nil if no such profile exists.

func (p *Profile) WriteTo(w io.Writer, debug int) error
// WriteTo writes a pprof-formatted snapshot of the profile to w.
// If a write to w returns an error, WriteTo returns that error.
// Otherwise, WriteTo returns nil.

Każdy profil ma unikalną nazwę. Kilka profili jest wstępnie zdefiniowanych:

goroutine - stos śladów wszystkich bieżących goroutines
heap - próbkowanie wszystkich alokacji sterty
threadcreate - ślady stosu, które doprowadziły do ​​utworzenia nowego
bloku wątków systemu operacyjnego - ślady stosu, które doprowadziły do ​​zablokowania na elementach pierwotnych synchronizacji

Na przykład:

pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
Intermernet
źródło
1
Czy drukuje ślad stosu wszystkich gorutyn?
Powinien, woła Stack. „Stos zwraca sformatowany ślad stosu goroutine, który go wywołuje. Dla każdej procedury zawiera informacje o wierszu źródłowym i wartość PC, a następnie próbuje znaleźć dla funkcji Go wywołującą funkcję lub metodę oraz tekst wiersza zawierającego wezwanie."
Intermernet
1
Przepraszamy, wyświetla tylko bieżący ślad stosu goroutine.
4
@HowardGuo Dodałem przykład użycia runtime / pprof do zrzucenia wszystkich śladów stosu.
Intermernet
1
Myślę, że to wyświetla tylko aktualnie uruchomiony goroutine każdego wątku, a nie wszystkie goroutines, np .: play.golang.org/p/0hVB0_LMdm
rogerdpack.
41

Istnieje nakładka HTTP dla runtime/pprofpakietu wymienionego w odpowiedzi Intermernet. Zaimportuj pakiet net / http / pprof , aby zarejestrować procedurę obsługi HTTP dla /debug/pprof:

import _ "net/http/pprof"
import _ "net/http"

Uruchom odbiornik HTTP, jeśli jeszcze go nie masz:

go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

Następnie wskaż w przeglądarce http://localhost:6060/debug/pprofmenu lub http://localhost:6060/debug/pprof/goroutine?debug=2pełny zrzut stosu goroutine.

W ten sposób możesz również dowiedzieć się innych ciekawych rzeczy o działającym kodzie. Sprawdź post na blogu, aby zapoznać się z przykładami i szczegółami: http://blog.golang.org/profiling-go-programs

lnmx
źródło
sprawiłem, że to działa, pokazuje tylko gorutyny wykonane, o ile widzę. Czy jest jakiś sposób, żebym mógł zobaczyć wszystkie „metody”, które są wykonywane po uruchomieniu main.go?
Lukas Lukac
38

Aby naśladować zachowanie Java podczas zrzutu stosu na SIGQUIT, ale nadal pozostawiając uruchomiony program:

go func() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGQUIT)
    buf := make([]byte, 1<<20)
    for {
        <-sigs
        stacklen := runtime.Stack(buf, true)
        log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
    }
}()
Bryan
źródło
4
Myślę, że tego właśnie szukał autor - naśladuje to, co robi Java, kiedy wysyłasz polecenie kill -QUIT. Jedną małą zmianą, jaką musiałem wprowadzić, była zmiana pierwszej linii pętli for () na: "<- sigs". Innymi słowy, po prostu odrzuć sygnał po odczekaniu na niego. Najnowsze wersje Go nie pozwalają na zadeklarowanie zmiennej bez późniejszego jej użycia.
George Armhold
@Bryan, czy chcesz udzielić licencji na to w ramach BSD lub innych, bardziej liberalnych warunków, oprócz CC-BY-SA 3.0 wymaganej przez StackOverflow?
Charles Duffy
1
@CharlesDuffy, możesz znaleźć prawie to samo tutaj na licencji Apache: github.com/weaveworks/weave/blob/ ...
Bryan
37

Podobnie jak w Javie, SIGQUIT może być używany do drukowania śladu stosu programu Go i jego goroutinów.
Kluczową różnicą jest jednak to, że domyślnie wysyłanie SIGQUIT do programów Java nie powoduje ich zakończenia, podczas gdy programy Go kończą działanie.

Takie podejście nie wymaga zmiany kodu, aby wydrukować ślad stosu wszystkich gorutyn istniejących programów.

Zmienna środowiskowa GOTRACEBACK ( zobacz dokumentację pakietu wykonawczego ) kontroluje ilość generowanych danych wyjściowych. Na przykład, aby uwzględnić wszystkie gorutyny, ustaw GOTRACEBACK = all.

Drukowanie śladu stosu jest wyzwalane przez nieoczekiwany warunek wykonawczy (nieobsługiwany sygnał), pierwotnie udokumentowany w tym zatwierdzeniu , udostępniając go co najmniej od wersji Go 1.1.


Alternatywnie, jeśli modyfikacja kodu źródłowego jest opcją, zobacz inne odpowiedzi.


Zwróć uwagę, że w terminalu Linux SIGQUIT można wygodnie wysłać za pomocą kombinacji klawiszy Ctrl+ \.

Rodolfo Carvalho
źródło
5
Przeglądając dokumenty nie znalazłem żadnych wzmianek o SIGQUIT, raczej o SIGABRT. Z moich własnych testów (z go 1.7) ten drugi również działał nad pierwszym.
soltysh
4
to powinna być najlepsza odpowiedź.
Steven Soroka
Dokumentacja odnosi się do „sytuacji, gdy program Go zawiedzie z powodu nieodwracalnej paniki lub nieoczekiwanego stanu środowiska wykonawczego”. Nieprzechwycony sygnał (SIGQUIT itp.) Jest jednym z drugich. Dlaczego wspomniałem o SIGQUIT? Ponieważ OP wyraża swoją miłość do używania SIGQUIT z Javą, a ta odpowiedź podkreśla podobieństwo. Przeformułowanie odpowiedzi, aby była jaśniejsza.
Rodolfo Carvalho
26

Możesz użyć runtime.Stack, aby uzyskać ślad stosu wszystkich goroutines:

buf := make([]byte, 1<<16)
runtime.Stack(buf, true)
fmt.Printf("%s", buf)

Z dokumentacji:

func Stack(buf []byte, all bool) int

Stack formatuje ślad stosu wywołującego gorutynę do bufora i zwraca liczbę bajtów zapisanych w buforze. Jeśli wszystko jest prawdziwe, Stack formatuje ślady wszystkich innych gorutyn w buforze po śladzie dla bieżącej gorutyny.

Robert Kajic
źródło
Obejmuje to ślady ze wszystkich gorutyn, fajnie!
rogerdpack
Czy to format, do którego sprowadza się nieodwracalna panika?
Ztyx
2
Nie zapomnij dodać string (buf) albo wydrukujesz tam surowe bajty.
koda
2
Może robię coś nie tak, a może funkcjonalność się zmieniła, ale to nie pobiera dla mnie niczego oprócz pustego kawałka bajtów?
17xande
1
@koda nie ma potrzeby robić string(buf)tu, fmt.Printf("%s", buf)i fmt.Printf("%s", string(buf))robić dokładnie to samo (patrz Dokumentacja dla fmtpakietu); jedyną różnicą jest to, że stringwersja będzie bufniepotrzebnie kopiować bajty
kbolino
20

Naciśnij CTRL + \

(Jeśli uruchamiasz go w terminalu i chcesz po prostu zabić program i zrzucić procedury go itp.)

Znalazłem to pytanie, szukając sekwencji klawiszy. Chciałem szybko i łatwo sprawdzić, czy mój program przecieka procedury go :)

Øyvind Skaar
źródło
11

W systemach * NIX (w tym OSX) wysyłanie przerwania sygnału SIGABRT:

pkill -SIGABRT program_name

Kyle Kloepper
źródło
Najwyraźniej wysłanie SIGQUIT do procesu Java nie kończy go tak , jak zrobi to SIGABRT.
Dave C
Uważam, że jest to najprostsze i najbardziej pasujące rozwiązanie do pierwotnego pytania. Często potrzebujesz śledzenia stosu od razu, bez zmiany kodu.
jotrocken
5

Konieczne jest użycie długości zwróconej przez, runtime.Stack()aby uniknąć drukowania kilku pustych wierszy po śladzie stosu. Następująca funkcja odzyskiwania drukuje ładnie sformatowany ślad:

if r := recover(); r != nil {
    log.Printf("Internal error: %v", r))
    buf := make([]byte, 1<<16)
    stackSize := runtime.Stack(buf, true)
    log.Printf("%s\n", string(buf[0:stackSize]))
}
David Tootill
źródło
Nie ma runtime.Trace; runtime.Stack wspomniano już półtora roku temu .
Dave C
Nigdy tego nie widziałem; na jakiej platformie pracujesz?
Bryan
Czego nie widziałeś? Kod powinien działać na wszystkich platformach; Przetestowałem to na Windows 7, Ubuntu 14.04 i Mac.
David Tootill
Nigdy nie widziałem pustych linii.
Bryan,
4

Domyślnie naciśnij ^\klawisze ( CTRL + \ ), aby zrzucić ślady stosu wszystkich goroutines.


W przeciwnym razie, aby uzyskać bardziej szczegółową kontrolę, możesz użyć panic. Prosty sposób na Go 1.6+ :

go func() {
    s := make(chan os.Signal, 1)
    signal.Notify(s, syscall.SIGQUIT)
    <-s
    panic("give me the stack")
}()

Następnie uruchom program w ten sposób:

# Press ^\ to dump the stack traces of all the user-created goroutines
$ GOTRACEBACK=all go run main.go

Jeśli chcesz również wydrukować goroutines go runtime:

$ GOTRACEBACK=system go run main.go

Oto wszystkie opcje GOTRACEBACK:

  • GOTRACEBACK=none całkowicie pomija ślady stosu goroutine.
  • GOTRACEBACK=single (domyślnie) zachowuje się jak opisano powyżej.
  • GOTRACEBACK=all dodaje ślady stosu dla wszystkich goroutines utworzonych przez użytkowników.
  • GOTRACEBACK=system jest jak `ʻall '', ale dodaje ramki stosu dla funkcji wykonawczych i pokazuje gorutynę utworzone wewnętrznie przez środowisko wykonawcze.
  • GOTRACEBACK=crashjest jak `` system '', ale ulega awarii w sposób specyficzny dla systemu operacyjnego zamiast kończyć działanie. Na przykład w systemach Unix awaria SIGABRTpojawia się, aby wywołać zrzut pamięci.

Oto dokumentacja

Zmienna GOTRACEBACK steruje ilością danych wyjściowych generowanych, gdy program Go zawiedzie z powodu nieodwracalnej paniki lub nieoczekiwanego stanu środowiska wykonawczego.

Domyślnie błąd powoduje wydrukowanie śladu stosu dla bieżącego goroutine, eliminując funkcje wewnętrzne systemu wykonawczego, a następnie kończy pracę z kodem zakończenia 2. Niepowodzenie powoduje wydrukowanie śladów stosu dla wszystkich gorutyn, jeśli nie ma aktualnego gorutyny lub wewnętrzne w czasie wykonywania.

Ze względów historycznych ustawienia GOTRACEBACK 0, 1 i 2 są synonimami odpowiednio: none, all i system.

Funkcja SetTraceback pakietu uruchomieniowego / pakietu debugowania umożliwia zwiększenie ilości danych wyjściowych w czasie wykonywania, ale nie może zmniejszyć ilości poniżej wartości określonej przez zmienną środowiskową. Zobacz https://golang.org/pkg/runtime/debug/#SetTraceback .

Inanc Gumus
źródło