main.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  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. if _, err = db.Exec(`CREATE UNIQUE INDEX UniqueIDIndex ON Reports (Date, UniqueID)`); err != nil {
  200. return err
  201. }
  202. }
  203. row = db.QueryRow(`SELECT 'ReceivedIndex'::regclass`)
  204. if err := row.Scan(&t); err != nil {
  205. if _, err = db.Exec(`CREATE INDEX ReceivedIndex ON Reports (Received)`); err != nil {
  206. return err
  207. }
  208. }
  209. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'reportversion'`)
  210. if err := row.Scan(&t); err != nil {
  211. // The ReportVersion column doesn't exist; add the new columns.
  212. _, err = db.Exec(`ALTER TABLE Reports
  213. ADD COLUMN ReportVersion INTEGER NOT NULL DEFAULT 0,
  214. ADD COLUMN NumCPU INTEGER NOT NULL DEFAULT 0,
  215. ADD COLUMN FolderRO INTEGER NOT NULL DEFAULT 0,
  216. ADD COLUMN FolderIgnorePerms INTEGER NOT NULL DEFAULT 0,
  217. ADD COLUMN FolderIgnoreDelete INTEGER NOT NULL DEFAULT 0,
  218. ADD COLUMN FolderAutoNormalize INTEGER NOT NULL DEFAULT 0,
  219. ADD COLUMN DeviceIntroducer INTEGER NOT NULL DEFAULT 0,
  220. ADD COLUMN DeviceCustomCertName INTEGER NOT NULL DEFAULT 0,
  221. ADD COLUMN DeviceCompressAlways INTEGER NOT NULL DEFAULT 0,
  222. ADD COLUMN DeviceCompressMetadata INTEGER NOT NULL DEFAULT 0,
  223. ADD COLUMN DeviceCompressNever INTEGER NOT NULL DEFAULT 0,
  224. ADD COLUMN DeviceDynamicAddr INTEGER NOT NULL DEFAULT 0,
  225. ADD COLUMN DeviceStaticAddr INTEGER NOT NULL DEFAULT 0,
  226. ADD COLUMN AnnounceGlobalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  227. ADD COLUMN AnnounceLocalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  228. ADD COLUMN AnnounceDefaultServersDNS INTEGER NOT NULL DEFAULT 0,
  229. ADD COLUMN AnnounceDefaultServersIP INTEGER NOT NULL DEFAULT 0,
  230. ADD COLUMN AnnounceOtherServers INTEGER NOT NULL DEFAULT 0,
  231. ADD COLUMN RelayEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  232. ADD COLUMN RelayDefaultServers INTEGER NOT NULL DEFAULT 0,
  233. ADD COLUMN RelayOtherServers INTEGER NOT NULL DEFAULT 0,
  234. ADD COLUMN RateLimitEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  235. ADD COLUMN UpgradeAllowedManual BOOLEAN NOT NULL DEFAULT FALSE,
  236. ADD COLUMN UpgradeAllowedAuto BOOLEAN NOT NULL DEFAULT FALSE,
  237. ADD COLUMN FolderSimpleVersioning INTEGER NOT NULL DEFAULT 0,
  238. ADD COLUMN FolderExternalVersioning INTEGER NOT NULL DEFAULT 0,
  239. ADD COLUMN FolderStaggeredVersioning INTEGER NOT NULL DEFAULT 0,
  240. ADD COLUMN FolderTrashcanVersioning INTEGER NOT NULL DEFAULT 0
  241. `)
  242. if err != nil {
  243. return err
  244. }
  245. }
  246. row = db.QueryRow(`SELECT 'ReportVersionIndex'::regclass`)
  247. if err := row.Scan(&t); err != nil {
  248. if _, err = db.Exec(`CREATE INDEX ReportVersionIndex ON Reports (ReportVersion)`); err != nil {
  249. return err
  250. }
  251. }
  252. return nil
  253. }
  254. func insertReport(db *sql.DB, r report) error {
  255. r.Received = time.Now().UTC()
  256. fields := r.FieldPointers()
  257. params := make([]string, len(fields))
  258. for i := range params {
  259. params[i] = fmt.Sprintf("$%d", i+1)
  260. }
  261. query := "INSERT INTO Reports (" + strings.Join(r.FieldNames(), ", ") + ") VALUES (" + strings.Join(params, ", ") + ")"
  262. _, err := db.Exec(query, fields...)
  263. return err
  264. }
  265. type withDBFunc func(*sql.DB, http.ResponseWriter, *http.Request)
  266. func withDB(db *sql.DB, f withDBFunc) http.HandlerFunc {
  267. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  268. f(db, w, r)
  269. })
  270. }
  271. func main() {
  272. log.SetFlags(log.Ltime | log.Ldate)
  273. log.SetOutput(os.Stdout)
  274. // Template
  275. fd, err := os.Open("static/index.html")
  276. if err != nil {
  277. log.Fatalln("template:", err)
  278. }
  279. bs, err := ioutil.ReadAll(fd)
  280. if err != nil {
  281. log.Fatalln("template:", err)
  282. }
  283. fd.Close()
  284. tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs)))
  285. // DB
  286. db, err := sql.Open("postgres", dbConn)
  287. if err != nil {
  288. log.Fatalln("database:", err)
  289. }
  290. err = setupDB(db)
  291. if err != nil {
  292. log.Fatalln("database:", err)
  293. }
  294. // TLS
  295. cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  296. if err != nil {
  297. log.Fatalln("tls:", err)
  298. }
  299. cfg := &tls.Config{
  300. Certificates: []tls.Certificate{cert},
  301. SessionTicketsDisabled: true,
  302. }
  303. // HTTPS
  304. listener, err := tls.Listen("tcp", listenAddr, cfg)
  305. if err != nil {
  306. log.Fatalln("https:", err)
  307. }
  308. srv := http.Server{
  309. ReadTimeout: 5 * time.Second,
  310. WriteTimeout: 5 * time.Second,
  311. }
  312. http.HandleFunc("/", withDB(db, rootHandler))
  313. http.HandleFunc("/newdata", withDB(db, newDataHandler))
  314. http.HandleFunc("/summary.json", withDB(db, summaryHandler))
  315. http.HandleFunc("/movement.json", withDB(db, movementHandler))
  316. http.HandleFunc("/performance.json", withDB(db, performanceHandler))
  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. func performanceHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  408. s, err := getPerformance(db)
  409. if err != nil {
  410. log.Println("performanceHandler:", err)
  411. http.Error(w, "Database Error", http.StatusInternalServerError)
  412. return
  413. }
  414. bs, err := json.Marshal(s)
  415. if err != nil {
  416. log.Println("performanceHandler:", err)
  417. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  418. return
  419. }
  420. w.Header().Set("Content-Type", "application/json")
  421. w.Write(bs)
  422. }
  423. type category struct {
  424. Values [4]float64
  425. Key string
  426. Descr string
  427. Unit string
  428. Binary bool
  429. }
  430. type feature struct {
  431. Key string
  432. Pct float64
  433. }
  434. func getReport(db *sql.DB) map[string]interface{} {
  435. nodes := 0
  436. var versions []string
  437. var platforms []string
  438. var numFolders []int
  439. var numDevices []int
  440. var totFiles []int
  441. var maxFiles []int
  442. var totMiB []int
  443. var maxMiB []int
  444. var memoryUsage []int
  445. var sha256Perf []float64
  446. var memorySize []int
  447. var compilers []string
  448. var builders []string
  449. v2Reports := 0
  450. features := map[string]float64{
  451. "Rate limiting": 0,
  452. "Upgrades allowed (automatic)": 0,
  453. "Upgrades allowed (manual)": 0,
  454. "Folders, automatic normalization": 0,
  455. "Folders, ignore deletes": 0,
  456. "Folders, ignore permissions": 0,
  457. "Folders, master mode": 0,
  458. "Folders, simple versioning": 0,
  459. "Folders, external versioning": 0,
  460. "Folders, staggered versioning": 0,
  461. "Folders, trashcan versioning": 0,
  462. "Devices, compress always": 0,
  463. "Devices, compress metadata": 0,
  464. "Devices, compress nothing": 0,
  465. "Devices, custom certificate": 0,
  466. "Devices, dynamic addresses": 0,
  467. "Devices, static addresses": 0,
  468. "Devices, introducer": 0,
  469. "Relaying, enabled": 0,
  470. "Relaying, default relays": 0,
  471. "Relaying, other relays": 0,
  472. "Discovery, global enabled": 0,
  473. "Discovery, local enabled": 0,
  474. "Discovery, default servers (using DNS)": 0,
  475. "Discovery, default servers (using IP)": 0,
  476. "Discovery, other servers": 0,
  477. }
  478. var numCPU []int
  479. var rep report
  480. rows, err := db.Query(`SELECT ` + strings.Join(rep.FieldNames(), ",") + ` FROM Reports WHERE Received > now() - '1 day'::INTERVAL`)
  481. if err != nil {
  482. log.Println("sql:", err)
  483. return nil
  484. }
  485. defer rows.Close()
  486. for rows.Next() {
  487. err := rows.Scan(rep.FieldPointers()...)
  488. if err != nil {
  489. log.Println("sql:", err)
  490. return nil
  491. }
  492. nodes++
  493. versions = append(versions, transformVersion(rep.Version))
  494. platforms = append(platforms, rep.Platform)
  495. if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 {
  496. compilers = append(compilers, m[1])
  497. builders = append(builders, m[2])
  498. }
  499. if rep.NumFolders > 0 {
  500. numFolders = append(numFolders, rep.NumFolders)
  501. }
  502. if rep.NumDevices > 0 {
  503. numDevices = append(numDevices, rep.NumDevices)
  504. }
  505. if rep.TotFiles > 0 {
  506. totFiles = append(totFiles, rep.TotFiles)
  507. }
  508. if rep.FolderMaxFiles > 0 {
  509. maxFiles = append(maxFiles, rep.FolderMaxFiles)
  510. }
  511. if rep.TotMiB > 0 {
  512. totMiB = append(totMiB, rep.TotMiB*(1<<20))
  513. }
  514. if rep.FolderMaxMiB > 0 {
  515. maxMiB = append(maxMiB, rep.FolderMaxMiB*(1<<20))
  516. }
  517. if rep.MemoryUsageMiB > 0 {
  518. memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
  519. }
  520. if rep.SHA256Perf > 0 {
  521. sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
  522. }
  523. if rep.MemorySize > 0 {
  524. memorySize = append(memorySize, rep.MemorySize*(1<<20))
  525. }
  526. if rep.URVersion >= 2 {
  527. v2Reports++
  528. numCPU = append(numCPU, rep.NumCPU)
  529. if rep.UsesRateLimit {
  530. features["Rate limiting"]++
  531. }
  532. if rep.UpgradeAllowedAuto {
  533. features["Upgrades allowed (automatic)"]++
  534. }
  535. if rep.UpgradeAllowedManual {
  536. features["Upgrades allowed (manual)"]++
  537. }
  538. if rep.FolderUses.AutoNormalize > 0 {
  539. features["Folders, automatic normalization"]++
  540. }
  541. if rep.FolderUses.IgnoreDelete > 0 {
  542. features["Folders, ignore deletes"]++
  543. }
  544. if rep.FolderUses.IgnorePerms > 0 {
  545. features["Folders, ignore permissions"]++
  546. }
  547. if rep.FolderUses.ReadOnly > 0 {
  548. features["Folders, master mode"]++
  549. }
  550. if rep.FolderUses.SimpleVersioning > 0 {
  551. features["Folders, simple versioning"]++
  552. }
  553. if rep.FolderUses.ExternalVersioning > 0 {
  554. features["Folders, external versioning"]++
  555. }
  556. if rep.FolderUses.StaggeredVersioning > 0 {
  557. features["Folders, staggered versioning"]++
  558. }
  559. if rep.FolderUses.TrashcanVersioning > 0 {
  560. features["Folders, trashcan versioning"]++
  561. }
  562. if rep.DeviceUses.CompressAlways > 0 {
  563. features["Devices, compress always"]++
  564. }
  565. if rep.DeviceUses.CompressMetadata > 0 {
  566. features["Devices, compress metadata"]++
  567. }
  568. if rep.DeviceUses.CompressNever > 0 {
  569. features["Devices, compress nothing"]++
  570. }
  571. if rep.DeviceUses.CustomCertName > 0 {
  572. features["Devices, custom certificate"]++
  573. }
  574. if rep.DeviceUses.DynamicAddr > 0 {
  575. features["Devices, dynamic addresses"]++
  576. }
  577. if rep.DeviceUses.StaticAddr > 0 {
  578. features["Devices, static addresses"]++
  579. }
  580. if rep.DeviceUses.Introducer > 0 {
  581. features["Devices, introducer"]++
  582. }
  583. if rep.Relays.Enabled {
  584. features["Relaying, enabled"]++
  585. }
  586. if rep.Relays.DefaultServers > 0 {
  587. features["Relaying, default relays"]++
  588. }
  589. if rep.Relays.OtherServers > 0 {
  590. features["Relaying, other relays"]++
  591. }
  592. if rep.Announce.GlobalEnabled {
  593. features["Discovery, global enabled"]++
  594. }
  595. if rep.Announce.LocalEnabled {
  596. features["Discovery, local enabled"]++
  597. }
  598. if rep.Announce.DefaultServersDNS > 0 {
  599. features["Discovery, default servers (using DNS)"]++
  600. }
  601. if rep.Announce.DefaultServersIP > 0 {
  602. features["Discovery, default servers (using IP)"]++
  603. }
  604. if rep.Announce.DefaultServersIP > 0 {
  605. features["Discovery, other servers"]++
  606. }
  607. }
  608. }
  609. var categories []category
  610. categories = append(categories, category{
  611. Values: statsForInts(totFiles),
  612. Descr: "Files Managed per Device",
  613. })
  614. categories = append(categories, category{
  615. Values: statsForInts(maxFiles),
  616. Descr: "Files in Largest Folder",
  617. })
  618. categories = append(categories, category{
  619. Values: statsForInts(totMiB),
  620. Descr: "Data Managed per Device",
  621. Unit: "B",
  622. Binary: true,
  623. })
  624. categories = append(categories, category{
  625. Values: statsForInts(maxMiB),
  626. Descr: "Data in Largest Folder",
  627. Unit: "B",
  628. Binary: true,
  629. })
  630. categories = append(categories, category{
  631. Values: statsForInts(numDevices),
  632. Descr: "Number of Devices in Cluster",
  633. })
  634. categories = append(categories, category{
  635. Values: statsForInts(numFolders),
  636. Descr: "Number of Folders Configured",
  637. })
  638. categories = append(categories, category{
  639. Values: statsForInts(memoryUsage),
  640. Descr: "Memory Usage",
  641. Unit: "B",
  642. Binary: true,
  643. })
  644. categories = append(categories, category{
  645. Values: statsForInts(memorySize),
  646. Descr: "System Memory",
  647. Unit: "B",
  648. Binary: true,
  649. })
  650. categories = append(categories, category{
  651. Values: statsForFloats(sha256Perf),
  652. Descr: "SHA-256 Hashing Performance",
  653. Unit: "B/s",
  654. Binary: true,
  655. })
  656. categories = append(categories, category{
  657. Values: statsForInts(numCPU),
  658. Descr: "Number of CPU cores",
  659. })
  660. var featureList []feature
  661. var featureNames []string
  662. for key := range features {
  663. featureNames = append(featureNames, key)
  664. }
  665. sort.Strings(featureNames)
  666. if v2Reports > 0 {
  667. for _, key := range featureNames {
  668. featureList = append(featureList, feature{
  669. Key: key,
  670. Pct: (100 * features[key]) / float64(v2Reports),
  671. })
  672. }
  673. sort.Sort(sort.Reverse(sortableFeatureList(featureList)))
  674. }
  675. r := make(map[string]interface{})
  676. r["nodes"] = nodes
  677. r["v2nodes"] = v2Reports
  678. r["categories"] = categories
  679. r["versions"] = group(byVersion, analyticsFor(versions, 2000), 5)
  680. r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 5)
  681. r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 3)
  682. r["builders"] = analyticsFor(builders, 12)
  683. r["features"] = featureList
  684. return r
  685. }
  686. func ensureDir(dir string, mode int) {
  687. fi, err := os.Stat(dir)
  688. if os.IsNotExist(err) {
  689. os.MkdirAll(dir, 0700)
  690. } else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
  691. os.Chmod(dir, os.FileMode(mode))
  692. }
  693. }
  694. var vRe = regexp.MustCompile(`^(v\d+\.\d+\.\d+(?:-[a-z]\w+)?)[+\.-]`)
  695. // transformVersion returns a version number formatted correctly, with all
  696. // development versions aggregated into one.
  697. func transformVersion(v string) string {
  698. if v == "unknown-dev" {
  699. return v
  700. }
  701. if !strings.HasPrefix(v, "v") {
  702. v = "v" + v
  703. }
  704. if m := vRe.FindStringSubmatch(v); len(m) > 0 {
  705. return m[1] + " (+dev)"
  706. }
  707. return v
  708. }
  709. type summary struct {
  710. versions map[string]int // version string to count index
  711. max map[string]int // version string to max users per day
  712. rows map[string][]int // date to list of counts
  713. }
  714. func newSummary() summary {
  715. return summary{
  716. versions: make(map[string]int),
  717. max: make(map[string]int),
  718. rows: make(map[string][]int),
  719. }
  720. }
  721. func (s *summary) setCount(date, version string, count int) {
  722. idx, ok := s.versions[version]
  723. if !ok {
  724. idx = len(s.versions)
  725. s.versions[version] = idx
  726. }
  727. if s.max[version] < count {
  728. s.max[version] = count
  729. }
  730. row := s.rows[date]
  731. if len(row) <= idx {
  732. old := row
  733. row = make([]int, idx+1)
  734. copy(row, old)
  735. s.rows[date] = row
  736. }
  737. row[idx] = count
  738. }
  739. func (s *summary) MarshalJSON() ([]byte, error) {
  740. var versions []string
  741. for v := range s.versions {
  742. versions = append(versions, v)
  743. }
  744. sort.Strings(versions)
  745. var filtered []string
  746. for _, v := range versions {
  747. if s.max[v] > 50 {
  748. filtered = append(filtered, v)
  749. }
  750. }
  751. versions = filtered
  752. headerRow := []interface{}{"Day"}
  753. for _, v := range versions {
  754. headerRow = append(headerRow, v)
  755. }
  756. var table [][]interface{}
  757. table = append(table, headerRow)
  758. var dates []string
  759. for k := range s.rows {
  760. dates = append(dates, k)
  761. }
  762. sort.Strings(dates)
  763. for _, date := range dates {
  764. row := []interface{}{date}
  765. for _, ver := range versions {
  766. idx := s.versions[ver]
  767. if len(s.rows[date]) > idx && s.rows[date][idx] > 0 {
  768. row = append(row, s.rows[date][idx])
  769. } else {
  770. row = append(row, nil)
  771. }
  772. }
  773. table = append(table, row)
  774. }
  775. return json.Marshal(table)
  776. }
  777. func getSummary(db *sql.DB) (summary, error) {
  778. s := newSummary()
  779. rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '2 year'::INTERVAL;`)
  780. if err != nil {
  781. return summary{}, err
  782. }
  783. defer rows.Close()
  784. for rows.Next() {
  785. var day time.Time
  786. var ver string
  787. var num int
  788. err := rows.Scan(&day, &ver, &num)
  789. if err != nil {
  790. return summary{}, err
  791. }
  792. if ver == "v0.0" {
  793. // ?
  794. continue
  795. }
  796. // SUPER UGLY HACK to avoid having to do sorting properly
  797. if len(ver) == 4 { // v0.x
  798. ver = ver[:3] + "0" + ver[3:] // now v0.0x
  799. }
  800. s.setCount(day.Format("2006-01-02"), ver, num)
  801. }
  802. return s, nil
  803. }
  804. func getMovement(db *sql.DB) ([][]interface{}, error) {
  805. rows, err := db.Query(`SELECT Day, Added, Removed, Bounced FROM UserMovement WHERE Day > now() - '2 year'::INTERVAL ORDER BY Day`)
  806. if err != nil {
  807. return nil, err
  808. }
  809. defer rows.Close()
  810. res := [][]interface{}{
  811. {"Day", "Joined", "Left", "Bounced"},
  812. }
  813. for rows.Next() {
  814. var day time.Time
  815. var added, removed, bounced int
  816. err := rows.Scan(&day, &added, &removed, &bounced)
  817. if err != nil {
  818. return nil, err
  819. }
  820. row := []interface{}{day.Format("2006-01-02"), added, -removed, bounced}
  821. if removed == 0 {
  822. row[2] = nil
  823. }
  824. if bounced == 0 {
  825. row[3] = nil
  826. }
  827. res = append(res, row)
  828. }
  829. return res, nil
  830. }
  831. func getPerformance(db *sql.DB) ([][]interface{}, error) {
  832. rows, err := db.Query(`SELECT Day, TotFiles, TotMiB, SHA256Perf, MemorySize, MemoryUsageMiB FROM Performance WHERE Day > '2014-06-20'::TIMESTAMP ORDER BY Day`)
  833. if err != nil {
  834. return nil, err
  835. }
  836. defer rows.Close()
  837. res := [][]interface{}{
  838. {"Day", "TotFiles", "TotMiB", "SHA256Perf", "MemorySize", "MemoryUsageMiB"},
  839. }
  840. for rows.Next() {
  841. var day time.Time
  842. var sha256Perf float64
  843. var totFiles, totMiB, memorySize, memoryUsage int
  844. err := rows.Scan(&day, &totFiles, &totMiB, &sha256Perf, &memorySize, &memoryUsage)
  845. if err != nil {
  846. return nil, err
  847. }
  848. row := []interface{}{day.Format("2006-01-02"), totFiles, totMiB, float64(int(sha256Perf*10)) / 10, memorySize, memoryUsage}
  849. res = append(res, row)
  850. }
  851. return res, nil
  852. }
  853. type sortableFeatureList []feature
  854. func (l sortableFeatureList) Len() int {
  855. return len(l)
  856. }
  857. func (l sortableFeatureList) Swap(a, b int) {
  858. l[a], l[b] = l[b], l[a]
  859. }
  860. func (l sortableFeatureList) Less(a, b int) bool {
  861. if l[a].Pct != l[b].Pct {
  862. return l[a].Pct < l[b].Pct
  863. }
  864. return l[a].Key > l[b].Key
  865. }