From caad69a31273682c93935def89ef5cd3432695d0 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 28 Jun 2014 11:24:25 +0200 Subject: [PATCH] Number formatting --- analytics.go | 69 +++++++++++++++ formatting.go | 71 +++++++++++++++ main.go | 220 ++++++++++++++++------------------------------ static/index.html | 14 +-- 4 files changed, 224 insertions(+), 150 deletions(-) create mode 100644 analytics.go create mode 100644 formatting.go diff --git a/analytics.go b/analytics.go new file mode 100644 index 000000000..3cabb6c8e --- /dev/null +++ b/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 +} diff --git a/formatting.go b/formatting.go new file mode 100644 index 000000000..ede4345d5 --- /dev/null +++ b/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() +} diff --git a/main.go b/main.go index f5846e81a..808e400f2 100644 --- a/main.go +++ b/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") diff --git a/static/index.html b/static/index.html index 1ea1883e6..f751a423b 100644 --- a/static/index.html +++ b/static/index.html @@ -30,7 +30,7 @@ found in the LICENSE file.

Syncthing Usage Data

- This is the aggregated usage report data for today and yesterday. Data based on {{.nodes}} nodes that have reported in. + This is the aggregated usage report data for the last 24 hours. Data based on {{.nodes}} nodes that have reported in.

@@ -40,13 +40,13 @@ found in the LICENSE file. - {{range $cat := .categories}} + {{range .categories}} - - - - - + + + + + {{end}}
{{$cat.Descr}}{{index $ $cat.Key "fp" | printf "%v" | commatize " "}} {{$cat.Unit}}{{index $ $cat.Key "med" | printf "%v" | commatize " "}} {{$cat.Unit}}{{index $ $cat.Key "nfp" | printf "%v" | commatize " "}} {{$cat.Unit}}{{index $ $cat.Key "max" | printf "%v" | commatize " "}} {{$cat.Unit}}{{.Descr}}{{index .Values 0 | number .Binary | commatize " "}}{{.Unit}}{{index .Values 1 | number .Binary | commatize " "}}{{.Unit}}{{index .Values 2 | number .Binary | commatize " "}}{{.Unit}}{{index .Values 3 | number .Binary | commatize " "}}{{.Unit}}