123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- // Copyright (C) 2018 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package serve
- import (
- "regexp"
- "sort"
- "strconv"
- "strings"
- )
- type analytic struct {
- Key string
- Count int
- Percentage float64
- Items []analytic `json:",omitempty"`
- }
- type analyticList []analytic
- func (l analyticList) Less(a, b int) bool {
- if l[a].Key == "Others" {
- return false
- }
- if l[b].Key == "Others" {
- return true
- }
- 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, cutoff int) []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{
- Key: k,
- Count: c,
- Percentage: 100 * float64(c) / float64(t),
- })
- }
- sort.Sort(analyticList(l))
- if cutoff > 0 && len(l) > cutoff {
- c := 0
- for _, i := range l[cutoff:] {
- c += i.Count
- }
- l = append(l[:cutoff], analytic{
- Key: "Others",
- Count: c,
- Percentage: 100 * float64(c) / float64(t),
- })
- }
- return l
- }
- // Find the points at which certain penetration levels are met
- func penetrationLevels(as []analytic, points []float64) []analytic {
- sort.Slice(as, func(a, b int) bool {
- return versionLess(as[b].Key, as[a].Key)
- })
- var res []analytic
- idx := 0
- sum := 0.0
- for _, a := range as {
- sum += a.Percentage
- if sum >= points[idx] {
- a.Count = int(points[idx])
- a.Percentage = sum
- res = append(res, a)
- idx++
- if idx == len(points) {
- break
- }
- }
- }
- return res
- }
- 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 statsForInt64s(data []int64) [4]float64 {
- var res [4]float64
- if len(data) == 0 {
- return res
- }
- sort.Slice(data, func(a, b int) bool {
- return data[a] < data[b]
- })
- 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
- }
- func group(by func(string) string, as []analytic, perGroup int, otherPct float64) []analytic {
- var res []analytic
- next:
- for _, a := range as {
- group := by(a.Key)
- for i := range res {
- if res[i].Key == group {
- res[i].Count += a.Count
- res[i].Percentage += a.Percentage
- if len(res[i].Items) < perGroup {
- res[i].Items = append(res[i].Items, a)
- }
- continue next
- }
- }
- res = append(res, analytic{
- Key: group,
- Count: a.Count,
- Percentage: a.Percentage,
- Items: []analytic{a},
- })
- }
- sort.Sort(analyticList(res))
- if otherPct > 0 {
- // Groups with less than otherPCt go into "Other"
- other := analytic{
- Key: "Other",
- }
- for i := 0; i < len(res); i++ {
- if res[i].Percentage < otherPct || res[i].Key == "Other" {
- other.Count += res[i].Count
- other.Percentage += res[i].Percentage
- res = append(res[:i], res[i+1:]...)
- i--
- }
- }
- if other.Count > 0 {
- res = append(res, other)
- }
- }
- return res
- }
- func byVersion(s string) string {
- parts := strings.Split(s, ".")
- if len(parts) >= 2 {
- return strings.Join(parts[:2], ".")
- }
- return s
- }
- func byPlatform(s string) string {
- parts := strings.Split(s, "-")
- if len(parts) >= 2 {
- return parts[0]
- }
- return s
- }
- var numericGoVersion = regexp.MustCompile(`^go[0-9]\.[0-9]+`)
- func byCompiler(s string) string {
- if m := numericGoVersion.FindString(s); m != "" {
- return m
- }
- return "Other"
- }
- func versionLess(a, b string) bool {
- arel, apre := versionParts(a)
- brel, bpre := versionParts(b)
- minlen := len(arel)
- if l := len(brel); l < minlen {
- minlen = l
- }
- for i := 0; i < minlen; i++ {
- if arel[i] != brel[i] {
- return arel[i] < brel[i]
- }
- }
- // Longer version is newer, when the preceding parts are equal
- if len(arel) != len(brel) {
- return len(arel) < len(brel)
- }
- if apre != bpre {
- // "(+dev)" versions are ahead
- if apre == plusStr {
- return false
- }
- if bpre == plusStr {
- return true
- }
- return apre < bpre
- }
- // don't actually care how the prerelease stuff compares for our purposes
- return false
- }
- // Split a version as returned from transformVersion into parts.
- // "1.2.3-beta.2" -> []int{1, 2, 3}, "beta.2"}
- func versionParts(v string) ([]int, string) {
- parts := strings.SplitN(v[1:], " ", 2) // " (+dev)" versions
- if len(parts) == 1 {
- parts = strings.SplitN(parts[0], "-", 2) // "-rc.1" type versions
- }
- fields := strings.Split(parts[0], ".")
- release := make([]int, len(fields))
- for i, s := range fields {
- v, _ := strconv.Atoi(s)
- release[i] = v
- }
- var prerelease string
- if len(parts) > 1 {
- prerelease = parts[1]
- }
- return release, prerelease
- }
|