cmd/syncthing, lib/config: Enable HTTP CPU/heap profile collection for users
This adds a config to enable debug functions on the API server, which is
by default disabled. When enabled, the /rest/debug things become
available and become available without requiring a CSRF token (although
authentication is required if configured).
We also add a new endpoint /rest/debug/cpuprof?duration=15s (with the
duration being configurable, defaulting to 30s). This runs a CPU profile
for the duration and returns it as a file. It sets headers so that a
browser will save the file with an informative name.
The same is done for heap profiles, /rest/debug/heapprof, which does not
take any parameters.
The purpose of this is that any user can enable debugging under
advanced, then point their browser to the endpoint above and get a file
that contains a CPU or heap profile we can use, with the filename
telling us what version and architecture the profile is from.
On the command line, this becomes
curl -O -J http://localhost:8082/rest/debug/cpuprof?duration=5s
curl: Saved to filename
'syncthing-cpu-darwin-amd64-v0.14.3+4-g935bcc0-110307.pprof'
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3467
This commit is contained in:
parent
08b5a7908f
commit
ffe7a2fcd7
@ -17,6 +17,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -268,8 +269,12 @@ func (s *apiService) Serve() {
|
|||||||
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
|
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
|
||||||
|
|
||||||
// Debug endpoints, not for general use
|
// Debug endpoints, not for general use
|
||||||
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
|
debugMux := http.NewServeMux()
|
||||||
getRestMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
|
debugMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
|
||||||
|
debugMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
|
||||||
|
debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
|
||||||
|
debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
|
||||||
|
getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux))
|
||||||
|
|
||||||
// A handler that splits requests between the two above and disables
|
// A handler that splits requests between the two above and disables
|
||||||
// caching
|
// caching
|
||||||
@ -364,6 +369,9 @@ func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
|
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
|
||||||
|
// No action required when this changes, so mask the fact that it changed at all.
|
||||||
|
from.GUI.Debugging = to.GUI.Debugging
|
||||||
|
|
||||||
if to.GUI == from.GUI {
|
if to.GUI == from.GUI {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -487,6 +495,18 @@ func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *apiService) whenDebugging(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.cfg.GUI().Debugging {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "Debugging disabled", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
|
func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
|
||||||
sendJSON(w, map[string]string{"ping": "pong"})
|
sendJSON(w, map[string]string{"ping": "pong"})
|
||||||
}
|
}
|
||||||
@ -1166,6 +1186,32 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
|||||||
sendJSON(w, ret)
|
sendJSON(w, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
||||||
|
duration, err := time.ParseDuration(r.FormValue("duration"))
|
||||||
|
if err != nil {
|
||||||
|
duration = 30 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("syncthing-cpu-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
|
||||||
|
pprof.StartCPUProfile(w)
|
||||||
|
time.Sleep(duration)
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiService) getHeapProf(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
pprof.WriteHeapProfile(w)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *apiService) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
|
func (s *apiService) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
|
||||||
res := make([]jsonDBFileInfo, len(fs))
|
res := make([]jsonDBFileInfo, len(fs))
|
||||||
for i, f := range fs {
|
for i, f := range fs {
|
||||||
|
|||||||
@ -41,6 +41,13 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/rest/debug") {
|
||||||
|
// Debugging functions are only available when explicitly
|
||||||
|
// enabled, and can be accessed without a CSRF token
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Allow requests for anything not under the protected path prefix,
|
// Allow requests for anything not under the protected path prefix,
|
||||||
// and set a CSRF cookie if there isn't already a valid one.
|
// and set a CSRF cookie if there isn't already a valid one.
|
||||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type GUIConfiguration struct {
|
|||||||
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
|
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
|
||||||
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
|
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
|
||||||
Theme string `xml:"theme" json:"theme" default:"default"`
|
Theme string `xml:"theme" json:"theme" default:"default"`
|
||||||
|
Debugging bool `xml:"debugging,attr" json:"debugging"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c GUIConfiguration) Address() string {
|
func (c GUIConfiguration) Address() string {
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false">
|
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false">
|
||||||
<address>tcp://127.0.0.1:22004</address>
|
<address>tcp://127.0.0.1:22004</address>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8081</address>
|
<address>127.0.0.1:8081</address>
|
||||||
<user>testuser</user>
|
<user>testuser</user>
|
||||||
<password>$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu</password>
|
<password>$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu</password>
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false">
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false">
|
||||||
<address>tcp://127.0.0.1:22003</address>
|
<address>tcp://127.0.0.1:22003</address>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8082</address>
|
<address>127.0.0.1:8082</address>
|
||||||
<apikey>abc123</apikey>
|
<apikey>abc123</apikey>
|
||||||
<theme>default</theme>
|
<theme>default</theme>
|
||||||
|
|||||||
@ -45,7 +45,7 @@
|
|||||||
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false">
|
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false">
|
||||||
<address>tcp://127.0.0.1:22003</address>
|
<address>tcp://127.0.0.1:22003</address>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8083</address>
|
<address>127.0.0.1:8083</address>
|
||||||
<apikey>abc123</apikey>
|
<apikey>abc123</apikey>
|
||||||
<theme>default</theme>
|
<theme>default</theme>
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false">
|
<device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false">
|
||||||
<address>dynamic</address>
|
<address>dynamic</address>
|
||||||
</device>
|
</device>
|
||||||
<gui enabled="true" tls="false">
|
<gui enabled="true" tls="false" debugging="true">
|
||||||
<address>127.0.0.1:8084</address>
|
<address>127.0.0.1:8084</address>
|
||||||
<apikey>PMA5yUTG-Mw98nJ0YEtWTCHlM5O4aNi0</apikey>
|
<apikey>PMA5yUTG-Mw98nJ0YEtWTCHlM5O4aNi0</apikey>
|
||||||
<theme>default</theme>
|
<theme>default</theme>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user