usage_report.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "context"
  10. "crypto/rand"
  11. "crypto/tls"
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "runtime"
  16. "sort"
  17. "strings"
  18. "time"
  19. "github.com/syncthing/syncthing/lib/config"
  20. "github.com/syncthing/syncthing/lib/dialer"
  21. "github.com/syncthing/syncthing/lib/model"
  22. "github.com/syncthing/syncthing/lib/protocol"
  23. "github.com/syncthing/syncthing/lib/scanner"
  24. "github.com/syncthing/syncthing/lib/upgrade"
  25. "github.com/thejerf/suture"
  26. )
  27. // Current version number of the usage report, for acceptance purposes. If
  28. // fields are added or changed this integer must be incremented so that users
  29. // are prompted for acceptance of the new report.
  30. const usageReportVersion = 2
  31. type usageReportingManager struct {
  32. cfg *config.Wrapper
  33. model *model.Model
  34. sup *suture.Supervisor
  35. }
  36. func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReportingManager {
  37. mgr := &usageReportingManager{
  38. cfg: cfg,
  39. model: m,
  40. }
  41. // Start UR if it's enabled.
  42. mgr.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
  43. // Listen to future config changes so that we can start and stop as
  44. // appropriate.
  45. cfg.Subscribe(mgr)
  46. return mgr
  47. }
  48. func (m *usageReportingManager) VerifyConfiguration(from, to config.Configuration) error {
  49. return nil
  50. }
  51. func (m *usageReportingManager) CommitConfiguration(from, to config.Configuration) bool {
  52. if to.Options.URAccepted >= usageReportVersion && m.sup == nil {
  53. // Usage reporting was turned on; lets start it.
  54. service := newUsageReportingService(m.cfg, m.model)
  55. m.sup = suture.NewSimple("usageReporting")
  56. m.sup.Add(service)
  57. m.sup.ServeBackground()
  58. } else if to.Options.URAccepted < usageReportVersion && m.sup != nil {
  59. // Usage reporting was turned off
  60. m.sup.Stop()
  61. m.sup = nil
  62. }
  63. return true
  64. }
  65. func (m *usageReportingManager) String() string {
  66. return fmt.Sprintf("usageReportingManager@%p", m)
  67. }
  68. // reportData returns the data to be sent in a usage report. It's used in
  69. // various places, so not part of the usageReportingManager object.
  70. func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
  71. opts := cfg.Options()
  72. res := make(map[string]interface{})
  73. res["urVersion"] = usageReportVersion
  74. res["uniqueID"] = opts.URUniqueID
  75. res["version"] = Version
  76. res["longVersion"] = LongVersion
  77. res["platform"] = runtime.GOOS + "-" + runtime.GOARCH
  78. res["numFolders"] = len(cfg.Folders())
  79. res["numDevices"] = len(cfg.Devices())
  80. var totFiles, maxFiles int
  81. var totBytes, maxBytes int64
  82. for folderID := range cfg.Folders() {
  83. global := m.GlobalSize(folderID)
  84. totFiles += global.Files
  85. totBytes += global.Bytes
  86. if global.Files > maxFiles {
  87. maxFiles = global.Files
  88. }
  89. if global.Bytes > maxBytes {
  90. maxBytes = global.Bytes
  91. }
  92. }
  93. res["totFiles"] = totFiles
  94. res["folderMaxFiles"] = maxFiles
  95. res["totMiB"] = totBytes / 1024 / 1024
  96. res["folderMaxMiB"] = maxBytes / 1024 / 1024
  97. var mem runtime.MemStats
  98. runtime.ReadMemStats(&mem)
  99. res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024
  100. res["sha256Perf"] = cpuBench(5, 125*time.Millisecond, false)
  101. res["hashPerf"] = cpuBench(5, 125*time.Millisecond, true)
  102. bytes, err := memorySize()
  103. if err == nil {
  104. res["memorySize"] = bytes / 1024 / 1024
  105. }
  106. res["numCPU"] = runtime.NumCPU()
  107. var rescanIntvs []int
  108. folderUses := map[string]int{
  109. "readonly": 0,
  110. "ignorePerms": 0,
  111. "ignoreDelete": 0,
  112. "autoNormalize": 0,
  113. "simpleVersioning": 0,
  114. "externalVersioning": 0,
  115. "staggeredVersioning": 0,
  116. "trashcanVersioning": 0,
  117. }
  118. for _, cfg := range cfg.Folders() {
  119. rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS)
  120. if cfg.Type == config.FolderTypeSendOnly {
  121. folderUses["readonly"]++
  122. }
  123. if cfg.IgnorePerms {
  124. folderUses["ignorePerms"]++
  125. }
  126. if cfg.IgnoreDelete {
  127. folderUses["ignoreDelete"]++
  128. }
  129. if cfg.AutoNormalize {
  130. folderUses["autoNormalize"]++
  131. }
  132. if cfg.Versioning.Type != "" {
  133. folderUses[cfg.Versioning.Type+"Versioning"]++
  134. }
  135. }
  136. sort.Ints(rescanIntvs)
  137. res["rescanIntvs"] = rescanIntvs
  138. res["folderUses"] = folderUses
  139. deviceUses := map[string]int{
  140. "introducer": 0,
  141. "customCertName": 0,
  142. "compressAlways": 0,
  143. "compressMetadata": 0,
  144. "compressNever": 0,
  145. "dynamicAddr": 0,
  146. "staticAddr": 0,
  147. }
  148. for _, cfg := range cfg.Devices() {
  149. if cfg.Introducer {
  150. deviceUses["introducer"]++
  151. }
  152. if cfg.CertName != "" && cfg.CertName != "syncthing" {
  153. deviceUses["customCertName"]++
  154. }
  155. if cfg.Compression == protocol.CompressAlways {
  156. deviceUses["compressAlways"]++
  157. } else if cfg.Compression == protocol.CompressMetadata {
  158. deviceUses["compressMetadata"]++
  159. } else if cfg.Compression == protocol.CompressNever {
  160. deviceUses["compressNever"]++
  161. }
  162. for _, addr := range cfg.Addresses {
  163. if addr == "dynamic" {
  164. deviceUses["dynamicAddr"]++
  165. } else {
  166. deviceUses["staticAddr"]++
  167. }
  168. }
  169. }
  170. res["deviceUses"] = deviceUses
  171. defaultAnnounceServersDNS, defaultAnnounceServersIP, otherAnnounceServers := 0, 0, 0
  172. for _, addr := range opts.GlobalAnnServers {
  173. if addr == "default" || addr == "default-v4" || addr == "default-v6" {
  174. defaultAnnounceServersDNS++
  175. } else {
  176. otherAnnounceServers++
  177. }
  178. }
  179. res["announce"] = map[string]interface{}{
  180. "globalEnabled": opts.GlobalAnnEnabled,
  181. "localEnabled": opts.LocalAnnEnabled,
  182. "defaultServersDNS": defaultAnnounceServersDNS,
  183. "defaultServersIP": defaultAnnounceServersIP,
  184. "otherServers": otherAnnounceServers,
  185. }
  186. defaultRelayServers, otherRelayServers := 0, 0
  187. for _, addr := range cfg.ListenAddresses() {
  188. switch {
  189. case addr == "dynamic+https://relays.syncthing.net/endpoint":
  190. defaultRelayServers++
  191. case strings.HasPrefix(addr, "relay://") || strings.HasPrefix(addr, "dynamic+http"):
  192. otherRelayServers++
  193. }
  194. }
  195. res["relays"] = map[string]interface{}{
  196. "enabled": defaultRelayServers+otherAnnounceServers > 0,
  197. "defaultServers": defaultRelayServers,
  198. "otherServers": otherRelayServers,
  199. }
  200. res["usesRateLimit"] = opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0
  201. res["upgradeAllowedManual"] = !(upgrade.DisabledByCompilation || noUpgradeFromEnv)
  202. res["upgradeAllowedAuto"] = !(upgrade.DisabledByCompilation || noUpgradeFromEnv) && opts.AutoUpgradeIntervalH > 0
  203. res["upgradeAllowedPre"] = !(upgrade.DisabledByCompilation || noUpgradeFromEnv) && opts.AutoUpgradeIntervalH > 0 && opts.UpgradeToPreReleases
  204. return res
  205. }
  206. type usageReportingService struct {
  207. cfg *config.Wrapper
  208. model *model.Model
  209. stop chan struct{}
  210. }
  211. func newUsageReportingService(cfg *config.Wrapper, model *model.Model) *usageReportingService {
  212. return &usageReportingService{
  213. cfg: cfg,
  214. model: model,
  215. stop: make(chan struct{}),
  216. }
  217. }
  218. func (s *usageReportingService) sendUsageReport() error {
  219. d := reportData(s.cfg, s.model)
  220. var b bytes.Buffer
  221. json.NewEncoder(&b).Encode(d)
  222. client := &http.Client{
  223. Transport: &http.Transport{
  224. Dial: dialer.Dial,
  225. Proxy: http.ProxyFromEnvironment,
  226. TLSClientConfig: &tls.Config{
  227. InsecureSkipVerify: s.cfg.Options().URPostInsecurely,
  228. },
  229. },
  230. }
  231. _, err := client.Post(s.cfg.Options().URURL, "application/json", &b)
  232. return err
  233. }
  234. func (s *usageReportingService) Serve() {
  235. s.stop = make(chan struct{})
  236. l.Infoln("Starting usage reporting")
  237. defer l.Infoln("Stopping usage reporting")
  238. t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second) // time to initial report at start
  239. for {
  240. select {
  241. case <-s.stop:
  242. return
  243. case <-t.C:
  244. err := s.sendUsageReport()
  245. if err != nil {
  246. l.Infoln("Usage report:", err)
  247. }
  248. t.Reset(24 * time.Hour) // next report tomorrow
  249. }
  250. }
  251. }
  252. func (s *usageReportingService) Stop() {
  253. close(s.stop)
  254. }
  255. // cpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
  256. func cpuBench(iterations int, duration time.Duration, useWeakHash bool) float64 {
  257. dataSize := 16 * protocol.BlockSize
  258. bs := make([]byte, dataSize)
  259. rand.Reader.Read(bs)
  260. var perf float64
  261. for i := 0; i < iterations; i++ {
  262. if v := cpuBenchOnce(duration, useWeakHash, bs); v > perf {
  263. perf = v
  264. }
  265. }
  266. blocksResult = nil
  267. return perf
  268. }
  269. var blocksResult []protocol.BlockInfo // so the result is not optimized away
  270. func cpuBenchOnce(duration time.Duration, useWeakHash bool, bs []byte) float64 {
  271. t0 := time.Now()
  272. b := 0
  273. for time.Since(t0) < duration {
  274. r := bytes.NewReader(bs)
  275. blocksResult, _ = scanner.Blocks(context.TODO(), r, protocol.BlockSize, int64(len(bs)), nil, useWeakHash)
  276. b += len(bs)
  277. }
  278. d := time.Since(t0)
  279. return float64(int(float64(b)/d.Seconds()/(1<<20)*100)) / 100
  280. }