main.go 27 KB

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