Anonymous Usage Reporting
This commit is contained in:
@@ -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")) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
109
cmd/syncthing/usage_report.go
Normal file
109
cmd/syncthing/usage_report.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user