HTRACE-278. htraced: dump thread stacks and GC statistics when SIGQUIT is sent (cmccabe)
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/process.go b/htrace-htraced/go/src/org/apache/htrace/common/process.go
index d138178..419d6fe 100644
--- a/htrace-htraced/go/src/org/apache/htrace/common/process.go
+++ b/htrace-htraced/go/src/org/apache/htrace/common/process.go
@@ -24,6 +24,8 @@
"org/apache/htrace/conf"
"os"
"os/signal"
+ "runtime"
+ "runtime/debug"
"syscall"
)
@@ -51,17 +53,52 @@
syscall.SIGBUS,
syscall.SIGFPE,
syscall.SIGILL,
- syscall.SIGQUIT,
syscall.SIGSEGV,
syscall.SIGTERM,
}
- sigChan := make(chan os.Signal, len(fatalSigs))
- signal.Notify(sigChan, fatalSigs...)
- lg := NewLogger("exit", cnf)
+ fatalSigChan := make(chan os.Signal, 1)
+ signal.Notify(fatalSigChan, fatalSigs...)
+ lg := NewLogger("signal", cnf)
go func() {
- sig := <-sigChan
+ sig := <-fatalSigChan
lg.Errorf("Terminating on signal: %v\n", sig)
lg.Close()
os.Exit(1)
}()
+
+ sigQuitChan := make(chan os.Signal, 1)
+ signal.Notify(sigQuitChan, syscall.SIGQUIT)
+ go func() {
+ bufSize := 1<<20
+ buf := make([]byte, bufSize)
+ for {
+ <-sigQuitChan
+ neededBytes := runtime.Stack(buf, true)
+ if neededBytes > bufSize {
+ bufSize = neededBytes
+ buf = make([]byte, bufSize)
+ runtime.Stack(buf, true)
+ }
+ lg.Info("=== received SIGQUIT ===\n")
+ lg.Info("=== GOROUTINE STACKS ===\n")
+ lg.Info(string(buf[:neededBytes]))
+ lg.Info("\n=== END GOROUTINE STACKS ===\n")
+ gcs := debug.GCStats{}
+ debug.ReadGCStats(&gcs)
+ lg.Info("=== GC STATISTICS ===\n")
+ lg.Infof("LastGC: %s\n", gcs.LastGC.UTC().String())
+ lg.Infof("NumGC: %d\n", gcs.NumGC)
+ lg.Infof("PauseTotal: %v\n", gcs.PauseTotal)
+ if gcs.Pause != nil {
+ pauseStr := ""
+ prefix := ""
+ for p := range gcs.Pause {
+ pauseStr += prefix + gcs.Pause[p].String()
+ prefix = ", "
+ }
+ lg.Infof("Pause History: %s\n", pauseStr)
+ }
+ lg.Info("=== END GC STATISTICS ===\n")
+ }
+ }()
}
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/process_test.go b/htrace-htraced/go/src/org/apache/htrace/common/process_test.go
new file mode 100644
index 0000000..7609133
--- /dev/null
+++ b/htrace-htraced/go/src/org/apache/htrace/common/process_test.go
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+package common
+
+import (
+ "bufio"
+ "fmt"
+ "org/apache/htrace/conf"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+)
+
+const HTRACED_TEST_HELPER_PROCESS = "HTRACED_TEST_HELPER_PROCESS"
+
+// This test runs a helper process which will install our htraced signal
+// handlers. We will send signals to the subprocess and verify that it has
+// caught them and responded appropriately.
+func TestSignals(t *testing.T) {
+ if os.Getenv(HTRACED_TEST_HELPER_PROCESS) == "1" {
+ runHelperProcess()
+ os.Exit(0)
+ }
+ helper := exec.Command(os.Args[0], "-test.run=TestSignals", "--")
+ helper.Env = []string { HTRACED_TEST_HELPER_PROCESS + "=1" }
+ stdoutPipe, err := helper.StdoutPipe()
+ if err != nil {
+ panic(fmt.Sprintf("Failed to open pipe to process stdout: %s",
+ err.Error()))
+ }
+ stderrPipe, err := helper.StderrPipe()
+ if err != nil {
+ panic(fmt.Sprintf("Failed to open pipe to process stderr: %s",
+ err.Error()))
+ }
+ err = helper.Start()
+ if err != nil {
+ t.Fatal("Failed to start command %s: %s\n", os.Args[0], err.Error())
+ }
+ t.Logf("Started suprocess...\n")
+ done := make(chan interface{})
+ go func() {
+ scanner := bufio.NewScanner(stdoutPipe)
+ for scanner.Scan() {
+ text := scanner.Text()
+ if strings.Contains(text, "=== GOROUTINE STACKS ===") {
+ break
+ }
+ }
+ t.Logf("Saw 'GOROUTINE STACKS on stdout.' Sending SIGINT.\n")
+ helper.Process.Signal(syscall.SIGINT)
+ for scanner.Scan() {
+ text := scanner.Text()
+ if strings.Contains(text, "Terminating on signal: SIGINT") {
+ break
+ }
+ }
+ t.Logf("Saw 'Terminating on signal: SIGINT'. " +
+ "Helper goroutine exiting.\n")
+ done<-nil
+ }()
+ scanner := bufio.NewScanner(stderrPipe)
+ for scanner.Scan() {
+ text := scanner.Text()
+ if strings.Contains(text, "Signal handler installed.") {
+ break
+ }
+ }
+ t.Logf("Saw 'Signal handler installed.' Sending SIGINT.")
+ helper.Process.Signal(syscall.SIGQUIT)
+ t.Logf("Waiting for helper goroutine to exit.\n")
+ <-done
+ t.Logf("Waiting for subprocess to exit.\n")
+ helper.Wait()
+ t.Logf("Done.")
+}
+
+// Run the helper process which TestSignals spawns.
+func runHelperProcess() {
+ cnfMap := map[string]string {
+ conf.HTRACE_LOG_LEVEL: "TRACE",
+ conf.HTRACE_LOG_PATH: "", // log to stdout
+ }
+ cnfBld := conf.Builder{Values: cnfMap, Defaults: conf.DEFAULTS}
+ cnf, err := cnfBld.Build()
+ if err != nil {
+ fmt.Printf("Error building configuration: %s\n", err.Error())
+ os.Exit(1)
+ }
+ InstallSignalHandlers(cnf)
+ fmt.Fprintf(os.Stderr, "Signal handler installed.\n")
+ // Wait for a signal to be delivered
+ for {
+ time.Sleep(time.Hour * 100)
+ }
+}