Browse Source

Number formatting

Jakob Borg 11 years ago
parent
commit
caad69a312
4 changed files with 224 additions and 150 deletions
  1. 69 0
      analytics.go
  2. 71 0
      formatting.go
  3. 77 143
      main.go
  4. 7 7
      static/index.html

+ 69 - 0
analytics.go

@@ -0,0 +1,69 @@
+package main
+
+import "sort"
+
+type analytic struct {
+	Key        string
+	Count      int
+	Percentage float64
+}
+
+type analyticList []analytic
+
+func (l analyticList) Less(a, b int) bool {
+	return l[b].Count < l[a].Count // inverse
+}
+
+func (l analyticList) Swap(a, b int) {
+	l[a], l[b] = l[b], l[a]
+}
+
+func (l analyticList) Len() int {
+	return len(l)
+}
+
+// Returns a list of frequency analytics for a given list of strings.
+func analyticsFor(ss []string) []analytic {
+	m := make(map[string]int)
+	t := 0
+	for _, s := range ss {
+		m[s]++
+		t++
+	}
+
+	l := make([]analytic, 0, len(m))
+	for k, c := range m {
+		l = append(l, analytic{k, c, 100 * float64(c) / float64(t)})
+	}
+
+	sort.Sort(analyticList(l))
+	return l
+}
+
+func statsForInts(data []int) [4]float64 {
+	var res [4]float64
+	if len(data) == 0 {
+		return res
+	}
+
+	sort.Ints(data)
+	res[0] = float64(data[int(float64(len(data))*0.05)])
+	res[1] = float64(data[len(data)/2])
+	res[2] = float64(data[int(float64(len(data))*0.95)])
+	res[3] = float64(data[len(data)-1])
+	return res
+}
+
+func statsForFloats(data []float64) [4]float64 {
+	var res [4]float64
+	if len(data) == 0 {
+		return res
+	}
+
+	sort.Float64s(data)
+	res[0] = data[int(float64(len(data))*0.05)]
+	res[1] = data[len(data)/2]
+	res[2] = data[int(float64(len(data))*0.95)]
+	res[3] = data[len(data)-1]
+	return res
+}

+ 71 - 0
formatting.go

@@ -0,0 +1,71 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+)
+
+func number(isBinary bool, v float64) string {
+	if isBinary {
+		return binary(v)
+	} else {
+		return metric(v)
+	}
+}
+
+type prefix struct {
+	Prefix     string
+	Multiplier float64
+}
+
+var metricPrefixes = []prefix{
+	{"G", 1e9},
+	{"M", 1e6},
+	{"k", 1e3},
+}
+
+var binaryPrefixes = []prefix{
+	{"Gi", 1 << 30},
+	{"Mi", 1 << 20},
+	{"Ki", 1 << 10},
+}
+
+func metric(v float64) string {
+	return withPrefix(v, metricPrefixes)
+}
+
+func binary(v float64) string {
+	return withPrefix(v, binaryPrefixes)
+}
+
+func withPrefix(v float64, ps []prefix) string {
+	for _, p := range ps {
+		if v >= p.Multiplier {
+			return fmt.Sprintf("%.1f %s", v/p.Multiplier, p.Prefix)
+		}
+	}
+	return fmt.Sprint(v)
+}
+
+// commatize returns a number with sep as thousands separators. Handles
+// integers and plain floats.
+func commatize(sep, s string) string {
+	var b bytes.Buffer
+	fs := strings.SplitN(s, ".", 2)
+
+	l := len(fs[0])
+	for i := range fs[0] {
+		b.Write([]byte{s[i]})
+		if i < l-1 && (l-i)%3 == 1 {
+			b.WriteString(sep)
+		}
+	}
+
+	if len(fs) > 1 && len(fs[1]) > 0 {
+		b.WriteString(".")
+		b.WriteString(fs[1])
+	}
+
+	return b.String()
+}

+ 77 - 143
main.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"bytes"
 	"encoding/json"
 	"flag"
 	"fmt"
@@ -14,7 +13,6 @@ import (
 	"path"
 	"path/filepath"
 	"regexp"
-	"sort"
 	"strings"
 	"sync"
 	"time"
@@ -28,48 +26,9 @@ var (
 	tpl      *template.Template
 )
 
-type category struct {
-	Key   string
-	Descr string
-	Unit  string
-}
-
-var categories = []category{
-	{Key: "totFiles", Descr: "Files Managed per Node", Unit: ""},
-	{Key: "maxFiles", Descr: "Files in Largest Repo", Unit: ""},
-	{Key: "totMiB", Descr: "Data Managed per Node", Unit: "MiB"},
-	{Key: "maxMiB", Descr: "Data in Largest Repo", Unit: "MiB"},
-	{Key: "numNodes", Descr: "Number of Nodes in Cluster", Unit: ""},
-	{Key: "numRepos", Descr: "Number of Repositories Configured", Unit: ""},
-	{Key: "memoryUsage", Descr: "Memory Usage", Unit: "MiB"},
-	{Key: "memorySize", Descr: "System Memory", Unit: "MiB"},
-	{Key: "sha256Perf", Descr: "SHA-256 Hashing Performance", Unit: "MiB/s"},
-}
-
-var numRe = regexp.MustCompile(`\d\d\d$`)
 var funcs = map[string]interface{}{
-	"number": func(n interface{}) string {
-		var s string
-		switch n := n.(type) {
-		case int:
-			s = fmt.Sprint(n)
-		case float64:
-			s = fmt.Sprintf("%.02f", n)
-		default:
-			return fmt.Sprint(n)
-		}
-
-		var b bytes.Buffer
-		l := len(s)
-		for i := range s {
-			b.Write([]byte{s[i]})
-			if (l-i)%3 == 1 {
-				b.WriteString(",")
-			}
-		}
-		return b.String()
-	},
 	"commatize": commatize,
+	"number":    number,
 }
 
 func main() {
@@ -203,12 +162,26 @@ func fileList() ([]string, error) {
 
 	l := make([]string, 0, len(files))
 	for _, f := range files {
-		l = append(l, f)
+		si, err := os.Stat(f)
+		if err != nil {
+			continue
+		}
+		if time.Since(si.ModTime()) < 24*time.Hour {
+			l = append(l, f)
+		}
 	}
 
 	return l, nil
 }
 
+type category struct {
+	Values [4]float64
+	Key    string
+	Descr  string
+	Unit   string
+	Binary bool
+}
+
 var reportCache map[string]interface{}
 var reportMutex sync.Mutex
 
@@ -270,22 +243,78 @@ func getReport(key string) map[string]interface{} {
 			maxFiles = append(maxFiles, rep.RepoMaxFiles)
 		}
 		if rep.TotMiB > 0 {
-			totMiB = append(totMiB, rep.TotMiB)
+			totMiB = append(totMiB, rep.TotMiB*(1<<20))
 		}
 		if rep.RepoMaxMiB > 0 {
-			maxMiB = append(maxMiB, rep.RepoMaxMiB)
+			maxMiB = append(maxMiB, rep.RepoMaxMiB*(1<<20))
 		}
 		if rep.MemoryUsageMiB > 0 {
-			memoryUsage = append(memoryUsage, rep.MemoryUsageMiB)
+			memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
 		}
 		if rep.SHA256Perf > 0 {
-			sha256Perf = append(sha256Perf, rep.SHA256Perf)
+			sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
 		}
 		if rep.MemorySize > 0 {
-			memorySize = append(memorySize, rep.MemorySize)
+			memorySize = append(memorySize, rep.MemorySize*(1<<20))
 		}
 	}
 
+	var categories []category
+	categories = append(categories, category{
+		Values: statsForInts(totFiles),
+		Descr:  "Files Managed per Node",
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(maxFiles),
+		Descr:  "Files in Largest Repo",
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(totMiB),
+		Descr:  "Data Managed per Node",
+		Unit:   "B",
+		Binary: true,
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(maxMiB),
+		Descr:  "Data in Largest Repo",
+		Unit:   "B",
+		Binary: true,
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(numNodes),
+		Descr:  "Number of Nodes in Cluster",
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(numRepos),
+		Descr:  "Number of Repositories Configured",
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(memoryUsage),
+		Descr:  "Memory Usage",
+		Unit:   "B",
+		Binary: true,
+	})
+
+	categories = append(categories, category{
+		Values: statsForInts(memorySize),
+		Descr:  "System Memory",
+		Unit:   "B",
+		Binary: true,
+	})
+
+	categories = append(categories, category{
+		Values: statsForFloats(sha256Perf),
+		Descr:  "SHA-256 Hashing Performance",
+		Unit:   "B/s",
+		Binary: true,
+	})
+
 	r := make(map[string]interface{})
 	r["key"] = key
 	r["nodes"] = nodes
@@ -293,85 +322,12 @@ func getReport(key string) map[string]interface{} {
 	r["versions"] = analyticsFor(versions)
 	r["platforms"] = analyticsFor(platforms)
 	r["os"] = analyticsFor(oses)
-	r["numRepos"] = statsForInts(numRepos)
-	r["numNodes"] = statsForInts(numNodes)
-	r["totFiles"] = statsForInts(totFiles)
-	r["maxFiles"] = statsForInts(maxFiles)
-	r["totMiB"] = statsForInts(totMiB)
-	r["maxMiB"] = statsForInts(maxMiB)
-	r["memoryUsage"] = statsForInts(memoryUsage)
-	r["sha256Perf"] = statsForFloats(sha256Perf)
-	r["memorySize"] = statsForInts(memorySize)
 
 	reportCache = r
 
 	return r
 }
 
-type analytic struct {
-	Key        string
-	Count      int
-	Percentage float64
-}
-
-type analyticList []analytic
-
-func (l analyticList) Less(a, b int) bool {
-	return l[b].Count < l[a].Count // inverse
-}
-
-func (l analyticList) Swap(a, b int) {
-	l[a], l[b] = l[b], l[a]
-}
-
-func (l analyticList) Len() int {
-	return len(l)
-}
-
-// Returns a list of frequency analytics for a given list of strings.
-func analyticsFor(ss []string) []analytic {
-	m := make(map[string]int)
-	t := 0
-	for _, s := range ss {
-		m[s]++
-		t++
-	}
-
-	l := make([]analytic, 0, len(m))
-	for k, c := range m {
-		l = append(l, analytic{k, c, 100 * float64(c) / float64(t)})
-	}
-
-	sort.Sort(analyticList(l))
-	return l
-}
-
-func statsForInts(data []int) map[string]int {
-	sort.Ints(data)
-	res := make(map[string]int, 4)
-	if len(data) == 0 {
-		return res
-	}
-	res["fp"] = data[int(float64(len(data))*0.05)]
-	res["med"] = data[len(data)/2]
-	res["nfp"] = data[int(float64(len(data))*0.95)]
-	res["max"] = data[len(data)-1]
-	return res
-}
-
-func statsForFloats(data []float64) map[string]float64 {
-	sort.Float64s(data)
-	res := make(map[string]float64, 4)
-	if len(data) == 0 {
-		return res
-	}
-	res["fp"] = data[int(float64(len(data))*0.05)]
-	res["med"] = data[len(data)/2]
-	res["nfp"] = data[int(float64(len(data))*0.95)]
-	res["max"] = data[len(data)-1]
-	return res
-}
-
 func ensureDir(dir string, mode int) {
 	fi, err := os.Stat(dir)
 	if os.IsNotExist(err) {
@@ -395,28 +351,6 @@ func transformVersion(v string) string {
 	return v
 }
 
-// commatize returns a number with sep as thousands separators. Handles
-// integers and plain floats.
-func commatize(sep, s string) string {
-	var b bytes.Buffer
-	fs := strings.SplitN(s, ".", 2)
-
-	l := len(fs[0])
-	for i := range fs[0] {
-		b.Write([]byte{s[i]})
-		if i < l-1 && (l-i)%3 == 1 {
-			b.WriteString(sep)
-		}
-	}
-
-	if len(fs) > 1 && len(fs[1]) > 0 {
-		b.WriteString(".")
-		b.WriteString(fs[1])
-	}
-
-	return b.String()
-}
-
 // timestamp returns a time stamp for the current hour, to be used as a cache key
 func timestamp() string {
 	return time.Now().Format("20060102T15")

+ 7 - 7
static/index.html

@@ -30,7 +30,7 @@ found in the LICENSE file.
       <div class="col-md-12">
         <h1>Syncthing Usage Data</h1>
         <p>
-          This is the aggregated usage report data for today and yesterday. Data based on <b>{{.nodes}}</b> nodes that have reported in.
+          This is the aggregated usage report data for the last 24 hours. Data based on <b>{{.nodes}}</b> nodes that have reported in.
         </p>
 
         <table class="table table-striped">
@@ -40,13 +40,13 @@ found in the LICENSE file.
             </tr>
           </thead>
           <tbody>
-            {{range $cat := .categories}}
+            {{range .categories}}
             <tr>
-              <td>{{$cat.Descr}}</td>
-              <td class="text-right">{{index $ $cat.Key "fp" | printf "%v" | commatize " "}} {{$cat.Unit}}</td>
-              <td class="text-right">{{index $ $cat.Key "med" | printf "%v" | commatize " "}} {{$cat.Unit}}</td>
-              <td class="text-right">{{index $ $cat.Key "nfp" | printf "%v" | commatize " "}} {{$cat.Unit}}</td>
-              <td class="text-right">{{index $ $cat.Key "max" | printf "%v" | commatize " "}} {{$cat.Unit}}</td>
+              <td>{{.Descr}}</td>
+              <td class="text-right">{{index .Values 0 | number .Binary | commatize " "}}{{.Unit}}</td>
+              <td class="text-right">{{index .Values 1 | number .Binary | commatize " "}}{{.Unit}}</td>
+              <td class="text-right">{{index .Values 2 | number .Binary | commatize " "}}{{.Unit}}</td>
+              <td class="text-right">{{index .Values 3 | number .Binary | commatize " "}}{{.Unit}}</td>
             </tr>
             {{end}}
           </tbody>