diff options
author | Jakob Borg <jakob@nym.se> | 2014-06-11 20:04:23 +0200 |
---|---|---|
committer | Jakob Borg <jakob@nym.se> | 2014-06-11 20:06:53 +0200 |
commit | f40f3b3b7be8127b6adf497f68f9450304eea47f (patch) | |
tree | 65f8f9408a049c2f58732d2d386642d7612cd826 /cmd | |
parent | 7454670b0a516d3d71e4e40999a3c352c34ebca9 (diff) |
Anonymous Usage Reporting
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/syncthing/gui.go | 59 | ||||
-rw-r--r-- | cmd/syncthing/main.go | 12 | ||||
-rw-r--r-- | cmd/syncthing/usage_report.go | 109 |
3 files changed, 179 insertions, 1 deletions
diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 89cdc5fb3c..48f43de3c8 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -98,6 +98,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro router.Get("/rest/system", restGetSystem) router.Get("/rest/errors", restGetErrors) router.Get("/rest/discovery", restGetDiscovery) + router.Get("/rest/report", restGetReport) router.Get("/qr/:text", getQR) router.Post("/rest/config", restPostConfig) @@ -107,6 +108,8 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro router.Post("/rest/error", restPostError) router.Post("/rest/error/clear", restClearErrors) router.Post("/rest/discovery/hint", restPostDiscoveryHint) + router.Post("/rest/report/enable", restPostReportEnable) + router.Post("/rest/report/disable", restPostReportDisable) mr := martini.New() mr.Use(csrfMiddleware) @@ -195,7 +198,7 @@ func restGetConfig(w http.ResponseWriter) { json.NewEncoder(w).Encode(encCfg) } -func restPostConfig(req *http.Request) { +func restPostConfig(req *http.Request, m *model.Model) { var newCfg config.Configuration err := json.NewDecoder(req.Body).Decode(&newCfg) if err != nil { @@ -242,6 +245,29 @@ func restPostConfig(req *http.Request) { } } + if newCfg.Options.UREnabled && !cfg.Options.UREnabled { + // UR was enabled + cfg.Options.UREnabled = true + cfg.Options.URDeclined = false + cfg.Options.URAccepted = usageReportVersion + // Set the corresponding options in newCfg so we don't trigger the restart check if this was the only option change + newCfg.Options.URDeclined = false + newCfg.Options.URAccepted = usageReportVersion + sendUsageRport(m) + go usageReportingLoop(m) + } else if !newCfg.Options.UREnabled && cfg.Options.UREnabled { + // UR was disabled + cfg.Options.UREnabled = false + cfg.Options.URDeclined = true + cfg.Options.URAccepted = 0 + // Set the corresponding options in newCfg so we don't trigger the restart check if this was the only option change + newCfg.Options.URDeclined = true + newCfg.Options.URAccepted = 0 + stopUsageReporting() + } else { + cfg.Options.URDeclined = newCfg.Options.URDeclined + } + if !reflect.DeepEqual(cfg.Options, newCfg.Options) { configInSync = false } @@ -347,6 +373,10 @@ func restGetDiscovery(w http.ResponseWriter) { json.NewEncoder(w).Encode(discoverer.All()) } +func restGetReport(w http.ResponseWriter, m *model.Model) { + json.NewEncoder(w).Encode(reportData(m)) +} + func getQR(w http.ResponseWriter, params martini.Params) { code, err := qr.Encode(params["text"], qr.M) if err != nil { @@ -358,6 +388,33 @@ func getQR(w http.ResponseWriter, params martini.Params) { w.Write(code.PNG()) } +func restPostReportEnable(m *model.Model) { + if cfg.Options.UREnabled { + return + } + + cfg.Options.UREnabled = true + cfg.Options.URDeclined = false + cfg.Options.URAccepted = usageReportVersion + + go usageReportingLoop(m) + sendUsageRport(m) + saveConfig() +} + +func restPostReportDisable(m *model.Model) { + if !cfg.Options.UREnabled { + return + } + + cfg.Options.UREnabled = false + cfg.Options.URDeclined = true + cfg.Options.URAccepted = 0 + + stopUsageReporting() + saveConfig() +} + func basic(username string, passhash string) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { if validAPIKey(req.Header.Get("X-API-Key")) { diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index c4e22f70f5..d02f5033cc 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -393,6 +393,18 @@ func main() { } } + if cfg.Options.UREnabled && cfg.Options.URAccepted < usageReportVersion { + l.Infoln("Anonymous usage report has changed; revoking acceptance") + cfg.Options.UREnabled = false + } + if cfg.Options.UREnabled { + go usageReportingLoop(m) + go func() { + time.Sleep(10 * time.Minute) + sendUsageRport(m) + }() + } + <-stop l.Okln("Exiting") } diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go new file mode 100644 index 0000000000..f7ed2558cb --- /dev/null +++ b/cmd/syncthing/usage_report.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/json" + "net/http" + "runtime" + "strings" + "time" + + "github.com/calmh/syncthing/model" +) + +// Current version number of the usage report, for acceptance purposes. If +// fields are added or changed this integer must be incremented so that users +// are prompted for acceptance of the new report. +const usageReportVersion = 1 + +var stopUsageReportingCh = make(chan struct{}) + +func reportData(m *model.Model) map[string]interface{} { + res := make(map[string]interface{}) + res["uniqueID"] = strings.ToLower(certID([]byte(myID)))[:6] + res["version"] = Version + res["platform"] = runtime.GOOS + "-" + runtime.GOARCH + res["numRepos"] = len(cfg.Repositories) + res["numNodes"] = len(cfg.Nodes) + + var totFiles, maxFiles int + var totBytes, maxBytes int64 + for _, repo := range cfg.Repositories { + files, _, bytes := m.GlobalSize(repo.ID) + totFiles += files + totBytes += bytes + if files > maxFiles { + maxFiles = files + } + if bytes > maxBytes { + maxBytes = bytes + } + } + + res["totFiles"] = totFiles + res["repoMaxFiles"] = maxFiles + res["totMiB"] = totBytes / 1024 / 1024 + res["repoMaxMiB"] = maxBytes / 1024 / 1024 + + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + res["memoryUsageMiB"] = mem.Sys / 1024 / 1024 + + var perf float64 + for i := 0; i < 5; i++ { + p := cpuBench() + if p > perf { + perf = p + } + } + res["sha256Perf"] = perf + + return res +} + +func sendUsageRport(m *model.Model) error { + d := reportData(m) + var b bytes.Buffer + json.NewEncoder(&b).Encode(d) + _, err := http.Post("https://data.syncthing.net/newdata", "application/json", &b) + return err +} + +func usageReportingLoop(m *model.Model) { + l.Infoln("Starting usage reporting") + t := time.NewTicker(86400 * time.Second) +loop: + for { + select { + case <-stopUsageReportingCh: + break loop + case <-t.C: + sendUsageRport(m) + } + } + l.Infoln("Stopping usage reporting") +} + +func stopUsageReporting() { + stopUsageReportingCh <- struct{}{} +} + +// Returns CPU performance as a measure of single threaded SHA-256 MiB/s +func cpuBench() float64 { + chunkSize := 100 * 1 << 10 + h := sha256.New() + bs := make([]byte, chunkSize) + rand.Reader.Read(bs) + + t0 := time.Now() + b := 0 + for time.Since(t0) < 125*time.Millisecond { + h.Write(bs) + b += chunkSize + } + h.Sum(nil) + d := time.Since(t0) + return float64(int(float64(b)/d.Seconds()/(1<<20)*100)) / 100 +} |