main.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "database/sql"
  6. "encoding/json"
  7. "fmt"
  8. "html/template"
  9. "io"
  10. "io/ioutil"
  11. "log"
  12. "net/http"
  13. "os"
  14. "regexp"
  15. "sort"
  16. "strings"
  17. "sync"
  18. "time"
  19. _ "github.com/lib/pq"
  20. )
  21. var (
  22. keyFile = getEnvDefault("UR_KEY_FILE", "key.pem")
  23. certFile = getEnvDefault("UR_CRT_FILE", "crt.pem")
  24. dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
  25. listenAddr = getEnvDefault("UR_LISTEN", "0.0.0.0:8443")
  26. tpl *template.Template
  27. compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\[email protected]]+)`)
  28. aggregateVersions = []string{"v0.7", "v0.8", "v0.9", "v0.10"}
  29. )
  30. var funcs = map[string]interface{}{
  31. "commatize": commatize,
  32. "number": number,
  33. }
  34. func getEnvDefault(key, def string) string {
  35. if val := os.Getenv(key); val != "" {
  36. return val
  37. }
  38. return def
  39. }
  40. type report struct {
  41. Received time.Time // Only from DB
  42. UniqueID string
  43. Version string
  44. LongVersion string
  45. Platform string
  46. NumFolders int
  47. NumDevices int
  48. TotFiles int
  49. FolderMaxFiles int
  50. TotMiB int
  51. FolderMaxMiB int
  52. MemoryUsageMiB int
  53. SHA256Perf float64
  54. MemorySize int
  55. // v2 fields
  56. URVersion int
  57. NumCPU int
  58. FolderUses struct {
  59. ReadOnly int
  60. IgnorePerms int
  61. IgnoreDelete int
  62. AutoNormalize int
  63. SimpleVersioning int
  64. ExternalVersioning int
  65. StaggeredVersioning int
  66. TrashcanVersioning int
  67. }
  68. DeviceUses struct {
  69. Introducer int
  70. CustomCertName int
  71. CompressAlways int
  72. CompressMetadata int
  73. CompressNever int
  74. DynamicAddr int
  75. StaticAddr int
  76. }
  77. Announce struct {
  78. GlobalEnabled bool
  79. LocalEnabled bool
  80. DefaultServersDNS int
  81. DefaultServersIP int
  82. OtherServers int
  83. }
  84. Relays struct {
  85. Enabled bool
  86. DefaultServers int
  87. OtherServers int
  88. }
  89. UsesRateLimit bool
  90. UpgradeAllowedManual bool
  91. UpgradeAllowedAuto bool
  92. // Generated
  93. Date string
  94. }
  95. func (r *report) Validate() error {
  96. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  97. return fmt.Errorf("missing required field")
  98. }
  99. if len(r.Date) != 8 {
  100. return fmt.Errorf("date not initialized")
  101. }
  102. return nil
  103. }
  104. func (r *report) FieldPointers() []interface{} {
  105. // All the fields of the report, in the same order as the database fields.
  106. return []interface{}{
  107. &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
  108. &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
  109. &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
  110. &r.MemorySize, &r.Date, &r.URVersion, &r.NumCPU,
  111. &r.FolderUses.ReadOnly, &r.FolderUses.IgnorePerms, &r.FolderUses.IgnoreDelete,
  112. &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
  113. &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
  114. &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
  115. &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
  116. &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
  117. &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
  118. &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
  119. &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
  120. &r.UpgradeAllowedAuto,
  121. &r.FolderUses.SimpleVersioning, &r.FolderUses.ExternalVersioning,
  122. &r.FolderUses.StaggeredVersioning, &r.FolderUses.TrashcanVersioning,
  123. }
  124. }
  125. func (r *report) FieldNames() []string {
  126. // The database fields that back this struct in PostgreSQL
  127. return []string{
  128. // V1
  129. "Received",
  130. "UniqueID",
  131. "Version",
  132. "LongVersion",
  133. "Platform",
  134. "NumFolders",
  135. "NumDevices",
  136. "TotFiles",
  137. "FolderMaxFiles",
  138. "TotMiB",
  139. "FolderMaxMiB",
  140. "MemoryUsageMiB",
  141. "SHA256Perf",
  142. "MemorySize",
  143. "Date",
  144. // V2
  145. "ReportVersion",
  146. "NumCPU",
  147. "FolderRO",
  148. "FolderIgnorePerms",
  149. "FolderIgnoreDelete",
  150. "FolderAutoNormalize",
  151. "DeviceIntroducer",
  152. "DeviceCustomCertName",
  153. "DeviceCompressAlways",
  154. "DeviceCompressMetadata",
  155. "DeviceCompressNever",
  156. "DeviceDynamicAddr",
  157. "DeviceStaticAddr",
  158. "AnnounceGlobalEnabled",
  159. "AnnounceLocalEnabled",
  160. "AnnounceDefaultServersDNS",
  161. "AnnounceDefaultServersIP",
  162. "AnnounceOtherServers",
  163. "RelayEnabled",
  164. "RelayDefaultServers",
  165. "RelayOtherServers",
  166. "RateLimitEnabled",
  167. "UpgradeAllowedManual",
  168. "UpgradeAllowedAuto",
  169. // v0.12.19+
  170. "FolderSimpleVersioning",
  171. "FolderExternalVersioning",
  172. "FolderStaggeredVersioning",
  173. "FolderTrashcanVersioning",
  174. }
  175. }
  176. func setupDB(db *sql.DB) error {
  177. _, err := db.Exec(`CREATE TABLE IF NOT EXISTS Reports (
  178. Received TIMESTAMP NOT NULL,
  179. UniqueID VARCHAR(32) NOT NULL,
  180. Version VARCHAR(32) NOT NULL,
  181. LongVersion VARCHAR(256) NOT NULL,
  182. Platform VARCHAR(32) NOT NULL,
  183. NumFolders INTEGER NOT NULL,
  184. NumDevices INTEGER NOT NULL,
  185. TotFiles INTEGER NOT NULL,
  186. FolderMaxFiles INTEGER NOT NULL,
  187. TotMiB INTEGER NOT NULL,
  188. FolderMaxMiB INTEGER NOT NULL,
  189. MemoryUsageMiB INTEGER NOT NULL,
  190. SHA256Perf DOUBLE PRECISION NOT NULL,
  191. MemorySize INTEGER NOT NULL,
  192. Date VARCHAR(8) NOT NULL
  193. )`)
  194. if err != nil {
  195. return err
  196. }
  197. var t string
  198. row := db.QueryRow(`SELECT 'UniqueIDIndex'::regclass`)
  199. if err := row.Scan(&t); err != nil {
  200. log.Println(err)
  201. if _, err = db.Exec(`CREATE UNIQUE INDEX UniqueIDIndex ON Reports (Date, UniqueID)`); err != nil {
  202. return err
  203. }
  204. }
  205. row = db.QueryRow(`SELECT 'ReceivedIndex'::regclass`)
  206. if err := row.Scan(&t); err != nil {
  207. if _, err = db.Exec(`CREATE INDEX ReceivedIndex ON Reports (Received)`); err != nil {
  208. return err
  209. }
  210. }
  211. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'reportversion'`)
  212. if err := row.Scan(&t); err != nil {
  213. // The ReportVersion column doesn't exist; add the new columns.
  214. _, err = db.Exec(`ALTER TABLE Reports
  215. ADD COLUMN ReportVersion INTEGER NOT NULL DEFAULT 0,
  216. ADD COLUMN NumCPU INTEGER NOT NULL DEFAULT 0,
  217. ADD COLUMN FolderRO INTEGER NOT NULL DEFAULT 0,
  218. ADD COLUMN FolderIgnorePerms INTEGER NOT NULL DEFAULT 0,
  219. ADD COLUMN FolderIgnoreDelete INTEGER NOT NULL DEFAULT 0,
  220. ADD COLUMN FolderAutoNormalize INTEGER NOT NULL DEFAULT 0,
  221. ADD COLUMN DeviceIntroducer INTEGER NOT NULL DEFAULT 0,
  222. ADD COLUMN DeviceCustomCertName INTEGER NOT NULL DEFAULT 0,
  223. ADD COLUMN DeviceCompressAlways INTEGER NOT NULL DEFAULT 0,
  224. ADD COLUMN DeviceCompressMetadata INTEGER NOT NULL DEFAULT 0,
  225. ADD COLUMN DeviceCompressNever INTEGER NOT NULL DEFAULT 0,
  226. ADD COLUMN DeviceDynamicAddr INTEGER NOT NULL DEFAULT 0,
  227. ADD COLUMN DeviceStaticAddr INTEGER NOT NULL DEFAULT 0,
  228. ADD COLUMN AnnounceGlobalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  229. ADD COLUMN AnnounceLocalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  230. ADD COLUMN AnnounceDefaultServersDNS INTEGER NOT NULL DEFAULT 0,
  231. ADD COLUMN AnnounceDefaultServersIP INTEGER NOT NULL DEFAULT 0,
  232. ADD COLUMN AnnounceOtherServers INTEGER NOT NULL DEFAULT 0,
  233. ADD COLUMN RelayEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  234. ADD COLUMN RelayDefaultServers INTEGER NOT NULL DEFAULT 0,
  235. ADD COLUMN RelayOtherServers INTEGER NOT NULL DEFAULT 0,
  236. ADD COLUMN RateLimitEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  237. ADD COLUMN UpgradeAllowedManual BOOLEAN NOT NULL DEFAULT FALSE,
  238. ADD COLUMN UpgradeAllowedAuto BOOLEAN NOT NULL DEFAULT FALSE,
  239. ADD COLUMN FolderSimpleVersioning INTEGER NOT NULL DEFAULT 0,
  240. ADD COLUMN FolderExternalVersioning INTEGER NOT NULL DEFAULT 0,
  241. ADD COLUMN FolderStaggeredVersioning INTEGER NOT NULL DEFAULT 0,
  242. ADD COLUMN FolderTrashcanVersioning INTEGER NOT NULL DEFAULT 0
  243. `)
  244. if err != nil {
  245. return err
  246. }
  247. }
  248. row = db.QueryRow(`SELECT 'ReportVersionIndex'::regclass`)
  249. if err := row.Scan(&t); err != nil {
  250. if _, err = db.Exec(`CREATE INDEX ReportVersionIndex ON Reports (ReportVersion)`); err != nil {
  251. return err
  252. }
  253. }
  254. return nil
  255. }
  256. func insertReport(db *sql.DB, r report) error {
  257. r.Received = time.Now().UTC()
  258. fields := r.FieldPointers()
  259. params := make([]string, len(fields))
  260. for i := range params {
  261. params[i] = fmt.Sprintf("$%d", i+1)
  262. }
  263. query := "INSERT INTO Reports (" + strings.Join(r.FieldNames(), ", ") + ") VALUES (" + strings.Join(params, ", ") + ")"
  264. _, err := db.Exec(query, fields...)
  265. return err
  266. }
  267. type withDBFunc func(*sql.DB, http.ResponseWriter, *http.Request)
  268. func withDB(db *sql.DB, f withDBFunc) http.HandlerFunc {
  269. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  270. f(db, w, r)
  271. })
  272. }
  273. func main() {
  274. log.SetFlags(log.Ltime | log.Ldate)
  275. log.SetOutput(os.Stdout)
  276. // Template
  277. fd, err := os.Open("static/index.html")
  278. if err != nil {
  279. log.Fatalln("template:", err)
  280. }
  281. bs, err := ioutil.ReadAll(fd)
  282. if err != nil {
  283. log.Fatalln("template:", err)
  284. }
  285. fd.Close()
  286. tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs)))
  287. // DB
  288. db, err := sql.Open("postgres", dbConn)
  289. if err != nil {
  290. log.Fatalln("database:", err)
  291. }
  292. err = setupDB(db)
  293. if err != nil {
  294. log.Fatalln("database:", err)
  295. }
  296. // TLS
  297. cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  298. if err != nil {
  299. log.Fatalln("tls:", err)
  300. }
  301. cfg := &tls.Config{
  302. Certificates: []tls.Certificate{cert},
  303. SessionTicketsDisabled: true,
  304. }
  305. // HTTPS
  306. listener, err := tls.Listen("tcp", listenAddr, cfg)
  307. if err != nil {
  308. log.Fatalln("https:", err)
  309. }
  310. srv := http.Server{
  311. ReadTimeout: 5 * time.Second,
  312. WriteTimeout: 5 * time.Second,
  313. }
  314. http.HandleFunc("/", withDB(db, rootHandler))
  315. http.HandleFunc("/newdata", withDB(db, newDataHandler))
  316. http.HandleFunc("/summary.json", withDB(db, summaryHandler))
  317. http.HandleFunc("/movement.json", withDB(db, movementHandler))
  318. http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  319. err = srv.Serve(listener)
  320. if err != nil {
  321. log.Fatalln("https:", err)
  322. }
  323. }
  324. var (
  325. cacheData []byte
  326. cacheTime time.Time
  327. cacheMut sync.Mutex
  328. )
  329. const maxCacheTime = 5 * 60 * time.Second
  330. func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  331. if r.URL.Path == "/" || r.URL.Path == "/index.html" {
  332. cacheMut.Lock()
  333. defer cacheMut.Unlock()
  334. if time.Since(cacheTime) > maxCacheTime {
  335. rep := getReport(db)
  336. buf := new(bytes.Buffer)
  337. err := tpl.Execute(buf, rep)
  338. if err != nil {
  339. log.Println(err)
  340. http.Error(w, "Template Error", http.StatusInternalServerError)
  341. return
  342. }
  343. cacheData = buf.Bytes()
  344. cacheTime = time.Now()
  345. }
  346. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  347. w.Write(cacheData)
  348. } else {
  349. http.Error(w, "Not found", 404)
  350. return
  351. }
  352. }
  353. func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  354. defer r.Body.Close()
  355. var rep report
  356. rep.Date = time.Now().UTC().Format("20060102")
  357. lr := &io.LimitedReader{R: r.Body, N: 10240}
  358. if err := json.NewDecoder(lr).Decode(&rep); err != nil {
  359. log.Println("json decode:", err)
  360. http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
  361. return
  362. }
  363. if err := rep.Validate(); err != nil {
  364. log.Println("validate:", err)
  365. log.Printf("%#v", rep)
  366. http.Error(w, "Validation Error", http.StatusInternalServerError)
  367. return
  368. }
  369. if err := insertReport(db, rep); err != nil {
  370. log.Println("insert:", err)
  371. log.Printf("%#v", rep)
  372. http.Error(w, "Database Error", http.StatusInternalServerError)
  373. return
  374. }
  375. }
  376. func summaryHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  377. s, err := getSummary(db)
  378. if err != nil {
  379. log.Println("summaryHandler:", err)
  380. http.Error(w, "Database Error", http.StatusInternalServerError)
  381. return
  382. }
  383. bs, err := s.MarshalJSON()
  384. if err != nil {
  385. log.Println("summaryHandler:", err)
  386. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  387. return
  388. }
  389. w.Header().Set("Content-Type", "application/json")
  390. w.Write(bs)
  391. }
  392. func movementHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  393. s, err := getMovement(db)
  394. if err != nil {
  395. log.Println("movementHandler:", err)
  396. http.Error(w, "Database Error", http.StatusInternalServerError)
  397. return
  398. }
  399. bs, err := json.Marshal(s)
  400. if err != nil {
  401. log.Println("movementHandler:", err)
  402. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  403. return
  404. }
  405. w.Header().Set("Content-Type", "application/json")
  406. w.Write(bs)
  407. }
  408. type category struct {
  409. Values [4]float64
  410. Key string
  411. Descr string
  412. Unit string
  413. Binary bool
  414. }
  415. type feature struct {
  416. Key string
  417. Pct int
  418. }
  419. func getReport(db *sql.DB) map[string]interface{} {
  420. nodes := 0
  421. var versions []string
  422. var platforms []string
  423. var oses []string
  424. var numFolders []int
  425. var numDevices []int
  426. var totFiles []int
  427. var maxFiles []int
  428. var totMiB []int
  429. var maxMiB []int
  430. var memoryUsage []int
  431. var sha256Perf []float64
  432. var memorySize []int
  433. var compilers []string
  434. var builders []string
  435. v2Reports := 0
  436. features := map[string]int{
  437. "Rate limiting": 0,
  438. "Upgrades allowed (automatic)": 0,
  439. "Upgrades allowed (manual)": 0,
  440. "Folders, automatic normalization": 0,
  441. "Folders, ignore deletes": 0,
  442. "Folders, ignore permissions": 0,
  443. "Folders, master mode": 0,
  444. "Folders, simple versioning": 0,
  445. "Folders, external versioning": 0,
  446. "Folders, staggered versioning": 0,
  447. "Folders, trashcan versioning": 0,
  448. "Devices, compress always": 0,
  449. "Devices, compress metadata": 0,
  450. "Devices, compress nothing": 0,
  451. "Devices, custom certificate": 0,
  452. "Devices, dynamic addresses": 0,
  453. "Devices, static addresses": 0,
  454. "Devices, introducer": 0,
  455. "Relaying, enabled": 0,
  456. "Relaying, default relays": 0,
  457. "Relaying, other relays": 0,
  458. "Discovery, global enabled": 0,
  459. "Discovery, local enabled": 0,
  460. "Discovery, default servers (using DNS)": 0,
  461. "Discovery, default servers (using IP)": 0,
  462. "Discovery, other servers": 0,
  463. }
  464. var numCPU []int
  465. var rep report
  466. rows, err := db.Query(`SELECT ` + strings.Join(rep.FieldNames(), ",") + ` FROM Reports WHERE Received > now() - '1 day'::INTERVAL`)
  467. if err != nil {
  468. log.Println("sql:", err)
  469. return nil
  470. }
  471. defer rows.Close()
  472. for rows.Next() {
  473. err := rows.Scan(rep.FieldPointers()...)
  474. if err != nil {
  475. log.Println("sql:", err)
  476. return nil
  477. }
  478. nodes++
  479. versions = append(versions, transformVersion(rep.Version))
  480. platforms = append(platforms, rep.Platform)
  481. ps := strings.Split(rep.Platform, "-")
  482. oses = append(oses, ps[0])
  483. if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 {
  484. compilers = append(compilers, m[1])
  485. builders = append(builders, m[2])
  486. }
  487. if rep.NumFolders > 0 {
  488. numFolders = append(numFolders, rep.NumFolders)
  489. }
  490. if rep.NumDevices > 0 {
  491. numDevices = append(numDevices, rep.NumDevices)
  492. }
  493. if rep.TotFiles > 0 {
  494. totFiles = append(totFiles, rep.TotFiles)
  495. }
  496. if rep.FolderMaxFiles > 0 {
  497. maxFiles = append(maxFiles, rep.FolderMaxFiles)
  498. }
  499. if rep.TotMiB > 0 {
  500. totMiB = append(totMiB, rep.TotMiB*(1<<20))
  501. }
  502. if rep.FolderMaxMiB > 0 {
  503. maxMiB = append(maxMiB, rep.FolderMaxMiB*(1<<20))
  504. }
  505. if rep.MemoryUsageMiB > 0 {
  506. memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
  507. }
  508. if rep.SHA256Perf > 0 {
  509. sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
  510. }
  511. if rep.MemorySize > 0 {
  512. memorySize = append(memorySize, rep.MemorySize*(1<<20))
  513. }
  514. if rep.URVersion >= 2 {
  515. v2Reports++
  516. numCPU = append(numCPU, rep.NumCPU)
  517. if rep.UsesRateLimit {
  518. features["Rate limiting"]++
  519. }
  520. if rep.UpgradeAllowedAuto {
  521. features["Upgrades allowed (automatic)"]++
  522. }
  523. if rep.UpgradeAllowedManual {
  524. features["Upgrades allowed (manual)"]++
  525. }
  526. if rep.FolderUses.AutoNormalize > 0 {
  527. features["Folders, automatic normalization"]++
  528. }
  529. if rep.FolderUses.IgnoreDelete > 0 {
  530. features["Folders, ignore deletes"]++
  531. }
  532. if rep.FolderUses.IgnorePerms > 0 {
  533. features["Folders, ignore permissions"]++
  534. }
  535. if rep.FolderUses.ReadOnly > 0 {
  536. features["Folders, master mode"]++
  537. }
  538. if rep.FolderUses.SimpleVersioning > 0 {
  539. features["Folders, simple versioning"]++
  540. }
  541. if rep.FolderUses.ExternalVersioning > 0 {
  542. features["Folders, external versioning"]++
  543. }
  544. if rep.FolderUses.StaggeredVersioning > 0 {
  545. features["Folders, staggered versioning"]++
  546. }
  547. if rep.FolderUses.TrashcanVersioning > 0 {
  548. features["Folders, trashcan versioning"]++
  549. }
  550. if rep.DeviceUses.CompressAlways > 0 {
  551. features["Devices, compress always"]++
  552. }
  553. if rep.DeviceUses.CompressMetadata > 0 {
  554. features["Devices, compress metadata"]++
  555. }
  556. if rep.DeviceUses.CompressNever > 0 {
  557. features["Devices, compress nothing"]++
  558. }
  559. if rep.DeviceUses.CustomCertName > 0 {
  560. features["Devices, custom certificate"]++
  561. }
  562. if rep.DeviceUses.DynamicAddr > 0 {
  563. features["Devices, dynamic addresses"]++
  564. }
  565. if rep.DeviceUses.StaticAddr > 0 {
  566. features["Devices, static addresses"]++
  567. }
  568. if rep.DeviceUses.Introducer > 0 {
  569. features["Devices, introducer"]++
  570. }
  571. if rep.Relays.Enabled {
  572. features["Relaying, enabled"]++
  573. }
  574. if rep.Relays.DefaultServers > 0 {
  575. features["Relaying, default relays"]++
  576. }
  577. if rep.Relays.OtherServers > 0 {
  578. features["Relaying, other relays"]++
  579. }
  580. if rep.Announce.GlobalEnabled {
  581. features["Discovery, global enabled"]++
  582. }
  583. if rep.Announce.LocalEnabled {
  584. features["Discovery, local enabled"]++
  585. }
  586. if rep.Announce.DefaultServersDNS > 0 {
  587. features["Discovery, default servers (using DNS)"]++
  588. }
  589. if rep.Announce.DefaultServersIP > 0 {
  590. features["Discovery, default servers (using IP)"]++
  591. }
  592. if rep.Announce.DefaultServersIP > 0 {
  593. features["Discovery, other servers"]++
  594. }
  595. }
  596. }
  597. var categories []category
  598. categories = append(categories, category{
  599. Values: statsForInts(totFiles),
  600. Descr: "Files Managed per Device",
  601. })
  602. categories = append(categories, category{
  603. Values: statsForInts(maxFiles),
  604. Descr: "Files in Largest Folder",
  605. })
  606. categories = append(categories, category{
  607. Values: statsForInts(totMiB),
  608. Descr: "Data Managed per Device",
  609. Unit: "B",
  610. Binary: true,
  611. })
  612. categories = append(categories, category{
  613. Values: statsForInts(maxMiB),
  614. Descr: "Data in Largest Folder",
  615. Unit: "B",
  616. Binary: true,
  617. })
  618. categories = append(categories, category{
  619. Values: statsForInts(numDevices),
  620. Descr: "Number of Devices in Cluster",
  621. })
  622. categories = append(categories, category{
  623. Values: statsForInts(numFolders),
  624. Descr: "Number of Folders Configured",
  625. })
  626. categories = append(categories, category{
  627. Values: statsForInts(memoryUsage),
  628. Descr: "Memory Usage",
  629. Unit: "B",
  630. Binary: true,
  631. })
  632. categories = append(categories, category{
  633. Values: statsForInts(memorySize),
  634. Descr: "System Memory",
  635. Unit: "B",
  636. Binary: true,
  637. })
  638. categories = append(categories, category{
  639. Values: statsForFloats(sha256Perf),
  640. Descr: "SHA-256 Hashing Performance",
  641. Unit: "B/s",
  642. Binary: true,
  643. })
  644. categories = append(categories, category{
  645. Values: statsForInts(numCPU),
  646. Descr: "Number of CPU cores",
  647. })
  648. var featureList []feature
  649. var featureNames []string
  650. for key := range features {
  651. featureNames = append(featureNames, key)
  652. }
  653. sort.Strings(featureNames)
  654. if v2Reports > 0 {
  655. for _, key := range featureNames {
  656. featureList = append(featureList, feature{
  657. Key: key,
  658. Pct: (100 * features[key]) / v2Reports,
  659. })
  660. }
  661. sort.Sort(sort.Reverse(sortableFeatureList(featureList)))
  662. }
  663. r := make(map[string]interface{})
  664. r["nodes"] = nodes
  665. r["v2nodes"] = v2Reports
  666. r["categories"] = categories
  667. r["versions"] = analyticsFor(versions, 10)
  668. r["platforms"] = analyticsFor(platforms, 0)
  669. r["os"] = analyticsFor(oses, 0)
  670. r["compilers"] = analyticsFor(compilers, 12)
  671. r["builders"] = analyticsFor(builders, 12)
  672. r["features"] = featureList
  673. return r
  674. }
  675. func ensureDir(dir string, mode int) {
  676. fi, err := os.Stat(dir)
  677. if os.IsNotExist(err) {
  678. os.MkdirAll(dir, 0700)
  679. } else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
  680. os.Chmod(dir, os.FileMode(mode))
  681. }
  682. }
  683. var vRe = regexp.MustCompile(`^(v\d+\.\d+\.\d+(?:-[a-z]\w+)?)[+\.-]`)
  684. // transformVersion returns a version number formatted correctly, with all
  685. // development versions aggregated into one.
  686. func transformVersion(v string) string {
  687. if v == "unknown-dev" {
  688. return v
  689. }
  690. if !strings.HasPrefix(v, "v") {
  691. v = "v" + v
  692. }
  693. if m := vRe.FindStringSubmatch(v); len(m) > 0 {
  694. return m[1] + " (+dev)"
  695. }
  696. // Truncate old versions to just the generation part
  697. for _, agg := range aggregateVersions {
  698. if strings.HasPrefix(v, agg) {
  699. return agg + ".x"
  700. }
  701. }
  702. return v
  703. }
  704. type summary struct {
  705. versions map[string]int // version string to count index
  706. rows map[string][]int // date to list of counts
  707. }
  708. func newSummary() summary {
  709. return summary{
  710. versions: make(map[string]int),
  711. rows: make(map[string][]int),
  712. }
  713. }
  714. func (s *summary) setCount(date, version string, count int) {
  715. idx, ok := s.versions[version]
  716. if !ok {
  717. idx = len(s.versions)
  718. s.versions[version] = idx
  719. }
  720. row := s.rows[date]
  721. if len(row) <= idx {
  722. old := row
  723. row = make([]int, idx+1)
  724. copy(row, old)
  725. s.rows[date] = row
  726. }
  727. row[idx] = count
  728. }
  729. func (s *summary) MarshalJSON() ([]byte, error) {
  730. var versions []string
  731. for v := range s.versions {
  732. versions = append(versions, v)
  733. }
  734. sort.Strings(versions)
  735. headerRow := []interface{}{"Day"}
  736. for _, v := range versions {
  737. headerRow = append(headerRow, v)
  738. }
  739. var table [][]interface{}
  740. table = append(table, headerRow)
  741. var dates []string
  742. for k := range s.rows {
  743. dates = append(dates, k)
  744. }
  745. sort.Strings(dates)
  746. for _, date := range dates {
  747. row := []interface{}{date}
  748. for _, ver := range versions {
  749. idx := s.versions[ver]
  750. if len(s.rows[date]) > idx && s.rows[date][idx] > 0 {
  751. row = append(row, s.rows[date][idx])
  752. } else {
  753. row = append(row, nil)
  754. }
  755. }
  756. table = append(table, row)
  757. }
  758. return json.Marshal(table)
  759. }
  760. func getSummary(db *sql.DB) (summary, error) {
  761. s := newSummary()
  762. rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '1 year'::INTERVAL;`)
  763. if err != nil {
  764. return summary{}, err
  765. }
  766. defer rows.Close()
  767. for rows.Next() {
  768. var day time.Time
  769. var ver string
  770. var num int
  771. err := rows.Scan(&day, &ver, &num)
  772. if err != nil {
  773. return summary{}, err
  774. }
  775. if ver == "v0.0" {
  776. // ?
  777. continue
  778. }
  779. // SUPER UGLY HACK to avoid having to do sorting properly
  780. if len(ver) == 4 { // v0.x
  781. ver = ver[:3] + "0" + ver[3:] // now v0.0x
  782. }
  783. s.setCount(day.Format("2006-01-02"), ver, num)
  784. }
  785. return s, nil
  786. }
  787. func getMovement(db *sql.DB) ([][]interface{}, error) {
  788. rows, err := db.Query(`SELECT Day, Added, Removed, Bounced FROM UserMovement WHERE Day > now() - '1 year'::INTERVAL ORDER BY Day`)
  789. if err != nil {
  790. return nil, err
  791. }
  792. defer rows.Close()
  793. res := [][]interface{}{
  794. {"Day", "Joined", "Left", "Bounced"},
  795. }
  796. for rows.Next() {
  797. var day time.Time
  798. var added, removed, bounced int
  799. err := rows.Scan(&day, &added, &removed, &bounced)
  800. if err != nil {
  801. return nil, err
  802. }
  803. row := []interface{}{day.Format("2006-01-02"), added, -removed, bounced}
  804. if removed == 0 {
  805. row[2] = nil
  806. }
  807. if bounced == 0 {
  808. row[3] = nil
  809. }
  810. res = append(res, row)
  811. }
  812. return res, nil
  813. }
  814. type sortableFeatureList []feature
  815. func (l sortableFeatureList) Len() int {
  816. return len(l)
  817. }
  818. func (l sortableFeatureList) Swap(a, b int) {
  819. l[a], l[b] = l[b], l[a]
  820. }
  821. func (l sortableFeatureList) Less(a, b int) bool {
  822. if l[a].Pct != l[b].Pct {
  823. return l[a].Pct < l[b].Pct
  824. }
  825. return l[a].Key > l[b].Key
  826. }