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