usage_report.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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 ur
  7. import (
  8. "bytes"
  9. "context"
  10. "crypto/rand"
  11. "crypto/tls"
  12. "encoding/json"
  13. "net"
  14. "net/http"
  15. "runtime"
  16. "sort"
  17. "strings"
  18. "sync"
  19. "time"
  20. "github.com/syncthing/syncthing/lib/build"
  21. "github.com/syncthing/syncthing/lib/config"
  22. "github.com/syncthing/syncthing/lib/connections"
  23. "github.com/syncthing/syncthing/lib/dialer"
  24. "github.com/syncthing/syncthing/lib/model"
  25. "github.com/syncthing/syncthing/lib/protocol"
  26. "github.com/syncthing/syncthing/lib/scanner"
  27. "github.com/syncthing/syncthing/lib/upgrade"
  28. "github.com/syncthing/syncthing/lib/util"
  29. "github.com/thejerf/suture"
  30. )
  31. // Current version number of the usage report, for acceptance purposes. If
  32. // fields are added or changed this integer must be incremented so that users
  33. // are prompted for acceptance of the new report.
  34. const Version = 3
  35. var StartTime = time.Now()
  36. type Service struct {
  37. suture.Service
  38. cfg config.Wrapper
  39. model model.Model
  40. connectionsService connections.Service
  41. noUpgrade bool
  42. forceRun chan struct{}
  43. }
  44. func New(cfg config.Wrapper, m model.Model, connectionsService connections.Service, noUpgrade bool) *Service {
  45. svc := &Service{
  46. cfg: cfg,
  47. model: m,
  48. connectionsService: connectionsService,
  49. noUpgrade: noUpgrade,
  50. forceRun: make(chan struct{}, 1), // Buffered to prevent locking
  51. }
  52. svc.Service = util.AsService(svc.serve, svc.String())
  53. return svc
  54. }
  55. // ReportData returns the data to be sent in a usage report with the currently
  56. // configured usage reporting version.
  57. func (s *Service) ReportData(ctx context.Context) map[string]interface{} {
  58. urVersion := s.cfg.Options().URAccepted
  59. return s.reportData(ctx, urVersion, false)
  60. }
  61. // ReportDataPreview returns a preview of the data to be sent in a usage report
  62. // with the given version.
  63. func (s *Service) ReportDataPreview(ctx context.Context, urVersion int) map[string]interface{} {
  64. return s.reportData(ctx, urVersion, true)
  65. }
  66. func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) map[string]interface{} {
  67. opts := s.cfg.Options()
  68. res := make(map[string]interface{})
  69. res["urVersion"] = urVersion
  70. res["uniqueID"] = opts.URUniqueID
  71. res["version"] = build.Version
  72. res["longVersion"] = build.LongVersion
  73. res["platform"] = runtime.GOOS + "-" + runtime.GOARCH
  74. res["numFolders"] = len(s.cfg.Folders())
  75. res["numDevices"] = len(s.cfg.Devices())
  76. var totFiles, maxFiles int
  77. var totBytes, maxBytes int64
  78. for folderID := range s.cfg.Folders() {
  79. snap, err := s.model.DBSnapshot(folderID)
  80. if err != nil {
  81. continue
  82. }
  83. global := snap.GlobalSize()
  84. snap.Release()
  85. totFiles += int(global.Files)
  86. totBytes += global.Bytes
  87. if int(global.Files) > maxFiles {
  88. maxFiles = int(global.Files)
  89. }
  90. if global.Bytes > maxBytes {
  91. maxBytes = global.Bytes
  92. }
  93. }
  94. res["totFiles"] = totFiles
  95. res["folderMaxFiles"] = maxFiles
  96. res["totMiB"] = totBytes / 1024 / 1024
  97. res["folderMaxMiB"] = maxBytes / 1024 / 1024
  98. var mem runtime.MemStats
  99. runtime.ReadMemStats(&mem)
  100. res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024
  101. res["sha256Perf"] = CpuBench(ctx, 5, 125*time.Millisecond, false)
  102. res["hashPerf"] = CpuBench(ctx, 5, 125*time.Millisecond, true)
  103. bytes, err := memorySize()
  104. if err == nil {
  105. res["memorySize"] = bytes / 1024 / 1024
  106. }
  107. res["numCPU"] = runtime.NumCPU()
  108. var rescanIntvs []int
  109. folderUses := map[string]int{
  110. "sendonly": 0,
  111. "sendreceive": 0,
  112. "receiveonly": 0,
  113. "ignorePerms": 0,
  114. "ignoreDelete": 0,
  115. "autoNormalize": 0,
  116. "simpleVersioning": 0,
  117. "externalVersioning": 0,
  118. "staggeredVersioning": 0,
  119. "trashcanVersioning": 0,
  120. }
  121. for _, cfg := range s.cfg.Folders() {
  122. rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS)
  123. switch cfg.Type {
  124. case config.FolderTypeSendOnly:
  125. folderUses["sendonly"]++
  126. case config.FolderTypeSendReceive:
  127. folderUses["sendreceive"]++
  128. case config.FolderTypeReceiveOnly:
  129. folderUses["receiveonly"]++
  130. }
  131. if cfg.IgnorePerms {
  132. folderUses["ignorePerms"]++
  133. }
  134. if cfg.IgnoreDelete {
  135. folderUses["ignoreDelete"]++
  136. }
  137. if cfg.AutoNormalize {
  138. folderUses["autoNormalize"]++
  139. }
  140. if cfg.Versioning.Type != "" {
  141. folderUses[cfg.Versioning.Type+"Versioning"]++
  142. }
  143. }
  144. sort.Ints(rescanIntvs)
  145. res["rescanIntvs"] = rescanIntvs
  146. res["folderUses"] = folderUses
  147. deviceUses := map[string]int{
  148. "introducer": 0,
  149. "customCertName": 0,
  150. "compressAlways": 0,
  151. "compressMetadata": 0,
  152. "compressNever": 0,
  153. "dynamicAddr": 0,
  154. "staticAddr": 0,
  155. }
  156. for _, cfg := range s.cfg.Devices() {
  157. if cfg.Introducer {
  158. deviceUses["introducer"]++
  159. }
  160. if cfg.CertName != "" && cfg.CertName != "syncthing" {
  161. deviceUses["customCertName"]++
  162. }
  163. if cfg.Compression == protocol.CompressAlways {
  164. deviceUses["compressAlways"]++
  165. } else if cfg.Compression == protocol.CompressMetadata {
  166. deviceUses["compressMetadata"]++
  167. } else if cfg.Compression == protocol.CompressNever {
  168. deviceUses["compressNever"]++
  169. }
  170. for _, addr := range cfg.Addresses {
  171. if addr == "dynamic" {
  172. deviceUses["dynamicAddr"]++
  173. } else {
  174. deviceUses["staticAddr"]++
  175. }
  176. }
  177. }
  178. res["deviceUses"] = deviceUses
  179. defaultAnnounceServersDNS, defaultAnnounceServersIP, otherAnnounceServers := 0, 0, 0
  180. for _, addr := range opts.RawGlobalAnnServers {
  181. if addr == "default" || addr == "default-v4" || addr == "default-v6" {
  182. defaultAnnounceServersDNS++
  183. } else {
  184. otherAnnounceServers++
  185. }
  186. }
  187. res["announce"] = map[string]interface{}{
  188. "globalEnabled": opts.GlobalAnnEnabled,
  189. "localEnabled": opts.LocalAnnEnabled,
  190. "defaultServersDNS": defaultAnnounceServersDNS,
  191. "defaultServersIP": defaultAnnounceServersIP,
  192. "otherServers": otherAnnounceServers,
  193. }
  194. defaultRelayServers, otherRelayServers := 0, 0
  195. for _, addr := range s.cfg.Options().ListenAddresses() {
  196. switch {
  197. case addr == "dynamic+https://relays.syncthing.net/endpoint":
  198. defaultRelayServers++
  199. case strings.HasPrefix(addr, "relay://") || strings.HasPrefix(addr, "dynamic+http"):
  200. otherRelayServers++
  201. }
  202. }
  203. res["relays"] = map[string]interface{}{
  204. "enabled": defaultRelayServers+otherAnnounceServers > 0,
  205. "defaultServers": defaultRelayServers,
  206. "otherServers": otherRelayServers,
  207. }
  208. res["usesRateLimit"] = opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0
  209. res["upgradeAllowedManual"] = !(upgrade.DisabledByCompilation || s.noUpgrade)
  210. res["upgradeAllowedAuto"] = !(upgrade.DisabledByCompilation || s.noUpgrade) && opts.AutoUpgradeIntervalH > 0
  211. res["upgradeAllowedPre"] = !(upgrade.DisabledByCompilation || s.noUpgrade) && opts.AutoUpgradeIntervalH > 0 && opts.UpgradeToPreReleases
  212. if urVersion >= 3 {
  213. res["uptime"] = s.UptimeS()
  214. res["natType"] = s.connectionsService.NATType()
  215. res["alwaysLocalNets"] = len(opts.AlwaysLocalNets) > 0
  216. res["cacheIgnoredFiles"] = opts.CacheIgnoredFiles
  217. res["overwriteRemoteDeviceNames"] = opts.OverwriteRemoteDevNames
  218. res["progressEmitterEnabled"] = opts.ProgressUpdateIntervalS > -1
  219. res["customDefaultFolderPath"] = opts.DefaultFolderPath != "~"
  220. res["customTrafficClass"] = opts.TrafficClass != 0
  221. res["customTempIndexMinBlocks"] = opts.TempIndexMinBlocks != 10
  222. res["temporariesDisabled"] = opts.KeepTemporariesH == 0
  223. res["temporariesCustom"] = opts.KeepTemporariesH != 24
  224. res["limitBandwidthInLan"] = opts.LimitBandwidthInLan
  225. res["customReleaseURL"] = opts.ReleasesURL != "https://upgrades.syncthing.net/meta.json"
  226. res["restartOnWakeup"] = opts.RestartOnWakeup
  227. folderUsesV3 := map[string]int{
  228. "scanProgressDisabled": 0,
  229. "conflictsDisabled": 0,
  230. "conflictsUnlimited": 0,
  231. "conflictsOther": 0,
  232. "disableSparseFiles": 0,
  233. "disableTempIndexes": 0,
  234. "alwaysWeakHash": 0,
  235. "customWeakHashThreshold": 0,
  236. "fsWatcherEnabled": 0,
  237. }
  238. pullOrder := make(map[string]int)
  239. filesystemType := make(map[string]int)
  240. var fsWatcherDelays []int
  241. for _, cfg := range s.cfg.Folders() {
  242. if cfg.ScanProgressIntervalS < 0 {
  243. folderUsesV3["scanProgressDisabled"]++
  244. }
  245. if cfg.MaxConflicts == 0 {
  246. folderUsesV3["conflictsDisabled"]++
  247. } else if cfg.MaxConflicts < 0 {
  248. folderUsesV3["conflictsUnlimited"]++
  249. } else {
  250. folderUsesV3["conflictsOther"]++
  251. }
  252. if cfg.DisableSparseFiles {
  253. folderUsesV3["disableSparseFiles"]++
  254. }
  255. if cfg.DisableTempIndexes {
  256. folderUsesV3["disableTempIndexes"]++
  257. }
  258. if cfg.WeakHashThresholdPct < 0 {
  259. folderUsesV3["alwaysWeakHash"]++
  260. } else if cfg.WeakHashThresholdPct != 25 {
  261. folderUsesV3["customWeakHashThreshold"]++
  262. }
  263. if cfg.FSWatcherEnabled {
  264. folderUsesV3["fsWatcherEnabled"]++
  265. }
  266. pullOrder[cfg.Order.String()]++
  267. filesystemType[cfg.FilesystemType.String()]++
  268. fsWatcherDelays = append(fsWatcherDelays, cfg.FSWatcherDelayS)
  269. }
  270. sort.Ints(fsWatcherDelays)
  271. folderUsesV3Interface := map[string]interface{}{
  272. "pullOrder": pullOrder,
  273. "filesystemType": filesystemType,
  274. "fsWatcherDelays": fsWatcherDelays,
  275. }
  276. for key, value := range folderUsesV3 {
  277. folderUsesV3Interface[key] = value
  278. }
  279. res["folderUsesV3"] = folderUsesV3Interface
  280. guiCfg := s.cfg.GUI()
  281. // Anticipate multiple GUI configs in the future, hence store counts.
  282. guiStats := map[string]int{
  283. "enabled": 0,
  284. "useTLS": 0,
  285. "useAuth": 0,
  286. "insecureAdminAccess": 0,
  287. "debugging": 0,
  288. "insecureSkipHostCheck": 0,
  289. "insecureAllowFrameLoading": 0,
  290. "listenLocal": 0,
  291. "listenUnspecified": 0,
  292. }
  293. theme := make(map[string]int)
  294. if guiCfg.Enabled {
  295. guiStats["enabled"]++
  296. if guiCfg.UseTLS() {
  297. guiStats["useTLS"]++
  298. }
  299. if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
  300. guiStats["useAuth"]++
  301. }
  302. if guiCfg.InsecureAdminAccess {
  303. guiStats["insecureAdminAccess"]++
  304. }
  305. if guiCfg.Debugging {
  306. guiStats["debugging"]++
  307. }
  308. if guiCfg.InsecureSkipHostCheck {
  309. guiStats["insecureSkipHostCheck"]++
  310. }
  311. if guiCfg.InsecureAllowFrameLoading {
  312. guiStats["insecureAllowFrameLoading"]++
  313. }
  314. addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address())
  315. if err == nil {
  316. if addr.IP.IsLoopback() {
  317. guiStats["listenLocal"]++
  318. } else if addr.IP.IsUnspecified() {
  319. guiStats["listenUnspecified"]++
  320. }
  321. }
  322. theme[guiCfg.Theme]++
  323. }
  324. guiStatsInterface := map[string]interface{}{
  325. "theme": theme,
  326. }
  327. for key, value := range guiStats {
  328. guiStatsInterface[key] = value
  329. }
  330. res["guiStats"] = guiStatsInterface
  331. }
  332. for key, value := range s.model.UsageReportingStats(urVersion, preview) {
  333. res[key] = value
  334. }
  335. return res
  336. }
  337. func (s *Service) UptimeS() int {
  338. return int(time.Since(StartTime).Seconds())
  339. }
  340. func (s *Service) sendUsageReport(ctx context.Context) error {
  341. d := s.ReportData(ctx)
  342. var b bytes.Buffer
  343. if err := json.NewEncoder(&b).Encode(d); err != nil {
  344. return err
  345. }
  346. client := &http.Client{
  347. Transport: &http.Transport{
  348. DialContext: dialer.DialContext,
  349. Proxy: http.ProxyFromEnvironment,
  350. TLSClientConfig: &tls.Config{
  351. InsecureSkipVerify: s.cfg.Options().URPostInsecurely,
  352. },
  353. },
  354. }
  355. req, err := http.NewRequest("POST", s.cfg.Options().URURL, &b)
  356. if err != nil {
  357. return err
  358. }
  359. req.Header.Set("Content-Type", "application/json")
  360. req.Cancel = ctx.Done()
  361. resp, err := client.Do(req)
  362. if err != nil {
  363. return err
  364. }
  365. resp.Body.Close()
  366. return nil
  367. }
  368. func (s *Service) serve(ctx context.Context) {
  369. s.cfg.Subscribe(s)
  370. defer s.cfg.Unsubscribe(s)
  371. t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second)
  372. for {
  373. select {
  374. case <-ctx.Done():
  375. return
  376. case <-s.forceRun:
  377. t.Reset(0)
  378. case <-t.C:
  379. if s.cfg.Options().URAccepted >= 2 {
  380. err := s.sendUsageReport(ctx)
  381. if err != nil {
  382. l.Infoln("Usage report:", err)
  383. } else {
  384. l.Infof("Sent usage report (version %d)", s.cfg.Options().URAccepted)
  385. }
  386. }
  387. t.Reset(24 * time.Hour) // next report tomorrow
  388. }
  389. }
  390. }
  391. func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
  392. return nil
  393. }
  394. func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
  395. if from.Options.URAccepted != to.Options.URAccepted || from.Options.URUniqueID != to.Options.URUniqueID || from.Options.URURL != to.Options.URURL {
  396. select {
  397. case s.forceRun <- struct{}{}:
  398. default:
  399. // s.forceRun is one buffered, so even though nothing
  400. // was sent, a run will still happen after this point.
  401. }
  402. }
  403. return true
  404. }
  405. func (*Service) String() string {
  406. return "ur.Service"
  407. }
  408. var (
  409. blocksResult []protocol.BlockInfo // so the result is not optimized away
  410. blocksResultMut sync.Mutex
  411. )
  412. // CpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
  413. func CpuBench(ctx context.Context, iterations int, duration time.Duration, useWeakHash bool) float64 {
  414. blocksResultMut.Lock()
  415. defer blocksResultMut.Unlock()
  416. dataSize := 16 * protocol.MinBlockSize
  417. bs := make([]byte, dataSize)
  418. rand.Reader.Read(bs)
  419. var perf float64
  420. for i := 0; i < iterations; i++ {
  421. if v := cpuBenchOnce(ctx, duration, useWeakHash, bs); v > perf {
  422. perf = v
  423. }
  424. }
  425. blocksResult = nil
  426. return perf
  427. }
  428. func cpuBenchOnce(ctx context.Context, duration time.Duration, useWeakHash bool, bs []byte) float64 {
  429. t0 := time.Now()
  430. b := 0
  431. var err error
  432. for time.Since(t0) < duration {
  433. r := bytes.NewReader(bs)
  434. blocksResult, err = scanner.Blocks(ctx, r, protocol.MinBlockSize, int64(len(bs)), nil, useWeakHash)
  435. if err != nil {
  436. return 0 // Context done
  437. }
  438. b += len(bs)
  439. }
  440. d := time.Since(t0)
  441. return float64(int(float64(b)/d.Seconds()/(1<<20)*100)) / 100
  442. }