main.go 23 KB

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