main.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package main
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "fmt"
  6. "html/template"
  7. "io"
  8. "io/ioutil"
  9. "log"
  10. "net/http"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "regexp"
  15. "strings"
  16. "sync"
  17. "time"
  18. )
  19. var (
  20. keyFile = flag.String("key", "", "Key file")
  21. certFile = flag.String("cert", "", "Certificate file")
  22. dbDir = flag.String("db", "", "Database directory")
  23. port = flag.Int("port", 8443, "Listen port")
  24. tpl *template.Template
  25. )
  26. var funcs = map[string]interface{}{
  27. "commatize": commatize,
  28. "number": number,
  29. }
  30. func main() {
  31. flag.Parse()
  32. log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
  33. fd, err := os.Open("static/index.html")
  34. if err != nil {
  35. log.Fatal(err)
  36. }
  37. bs, err := ioutil.ReadAll(fd)
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. fd.Close()
  42. tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs)))
  43. http.HandleFunc("/", rootHandler)
  44. http.HandleFunc("/newdata", newDataHandler)
  45. http.HandleFunc("/report", reportHandler)
  46. http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  47. err = http.ListenAndServeTLS(fmt.Sprintf(":%d", *port), *certFile, *keyFile, nil)
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. }
  52. func rootHandler(w http.ResponseWriter, r *http.Request) {
  53. if r.URL.Path == "/" || r.URL.Path == "/index.html" {
  54. k := timestamp()
  55. rep := getReport(k)
  56. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  57. err := tpl.Execute(w, rep)
  58. if err != nil {
  59. log.Println(err)
  60. }
  61. } else {
  62. http.Error(w, "Not found", 404)
  63. }
  64. }
  65. func reportHandler(w http.ResponseWriter, r *http.Request) {
  66. w.Header().Set("Content-Type", "application/json")
  67. k := timestamp()
  68. rep := getReport(k)
  69. json.NewEncoder(w).Encode(rep)
  70. }
  71. func newDataHandler(w http.ResponseWriter, r *http.Request) {
  72. today := time.Now().Format("20060102")
  73. dir := filepath.Join(*dbDir, today)
  74. ensureDir(dir, 0700)
  75. var m map[string]interface{}
  76. lr := &io.LimitedReader{R: r.Body, N: 10240}
  77. err := json.NewDecoder(lr).Decode(&m)
  78. if err != nil {
  79. log.Println(err)
  80. http.Error(w, err.Error(), 500)
  81. return
  82. }
  83. id, ok := m["uniqueID"]
  84. if ok {
  85. idStr, ok := id.(string)
  86. if !ok {
  87. if err != nil {
  88. log.Println("No ID")
  89. http.Error(w, "No ID", 500)
  90. return
  91. }
  92. }
  93. f, err := os.Create(path.Join(dir, idStr+".json"))
  94. if err != nil {
  95. log.Println(err)
  96. http.Error(w, err.Error(), 500)
  97. return
  98. }
  99. json.NewEncoder(f).Encode(m)
  100. f.Close()
  101. } else {
  102. log.Println("No ID")
  103. http.Error(w, "No ID", 500)
  104. return
  105. }
  106. }
  107. type report struct {
  108. UniqueID string
  109. Version string
  110. Platform string
  111. NumRepos int
  112. NumNodes int
  113. TotFiles int
  114. RepoMaxFiles int
  115. TotMiB int
  116. RepoMaxMiB int
  117. MemoryUsageMiB int
  118. SHA256Perf float64
  119. MemorySize int
  120. }
  121. func fileList() ([]string, error) {
  122. files := make(map[string]string)
  123. t0 := time.Now().Add(-24 * time.Hour).Format("20060102")
  124. t1 := time.Now().Format("20060102")
  125. dir := filepath.Join(*dbDir, t0)
  126. gr, err := filepath.Glob(filepath.Join(dir, "*.json"))
  127. if err != nil {
  128. return nil, err
  129. }
  130. for _, f := range gr {
  131. bn := filepath.Base(f)
  132. files[bn] = f
  133. }
  134. dir = filepath.Join(*dbDir, t1)
  135. gr, err = filepath.Glob(filepath.Join(dir, "*.json"))
  136. if err != nil {
  137. return nil, err
  138. }
  139. for _, f := range gr {
  140. bn := filepath.Base(f)
  141. files[bn] = f
  142. }
  143. l := make([]string, 0, len(files))
  144. for _, f := range files {
  145. si, err := os.Stat(f)
  146. if err != nil {
  147. continue
  148. }
  149. if time.Since(si.ModTime()) < 24*time.Hour {
  150. l = append(l, f)
  151. }
  152. }
  153. return l, nil
  154. }
  155. type category struct {
  156. Values [4]float64
  157. Key string
  158. Descr string
  159. Unit string
  160. Binary bool
  161. }
  162. var reportCache map[string]interface{}
  163. var reportMutex sync.Mutex
  164. func getReport(key string) map[string]interface{} {
  165. reportMutex.Lock()
  166. defer reportMutex.Unlock()
  167. if k := reportCache["key"]; k == key {
  168. return reportCache
  169. }
  170. files, err := fileList()
  171. if err != nil {
  172. return nil
  173. }
  174. nodes := 0
  175. var versions []string
  176. var platforms []string
  177. var oses []string
  178. var numRepos []int
  179. var numNodes []int
  180. var totFiles []int
  181. var maxFiles []int
  182. var totMiB []int
  183. var maxMiB []int
  184. var memoryUsage []int
  185. var sha256Perf []float64
  186. var memorySize []int
  187. for _, fn := range files {
  188. f, err := os.Open(fn)
  189. if err != nil {
  190. continue
  191. }
  192. var rep report
  193. err = json.NewDecoder(f).Decode(&rep)
  194. if err != nil {
  195. continue
  196. }
  197. f.Close()
  198. nodes++
  199. versions = append(versions, transformVersion(rep.Version))
  200. platforms = append(platforms, rep.Platform)
  201. ps := strings.Split(rep.Platform, "-")
  202. oses = append(oses, ps[0])
  203. if rep.NumRepos > 0 {
  204. numRepos = append(numRepos, rep.NumRepos)
  205. }
  206. if rep.NumNodes > 0 {
  207. numNodes = append(numNodes, rep.NumNodes)
  208. }
  209. if rep.TotFiles > 0 {
  210. totFiles = append(totFiles, rep.TotFiles)
  211. }
  212. if rep.RepoMaxFiles > 0 {
  213. maxFiles = append(maxFiles, rep.RepoMaxFiles)
  214. }
  215. if rep.TotMiB > 0 {
  216. totMiB = append(totMiB, rep.TotMiB*(1<<20))
  217. }
  218. if rep.RepoMaxMiB > 0 {
  219. maxMiB = append(maxMiB, rep.RepoMaxMiB*(1<<20))
  220. }
  221. if rep.MemoryUsageMiB > 0 {
  222. memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
  223. }
  224. if rep.SHA256Perf > 0 {
  225. sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
  226. }
  227. if rep.MemorySize > 0 {
  228. memorySize = append(memorySize, rep.MemorySize*(1<<20))
  229. }
  230. }
  231. var categories []category
  232. categories = append(categories, category{
  233. Values: statsForInts(totFiles),
  234. Descr: "Files Managed per Node",
  235. })
  236. categories = append(categories, category{
  237. Values: statsForInts(maxFiles),
  238. Descr: "Files in Largest Repo",
  239. })
  240. categories = append(categories, category{
  241. Values: statsForInts(totMiB),
  242. Descr: "Data Managed per Node",
  243. Unit: "B",
  244. Binary: true,
  245. })
  246. categories = append(categories, category{
  247. Values: statsForInts(maxMiB),
  248. Descr: "Data in Largest Repo",
  249. Unit: "B",
  250. Binary: true,
  251. })
  252. categories = append(categories, category{
  253. Values: statsForInts(numNodes),
  254. Descr: "Number of Nodes in Cluster",
  255. })
  256. categories = append(categories, category{
  257. Values: statsForInts(numRepos),
  258. Descr: "Number of Repositories Configured",
  259. })
  260. categories = append(categories, category{
  261. Values: statsForInts(memoryUsage),
  262. Descr: "Memory Usage",
  263. Unit: "B",
  264. Binary: true,
  265. })
  266. categories = append(categories, category{
  267. Values: statsForInts(memorySize),
  268. Descr: "System Memory",
  269. Unit: "B",
  270. Binary: true,
  271. })
  272. categories = append(categories, category{
  273. Values: statsForFloats(sha256Perf),
  274. Descr: "SHA-256 Hashing Performance",
  275. Unit: "B/s",
  276. Binary: true,
  277. })
  278. r := make(map[string]interface{})
  279. r["key"] = key
  280. r["nodes"] = nodes
  281. r["categories"] = categories
  282. r["versions"] = analyticsFor(versions)
  283. r["platforms"] = analyticsFor(platforms)
  284. r["os"] = analyticsFor(oses)
  285. reportCache = r
  286. return r
  287. }
  288. func ensureDir(dir string, mode int) {
  289. fi, err := os.Stat(dir)
  290. if os.IsNotExist(err) {
  291. os.MkdirAll(dir, 0700)
  292. } else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
  293. os.Chmod(dir, os.FileMode(mode))
  294. }
  295. }
  296. var vRe = regexp.MustCompile(`^(v\d+\.\d+\.\d+)-.+`)
  297. // transformVersion returns a version number formatted correctly, with all
  298. // development versions aggregated into one.
  299. func transformVersion(v string) string {
  300. if !strings.HasPrefix(v, "v") {
  301. v = "v" + v
  302. }
  303. if m := vRe.FindStringSubmatch(v); len(m) > 0 {
  304. return m[1] + " (+dev)"
  305. }
  306. return v
  307. }
  308. // timestamp returns a time stamp for the current hour, to be used as a cache key
  309. func timestamp() string {
  310. return time.Now().Format("20060102T15")
  311. }