diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go
index d8a6271d..34e26175 100644
--- a/cmd/syncthing/gui.go
+++ b/cmd/syncthing/gui.go
@@ -17,6 +17,7 @@ import (
"path/filepath"
"reflect"
"runtime"
+ "runtime/pprof"
"sort"
"strconv"
"strings"
@@ -268,8 +269,12 @@ func (s *apiService) Serve() {
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
// Debug endpoints, not for general use
- getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
- getRestMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
+ debugMux := http.NewServeMux()
+ 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
// caching
@@ -364,6 +369,9 @@ func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
}
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 {
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) {
sendJSON(w, map[string]string{"ping": "pong"})
}
@@ -1166,6 +1186,32 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
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 {
res := make([]jsonDBFileInfo, len(fs))
for i, f := range fs {
diff --git a/cmd/syncthing/gui_csrf.go b/cmd/syncthing/gui_csrf.go
index 30bc8232..71a9770b 100644
--- a/cmd/syncthing/gui_csrf.go
+++ b/cmd/syncthing/gui_csrf.go
@@ -41,6 +41,13 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
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,
// and set a CSRF cookie if there isn't already a valid one.
if !strings.HasPrefix(r.URL.Path, prefix) {
diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index e22eb71d..17cf1281 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -16,7 +16,6 @@ import (
"log"
"net"
"net/http"
- _ "net/http/pprof"
"net/url"
"os"
"os/signal"
diff --git a/lib/config/guiconfiguration.go b/lib/config/guiconfiguration.go
index fb6c3764..6a4e7e47 100644
--- a/lib/config/guiconfiguration.go
+++ b/lib/config/guiconfiguration.go
@@ -21,6 +21,7 @@ type GUIConfiguration struct {
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
InsecureAdminAccess bool `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
Theme string `xml:"theme" json:"theme" default:"default"`
+ Debugging bool `xml:"debugging,attr" json:"debugging"`
}
func (c GUIConfiguration) Address() string {
diff --git a/test/h1/config.xml b/test/h1/config.xml
index 32edc7bf..9a8f4666 100644
--- a/test/h1/config.xml
+++ b/test/h1/config.xml
@@ -50,7 +50,7 @@
tcp://127.0.0.1:22004
-
+
127.0.0.1:8081
testuser
$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu
diff --git a/test/h2/config.xml b/test/h2/config.xml
index 81a7b76c..9702453f 100644
--- a/test/h2/config.xml
+++ b/test/h2/config.xml
@@ -62,7 +62,7 @@
tcp://127.0.0.1:22003
-
+
127.0.0.1:8082
abc123
default
diff --git a/test/h3/config.xml b/test/h3/config.xml
index 201247f7..93977b15 100644
--- a/test/h3/config.xml
+++ b/test/h3/config.xml
@@ -45,7 +45,7 @@
tcp://127.0.0.1:22003
-
+
127.0.0.1:8083
abc123
default
diff --git a/test/h4/config.xml b/test/h4/config.xml
index b3dda584..74053766 100644
--- a/test/h4/config.xml
+++ b/test/h4/config.xml
@@ -18,7 +18,7 @@
dynamic
-
+
127.0.0.1:8084
PMA5yUTG-Mw98nJ0YEtWTCHlM5O4aNi0
default