summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorJakob Borg <jakob@nym.se>2014-06-11 20:04:23 +0200
committerJakob Borg <jakob@nym.se>2014-06-11 20:06:53 +0200
commitf40f3b3b7be8127b6adf497f68f9450304eea47f (patch)
tree65f8f9408a049c2f58732d2d386642d7612cd826 /cmd
parent7454670b0a516d3d71e4e40999a3c352c34ebca9 (diff)
Anonymous Usage Reporting
Diffstat (limited to 'cmd')
-rw-r--r--cmd/syncthing/gui.go59
-rw-r--r--cmd/syncthing/main.go12
-rw-r--r--cmd/syncthing/usage_report.go109
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
+}