contract.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // Copyright (C) 2020 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 contract
  7. import (
  8. "database/sql/driver"
  9. "encoding/json"
  10. "errors"
  11. "reflect"
  12. "strconv"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/structutil"
  15. )
  16. type Report struct {
  17. // v1 fields
  18. UniqueID string `json:"uniqueID,omitempty" metric:"-" since:"1"`
  19. Version string `json:"version,omitempty" metric:"reports_total,gaugeVec:version" since:"1"`
  20. LongVersion string `json:"longVersion,omitempty" metric:"-" since:"1"`
  21. Platform string `json:"platform,omitempty" metric:"-" since:"1"`
  22. NumFolders int `json:"numFolders,omitempty" metric:"num_folders,summary" since:"1"`
  23. NumDevices int `json:"numDevices,omitempty" metric:"num_devices,summary" since:"1"`
  24. TotFiles int `json:"totFiles,omitempty" metric:"total_files,summary" since:"1"`
  25. FolderMaxFiles int `json:"folderMaxFiles,omitempty" metric:"folder_max_files,summary" since:"1"`
  26. TotMiB int `json:"totMiB,omitempty" metric:"total_data_mib,summary" since:"1"`
  27. FolderMaxMiB int `json:"folderMaxMiB,omitempty" metric:"folder_max_data_mib,summary" since:"1"`
  28. MemoryUsageMiB int `json:"memoryUsageMiB,omitempty" metric:"memory_usage_mib,summary" since:"1"`
  29. SHA256Perf float64 `json:"sha256Perf,omitempty" metric:"sha256_perf_mibps,summary" since:"1"`
  30. HashPerf float64 `json:"hashPerf,omitempty" metric:"hash_perf_mibps,summary" since:"1"`
  31. MemorySize int `json:"memorySize,omitempty" metric:"memory_size_mib,summary" since:"1"`
  32. ProcessRSSMiB int `json:"processRSSMiB,omitempty" metric:"process_rss_mib,summary" since:"1"`
  33. // v2 fields
  34. URVersion int `json:"urVersion,omitempty" metric:"reports_by_urversion_total,gaugeVec:version" since:"2"`
  35. NumCPU int `json:"numCPU,omitempty" metric:"num_cpu,summary" since:"2"`
  36. FolderUses struct {
  37. SendOnly int `json:"sendonly,omitempty" metric:"folder_feature{feature=ModeSendonly},summary" since:"2"`
  38. SendReceive int `json:"sendreceive,omitempty" metric:"folder_feature{feature=ModeSendReceive},summary" since:"2"`
  39. ReceiveOnly int `json:"receiveonly,omitempty" metric:"folder_feature{feature=ModeReceiveOnly},summary" since:"2"`
  40. IgnorePerms int `json:"ignorePerms,omitempty" metric:"folder_feature{feature=IgnorePerms},summary" since:"2"`
  41. IgnoreDelete int `json:"ignoreDelete,omitempty" metric:"folder_feature{feature=IgnoreDelete},summary" since:"2"`
  42. AutoNormalize int `json:"autoNormalize,omitempty" metric:"folder_feature{feature=AutoNormalize},summary" since:"2"`
  43. SimpleVersioning int `json:"simpleVersioning,omitempty" metric:"folder_feature{feature=VersioningSimple},summary" since:"2"`
  44. ExternalVersioning int `json:"externalVersioning,omitempty" metric:"folder_feature{feature=VersioningExternal},summary" since:"2"`
  45. StaggeredVersioning int `json:"staggeredVersioning,omitempty" metric:"folder_feature{feature=VersioningStaggered},summary" since:"2"`
  46. TrashcanVersioning int `json:"trashcanVersioning,omitempty" metric:"folder_feature{feature=VersioningTrashcan},summary" since:"2"`
  47. } `json:"folderUses,omitzero" since:"2"`
  48. DeviceUses struct {
  49. Introducer int `json:"introducer,omitempty" metric:"device_feature{feature=Introducer},summary" since:"2"`
  50. CustomCertName int `json:"customCertName,omitempty" metric:"device_feature{feature=CustomCertName},summary" since:"2"`
  51. CompressAlways int `json:"compressAlways,omitempty" metric:"device_feature{feature=CompressAlways},summary" since:"2"`
  52. CompressMetadata int `json:"compressMetadata,omitempty" metric:"device_feature{feature=CompressMetadata},summary" since:"2"`
  53. CompressNever int `json:"compressNever,omitempty" metric:"device_feature{feature=CompressNever},summary" since:"2"`
  54. DynamicAddr int `json:"dynamicAddr,omitempty" metric:"device_feature{feature=AddressDynamic},summary" since:"2"`
  55. StaticAddr int `json:"staticAddr,omitempty" metric:"device_feature{feature=AddressStatic},summary" since:"2"`
  56. } `json:"deviceUses,omitzero" since:"2"`
  57. Announce struct {
  58. GlobalEnabled bool `json:"globalEnabled,omitempty" metric:"discovery_feature_count{feature=GlobalEnabled},gauge" since:"2"`
  59. LocalEnabled bool `json:"localEnabled,omitempty" metric:"discovery_feature_count{feature=LocalEnabled},gauge" since:"2"`
  60. DefaultServersDNS int `json:"defaultServersDNS,omitempty" metric:"discovery_default_servers,summary" since:"2"`
  61. OtherServers int `json:"otherServers,omitempty" metric:"discovery_other_servers,summary" since:"2"`
  62. } `json:"announce,omitzero" since:"2"`
  63. Relays struct {
  64. Enabled bool `json:"enabled,omitempty" metric:"relay_feature_enabled,gauge" since:"2"`
  65. DefaultServers int `json:"defaultServers,omitempty" metric:"relay_feature_count{feature=DefaultServers},summary" since:"2"`
  66. OtherServers int `json:"otherServers,omitempty" metric:"relay_feature_count{feature=OtherServers},summary" since:"2"`
  67. } `json:"relays,omitzero" since:"2"`
  68. UsesRateLimit bool `json:"usesRateLimit,omitempty" metric:"feature_count{feature=RateLimitsEnabled},gauge" since:"2"`
  69. UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" metric:"feature_count{feature=UpgradeAllowedManual},gauge" since:"2"`
  70. UpgradeAllowedAuto bool `json:"upgradeAllowedAuto,omitempty" metric:"feature_count{feature=UpgradeAllowedAuto},gauge" since:"2"`
  71. // V2.5 fields (fields that were in v2 but never added to the database
  72. UpgradeAllowedPre bool `json:"upgradeAllowedPre,omitempty" metric:"upgrade_allowed_pre,gauge" since:"2"`
  73. RescanIntvs []int `json:"rescanIntvs,omitempty" metric:"folder_rescan_intervals,summary" since:"2"`
  74. // v3 fields
  75. Uptime int `json:"uptime,omitempty" metric:"uptime_seconds,summary" since:"3"`
  76. NATType string `json:"natType,omitempty" metric:"nat_detection,gaugeVec:type" since:"3"`
  77. AlwaysLocalNets bool `json:"alwaysLocalNets,omitempty" metric:"feature_count{feature=AlwaysLocalNets},gauge" since:"3"`
  78. CacheIgnoredFiles bool `json:"cacheIgnoredFiles,omitempty" metric:"feature_count{feature=CacheIgnoredFiles},gauge" since:"3"`
  79. OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" metric:"feature_count{feature=OverwriteRemoteDeviceNames},gauge" since:"3"`
  80. ProgressEmitterEnabled bool `json:"progressEmitterEnabled,omitempty" metric:"feature_count{feature=ProgressEmitterEnabled},gauge" since:"3"`
  81. CustomDefaultFolderPath bool `json:"customDefaultFolderPath,omitempty" metric:"feature_count{feature=CustomDefaultFolderPath},gauge" since:"3"`
  82. CustomTrafficClass bool `json:"customTrafficClass,omitempty" metric:"feature_count{feature=CustomTrafficClass},gauge" since:"3"`
  83. CustomTempIndexMinBlocks bool `json:"customTempIndexMinBlocks,omitempty" metric:"feature_count{feature=CustomTempIndexMinBlocks},gauge" since:"3"`
  84. TemporariesDisabled bool `json:"temporariesDisabled,omitempty" metric:"feature_count{feature=TemporariesDisabled},gauge" since:"3"`
  85. TemporariesCustom bool `json:"temporariesCustom,omitempty" metric:"feature_count{feature=TemporariesCustom},gauge" since:"3"`
  86. LimitBandwidthInLan bool `json:"limitBandwidthInLan,omitempty" metric:"feature_count{feature=LimitBandwidthInLAN},gauge" since:"3"`
  87. CustomReleaseURL bool `json:"customReleaseURL,omitempty" metric:"feature_count{feature=CustomReleaseURL},gauge" since:"3"`
  88. RestartOnWakeup bool `json:"restartOnWakeup,omitempty" metric:"feature_count{feature=RestartOnWakeup},gauge" since:"3"`
  89. CustomStunServers bool `json:"customStunServers,omitempty" metric:"feature_count{feature=CustomSTUNServers},gauge" since:"3"`
  90. FolderUsesV3 struct {
  91. ScanProgressDisabled int `json:"scanProgressDisabled,omitempty" metric:"folder_feature{feature=ScanProgressDisabled},summary" since:"3"`
  92. ConflictsDisabled int `json:"conflictsDisabled,omitempty" metric:"folder_feature{feature=ConflictsDisabled},summary" since:"3"`
  93. ConflictsUnlimited int `json:"conflictsUnlimited,omitempty" metric:"folder_feature{feature=ConflictsUnlimited},summary" since:"3"`
  94. ConflictsOther int `json:"conflictsOther,omitempty" metric:"folder_feature{feature=ConflictsOther},summary" since:"3"`
  95. DisableSparseFiles int `json:"disableSparseFiles,omitempty" metric:"folder_feature{feature=DisableSparseFiles},summary" since:"3"`
  96. FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" metric:"folder_feature{feature=FSWatcherEnabled},summary" since:"3"`
  97. PullOrder map[string]int `json:"pullOrder,omitempty" metric:"folder_pull_order,summaryVec:order" since:"3"`
  98. FilesystemType map[string]int `json:"filesystemType,omitempty" metric:"folder_file_system_type,summaryVec:type" since:"3"`
  99. FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" metric:"folder_fswatcher_delays,summary" since:"3"`
  100. CustomMarkerName int `json:"customMarkerName,omitempty" metric:"folder_feature{feature=CustomMarkername},summary" since:"3"`
  101. CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" metric:"folder_feature{feature=CopyParentOwnership},summary" since:"3"`
  102. ModTimeWindowS []int `json:"modTimeWindowS,omitempty" metric:"folder_modtime_window_s,summary" since:"3"`
  103. MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" metric:"folder_max_concurrent_writes,summary" since:"3"`
  104. DisableFsync int `json:"disableFsync,omitempty" metric:"folder_feature{feature=DisableFsync},summary" since:"3"`
  105. BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" metric:"folder_block_pull_order:summaryVec:order" since:"3"`
  106. CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" metric:"folder_copy_range_method:summaryVec:method" since:"3"`
  107. CaseSensitiveFS int `json:"caseSensitiveFS,omitempty" metric:"folder_feature{feature=CaseSensitiveFS},summary" since:"3"`
  108. ReceiveEncrypted int `json:"receiveencrypted,omitempty" metric:"folder_feature{feature=ReceiveEncrypted},summary" since:"3"`
  109. SendXattrs int `json:"sendXattrs,omitempty" metric:"folder_feature{feature=SendXattrs},summary" since:"3"`
  110. SyncXattrs int `json:"syncXattrs,omitempty" metric:"folder_feature{feature=SyncXattrs},summary" since:"3"`
  111. SendOwnership int `json:"sendOwnership,omitempty" metric:"folder_feature{feature=SendOwnership},summary" since:"3"`
  112. SyncOwnership int `json:"syncOwnership,omitempty" metric:"folder_feature{feature=SyncOwnership},summary" since:"3"`
  113. } `json:"folderUsesV3,omitzero" since:"3"`
  114. DeviceUsesV3 struct {
  115. Untrusted int `json:"untrusted,omitempty" metric:"device_feature{feature=Untrusted},summary" since:"3"`
  116. UsesRateLimit int `json:"usesRateLimit,omitempty" metric:"device_feature{feature=RateLimitsEnabled},summary" since:"3"`
  117. MultipleConnections int `json:"multipleConnections,omitempty" metric:"device_feature{feature=MultipleConnections},summary" since:"3"`
  118. } `json:"deviceUsesV3,omitzero" since:"3"`
  119. GUIStats struct {
  120. Enabled int `json:"enabled,omitempty" metric:"gui_feature_count{feature=Enabled},summary" since:"3"`
  121. UseTLS int `json:"useTLS,omitempty" metric:"gui_feature_count{feature=TLS},summary" since:"3"`
  122. UseAuth int `json:"useAuth,omitempty" metric:"gui_feature_count{feature=Authentication},summary" since:"3"`
  123. InsecureAdminAccess int `json:"insecureAdminAccess,omitempty" metric:"gui_feature_count{feature=InsecureAdminAccess},summary" since:"3"`
  124. Debugging int `json:"debugging,omitempty" metric:"gui_feature_count{feature=Debugging},summary" since:"3"`
  125. InsecureSkipHostCheck int `json:"insecureSkipHostCheck,omitempty" metric:"gui_feature_count{feature=InsecureSkipHostCheck},summary" since:"3"`
  126. InsecureAllowFrameLoading int `json:"insecureAllowFrameLoading,omitempty" metric:"gui_feature_count{feature=InsecureAllowFrameLoading},summary" since:"3"`
  127. ListenLocal int `json:"listenLocal,omitempty" metric:"gui_feature_count{feature=ListenLocal},summary" since:"3"`
  128. ListenUnspecified int `json:"listenUnspecified,omitempty" metric:"gui_feature_count{feature=ListenUnspecified},summary" since:"3"`
  129. Theme map[string]int `json:"theme,omitempty" metric:"gui_theme,summaryVec:theme" since:"3"`
  130. } `json:"guiStats,omitzero" since:"3"`
  131. BlockStats struct {
  132. Total int `json:"total,omitempty" metric:"blocks_processed_total,gauge" since:"3"`
  133. Renamed int `json:"renamed,omitempty" metric:"blocks_processed{source=renamed},gauge" since:"3"`
  134. Reused int `json:"reused,omitempty" metric:"blocks_processed{source=reused},gauge" since:"3"`
  135. Pulled int `json:"pulled,omitempty" metric:"blocks_processed{source=pulled},gauge" since:"3"`
  136. CopyOrigin int `json:"copyOrigin,omitempty" metric:"blocks_processed{source=copy_origin},gauge" since:"3"`
  137. CopyElsewhere int `json:"copyElsewhere,omitempty" metric:"blocks_processed{source=copy_elsewhere},gauge" since:"3"`
  138. } `json:"blockStats,omitzero" since:"3"`
  139. TransportStats map[string]int `json:"transportStats,omitempty" since:"3"`
  140. IgnoreStats struct {
  141. Lines int `json:"lines,omitempty" metric:"folder_ignore_lines_total,summary" since:"3"`
  142. Inverts int `json:"inverts,omitempty" metric:"folder_ignore_lines{kind=inverts},summary" since:"3"`
  143. Folded int `json:"folded,omitempty" metric:"folder_ignore_lines{kind=folded},summary" since:"3"`
  144. Deletable int `json:"deletable,omitempty" metric:"folder_ignore_lines{kind=deletable},summary" since:"3"`
  145. Rooted int `json:"rooted,omitempty" metric:"folder_ignore_lines{kind=rooted},summary" since:"3"`
  146. Includes int `json:"includes,omitempty" metric:"folder_ignore_lines{kind=includes},summary" since:"3"`
  147. EscapedIncludes int `json:"escapedIncludes,omitempty" metric:"folder_ignore_lines{kind=escapedIncludes},summary" since:"3"`
  148. DoubleStars int `json:"doubleStars,omitempty" metric:"folder_ignore_lines{kind=doubleStars},summary" since:"3"`
  149. Stars int `json:"stars,omitempty" metric:"folder_ignore_lines{kind=stars},summary" since:"3"`
  150. } `json:"ignoreStats,omitzero" since:"3"`
  151. // Added in post processing
  152. Received time.Time `json:"received,omitzero"`
  153. Date string `json:"date,omitempty"`
  154. Address string `json:"address,omitempty"`
  155. OS string `json:"os,omitempty" metric:"reports_total,gaugeVec:os"`
  156. Arch string `json:"arch,omitempty" metric:"reports_total,gaugeVec:arch"`
  157. Compiler string `json:"compiler,omitempty" metric:"builder,gaugeVec:compiler"`
  158. Builder string `json:"builder,omitempty" metric:"builder,gaugeVec:builder"`
  159. Distribution string `json:"distribution,omitempty" metric:"builder,gaugeVec:distribution"`
  160. Country string `json:"country,omitempty" metric:"location,gaugeVec:country"`
  161. CountryCode string `json:"countryCode,omitempty" metric:"location,gaugeVec:countryCode"`
  162. MajorVersion string `json:"majorVersion,omitempty" metric:"reports_by_major_total,gaugeVec:version"`
  163. // Once more to create a metric on OS, arch, distribution
  164. DistDist string `json:"distDist,omitempty" metric:"distribution,gaugeVec:distribution"`
  165. DistOS string `json:"distOS,omitempty" metric:"distribution,gaugeVec:os"`
  166. DistArch string `json:"distArch,omitempty" metric:"distribution,gaugeVec:arch"`
  167. // Database counts
  168. Database struct {
  169. ModernCSQLite bool `json:"modernCSQLite,omitempty" metric:"database_engine{engine=modernc-sqlite},gauge"`
  170. MattnSQLite bool `json:"mattnSQLite,omitempty" metric:"database_engine{engine=mattn-sqlite},gauge"`
  171. LevelDB bool `json:"levelDB,omitempty" metric:"database_engine{engine=leveldb},gauge"`
  172. } `json:"database,omitzero"`
  173. }
  174. func New() *Report {
  175. r := &Report{}
  176. structutil.FillNil(r)
  177. return r
  178. }
  179. func (r *Report) Validate() error {
  180. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  181. return errors.New("missing required field")
  182. }
  183. if len(r.Date) != 8 {
  184. return errors.New("date not initialized")
  185. }
  186. // Some fields may not be null.
  187. if r.RescanIntvs == nil {
  188. r.RescanIntvs = []int{}
  189. }
  190. if r.FolderUsesV3.FsWatcherDelays == nil {
  191. r.FolderUsesV3.FsWatcherDelays = []int{}
  192. }
  193. return nil
  194. }
  195. func (r *Report) ClearForVersion(version int) error {
  196. return clear(r, version)
  197. }
  198. func (r Report) Value() (driver.Value, error) {
  199. // This needs to be string, yet we read back bytes..
  200. bs, err := json.Marshal(r)
  201. return string(bs), err
  202. }
  203. func (r *Report) Scan(value interface{}) error {
  204. // Zero out the previous value
  205. // JSON un-marshaller does not touch fields that are not in the payload, so we carry over values from a previous
  206. // scan.
  207. *r = Report{}
  208. b, ok := value.([]byte)
  209. if !ok {
  210. return errors.New("type assertion to []byte failed")
  211. }
  212. return json.Unmarshal(b, &r)
  213. }
  214. func clear(v interface{}, since int) error {
  215. s := reflect.ValueOf(v).Elem()
  216. t := s.Type()
  217. for i := 0; i < s.NumField(); i++ {
  218. f := s.Field(i)
  219. tag := t.Field(i).Tag
  220. v := tag.Get("since")
  221. if v == "" {
  222. f.Set(reflect.Zero(f.Type()))
  223. continue
  224. }
  225. vn, err := strconv.Atoi(v)
  226. if err != nil {
  227. return err
  228. }
  229. if vn > since {
  230. f.Set(reflect.Zero(f.Type()))
  231. continue
  232. }
  233. // Dive deeper
  234. if f.Kind() == reflect.Ptr {
  235. f = f.Elem()
  236. }
  237. if f.Kind() == reflect.Struct {
  238. if err := clear(f.Addr().Interface(), since); err != nil {
  239. return err
  240. }
  241. }
  242. }
  243. return nil
  244. }