Kaynağa Gözat

feat(ursrv): new metrics based approach

Jakob Borg 1 yıl önce
ebeveyn
işleme
4d842f7d3b

+ 0 - 1
cmd/infra/strelaypoolsrv/README.md

@@ -21,4 +21,3 @@ See `relaypoolsrv -help` for configuration options.
 
 [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang), [oschwald/maxminddb-golang](https://github.com/oschwald/maxminddb-golang), Copyright (C) 2015 [Gregory J. Oschwald](mailto:[email protected]).
 
-[lib/pq](https://github.com/lib/pq)</a>, Copyright (C) 2011-2013 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany.

+ 0 - 226
cmd/infra/ursrv/aggregate/aggregate.go

@@ -1,226 +0,0 @@
-// 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 aggregate
-
-import (
-	"database/sql"
-	"fmt"
-	"log"
-	"os"
-	"time"
-
-	_ "github.com/lib/pq"
-)
-
-type CLI struct {
-	DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"`
-}
-
-func (cli *CLI) Run() error {
-	log.SetFlags(log.Ltime | log.Ldate)
-	log.SetOutput(os.Stdout)
-
-	db, err := sql.Open("postgres", cli.DBConn)
-	if err != nil {
-		return fmt.Errorf("database: %w", err)
-	}
-	err = setupDB(db)
-	if err != nil {
-		return fmt.Errorf("database: %w", err)
-	}
-
-	for {
-		runAggregation(db)
-		// Sleep until one minute past next midnight
-		sleepUntilNext(24*time.Hour, 1*time.Minute)
-	}
-}
-
-func runAggregation(db *sql.DB) {
-	since := maxIndexedDay(db, "VersionSummary")
-	log.Println("Aggregating VersionSummary data since", since)
-	rows, err := aggregateVersionSummary(db, since.Add(24*time.Hour))
-	if err != nil {
-		log.Println("aggregate:", err)
-	}
-	log.Println("Inserted", rows, "rows")
-
-	since = maxIndexedDay(db, "Performance")
-	log.Println("Aggregating Performance data since", since)
-	rows, err = aggregatePerformance(db, since.Add(24*time.Hour))
-	if err != nil {
-		log.Println("aggregate:", err)
-	}
-	log.Println("Inserted", rows, "rows")
-
-	since = maxIndexedDay(db, "BlockStats")
-	log.Println("Aggregating BlockStats data since", since)
-	rows, err = aggregateBlockStats(db, since.Add(24*time.Hour))
-	if err != nil {
-		log.Println("aggregate:", err)
-	}
-	log.Println("Inserted", rows, "rows")
-}
-
-func sleepUntilNext(intv, margin time.Duration) {
-	now := time.Now().UTC()
-	next := now.Truncate(intv).Add(intv).Add(margin)
-	log.Println("Sleeping until", next)
-	time.Sleep(next.Sub(now))
-}
-
-func setupDB(db *sql.DB) error {
-	_, err := db.Exec(`CREATE TABLE IF NOT EXISTS VersionSummary (
-		Day TIMESTAMP NOT NULL,
-		Version VARCHAR(8) NOT NULL,
-		Count INTEGER NOT NULL
-	)`)
-	if err != nil {
-		return err
-	}
-
-	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS Performance (
-		Day TIMESTAMP NOT NULL,
-		TotFiles INTEGER NOT NULL,
-		TotMiB INTEGER NOT NULL,
-		SHA256Perf DOUBLE PRECISION NOT NULL,
-		MemorySize INTEGER NOT NULL,
-		MemoryUsageMiB INTEGER NOT NULL
-	)`)
-	if err != nil {
-		return err
-	}
-
-	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS BlockStats (
-		Day TIMESTAMP NOT NULL,
-		Reports INTEGER NOT NULL,
-		Total BIGINT NOT NULL,
-		Renamed BIGINT NOT NULL,
-		Reused BIGINT NOT NULL,
-		Pulled BIGINT NOT NULL,
-		CopyOrigin BIGINT NOT NULL,
-		CopyOriginShifted BIGINT NOT NULL,
-		CopyElsewhere BIGINT NOT NULL
-	)`)
-	if err != nil {
-		return err
-	}
-
-	var t string
-
-	row := db.QueryRow(`SELECT 'UniqueDayVersionIndex'::regclass`)
-	if err := row.Scan(&t); err != nil {
-		_, _ = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`)
-	}
-
-	row = db.QueryRow(`SELECT 'VersionDayIndex'::regclass`)
-	if err := row.Scan(&t); err != nil {
-		_, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
-	}
-
-	row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`)
-	if err := row.Scan(&t); err != nil {
-		_, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
-	}
-
-	row = db.QueryRow(`SELECT 'BlockStatsDayIndex'::regclass`)
-	if err := row.Scan(&t); err != nil {
-		_, _ = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`)
-	}
-
-	return nil
-}
-
-func maxIndexedDay(db *sql.DB, table string) time.Time {
-	var t time.Time
-	row := db.QueryRow("SELECT MAX(DATE_TRUNC('day', Day)) FROM " + table)
-	err := row.Scan(&t)
-	if err != nil {
-		return time.Time{}
-	}
-	return t
-}
-
-func aggregateVersionSummary(db *sql.DB, since time.Time) (int64, error) {
-	res, err := db.Exec(`INSERT INTO VersionSummary (
-	SELECT
-		DATE_TRUNC('day', Received) AS Day,
-		SUBSTRING(Report->>'version' FROM '^v\d.\d+') AS Ver,
-		COUNT(*) AS Count
-		FROM ReportsJson
-		WHERE
-			Received > $1
-			AND Received < DATE_TRUNC('day', NOW())
-			AND Report->>'version' like 'v_.%'
-		GROUP BY Day, Ver
-		);
-	`, since)
-	if err != nil {
-		return 0, err
-	}
-
-	return res.RowsAffected()
-}
-
-func aggregatePerformance(db *sql.DB, since time.Time) (int64, error) {
-	res, err := db.Exec(`INSERT INTO Performance (
-	SELECT
-		DATE_TRUNC('day', Received) AS Day,
-		AVG((Report->>'totFiles')::numeric) As TotFiles,
-		AVG((Report->>'totMiB')::numeric) As TotMiB,
-		AVG((Report->>'sha256Perf')::numeric) As SHA256Perf,
-		AVG((Report->>'memorySize')::numeric) As MemorySize,
-		AVG((Report->>'memoryUsageMiB')::numeric) As MemoryUsageMiB
-		FROM ReportsJson
-		WHERE
-			Received > $1
-			AND Received < DATE_TRUNC('day', NOW())
-			AND Report->>'version' like 'v_.%'
-			/* Some custom implementation reported bytes when we expect megabytes, cap at petabyte */
-			AND (Report->>'memorySize')::numeric < 1073741824
-		GROUP BY Day
-		);
-	`, since)
-	if err != nil {
-		return 0, err
-	}
-
-	return res.RowsAffected()
-}
-
-func aggregateBlockStats(db *sql.DB, since time.Time) (int64, error) {
-	// Filter out anything prior 0.14.41 as that has sum aggregations which
-	// made no sense.
-	res, err := db.Exec(`INSERT INTO BlockStats (
-	SELECT
-		DATE_TRUNC('day', Received) AS Day,
-		COUNT(1) As Reports,
-		SUM((Report->'blockStats'->>'total')::numeric)::bigint AS Total,
-		SUM((Report->'blockStats'->>'renamed')::numeric)::bigint AS Renamed,
-		SUM((Report->'blockStats'->>'reused')::numeric)::bigint AS Reused,
-		SUM((Report->'blockStats'->>'pulled')::numeric)::bigint AS Pulled,
-		SUM((Report->'blockStats'->>'copyOrigin')::numeric)::bigint AS CopyOrigin,
-		SUM((Report->'blockStats'->>'copyOriginShifted')::numeric)::bigint AS CopyOriginShifted,
-		SUM((Report->'blockStats'->>'copyElsewhere')::numeric)::bigint AS CopyElsewhere
-		FROM ReportsJson
-		WHERE
-			Received > $1
-			AND Received < DATE_TRUNC('day', NOW())
-			AND (Report->>'urVersion')::numeric >= 3
-			AND Report->>'version' like 'v_.%'
-			AND Report->>'version' NOT LIKE 'v0.14.40%'
-			AND Report->>'version' NOT LIKE 'v0.14.39%'
-			AND Report->>'version' NOT LIKE 'v0.14.38%'
-		GROUP BY Day
-	);
-	`, since)
-	if err != nil {
-		return 0, err
-	}
-
-	return res.RowsAffected()
-}

+ 5 - 5
cmd/infra/ursrv/main.go

@@ -8,22 +8,22 @@ package main
 
 import (
 	"log"
+	"log/slog"
 	"os"
 
 	"github.com/alecthomas/kong"
-	"github.com/syncthing/syncthing/cmd/infra/ursrv/aggregate"
 	"github.com/syncthing/syncthing/cmd/infra/ursrv/serve"
 	_ "github.com/syncthing/syncthing/lib/automaxprocs"
 )
 
 type CLI struct {
-	Serve     serve.CLI     `cmd:"" default:""`
-	Aggregate aggregate.CLI `cmd:""`
+	Serve serve.CLI `cmd:"" default:""`
 }
 
 func main() {
-	log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
-	log.SetOutput(os.Stdout)
+	slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+		Level: slog.LevelInfo,
+	})))
 
 	var cli CLI
 	ctx := kong.Parse(&cli)

+ 0 - 276
cmd/infra/ursrv/serve/analytics.go

@@ -1,276 +0,0 @@
-// 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
-}

+ 0 - 131
cmd/infra/ursrv/serve/formatting.go

@@ -1,131 +0,0 @@
-// 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 (
-	"bytes"
-	"fmt"
-	"strings"
-)
-
-type NumberType int
-
-const (
-	NumberMetric NumberType = iota
-	NumberBinary
-	NumberDuration
-)
-
-func number(ntype NumberType, v float64) string {
-	switch ntype {
-	case NumberMetric:
-		return metric(v)
-	case NumberDuration:
-		return duration(v)
-	case NumberBinary:
-		return binary(v)
-	default:
-		return metric(v)
-	}
-}
-
-type suffix struct {
-	Suffix     string
-	Multiplier float64
-}
-
-var metricSuffixes = []suffix{
-	{"G", 1e9},
-	{"M", 1e6},
-	{"k", 1e3},
-}
-
-var binarySuffixes = []suffix{
-	{"Gi", 1 << 30},
-	{"Mi", 1 << 20},
-	{"Ki", 1 << 10},
-}
-
-var durationSuffix = []suffix{
-	{"year", 365 * 24 * 60 * 60},
-	{"month", 30 * 24 * 60 * 60},
-	{"day", 24 * 60 * 60},
-	{"hour", 60 * 60},
-	{"minute", 60},
-	{"second", 1},
-}
-
-func metric(v float64) string {
-	return withSuffix(v, metricSuffixes, false)
-}
-
-func binary(v float64) string {
-	return withSuffix(v, binarySuffixes, false)
-}
-
-func duration(v float64) string {
-	return withSuffix(v, durationSuffix, true)
-}
-
-func withSuffix(v float64, ps []suffix, pluralize bool) string {
-	for _, p := range ps {
-		if v >= p.Multiplier {
-			suffix := p.Suffix
-			if pluralize && v/p.Multiplier != 1.0 {
-				suffix += "s"
-			}
-			// If the number only has decimal zeroes, strip em off.
-			num := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v/p.Multiplier), "0"), ".")
-			return fmt.Sprintf("%s %s", num, suffix)
-		}
-	}
-	return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
-}
-
-// commatize returns a number with sep as thousands separators. Handles
-// integers and plain floats.
-func commatize(sep, s string) string {
-	// If no dot, don't do anything.
-	if !strings.ContainsRune(s, '.') {
-		return s
-	}
-	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()
-}
-
-func proportion(m map[string]int, count int) float64 {
-	total := 0
-	isMax := true
-	for _, n := range m {
-		total += n
-		if n > count {
-			isMax = false
-		}
-	}
-	pct := (100 * float64(count)) / float64(total)
-	// To avoid rounding errors in the template, surpassing 100% and breaking
-	// the progress bars.
-	if isMax && len(m) > 1 && count != total {
-		pct -= 0.01
-	}
-	return pct
-}

+ 29 - 9
cmd/infra/ursrv/serve/metrics.go

@@ -11,16 +11,36 @@ import (
 	"github.com/prometheus/client_golang/prometheus/promauto"
 )
 
-var metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
-	Namespace: "syncthing",
-	Subsystem: "ursrv",
-	Name:      "reports_total",
-}, []string{"version"})
+var (
+	metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
+		Namespace: "syncthing",
+		Subsystem: "ursrv_v2",
+		Name:      "incoming_reports_total",
+	}, []string{"result"})
+	metricsCollectsTotal = promauto.NewCounter(prometheus.CounterOpts{
+		Namespace: "syncthing",
+		Subsystem: "ursrv_v2",
+		Name:      "collects_total",
+	})
+	metricsCollectSecondsTotal = promauto.NewCounter(prometheus.CounterOpts{
+		Namespace: "syncthing",
+		Subsystem: "ursrv_v2",
+		Name:      "collect_seconds_total",
+	})
+	metricsCollectSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{
+		Namespace: "syncthing",
+		Subsystem: "ursrv_v2",
+		Name:      "collect_seconds_last",
+	})
+	metricsWriteSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{
+		Namespace: "syncthing",
+		Subsystem: "ursrv_v2",
+		Name:      "write_seconds_last",
+	})
+)
 
 func init() {
 	metricReportsTotal.WithLabelValues("fail")
-	metricReportsTotal.WithLabelValues("duplicate")
-	metricReportsTotal.WithLabelValues("v1")
-	metricReportsTotal.WithLabelValues("v2")
-	metricReportsTotal.WithLabelValues("v3")
+	metricReportsTotal.WithLabelValues("replace")
+	metricReportsTotal.WithLabelValues("accept")
 }

+ 314 - 0
cmd/infra/ursrv/serve/prometheus.go

@@ -0,0 +1,314 @@
+// Copyright (C) 2024 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 (
+	"reflect"
+	"slices"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/syncthing/syncthing/lib/ur/contract"
+)
+
+const namePrefix = "syncthing_usage_"
+
+type metricsSet struct {
+	srv *server
+
+	gauges         map[string]prometheus.Gauge
+	gaugeVecs      map[string]*prometheus.GaugeVec
+	gaugeVecLabels map[string][]string
+	summaries      map[string]*metricSummary
+
+	collectMut    sync.Mutex
+	collectCutoff time.Duration
+}
+
+func newMetricsSet(srv *server) *metricsSet {
+	s := &metricsSet{
+		srv:            srv,
+		gauges:         make(map[string]prometheus.Gauge),
+		gaugeVecs:      make(map[string]*prometheus.GaugeVec),
+		gaugeVecLabels: make(map[string][]string),
+		summaries:      make(map[string]*metricSummary),
+		collectCutoff:  -24 * time.Hour,
+	}
+
+	var initForType func(reflect.Type)
+	initForType = func(t reflect.Type) {
+		for i := 0; i < t.NumField(); i++ {
+			field := t.Field(i)
+			if field.Type.Kind() == reflect.Struct {
+				initForType(field.Type)
+				continue
+			}
+			name, typ, label := fieldNameTypeLabel(field)
+			sname, labels := nameConstLabels(name)
+			switch typ {
+			case "gauge":
+				s.gauges[name] = prometheus.NewGauge(prometheus.GaugeOpts{
+					Name:        namePrefix + sname,
+					ConstLabels: labels,
+				})
+			case "summary":
+				s.summaries[name] = newMetricSummary(namePrefix+sname, nil, labels)
+			case "gaugeVec":
+				s.gaugeVecLabels[name] = append(s.gaugeVecLabels[name], label)
+			case "summaryVec":
+				s.summaries[name] = newMetricSummary(namePrefix+sname, []string{label}, labels)
+			}
+		}
+	}
+	initForType(reflect.ValueOf(contract.Report{}).Type())
+
+	for name, labels := range s.gaugeVecLabels {
+		s.gaugeVecs[name] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: namePrefix + name,
+		}, labels)
+	}
+
+	return s
+}
+
+func fieldNameTypeLabel(rf reflect.StructField) (string, string, string) {
+	metric := rf.Tag.Get("metric")
+	name, typ, ok := strings.Cut(metric, ",")
+	if !ok {
+		return "", "", ""
+	}
+	gv, label, ok := strings.Cut(typ, ":")
+	if ok {
+		typ = gv
+	}
+	return name, typ, label
+}
+
+func nameConstLabels(name string) (string, prometheus.Labels) {
+	if name == "-" {
+		return "", nil
+	}
+	name, labels, ok := strings.Cut(name, "{")
+	if !ok {
+		return name, nil
+	}
+	lls := strings.Split(labels[:len(labels)-1], ",")
+	m := make(map[string]string)
+	for _, l := range lls {
+		k, v, _ := strings.Cut(l, "=")
+		m[k] = v
+	}
+	return name, m
+}
+
+func (s *metricsSet) addReport(r *contract.Report) {
+	gaugeVecs := make(map[string][]string)
+	s.addReportStruct(reflect.ValueOf(r).Elem(), gaugeVecs)
+	for name, lv := range gaugeVecs {
+		s.gaugeVecs[name].WithLabelValues(lv...).Add(1)
+	}
+}
+
+func (s *metricsSet) addReportStruct(v reflect.Value, gaugeVecs map[string][]string) {
+	t := v.Type()
+	for i := 0; i < v.NumField(); i++ {
+		field := v.Field(i)
+		if field.Kind() == reflect.Struct {
+			s.addReportStruct(field, gaugeVecs)
+			continue
+		}
+
+		name, typ, label := fieldNameTypeLabel(t.Field(i))
+		switch typ {
+		case "gauge":
+			switch v := field.Interface().(type) {
+			case int:
+				s.gauges[name].Add(float64(v))
+			case string:
+				s.gaugeVecs[name].WithLabelValues(v).Add(1)
+			case bool:
+				if v {
+					s.gauges[name].Add(1)
+				}
+			}
+		case "gaugeVec":
+			var labelValue string
+			switch v := field.Interface().(type) {
+			case string:
+				labelValue = v
+			case int:
+				labelValue = strconv.Itoa(v)
+			case map[string]int:
+				for k, v := range v {
+					labelValue = k
+					field.SetInt(int64(v))
+					break
+				}
+			}
+			if _, ok := gaugeVecs[name]; !ok {
+				gaugeVecs[name] = make([]string, len(s.gaugeVecLabels[name]))
+			}
+			for i, l := range s.gaugeVecLabels[name] {
+				if l == label {
+					gaugeVecs[name][i] = labelValue
+					break
+				}
+			}
+		case "summary", "summaryVec":
+			switch v := field.Interface().(type) {
+			case int:
+				s.summaries[name].Observe("", float64(v))
+			case float64:
+				s.summaries[name].Observe("", v)
+			case []int:
+				for _, v := range v {
+					s.summaries[name].Observe("", float64(v))
+				}
+			case map[string]int:
+				for k, v := range v {
+					if k == "" {
+						// avoid empty string labels as those are the sign
+						// of a non-vec summary
+						k = "unknown"
+					}
+					s.summaries[name].Observe(k, float64(v))
+				}
+			}
+		}
+	}
+}
+
+func (s *metricsSet) Describe(c chan<- *prometheus.Desc) {
+	for _, g := range s.gauges {
+		g.Describe(c)
+	}
+	for _, g := range s.gaugeVecs {
+		g.Describe(c)
+	}
+	for _, g := range s.summaries {
+		g.Describe(c)
+	}
+}
+
+func (s *metricsSet) Collect(c chan<- prometheus.Metric) {
+	s.collectMut.Lock()
+	defer s.collectMut.Unlock()
+
+	t0 := time.Now()
+	defer func() {
+		dur := time.Since(t0).Seconds()
+		metricsCollectSecondsLast.Set(dur)
+		metricsCollectSecondsTotal.Add(dur)
+		metricsCollectsTotal.Inc()
+	}()
+
+	for _, g := range s.gauges {
+		g.Set(0)
+	}
+	for _, g := range s.gaugeVecs {
+		g.Reset()
+	}
+	for _, g := range s.summaries {
+		g.Reset()
+	}
+
+	cutoff := time.Now().Add(s.collectCutoff)
+	s.srv.reports.Range(func(key string, r *contract.Report) bool {
+		if s.collectCutoff < 0 && r.Received.Before(cutoff) {
+			s.srv.reports.Delete(key)
+			return true
+		}
+		s.addReport(r)
+		return true
+	})
+
+	for _, g := range s.gauges {
+		c <- g
+	}
+	for _, g := range s.gaugeVecs {
+		g.Collect(c)
+	}
+	for _, g := range s.summaries {
+		g.Collect(c)
+	}
+}
+
+type metricSummary struct {
+	name   string
+	values map[string][]float64
+	zeroes map[string]int
+
+	qDesc     *prometheus.Desc
+	countDesc *prometheus.Desc
+	sumDesc   *prometheus.Desc
+	zDesc     *prometheus.Desc
+}
+
+func newMetricSummary(name string, labels []string, constLabels prometheus.Labels) *metricSummary {
+	return &metricSummary{
+		name:      name,
+		values:    make(map[string][]float64),
+		zeroes:    make(map[string]int),
+		qDesc:     prometheus.NewDesc(name, "", append(labels, "quantile"), constLabels),
+		countDesc: prometheus.NewDesc(name+"_nonzero_count", "", labels, constLabels),
+		sumDesc:   prometheus.NewDesc(name+"_sum", "", labels, constLabels),
+		zDesc:     prometheus.NewDesc(name+"_zero_count", "", labels, constLabels),
+	}
+}
+
+func (q *metricSummary) Observe(labelValue string, v float64) {
+	if v == 0 {
+		q.zeroes[labelValue]++
+		return
+	}
+	q.values[labelValue] = append(q.values[labelValue], v)
+}
+
+func (q *metricSummary) Describe(c chan<- *prometheus.Desc) {
+	c <- q.qDesc
+	c <- q.countDesc
+	c <- q.sumDesc
+	c <- q.zDesc
+}
+
+func (q *metricSummary) Collect(c chan<- prometheus.Metric) {
+	for lv, vs := range q.values {
+		var labelVals []string
+		if lv != "" {
+			labelVals = []string{lv}
+		}
+
+		c <- prometheus.MustNewConstMetric(q.countDesc, prometheus.GaugeValue, float64(len(vs)), labelVals...)
+		c <- prometheus.MustNewConstMetric(q.zDesc, prometheus.GaugeValue, float64(q.zeroes[lv]), labelVals...)
+
+		var sum float64
+		for _, v := range vs {
+			sum += v
+		}
+		c <- prometheus.MustNewConstMetric(q.sumDesc, prometheus.GaugeValue, sum, labelVals...)
+
+		if len(vs) == 0 {
+			return
+		}
+
+		slices.Sort(vs)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[0], append(labelVals, "0")...)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*5/100], append(labelVals, "0.05")...)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)/2], append(labelVals, "0.5")...)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*9/10], append(labelVals, "0.9")...)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*95/100], append(labelVals, "0.95")...)
+		c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)-1], append(labelVals, "1")...)
+	}
+}
+
+func (q *metricSummary) Reset() {
+	clear(q.values)
+	clear(q.zeroes)
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 218 - 721
cmd/infra/ursrv/serve/serve.go


BIN
cmd/infra/ursrv/serve/static/assets/img/favicon.png


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 6
cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap-theme.min.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 6
cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap.min.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 6
cmd/infra/ursrv/serve/static/bootstrap/js/bootstrap.min.js


BIN
cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.eot


+ 0 - 229
cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.svg

@@ -1,229 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
-<font-face units-per-em="1200" ascent="960" descent="-240" />
-<missing-glyph horiz-adv-x="500" />
-<glyph />
-<glyph />
-<glyph unicode="&#xd;" />
-<glyph unicode=" " />
-<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
-<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
-<glyph unicode="&#xa0;" />
-<glyph unicode="&#x2000;" horiz-adv-x="652" />
-<glyph unicode="&#x2001;" horiz-adv-x="1304" />
-<glyph unicode="&#x2002;" horiz-adv-x="652" />
-<glyph unicode="&#x2003;" horiz-adv-x="1304" />
-<glyph unicode="&#x2004;" horiz-adv-x="434" />
-<glyph unicode="&#x2005;" horiz-adv-x="326" />
-<glyph unicode="&#x2006;" horiz-adv-x="217" />
-<glyph unicode="&#x2007;" horiz-adv-x="217" />
-<glyph unicode="&#x2008;" horiz-adv-x="163" />
-<glyph unicode="&#x2009;" horiz-adv-x="260" />
-<glyph unicode="&#x200a;" horiz-adv-x="72" />
-<glyph unicode="&#x202f;" horiz-adv-x="260" />
-<glyph unicode="&#x205f;" horiz-adv-x="326" />
-<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
-<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
-<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
-<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
-<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
-<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
-<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
-<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
-<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
-<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
-<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
-<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
-<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
-<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
-<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
-<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
-<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
-<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
-<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
-<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
-<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
-<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
-<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
-<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
-<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
-<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
-<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
-<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
-<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
-<glyph unicode="&#xe028;" d="M0 25v475l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
-<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
-<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
-<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
-<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
-<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
-<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
-<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
-<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
-<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
-<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
-<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
-<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
-<glyph unicode="&#xe041;" d="M1 700v475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
-<glyph unicode="&#xe042;" d="M2 700v475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
-<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
-<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
-<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
-<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
-<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
-<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v70h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
-<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
-<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
-<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
-<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
-<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
-<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
-<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
-<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
-<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
-<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
-<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
-<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l566 567l-136 137l-430 -431l-147 147z" />
-<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
-<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
-<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
-<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
-<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
-<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
-<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
-<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
-<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
-<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
-<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
-<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
-<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h600v200h-600v-200z" />
-<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141z" />
-<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
-<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM363 700h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26 q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-105 0 -172 -56t-67 -183zM500 300h200v100h-200v-100z" />
-<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
-<glyph unicode="&#xe087;" d="M0 500v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200 v-206q149 48 201 206h-201v200h200q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210z" />
-<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
-<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
-<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
-<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
-<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
-<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
-<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
-<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
-<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
-<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
-<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
-<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100z M100 0h400v400h-400v-400zM200 900q-3 0 14 48t35 96l18 47l214 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
-<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
-<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
-<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
-<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
-<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
-<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64 q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
-<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
-<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
-<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
-<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
-<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
-<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
-<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
-<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
-<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
-<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
-<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
-<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
-<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
-<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM99 500v250v5q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351z M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37 t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
-<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
-<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 212l100 213h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
-<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
-<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
-<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM999 201v600h200v-600h-200z" />
-<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
-<glyph unicode="&#xe130;" d="M1 585q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM76 565l237 339h503l89 -100v-294l-340 -130 q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
-<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 500h300l-2 -194l402 294l-402 298v-197h-298v-201z" />
-<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l400 -294v194h302v201h-300v197z" />
-<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
-<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
-<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -34 5.5 -93t7.5 -87q0 -9 17 -44t16 -60q12 0 23 -5.5 t23 -15t20 -13.5q20 -10 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55.5t-20 -57.5q12 -21 22.5 -34.5t28 -27t36.5 -17.5q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q101 -2 221 111q31 30 47 48t34 49t21 62q-14 9 -37.5 9.5t-35.5 7.5q-14 7 -49 15t-52 19 q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q8 16 22 22q6 -1 26 -1.5t33.5 -4.5t19.5 -13q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5 t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23 q-19 -3 -37 0q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -46 0t-45 -3q-20 -6 -51.5 -25.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79zM518 915q3 12 16 30.5t16 25.5q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -18 8 -42.5t16.5 -44 t9.5 -23.5q-6 1 -39 5t-53.5 10t-36.5 16z" />
-<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
-<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
-<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
-<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
-<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
-<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM513 609q0 32 21 56.5t52 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-16 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5q-37 0 -62.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
-<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36 q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60l517 511 q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
-<glyph unicode="&#xe143;" d="M79 784q0 131 99 229.5t230 98.5q144 0 242 -129q103 129 245 129q130 0 227 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100l-84.5 84.5t-68 74t-60 78t-33.5 70.5t-15 78z M250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-106 48.5q-73 0 -131 -83l-118 -171l-114 174q-51 80 -124 80q-59 0 -108.5 -49.5t-49.5 -118.5z" />
-<glyph unicode="&#xe144;" d="M57 353q0 -94 66 -160l141 -141q66 -66 159 -66q95 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141l19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
-<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
-<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
-<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5v-307l64 -14 q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5zM700 237 q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
-<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5 t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10 t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221z" />
-<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
-<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
-<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
-<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
-<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
-<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
-<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
-<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
-<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
-<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
-<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
-<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
-<glyph unicode="&#xe162;" d="M216 519q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40z" />
-<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
-<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
-<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
-<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 401h700v699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l248 -237v700h-699zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
-<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
-<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
-<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
-<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
-<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
-<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
-<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
-<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359z" />
-<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
-<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
-<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118q17 17 20 41.5 t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
-<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
-<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
-<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
-<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
-<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
-<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
-<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
-<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
-<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
-<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
-<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
-<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
-<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
-<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300 h200l-300 -300z" />
-<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
-<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
-<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
-</font>
-</defs></svg> 

BIN
cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.ttf


BIN
cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.woff


+ 0 - 623
cmd/infra/ursrv/serve/static/index.html

@@ -1,623 +0,0 @@
-<!DOCTYPE html>
-<!--
-Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
-Use of this source code is governed by an MIT-style license that can be
-found in the LICENSE file.
--->
-<html lang="en">
-<head>
-  <meta charset="utf-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta name="description" content="">
-  <meta name="author" content="">
-  <link rel="shortcut icon" href="static/assets/img/favicon.png">
-
-  <title>Syncthing Usage Reports</title>
-  <link href="static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
-  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
-  <script type="text/javascript" src="static/bootstrap/js/bootstrap.min.js"></script>
-  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css">
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script>
-  <script src="https://cdn.jsdelivr.net/npm/[email protected]/heatmap.min.js"></script>
-  <script src="https://cdn.jsdelivr.net/npm/[email protected]/leaflet-heatmap.js"></script>
-
-  <style type="text/css">
-    body {
-      margin: 40px;
-      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-    }
-    tr.main td {
-      font-weight: bold;
-    }
-    tr.child td.first {
-      padding-left: 2em;
-    }
-    .progress-bar {
-      overflow:hidden;
-      white-space:nowrap;
-      text-overflow: ellipsis;
-    }
-  </style>
-  <script type="text/javascript"
-          src='https://www.google.com/jsapi?autoload={
-            "modules":[{
-              "name":"visualization",
-              "version":"1",
-              "packages":["corechart"]
-            }]
-          }'></script>
-
-  <script type="text/javascript">
-    google.setOnLoadCallback(drawVersionChart);
-    google.setOnLoadCallback(drawBlockStatsChart);
-    google.setOnLoadCallback(drawPerformanceCharts);
-
-    function drawVersionChart() {
-      // Summary version chart for versions that at some point in the chart
-      // reaches 250 devices. This filters out versions that are old and
-      // uninteresting yet linger forever with like four users.
-      var jsonData = $.ajax({url: "summary.json?min=250", dataType:"json", async: false}).responseText;
-      var rows = JSON.parse(jsonData);
-
-      var data = new google.visualization.DataTable();
-      data.addColumn('date', 'Day');
-      for (var i = 1; i < rows[0].length; i++){
-        data.addColumn('number', rows[0][i]);
-      }
-      for (var i = 1; i < rows.length; i++){
-        rows[i][0] = new Date(rows[i][0]);
-        data.addRow(rows[i]);
-      };
-
-      var options = {
-        legend: { position: 'bottom', alignment: 'center' },
-        isStacked: true,
-        colors: ['rgb(102,194,165)','rgb(252,141,98)','rgb(141,160,203)','rgb(231,138,195)','rgb(166,216,84)','rgb(255,217,47)'],
-        chartArea: {left: 80, top: 20, width: '1020', height: '300'},
-      };
-
-      var chart = new google.visualization.AreaChart(document.getElementById('versionChart'));
-      chart.draw(data, options);
-    }
-
-    function formatGibibytes(gibibytes, decimals) {
-      if(gibibytes == 0) return '0 GiB';
-      var k = 1024,
-        dm = decimals || 2,
-        sizes = ['GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'],
-        i = Math.floor(Math.log(gibibytes) / Math.log(k));
-      if (i < 0) {
-        sizes = 'MiB';
-      } else {
-        sizes = sizes[i];
-      }
-      return parseFloat((gibibytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes;
-    }
-
-
-    function drawBlockStatsChart() {
-      var jsonData = $.ajax({url: "blockstats.json", dataType:"json", async: false}).responseText;
-      var rows = JSON.parse(jsonData);
-
-      var data = new google.visualization.DataTable();
-      data.addColumn('date', 'Day');
-      for (var i = 1; i < rows[0].length; i++){
-        data.addColumn('number', rows[0][i]);
-      }
-
-      var totals = [0, 0, 0, 0, 0, 0];
-      for (var i = 1; i < rows.length; i++){
-        rows[i][0] = new Date(rows[i][0]);
-        for (var j = 2; j < rows[i].length; j++) {
-          totals[j-2] += rows[i][j];
-        }
-        data.addRow(rows[i]);
-      };
-
-      var totalTotals = totals.reduce(function(a, b) { return a + b; }, 0);
-
-      if (totalTotals > 0) {
-        var content = "<table class='table'>\n"
-        for (var j = 2; j < rows[0].length; j++) {
-          content += "<tr><td><b>" + rows[0][j].replace(' (GiB)', '') + "</b></td><td>" + formatGibibytes(totals[j-2].toFixed(2)) + " (" + ((100*totals[j-2])/totalTotals).toFixed(2) +"%)</td></tr>\n";
-        }
-        content += "</table>";
-        document.getElementById("data-to-date").innerHTML = content;
-      } else {
-        // No data, hide it.
-        document.getElementById("block-stats").outerHTML  = "";
-        return;
-      }
-
-      var options = {
-        focusTarget: 'category',
-        vAxes: {0: {}, 1: {}},
-        series: {0: {type: 'line', targetAxisIndex:1}},
-        isStacked: true,
-        legend: {position: 'none'},
-        colors: ['rgb(102,194,165)','rgb(252,141,98)','rgb(141,160,203)','rgb(231,138,195)','rgb(166,216,84)','rgb(255,217,47)'],
-        chartArea: {left: 80, top: 20, width: '1020', height: '300'},
-      };
-
-      var chart = new google.visualization.AreaChart(document.getElementById('blockStatsChart'));
-      chart.draw(data, options);
-    }
-
-    function drawPerformanceCharts() {
-      var jsonData = $.ajax({url: "/performance.json", dataType:"json", async: false}).responseText;
-      var rows = JSON.parse(jsonData);
-      for (var i = 1; i < rows.length; i++){
-        rows[i][0] = new Date(rows[i][0]);
-      }
-
-      drawChart(rows, 1, 'Total Number of Files', 'totFilesChart', 1e6, 1);
-      drawChart(rows, 2, 'Total Folder Size (GiB)', 'totMiBChart', 1e6, 1024);
-      drawChart(rows, 3, 'Hash Performance (MiB/s)', 'hashPerfChart', 1000, 1);
-      drawChart(rows, 4, 'System RAM Size (GiB)', 'memSizeChart', 1e6, 1024);
-      drawChart(rows, 5, 'Memory Usage (MiB)', 'memUsageChart', 250, 1);
-    }
-
-    function drawChart(rows, index, title, id, cutoff, divisor) {
-      var data = new google.visualization.DataTable();
-      data.addColumn('date', 'Day');
-      data.addColumn('number', title);
-
-      var row;
-      for (var i = 1; i < rows.length; i++){
-          row = [rows[i][0], rows[i][index] / divisor];
-        if (row[1] > cutoff) {
-          row[1] = null;
-        }
-        data.addRow(row);
-      }
-
-      var options = {
-        legend: { position: 'bottom', alignment: 'center' },
-        colors: ['rgb(102,194,165)','rgb(252,141,98)','rgb(141,160,203)','rgb(231,138,195)','rgb(166,216,84)','rgb(255,217,47)'],
-        chartArea: {left: 80, top: 20, width: '1020', height: '300'},
-        vAxes: {0: {minValue: 0}},
-      };
-
-      var chart = new google.visualization.LineChart(document.getElementById(id));
-      chart.draw(data, options);
-    }
-
-    var locations = [];
-    {{range $location, $weight := .locations}}
-    locations.push({lat:{{- $location.Latitude -}},lng:{{- $location.Longitude -}},count:Math.min(100, {{- $weight -}})});
-    {{- end}}
-
-    function drawHeatMap() {
-      if (locations.length == 0) {
-        return;
-      }
-      var testData = {
-        data: locations
-      };
-
-      var baseLayer = L.tileLayer(
-        'https://tile.openstreetmap.org/{z}/{x}/{y}.png',{
-          attribution: '...',
-          maxZoom: 18
-        }
-      );
-      var cfg = {
-        "radius": 1,
-        "minOpacity": .25,
-        "maxOpacity": .8,
-        "scaleRadius": true,
-        "useLocalExtrema": true,
-        latField: 'lat',
-        lngField: 'lng',
-        valueField: 'count',
-        gradient: {
-            '.1': 'cyan',
-            '.8': 'blue',
-            '.95': 'red'
-          }
-      };
-      var heatmapLayer = new HeatmapOverlay(cfg);
-
-      var map = new L.Map('map', {
-        center: new L.LatLng(25, 0),
-        zoom: 1,
-        layers: [baseLayer, heatmapLayer]
-      });
-      heatmapLayer.setData(testData);
-    }
-  </script>
-</head>
-
-<body>
-  <div class="container">
-    <div class="row">
-      <div class="col-md-12">
-        <h1>Syncthing Usage Data</h1>
-
-        <h4 id="active-users">Active Users per Day and Version</h4>
-        <p>
-          This is the total number of unique users with reporting enabled, per day. Area color represents the major version.
-        </p>
-        <div class="img-thumbnail" id="versionChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-
-        <div id="block-stats">
-          <h4>Data Transfers per Day</h4>
-          <p>
-            This is total data transferred per day. Also shows how much data was saved (not transferred) by each of the methods syncthing uses.
-          </p>
-          <div class="img-thumbnail" id="blockStatsChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-          <h4 id="totals-to-date">Totals to date</h4>
-          <p id="data-to-date">
-            No data
-          </p>
-        </div>
-
-        <h4 id="metrics">Usage Metrics</h4>
-        <p>
-          This is the aggregated usage report data for the last 24 hours. Data based on <b>{{.nodes}}</b> devices that have reported in.
-        </p>
-
-        {{if .locations}}
-        <div class="img-thumbnail" id="map" style="width: 1130px; height: 400px; padding: 10px;"></div>
-        <p class="text-muted">
-         Heatmap max intensity is capped at 100 reports within a location.
-        </p>
-        <div class="panel panel-default">
-          <div class="panel-heading">
-            <h4 class="panel-title">
-              <a data-toggle="collapse" href="#collapseTwo">Break down per country</a>
-            </h4>
-          </div>
-          <div id="collapseTwo" class="panel-collapse collapse">
-            <div class="panel-body less-padding">
-              <div class="row">
-                <div class="col-md-6">
-                  <table class="table table-striped">
-                    <tbody>
-                      {{range .countries | slice 2 1}}
-                      <tr>
-                        <td style="width: 45%">{{.Key}}</td>
-                        <td style="width: 5%" class="text-right">{{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}%</td>
-                        <td style="width: 5%" class="text-right">{{.Count}}</td>
-                        <td>
-                            <div class="progress-bar" role="progressbar" aria-valuenow="{{.Pct | printf "%.02f"}}" aria-valuemin="0" aria-valuemax="100" style="width: {{.Pct | printf "%.02f"}}%; height:20px"></div>
-                        </td>
-                      </tr>
-                      {{end}}
-                    </tbody>
-                  </table>
-                </div>
-                <div class="col-md-6">
-                  <table class="table table-striped">
-                    <tbody>
-                      {{range .countries | slice 2 2}}
-                      <tr>
-                        <td style="width: 45%">{{.Key}}</td>
-                        <td style="width: 5%" class="text-right">{{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}%</td>
-                        <td style="width: 5%" class="text-right">{{.Count}}</td>
-                        <td>
-                            <div class="progress-bar" role="progressbar" aria-valuenow="{{.Pct | printf "%.02f"}}" aria-valuemin="0" aria-valuemax="100" style="width: {{.Pct | printf "%.02f"}}%; height:20px"></div>
-                        </td>
-                      </tr>
-                      {{end}}
-                    </tbody>
-                  </table>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-        {{end}}
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th></th>
-              <th colspan="4" class="text-center">
-                <a href="https://en.wikipedia.org/wiki/Percentile">Percentile</a>
-              </th>
-            </tr>
-            <tr>
-              <th></th>
-              <th class="text-right">5%</th>
-              <th class="text-right">50%</th>
-              <th class="text-right">95%</th>
-              <th class="text-right">100%</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .categories}}
-            <tr>
-              <td>{{.Descr}}</td>
-              <td class="text-right">{{index .Values 0 | number .Type | commatize " "}}{{.Unit}}</td>
-              <td class="text-right">{{index .Values 1 | number .Type | commatize " "}}{{.Unit}}</td>
-              <td class="text-right">{{index .Values 2 | number .Type | commatize " "}}{{.Unit}}</td>
-              <td class="text-right">{{index .Values 3 | number .Type | commatize " "}}{{.Unit}}</td>
-            </tr>
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-    </div>
-
-    <div class="row">
-      <div class="col-md-6">
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th>Version</th><th class="text-right">Devices</th><th class="text-right">Share</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .versions}}
-              {{if gt .Percentage 0.1}}
-                <tr class="main">
-                  <td>{{.Key}}</td>
-                  <td class="text-right">{{.Count}}</td>
-                  <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-                </tr>
-                {{range .Items}}
-                  {{if gt .Percentage 0.1}}
-                    <tr class="child">
-                      <td class="first">{{.Key}}</td>
-                      <td class="text-right">{{.Count}}</td>
-                      <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-                    </tr>
-                  {{end}}
-                {{end}}
-              {{end}}
-            {{end}}
-          </tbody>
-        </table>
-        <table class="table table-striped">
-          <thead>
-            <tr>
-                <th>Penetration Level</th>
-                <th>Version</th>
-              <th class="text-right">Actual</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .versionPenetrations}}
-            <tr>
-                <td>{{.Count}}%</td>
-                <td>&ge; {{.Key}}</td>
-              <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-            </tr>
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-
-      <div class="col-md-6">
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th>Platform</th>
-              <th class="text-right">Devices</th>
-              <th class="text-right">Share</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .platforms}}
-              <tr class="main">
-                <td>{{.Key}}</td>
-                <td class="text-right">{{.Count}}</td>
-                <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-              </tr>
-              {{range .Items}}
-                <tr class="child">
-                  <td class="first">{{.Key}}</td>
-                  <td class="text-right">{{.Count}}</td>
-                  <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-                </tr>
-              {{end}}
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-
-    </div>
-    <div class="row">
-
-      <div class="col-md-6">
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th>Compiler</th>
-              <th class="text-right">Devices</th>
-              <th class="text-right">Share</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .compilers}}
-              <tr class="main">
-                <td>{{.Key}}</td>
-                <td class="text-right">{{.Count}}</td>
-                <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-              </tr>
-              {{range .Items}}
-                {{if or (gt .Percentage 0.1) (eq .Key "Others")}}
-                  <tr class="child">
-                    <td class="first">{{.Key}}</td>
-                    <td class="text-right">{{.Count}}</td>
-                    <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-                  </tr>
-                {{end}}
-              {{end}}
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-
-      <div class="col-md-6">
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th>Distribution Channel</th>
-              <th class="text-right">Devices</th>
-              <th class="text-right">Share</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .distributions}}
-            <tr>
-              <td>{{.Key}}</td>
-              <td class="text-right">{{.Count}}</td>
-              <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-            </tr>
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-
-      <div class="col-md-6">
-        <table class="table table-striped">
-          <thead>
-            <tr>
-              <th>Builder</th>
-              <th class="text-right">Devices</th>
-              <th class="text-right">Share</th>
-            </tr>
-          </thead>
-          <tbody>
-            {{range .builders}}
-            <tr>
-              <td>{{.Key}}</td>
-              <td class="text-right">{{.Count}}</td>
-              <td class="text-right">{{.Percentage | printf "%.01f"}}%</td>
-            </tr>
-            {{end}}
-          </tbody>
-        </table>
-      </div>
-
-    </div>
-
-    <div class="row">
-        <div class="col-md-12">
-            <h4 id="features">Feature Usage</h4>
-            <p>
-                The following lists feature usage. Some features are reported per report, some are per sum of units within report (eg. devices with static addresses among all known devices per report).
-                Currently there are <b>{{.versionNodes.v2}}</b> devices reporting for version 2 and <b>{{.versionNodes.v3}}</b> for version 3.
-            </p>
-        </div>
-    </div>
-
-
-    <div class="row">
-    {{$i := counter}}
-    {{range $featureName := .featureOrder}}
-      {{$featureValues := index $.features $featureName }}
-      {{if $i.DrawTwoDivider}}
-        </div>
-        <div class="row">
-      {{end}}
-      {{ $i.Increment }}
-      <div class="col-md-6">
-          <table class="table table-striped">
-              <thead><tr>
-                  <th>{{$featureName}} Features</th><th colspan="2" class="text-center">Usage</th>
-              </tr></thead>
-              <tbody>
-                  {{range $featureValues}}
-                  <tr>
-                      <td style="width: 50%">{{.Key}} ({{.Version}})</td>
-                      <td style="width: 10%" class="text-right">{{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}%</td>
-                      <td style="width: 40%" {{if lt .Pct 5.0}}data-toggle="tooltip" title='{{.Count}}'{{end}}>
-                          <div class="progress-bar" role="progressbar" aria-valuenow="{{.Pct | printf "%.02f"}}" aria-valuemin="0" aria-valuemax="100" style="width: {{.Pct | printf "%.02f"}}%; height:20px" {{if ge .Pct 5.0}}data-toggle="tooltip" title='{{.Count}}'{{end}}></div>
-                      </td>
-                  </tr>
-                  {{end}}
-              </tbody>
-          </table>
-      </div>
-    {{end}}
-    </div>
-
-    <div class="row">
-        <div class="col-md-12">
-            <h4 id="features">Feature Group Usage</h4>
-            <p>
-                The following lists feature usage groups, which might include multiple occourances of a feature use per report.
-            </p>
-        </div>
-    </div>
-
-    <div class="row">
-    {{$i := counter}}
-    {{range $featureName := .featureOrder}}
-      {{$featureValues := index $.featureGroups $featureName }}
-      {{if $i.DrawTwoDivider}}
-        </div>
-        <div class="row">
-      {{end}}
-      {{ $i.Increment }}
-      <div class="col-md-6">
-          <table class="table table-striped">
-              <thead><tr>
-                  <th>{{$featureName}} Group Features</th><th colspan="2" class="text-center">Usage</th>
-              </tr></thead>
-              <tbody>
-                  {{range $featureValues}}
-                  {{$counts := .Counts}}
-                  <tr>
-                      <td style="width: 50%">
-                          <div data-toggle="tooltip" title='{{range $key, $value := .Counts}}{{$key}} ({{$value | proportion $counts | printf "%.02f"}}% - {{$value}})</br>{{end}}'>
-                              {{.Key}} ({{.Version}})
-                          </div>
-                      </td>
-                      <td style="width: 50%">
-                          <div class="progress" role="progressbar" style="width: 100%">
-                          {{$j := counter}}
-                          {{range $key, $value := .Counts}}
-                              {{with $valuePct := $value | proportion $counts}}
-                              <div class="progress-bar {{ $j.Current | progressBarClassByIndex }}" style='width: {{$valuePct | printf "%.02f"}}%' data-toggle="tooltip" title='{{$key}} ({{$valuePct | printf "%.02f"}}% - {{$value}})'>
-                                  {{if ge $valuePct 30.0}}{{$key}}{{end}}
-                              </div>
-                              {{end}}
-                              {{ $j.Increment }}
-                          {{end}}
-                          </div>
-                      </td>
-                  </tr>
-                  {{end}}
-              </tbody>
-          </table>
-      </div>
-    {{end}}
-    </div>
-     <div class="row">
-      <div class="col-md-12">
-        <h1 id="performance-charts">Historical Performance Data</h1>
-        <p>These charts are all the average of the corresponding metric, for the entire population of a given day.</p>
-
-        <h4 id="hash-performance">Hash Performance (MiB/s)</h4>
-        <div class="img-thumbnail" id="hashPerfChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-
-        <h4 id="memory-usage">Memory Usage (MiB)</h4>
-        <div class="img-thumbnail" id="memUsageChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-
-        <h4 id="total-files">Total Number of Files</h4>
-        <div class="img-thumbnail" id="totFilesChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-
-        <h4 id="total-size">Total Folder Size (GiB)</h4>
-        <div class="img-thumbnail" id="totMiBChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-
-        <h4 id="system-ram">System RAM Size (GiB)</h4>
-        <div class="img-thumbnail" id="memSizeChart" style="width: 1130px; height: 400px; padding: 10px;"></div>
-      </div>
-    </div>
-  </div>
-  <hr>
-  <p>
-    <a href="https://github.com/syncthing/syncthing/">Source code</a>.
-    This product includes GeoLite2 data created by MaxMind, available from
-    <a href="http://www.maxmind.com">http://www.maxmind.com</a>.
-  </p>
-  <script type="text/javascript">
-    $('[data-toggle="tooltip"]').tooltip({html:true});
-    drawHeatMap();
-  </script>
-</body>
-</html>

+ 19 - 6
cmd/stdiscosrv/database.go

@@ -26,6 +26,8 @@ import (
 
 	"github.com/puzpuzpuz/xsync/v3"
 	"github.com/syncthing/syncthing/lib/protocol"
+	"github.com/syncthing/syncthing/lib/rand"
+	"github.com/syncthing/syncthing/lib/s3"
 )
 
 type clock interface {
@@ -48,27 +50,38 @@ type inMemoryStore struct {
 	m             *xsync.MapOf[protocol.DeviceID, DatabaseRecord]
 	dir           string
 	flushInterval time.Duration
-	s3            *s3Copier
+	s3            *s3.Session
+	objKey        string
 	clock         clock
 }
 
-func newInMemoryStore(dir string, flushInterval time.Duration, s3 *s3Copier) *inMemoryStore {
+func newInMemoryStore(dir string, flushInterval time.Duration, s3sess *s3.Session) *inMemoryStore {
+	hn, err := os.Hostname()
+	if err != nil {
+		hn = rand.String(8)
+	}
 	s := &inMemoryStore{
 		m:             xsync.NewMapOf[protocol.DeviceID, DatabaseRecord](),
 		dir:           dir,
 		flushInterval: flushInterval,
-		s3:            s3,
+		s3:            s3sess,
+		objKey:        hn + ".db",
 		clock:         defaultClock{},
 	}
 	nr, err := s.read()
-	if os.IsNotExist(err) && s3 != nil {
+	if os.IsNotExist(err) && s3sess != nil {
 		// Try to read from AWS
+		latestKey, cerr := s3sess.LatestKey()
+		if cerr != nil {
+			log.Println("Error reading database from S3:", err)
+			return s
+		}
 		fd, cerr := os.Create(path.Join(s.dir, "records.db"))
 		if cerr != nil {
 			log.Println("Error creating database file:", err)
 			return s
 		}
-		if err := s3.downloadLatest(fd); err != nil {
+		if cerr := s3sess.Download(fd, latestKey); cerr != nil {
 			log.Printf("Error reading database from S3: %v", err)
 		}
 		_ = fd.Close()
@@ -303,7 +316,7 @@ func (s *inMemoryStore) write() (err error) {
 			return nil
 		}
 		defer fd.Close()
-		if err := s.s3.upload(fd); err != nil {
+		if err := s.s3.Upload(fd, s.objKey); err != nil {
 			log.Printf("Error uploading database to S3: %v", err)
 		}
 		log.Println("Finished uploading database")

+ 5 - 5
cmd/stdiscosrv/main.go

@@ -24,6 +24,7 @@ import (
 	"github.com/syncthing/syncthing/lib/build"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/rand"
+	"github.com/syncthing/syncthing/lib/s3"
 	"github.com/syncthing/syncthing/lib/tlsutil"
 	"github.com/thejerf/suture/v4"
 )
@@ -117,14 +118,13 @@ func main() {
 	})
 
 	// If configured, use S3 for database backups.
-	var s3c *s3Copier
+	var s3c *s3.Session
 	if cli.DBS3Endpoint != "" {
-		hostname, err := os.Hostname()
+		var err error
+		s3c, err = s3.NewSession(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, cli.DBS3AccessKeyID, cli.DBS3SecretKey)
 		if err != nil {
-			log.Fatalf("Failed to get hostname: %v", err)
+			log.Fatalf("Failed to create S3 session: %v", err)
 		}
-		key := hostname + ".db"
-		s3c = newS3Copier(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, key, cli.DBS3AccessKeyID, cli.DBS3SecretKey)
 	}
 
 	// Start the database.

+ 0 - 97
cmd/stdiscosrv/s3.go

@@ -1,97 +0,0 @@
-// Copyright (C) 2024 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 main
-
-import (
-	"io"
-	"log"
-	"time"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/s3"
-	"github.com/aws/aws-sdk-go/service/s3/s3manager"
-)
-
-type s3Copier struct {
-	endpoint    string
-	region      string
-	bucket      string
-	key         string
-	accessKeyID string
-	secretKey   string
-}
-
-func newS3Copier(endpoint, region, bucket, key, accessKeyID, secretKey string) *s3Copier {
-	return &s3Copier{
-		endpoint:    endpoint,
-		region:      region,
-		bucket:      bucket,
-		key:         key,
-		accessKeyID: accessKeyID,
-		secretKey:   secretKey,
-	}
-}
-
-func (s *s3Copier) upload(r io.Reader) error {
-	sess, err := session.NewSession(&aws.Config{
-		Region:      aws.String(s.region),
-		Endpoint:    aws.String(s.endpoint),
-		Credentials: credentials.NewStaticCredentials(s.accessKeyID, s.secretKey, ""),
-	})
-	if err != nil {
-		return err
-	}
-
-	uploader := s3manager.NewUploader(sess)
-	_, err = uploader.Upload(&s3manager.UploadInput{
-		Bucket: aws.String(s.bucket),
-		Key:    aws.String(s.key),
-		Body:   r,
-	})
-	return err
-}
-
-func (s *s3Copier) downloadLatest(w io.WriterAt) error {
-	sess, err := session.NewSession(&aws.Config{
-		Region:      aws.String(s.region),
-		Endpoint:    aws.String(s.endpoint),
-		Credentials: credentials.NewStaticCredentials(s.accessKeyID, s.secretKey, ""),
-	})
-	if err != nil {
-		return err
-	}
-
-	svc := s3.New(sess)
-	resp, err := svc.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(s.bucket)})
-	if err != nil {
-		return err
-	}
-
-	var lastKey string
-	var lastModified time.Time
-	var lastSize int64
-	for _, item := range resp.Contents {
-		if item.LastModified.After(lastModified) && *item.Size > lastSize {
-			lastKey = *item.Key
-			lastModified = *item.LastModified
-			lastSize = *item.Size
-		} else if lastModified.Sub(*item.LastModified) < 5*time.Minute && *item.Size > lastSize {
-			lastKey = *item.Key
-			lastSize = *item.Size
-		}
-	}
-
-	log.Println("Downloading database from", lastKey)
-	downloader := s3manager.NewDownloader(sess)
-	_, err = downloader.Download(w, &s3.GetObjectInput{
-		Bucket: aws.String(s.bucket),
-		Key:    aws.String(lastKey),
-	})
-	return err
-}

+ 0 - 1
go.mod

@@ -21,7 +21,6 @@ require (
 	github.com/jackpal/go-nat-pmp v1.0.2
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
-	github.com/lib/pq v1.10.9
 	github.com/maruel/panicparse/v2 v2.3.1
 	github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1
 	github.com/maxmind/geoipupdate/v6 v6.1.0

+ 0 - 2
go.sum

@@ -144,8 +144,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
 github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
 github.com/maruel/panicparse/v2 v2.3.1 h1:NtJavmbMn0DyzmmSStE8yUsmPZrZmudPH7kplxBinOA=

+ 0 - 1
gui/default/syncthing/core/aboutModalView.html

@@ -61,7 +61,6 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
           <li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright &copy; 2011 The Snappy-Go Authors.</li>
           <li><a href="https://github.com/jackpal/gateway">jackpal/gateway</a>, Copyright &copy; 2010 Jack Palevich.</li>
           <li><a href="https://github.com/kballard/go-shellquote">kballard/go-shellquote</a>, Copyright &copy; 2014 Kevin Ballard.</li>
-          <li><a href="https://github.com/lib/pq">lib/pq</a>, Copyright &copy; 2011-2013, 'pq' Contributors, portions Copyright &copy; 2011 Blake Mizerany.</li>
           <li><a href="https://github.com/mattn/go-isatty">mattn/go-isatty</a>, Copyright &copy; Yasuhiro MATSUMOTO.</li>
           <li><a href="https://github.com/matttproud/golang_protobuf_extensions">matttproud/golang_protobuf_extensions</a>, Copyright &copy; 2012 Matt T. Proud.</li>
           <li><a href="https://github.com/oschwald/geoip2-golang">oschwald/geoip2-golang</a>, Copyright &copy; 2015, Gregory J. Oschwald.</li>

+ 101 - 0
lib/s3/s3.go

@@ -0,0 +1,101 @@
+// Copyright (C) 2024 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 s3
+
+import (
+	"io"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/s3"
+	"github.com/aws/aws-sdk-go/service/s3/s3manager"
+)
+
+type Session struct {
+	bucket string
+	s3sess *session.Session
+}
+
+type Object = s3.Object
+
+func NewSession(endpoint, region, bucket, accessKeyID, secretKey string) (*Session, error) {
+	sess, err := session.NewSession(&aws.Config{
+		Region:      aws.String(region),
+		Endpoint:    aws.String(endpoint),
+		Credentials: credentials.NewStaticCredentials(accessKeyID, secretKey, ""),
+	})
+	if err != nil {
+		return nil, err
+	}
+	return &Session{
+		bucket: bucket,
+		s3sess: sess,
+	}, nil
+}
+
+func (s *Session) Upload(r io.Reader, key string) error {
+	uploader := s3manager.NewUploader(s.s3sess)
+	_, err := uploader.Upload(&s3manager.UploadInput{
+		Bucket: aws.String(s.bucket),
+		Key:    aws.String(key),
+		Body:   r,
+	})
+	return err
+}
+
+func (s *Session) List(fn func(*Object) bool) error {
+	svc := s3.New(s.s3sess)
+
+	opts := &s3.ListObjectsV2Input{
+		Bucket: aws.String(s.bucket),
+	}
+	for {
+		resp, err := svc.ListObjectsV2(opts)
+		if err != nil {
+			return err
+		}
+
+		for _, item := range resp.Contents {
+			if !fn(item) {
+				return nil
+			}
+		}
+
+		if resp.NextContinuationToken == nil || *resp.NextContinuationToken == "" {
+			break
+		}
+		opts.ContinuationToken = resp.NextContinuationToken
+	}
+
+	return nil
+}
+
+func (s *Session) LatestKey() (string, error) {
+	var latestKey string
+	var lastModified time.Time
+	if err := s.List(func(obj *Object) bool {
+		if latestKey == "" || obj.LastModified.After(lastModified) {
+			latestKey = *obj.Key
+			lastModified = *obj.LastModified
+		}
+		return true
+	}); err != nil {
+		return "", err
+	}
+	return latestKey, nil
+}
+
+func (s *Session) Download(w io.WriterAt, key string) error {
+	downloader := s3manager.NewDownloader(s.s3sess)
+	_, err := downloader.Download(w, &s3.GetObjectInput{
+		Bucket: aws.String(s.bucket),
+		Key:    aws.String(key),
+	})
+	return err
+}

+ 130 - 292
lib/ur/contract/contract.go

@@ -18,163 +18,177 @@ import (
 )
 
 type Report struct {
-	// Generated
-	Received time.Time `json:"-"` // Only from DB
-	Date     string    `json:"date,omitempty"`
-	Address  string    `json:"address,omitempty"`
-
 	// v1 fields
 
-	UniqueID       string  `json:"uniqueID,omitempty" since:"1"`
-	Version        string  `json:"version,omitempty" since:"1"`
-	LongVersion    string  `json:"longVersion,omitempty" since:"1"`
-	Platform       string  `json:"platform,omitempty" since:"1"`
-	NumFolders     int     `json:"numFolders,omitempty" since:"1"`
-	NumDevices     int     `json:"numDevices,omitempty" since:"1"`
-	TotFiles       int     `json:"totFiles,omitempty" since:"1"`
-	FolderMaxFiles int     `json:"folderMaxFiles,omitempty" since:"1"`
-	TotMiB         int     `json:"totMiB,omitempty" since:"1"`
-	FolderMaxMiB   int     `json:"folderMaxMiB,omitempty" since:"1"`
-	MemoryUsageMiB int     `json:"memoryUsageMiB,omitempty" since:"1"`
-	SHA256Perf     float64 `json:"sha256Perf,omitempty" since:"1"`
-	HashPerf       float64 `json:"hashPerf,omitempty" since:"1"` // Was previously not stored server-side
-	MemorySize     int     `json:"memorySize,omitempty" since:"1"`
+	UniqueID       string  `json:"uniqueID,omitempty" metric:"-" since:"1"`
+	Version        string  `json:"version,omitempty" metric:"reports_total,gaugeVec:version" since:"1"`
+	LongVersion    string  `json:"longVersion,omitempty" metric:"-" since:"1"`
+	Platform       string  `json:"platform,omitempty" metric:"-" since:"1"`
+	NumFolders     int     `json:"numFolders,omitempty" metric:"num_folders,summary" since:"1"`
+	NumDevices     int     `json:"numDevices,omitempty" metric:"num_devices,summary" since:"1"`
+	TotFiles       int     `json:"totFiles,omitempty" metric:"total_files,summary" since:"1"`
+	FolderMaxFiles int     `json:"folderMaxFiles,omitempty" metric:"folder_max_files,summary" since:"1"`
+	TotMiB         int     `json:"totMiB,omitempty" metric:"total_data_mib,summary" since:"1"`
+	FolderMaxMiB   int     `json:"folderMaxMiB,omitempty" metric:"folder_max_data_mib,summary" since:"1"`
+	MemoryUsageMiB int     `json:"memoryUsageMiB,omitempty" metric:"memory_usage_mib,summary" since:"1"`
+	SHA256Perf     float64 `json:"sha256Perf,omitempty" metric:"sha256_perf_mibps,summary" since:"1"`
+	HashPerf       float64 `json:"hashPerf,omitempty" metric:"hash_perf_mibps,summary" since:"1"`
+	MemorySize     int     `json:"memorySize,omitempty" metric:"memory_size_mib,summary" since:"1"`
 
 	// v2 fields
 
-	URVersion  int `json:"urVersion,omitempty" since:"2"`
-	NumCPU     int `json:"numCPU,omitempty" since:"2"`
+	URVersion int `json:"urVersion,omitempty" metric:"reports_by_urversion_total,gaugeVec:version" since:"2"`
+	NumCPU    int `json:"numCPU,omitempty" metric:"num_cpu,summary" since:"2"`
+
 	FolderUses struct {
-		SendOnly            int `json:"sendonly,omitempty" since:"2"`
-		SendReceive         int `json:"sendreceive,omitempty" since:"2"` // Was previously not stored server-side
-		ReceiveOnly         int `json:"receiveonly,omitempty" since:"2"`
-		IgnorePerms         int `json:"ignorePerms,omitempty" since:"2"`
-		IgnoreDelete        int `json:"ignoreDelete,omitempty" since:"2"`
-		AutoNormalize       int `json:"autoNormalize,omitempty" since:"2"`
-		SimpleVersioning    int `json:"simpleVersioning,omitempty" since:"2"`
-		ExternalVersioning  int `json:"externalVersioning,omitempty" since:"2"`
-		StaggeredVersioning int `json:"staggeredVersioning,omitempty" since:"2"`
-		TrashcanVersioning  int `json:"trashcanVersioning,omitempty" since:"2"`
+		SendOnly            int `json:"sendonly,omitempty" metric:"folder_feature{feature=ModeSendonly},summary" since:"2"`
+		SendReceive         int `json:"sendreceive,omitempty" metric:"folder_feature{feature=ModeSendReceive},summary" since:"2"`
+		ReceiveOnly         int `json:"receiveonly,omitempty" metric:"folder_feature{feature=ModeReceiveOnly},summary" since:"2"`
+		IgnorePerms         int `json:"ignorePerms,omitempty" metric:"folder_feature{feature=IgnorePerms},summary" since:"2"`
+		IgnoreDelete        int `json:"ignoreDelete,omitempty" metric:"folder_feature{feature=IgnoreDelete},summary" since:"2"`
+		AutoNormalize       int `json:"autoNormalize,omitempty" metric:"folder_feature{feature=AutoNormalize},summary" since:"2"`
+		SimpleVersioning    int `json:"simpleVersioning,omitempty" metric:"folder_feature{feature=VersioningSimple},summary" since:"2"`
+		ExternalVersioning  int `json:"externalVersioning,omitempty" metric:"folder_feature{feature=VersioningExternal},summary" since:"2"`
+		StaggeredVersioning int `json:"staggeredVersioning,omitempty" metric:"folder_feature{feature=VersioningStaggered},summary" since:"2"`
+		TrashcanVersioning  int `json:"trashcanVersioning,omitempty" metric:"folder_feature{feature=VersioningTrashcan},summary" since:"2"`
 	} `json:"folderUses,omitempty" since:"2"`
 
 	DeviceUses struct {
-		Introducer       int `json:"introducer,omitempty" since:"2"`
-		CustomCertName   int `json:"customCertName,omitempty" since:"2"`
-		CompressAlways   int `json:"compressAlways,omitempty" since:"2"`
-		CompressMetadata int `json:"compressMetadata,omitempty" since:"2"`
-		CompressNever    int `json:"compressNever,omitempty" since:"2"`
-		DynamicAddr      int `json:"dynamicAddr,omitempty" since:"2"`
-		StaticAddr       int `json:"staticAddr,omitempty" since:"2"`
+		Introducer       int `json:"introducer,omitempty" metric:"device_feature{feature=Introducer},summary" since:"2"`
+		CustomCertName   int `json:"customCertName,omitempty" metric:"device_feature{feature=CustomCertName},summary" since:"2"`
+		CompressAlways   int `json:"compressAlways,omitempty" metric:"device_feature{feature=CompressAlways},summary" since:"2"`
+		CompressMetadata int `json:"compressMetadata,omitempty" metric:"device_feature{feature=CompressMetadata},summary" since:"2"`
+		CompressNever    int `json:"compressNever,omitempty" metric:"device_feature{feature=CompressNever},summary" since:"2"`
+		DynamicAddr      int `json:"dynamicAddr,omitempty" metric:"device_feature{feature=AddressDynamic},summary" since:"2"`
+		StaticAddr       int `json:"staticAddr,omitempty" metric:"device_feature{feature=AddressStatic},summary" since:"2"`
 	} `json:"deviceUses,omitempty" since:"2"`
 
 	Announce struct {
-		GlobalEnabled     bool `json:"globalEnabled,omitempty" since:"2"`
-		LocalEnabled      bool `json:"localEnabled,omitempty" since:"2"`
-		DefaultServersDNS int  `json:"defaultServersDNS,omitempty" since:"2"`
-		DefaultServersIP  int  `json:"defaultServersIP,omitempty" since:"2"` // Deprecated and not provided client-side anymore
-		OtherServers      int  `json:"otherServers,omitempty" since:"2"`
+		GlobalEnabled     bool `json:"globalEnabled,omitempty" metric:"discovery_feature_count{feature=GlobalEnabled},gauge" since:"2"`
+		LocalEnabled      bool `json:"localEnabled,omitempty" metric:"discovery_feature_count{feature=LocalEnabled},gauge" since:"2"`
+		DefaultServersDNS int  `json:"defaultServersDNS,omitempty" metric:"discovery_default_servers,summary" since:"2"`
+		OtherServers      int  `json:"otherServers,omitempty" metric:"discovery_other_servers,summary" since:"2"`
 	} `json:"announce,omitempty" since:"2"`
 
 	Relays struct {
-		Enabled        bool `json:"enabled,omitempty" since:"2"`
-		DefaultServers int  `json:"defaultServers,omitempty" since:"2"`
-		OtherServers   int  `json:"otherServers,omitempty" since:"2"`
+		Enabled        bool `json:"enabled,omitempty" metric:"relay_feature_enabled,gauge" since:"2"`
+		DefaultServers int  `json:"defaultServers,omitempty" metric:"relay_feature_count{feature=DefaultServers},summary" since:"2"`
+		OtherServers   int  `json:"otherServers,omitempty" metric:"relay_feature_count{feature=OtherServers},summary" since:"2"`
 	} `json:"relays,omitempty" since:"2"`
 
-	UsesRateLimit        bool `json:"usesRateLimit,omitempty" since:"2"`
-	UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" since:"2"`
-	UpgradeAllowedAuto   bool `json:"upgradeAllowedAuto,omitempty" since:"2"`
+	UsesRateLimit        bool `json:"usesRateLimit,omitempty" metric:"feature_count{feature=RateLimitsEnabled},gauge" since:"2"`
+	UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" metric:"feature_count{feature=UpgradeAllowedManual},gauge" since:"2"`
+	UpgradeAllowedAuto   bool `json:"upgradeAllowedAuto,omitempty" metric:"feature_count{feature=UpgradeAllowedAuto},gauge" since:"2"`
 
 	// V2.5 fields (fields that were in v2 but never added to the database
-	UpgradeAllowedPre bool  `json:"upgradeAllowedPre,omitempty" since:"2"`
-	RescanIntvs       []int `json:"rescanIntvs,omitempty" since:"2"`
+	UpgradeAllowedPre bool  `json:"upgradeAllowedPre,omitempty" metric:"upgrade_allowed_pre,gauge" since:"2"`
+	RescanIntvs       []int `json:"rescanIntvs,omitempty" metric:"folder_rescan_intervals,summary" since:"2"`
 
 	// v3 fields
 
-	Uptime                     int    `json:"uptime,omitempty" since:"3"`
-	NATType                    string `json:"natType,omitempty" since:"3"`
-	AlwaysLocalNets            bool   `json:"alwaysLocalNets,omitempty" since:"3"`
-	CacheIgnoredFiles          bool   `json:"cacheIgnoredFiles,omitempty" since:"3"`
-	OverwriteRemoteDeviceNames bool   `json:"overwriteRemoteDeviceNames,omitempty" since:"3"`
-	ProgressEmitterEnabled     bool   `json:"progressEmitterEnabled,omitempty" since:"3"`
-	CustomDefaultFolderPath    bool   `json:"customDefaultFolderPath,omitempty" since:"3"`
-	WeakHashSelection          string `json:"weakHashSelection,omitempty" since:"3"` // Deprecated and not provided client-side anymore
-	CustomTrafficClass         bool   `json:"customTrafficClass,omitempty" since:"3"`
-	CustomTempIndexMinBlocks   bool   `json:"customTempIndexMinBlocks,omitempty" since:"3"`
-	TemporariesDisabled        bool   `json:"temporariesDisabled,omitempty" since:"3"`
-	TemporariesCustom          bool   `json:"temporariesCustom,omitempty" since:"3"`
-	LimitBandwidthInLan        bool   `json:"limitBandwidthInLan,omitempty" since:"3"`
-	CustomReleaseURL           bool   `json:"customReleaseURL,omitempty" since:"3"`
-	RestartOnWakeup            bool   `json:"restartOnWakeup,omitempty" since:"3"`
-	CustomStunServers          bool   `json:"customStunServers,omitempty" since:"3"`
+	Uptime  int    `json:"uptime,omitempty" metric:"uptime_seconds,summary" since:"3"`
+	NATType string `json:"natType,omitempty" metric:"nat_detection,gaugeVec:type" since:"3"`
+
+	AlwaysLocalNets            bool `json:"alwaysLocalNets,omitempty" metric:"feature_count{feature=AlwaysLocalNets},gauge" since:"3"`
+	CacheIgnoredFiles          bool `json:"cacheIgnoredFiles,omitempty" metric:"feature_count{feature=CacheIgnoredFiles},gauge" since:"3"`
+	OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" metric:"feature_count{feature=OverwriteRemoteDeviceNames},gauge" since:"3"`
+	ProgressEmitterEnabled     bool `json:"progressEmitterEnabled,omitempty" metric:"feature_count{feature=ProgressEmitterEnabled},gauge" since:"3"`
+	CustomDefaultFolderPath    bool `json:"customDefaultFolderPath,omitempty" metric:"feature_count{feature=CustomDefaultFolderPath},gauge" since:"3"`
+	CustomTrafficClass         bool `json:"customTrafficClass,omitempty" metric:"feature_count{feature=CustomTrafficClass},gauge" since:"3"`
+	CustomTempIndexMinBlocks   bool `json:"customTempIndexMinBlocks,omitempty" metric:"feature_count{feature=CustomTempIndexMinBlocks},gauge" since:"3"`
+	TemporariesDisabled        bool `json:"temporariesDisabled,omitempty" metric:"feature_count{feature=TemporariesDisabled},gauge" since:"3"`
+	TemporariesCustom          bool `json:"temporariesCustom,omitempty" metric:"feature_count{feature=TemporariesCustom},gauge" since:"3"`
+	LimitBandwidthInLan        bool `json:"limitBandwidthInLan,omitempty" metric:"feature_count{feature=LimitBandwidthInLAN},gauge" since:"3"`
+	CustomReleaseURL           bool `json:"customReleaseURL,omitempty" metric:"feature_count{feature=CustomReleaseURL},gauge" since:"3"`
+	RestartOnWakeup            bool `json:"restartOnWakeup,omitempty" metric:"feature_count{feature=RestartOnWakeup},gauge" since:"3"`
+	CustomStunServers          bool `json:"customStunServers,omitempty" metric:"feature_count{feature=CustomSTUNServers},gauge" since:"3"`
 
 	FolderUsesV3 struct {
-		ScanProgressDisabled    int            `json:"scanProgressDisabled,omitempty" since:"3"`
-		ConflictsDisabled       int            `json:"conflictsDisabled,omitempty" since:"3"`
-		ConflictsUnlimited      int            `json:"conflictsUnlimited,omitempty" since:"3"`
-		ConflictsOther          int            `json:"conflictsOther,omitempty" since:"3"`
-		DisableSparseFiles      int            `json:"disableSparseFiles,omitempty" since:"3"`
-		DisableTempIndexes      int            `json:"disableTempIndexes,omitempty" since:"3"`
-		AlwaysWeakHash          int            `json:"alwaysWeakHash,omitempty" since:"3"`
-		CustomWeakHashThreshold int            `json:"customWeakHashThreshold,omitempty" since:"3"`
-		FsWatcherEnabled        int            `json:"fsWatcherEnabled,omitempty" since:"3"`
-		PullOrder               map[string]int `json:"pullOrder,omitempty" since:"3"`
-		FilesystemType          map[string]int `json:"filesystemType,omitempty" since:"3"`
-		FsWatcherDelays         []int          `json:"fsWatcherDelays,omitempty" since:"3"`
-		CustomMarkerName        int            `json:"customMarkerName,omitempty" since:"3"`
-		CopyOwnershipFromParent int            `json:"copyOwnershipFromParent,omitempty" since:"3"`
-		ModTimeWindowS          []int          `json:"modTimeWindowS,omitempty" since:"3"`
-		MaxConcurrentWrites     []int          `json:"maxConcurrentWrites,omitempty" since:"3"`
-		DisableFsync            int            `json:"disableFsync,omitempty" since:"3"`
-		BlockPullOrder          map[string]int `json:"blockPullOrder,omitempty" since:"3"`
-		CopyRangeMethod         map[string]int `json:"copyRangeMethod,omitempty" since:"3"`
-		CaseSensitiveFS         int            `json:"caseSensitiveFS,omitempty" since:"3"`
-		ReceiveEncrypted        int            `json:"receiveencrypted,omitempty" since:"3"`
+		ScanProgressDisabled    int            `json:"scanProgressDisabled,omitempty" metric:"folder_feature{feature=ScanProgressDisabled},summary" since:"3"`
+		ConflictsDisabled       int            `json:"conflictsDisabled,omitempty" metric:"folder_feature{feature=ConflictsDisabled},summary" since:"3"`
+		ConflictsUnlimited      int            `json:"conflictsUnlimited,omitempty" metric:"folder_feature{feature=ConflictsUnlimited},summary" since:"3"`
+		ConflictsOther          int            `json:"conflictsOther,omitempty" metric:"folder_feature{feature=ConflictsOther},summary" since:"3"`
+		DisableSparseFiles      int            `json:"disableSparseFiles,omitempty" metric:"folder_feature{feature=DisableSparseFiles},summary" since:"3"`
+		DisableTempIndexes      int            `json:"disableTempIndexes,omitempty" metric:"folder_feature{feature=DisableTempIndexes},summary" since:"3"`
+		AlwaysWeakHash          int            `json:"alwaysWeakHash,omitempty" metric:"folder_feature{feature=AlwaysWeakhash},summary" since:"3"`
+		CustomWeakHashThreshold int            `json:"customWeakHashThreshold,omitempty" metric:"folder_feature{feature=CustomWeakhashThreshold},summary" since:"3"`
+		FsWatcherEnabled        int            `json:"fsWatcherEnabled,omitempty" metric:"folder_feature{feature=FSWatcherEnabled},summary" since:"3"`
+		PullOrder               map[string]int `json:"pullOrder,omitempty" metric:"folder_pull_order,summaryVec:order" since:"3"`
+		FilesystemType          map[string]int `json:"filesystemType,omitempty" metric:"folder_file_system_type,summaryVec:type" since:"3"`
+		FsWatcherDelays         []int          `json:"fsWatcherDelays,omitempty" metric:"folder_fswatcher_delays,summary" since:"3"`
+		CustomMarkerName        int            `json:"customMarkerName,omitempty" metric:"folder_feature{feature=CustomMarkername},summary" since:"3"`
+		CopyOwnershipFromParent int            `json:"copyOwnershipFromParent,omitempty" metric:"folder_feature{feature=CopyParentOwnership},summary" since:"3"`
+		ModTimeWindowS          []int          `json:"modTimeWindowS,omitempty" metric:"folder_modtime_window_s,summary" since:"3"`
+		MaxConcurrentWrites     []int          `json:"maxConcurrentWrites,omitempty" metric:"folder_max_concurrent_writes,summary" since:"3"`
+		DisableFsync            int            `json:"disableFsync,omitempty" metric:"folder_feature{feature=DisableFsync},summary" since:"3"`
+		BlockPullOrder          map[string]int `json:"blockPullOrder,omitempty" metric:"folder_block_pull_order:summaryVec:order" since:"3"`
+		CopyRangeMethod         map[string]int `json:"copyRangeMethod,omitempty" metric:"folder_copy_range_method:summaryVec:method" since:"3"`
+		CaseSensitiveFS         int            `json:"caseSensitiveFS,omitempty" metric:"folder_feature{feature=CaseSensitiveFS},summary" since:"3"`
+		ReceiveEncrypted        int            `json:"receiveencrypted,omitempty" metric:"folder_feature{feature=ReceiveEncrypted},summary" since:"3"`
+		SendXattrs              int            `json:"sendXattrs,omitempty" metric:"folder_feature{feature=SendXattrs},summary" since:"3"`
+		SyncXattrs              int            `json:"syncXattrs,omitempty" metric:"folder_feature{feature=SyncXattrs},summary" since:"3"`
+		SendOwnership           int            `json:"sendOwnership,omitempty" metric:"folder_feature{feature=SendOwnership},summary" since:"3"`
+		SyncOwnership           int            `json:"syncOwnership,omitempty" metric:"folder_feature{feature=SyncOwnership},summary" since:"3"`
 	} `json:"folderUsesV3,omitempty" since:"3"`
 
 	DeviceUsesV3 struct {
-		Untrusted int `json:"untrusted,omitempty" since:"3"`
+		Untrusted           int `json:"untrusted,omitempty" metric:"device_feature{feature=Untrusted},summary" since:"3"`
+		UsesRateLimit       int `json:"usesRateLimit,omitempty" metric:"device_feature{feature=RateLimitsEnabled},summary" since:"3"`
+		MultipleConnections int `json:"multipleConnections,omitempty" metric:"device_feature{feature=MultipleConnections},summary" since:"3"`
 	} `json:"deviceUsesV3,omitempty" since:"3"`
 
 	GUIStats struct {
-		Enabled                   int            `json:"enabled,omitempty" since:"3"`
-		UseTLS                    int            `json:"useTLS,omitempty" since:"3"`
-		UseAuth                   int            `json:"useAuth,omitempty" since:"3"`
-		InsecureAdminAccess       int            `json:"insecureAdminAccess,omitempty" since:"3"`
-		Debugging                 int            `json:"debugging,omitempty" since:"3"`
-		InsecureSkipHostCheck     int            `json:"insecureSkipHostCheck,omitempty" since:"3"`
-		InsecureAllowFrameLoading int            `json:"insecureAllowFrameLoading,omitempty" since:"3"`
-		ListenLocal               int            `json:"listenLocal,omitempty" since:"3"`
-		ListenUnspecified         int            `json:"listenUnspecified,omitempty" since:"3"`
-		Theme                     map[string]int `json:"theme,omitempty" since:"3"`
+		Enabled                   int            `json:"enabled,omitempty" metric:"gui_feature_count{feature=Enabled},summary" since:"3"`
+		UseTLS                    int            `json:"useTLS,omitempty" metric:"gui_feature_count{feature=TLS},summary" since:"3"`
+		UseAuth                   int            `json:"useAuth,omitempty" metric:"gui_feature_count{feature=Authentication},summary" since:"3"`
+		InsecureAdminAccess       int            `json:"insecureAdminAccess,omitempty" metric:"gui_feature_count{feature=InsecureAdminAccess},summary" since:"3"`
+		Debugging                 int            `json:"debugging,omitempty" metric:"gui_feature_count{feature=Debugging},summary" since:"3"`
+		InsecureSkipHostCheck     int            `json:"insecureSkipHostCheck,omitempty" metric:"gui_feature_count{feature=InsecureSkipHostCheck},summary" since:"3"`
+		InsecureAllowFrameLoading int            `json:"insecureAllowFrameLoading,omitempty" metric:"gui_feature_count{feature=InsecureAllowFrameLoading},summary" since:"3"`
+		ListenLocal               int            `json:"listenLocal,omitempty" metric:"gui_feature_count{feature=ListenLocal},summary" since:"3"`
+		ListenUnspecified         int            `json:"listenUnspecified,omitempty" metric:"gui_feature_count{feature=ListenUnspecified},summary" since:"3"`
+		Theme                     map[string]int `json:"theme,omitempty" metric:"gui_theme,summaryVec:theme" since:"3"`
 	} `json:"guiStats,omitempty" since:"3"`
 
 	BlockStats struct {
-		Total             int `json:"total,omitempty" since:"3"`
-		Renamed           int `json:"renamed,omitempty" since:"3"`
-		Reused            int `json:"reused,omitempty" since:"3"`
-		Pulled            int `json:"pulled,omitempty" since:"3"`
-		CopyOrigin        int `json:"copyOrigin,omitempty" since:"3"`
-		CopyOriginShifted int `json:"copyOriginShifted,omitempty" since:"3"`
-		CopyElsewhere     int `json:"copyElsewhere,omitempty" since:"3"`
+		Total             int `json:"total,omitempty" metric:"blocks_processed_total,gauge" since:"3"`
+		Renamed           int `json:"renamed,omitempty" metric:"blocks_processed{source=renamed},gauge" since:"3"`
+		Reused            int `json:"reused,omitempty" metric:"blocks_processed{source=reused},gauge" since:"3"`
+		Pulled            int `json:"pulled,omitempty" metric:"blocks_processed{source=pulled},gauge" since:"3"`
+		CopyOrigin        int `json:"copyOrigin,omitempty" metric:"blocks_processed{source=copy_origin},gauge" since:"3"`
+		CopyOriginShifted int `json:"copyOriginShifted,omitempty" metric:"blocks_processed{source=copy_origin_shifted},gauge" since:"3"`
+		CopyElsewhere     int `json:"copyElsewhere,omitempty" metric:"blocks_processed{source=copy_elsewhere},gauge" since:"3"`
 	} `json:"blockStats,omitempty" since:"3"`
 
 	TransportStats map[string]int `json:"transportStats,omitempty" since:"3"`
 
 	IgnoreStats struct {
-		Lines           int `json:"lines,omitempty" since:"3"`
-		Inverts         int `json:"inverts,omitempty" since:"3"`
-		Folded          int `json:"folded,omitempty" since:"3"`
-		Deletable       int `json:"deletable,omitempty" since:"3"`
-		Rooted          int `json:"rooted,omitempty" since:"3"`
-		Includes        int `json:"includes,omitempty" since:"3"`
-		EscapedIncludes int `json:"escapedIncludes,omitempty" since:"3"`
-		DoubleStars     int `json:"doubleStars,omitempty" since:"3"`
-		Stars           int `json:"stars,omitempty" since:"3"`
+		Lines           int `json:"lines,omitempty" metric:"folder_ignore_lines_total,summary" since:"3"`
+		Inverts         int `json:"inverts,omitempty" metric:"folder_ignore_lines{kind=inverts},summary" since:"3"`
+		Folded          int `json:"folded,omitempty" metric:"folder_ignore_lines{kind=folded},summary" since:"3"`
+		Deletable       int `json:"deletable,omitempty" metric:"folder_ignore_lines{kind=deletable},summary" since:"3"`
+		Rooted          int `json:"rooted,omitempty" metric:"folder_ignore_lines{kind=rooted},summary" since:"3"`
+		Includes        int `json:"includes,omitempty" metric:"folder_ignore_lines{kind=includes},summary" since:"3"`
+		EscapedIncludes int `json:"escapedIncludes,omitempty" metric:"folder_ignore_lines{kind=escapedIncludes},summary" since:"3"`
+		DoubleStars     int `json:"doubleStars,omitempty" metric:"folder_ignore_lines{kind=doubleStars},summary" since:"3"`
+		Stars           int `json:"stars,omitempty" metric:"folder_ignore_lines{kind=stars},summary" since:"3"`
 	} `json:"ignoreStats,omitempty" since:"3"`
 
 	// V3 fields added late in the RC
-	WeakHashEnabled bool `json:"weakHashEnabled,omitempty" since:"3"` // Deprecated and not provided client-side anymore
+	WeakHashEnabled bool `json:"weakHashEnabled,omitempty" metric:"-" since:"3"` // Deprecated and not provided client-side anymore
+
+	// Added in post processing
+	Received     time.Time `json:"received,omitempty"`
+	Date         string    `json:"date,omitempty"`
+	Address      string    `json:"address,omitempty"`
+	OS           string    `json:"os" metric:"reports_total,gaugeVec:os"`
+	Arch         string    `json:"arch" metric:"reports_total,gaugeVec:arch"`
+	Compiler     string    `json:"compiler" metric:"builder,gaugeVec:compiler"`
+	Builder      string    `json:"builder" metric:"builder,gaugeVec:builder"`
+	Distribution string    `json:"distribution" metric:"builder,gaugeVec:distribution"`
+	Country      string    `json:"country" metric:"location,gaugeVec:country"`
+	CountryCode  string    `json:"countryCode" metric:"location,gaugeVec:countryCode"`
+	MajorVersion string    `json:"majorVersion" metric:"reports_by_major_total,gaugeVec:version"`
 }
 
 func New() *Report {
@@ -206,182 +220,6 @@ func (r *Report) ClearForVersion(version int) error {
 	return clear(r, version)
 }
 
-func (r *Report) FieldPointers() []interface{} {
-	// All the fields of the Report, in the same order as the database fields.
-	return []interface{}{
-		&r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
-		&r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
-		&r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
-		&r.MemorySize, &r.Date,
-		// V2
-		&r.URVersion, &r.NumCPU, &r.FolderUses.SendOnly, &r.FolderUses.IgnorePerms,
-		&r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
-		&r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
-		&r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
-		&r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
-		&r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
-		&r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
-		&r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
-		&r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
-		&r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning,
-		&r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning,
-		&r.FolderUses.TrashcanVersioning,
-
-		// V2.5
-		&r.UpgradeAllowedPre,
-
-		// V3
-		&r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles,
-		&r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath,
-		&r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks,
-		&r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan,
-		&r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers,
-
-		&r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled,
-		&r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther,
-		&r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes,
-		&r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold,
-		&r.FolderUsesV3.FsWatcherEnabled,
-
-		&r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth,
-		&r.GUIStats.InsecureAdminAccess,
-		&r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck,
-		&r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal,
-		&r.GUIStats.ListenUnspecified,
-
-		&r.BlockStats.Total, &r.BlockStats.Renamed,
-		&r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin,
-		&r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere,
-
-		&r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded,
-		&r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes,
-		&r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars,
-
-		// V3 added late in the RC
-		&r.WeakHashEnabled,
-		&r.Address,
-
-		// Receive only folders
-		&r.FolderUses.ReceiveOnly,
-	}
-}
-
-func (*Report) FieldNames() []string {
-	// The database fields that back this struct in PostgreSQL
-	return []string{
-		// V1
-		"Received",
-		"UniqueID",
-		"Version",
-		"LongVersion",
-		"Platform",
-		"NumFolders",
-		"NumDevices",
-		"TotFiles",
-		"FolderMaxFiles",
-		"TotMiB",
-		"FolderMaxMiB",
-		"MemoryUsageMiB",
-		"SHA256Perf",
-		"MemorySize",
-		"Date",
-		// V2
-		"ReportVersion",
-		"NumCPU",
-		"FolderRO",
-		"FolderIgnorePerms",
-		"FolderIgnoreDelete",
-		"FolderAutoNormalize",
-		"DeviceIntroducer",
-		"DeviceCustomCertName",
-		"DeviceCompressionAlways",
-		"DeviceCompressionMetadata",
-		"DeviceCompressionNever",
-		"DeviceDynamicAddr",
-		"DeviceStaticAddr",
-		"AnnounceGlobalEnabled",
-		"AnnounceLocalEnabled",
-		"AnnounceDefaultServersDNS",
-		"AnnounceDefaultServersIP",
-		"AnnounceOtherServers",
-		"RelayEnabled",
-		"RelayDefaultServers",
-		"RelayOtherServers",
-		"RateLimitEnabled",
-		"UpgradeAllowedManual",
-		"UpgradeAllowedAuto",
-		// v0.12.19+
-		"FolderSimpleVersioning",
-		"FolderExternalVersioning",
-		"FolderStaggeredVersioning",
-		"FolderTrashcanVersioning",
-		// V2.5
-		"UpgradeAllowedPre",
-		// V3
-		"Uptime",
-		"NATType",
-		"AlwaysLocalNets",
-		"CacheIgnoredFiles",
-		"OverwriteRemoteDeviceNames",
-		"ProgressEmitterEnabled",
-		"CustomDefaultFolderPath",
-		"WeakHashSelection",
-		"CustomTrafficClass",
-		"CustomTempIndexMinBlocks",
-		"TemporariesDisabled",
-		"TemporariesCustom",
-		"LimitBandwidthInLan",
-		"CustomReleaseURL",
-		"RestartOnWakeup",
-		"CustomStunServers",
-
-		"FolderScanProgressDisabled",
-		"FolderConflictsDisabled",
-		"FolderConflictsUnlimited",
-		"FolderConflictsOther",
-		"FolderDisableSparseFiles",
-		"FolderDisableTempIndexes",
-		"FolderAlwaysWeakHash",
-		"FolderCustomWeakHashThreshold",
-		"FolderFsWatcherEnabled",
-
-		"GUIEnabled",
-		"GUIUseTLS",
-		"GUIUseAuth",
-		"GUIInsecureAdminAccess",
-		"GUIDebugging",
-		"GUIInsecureSkipHostCheck",
-		"GUIInsecureAllowFrameLoading",
-		"GUIListenLocal",
-		"GUIListenUnspecified",
-
-		"BlocksTotal",
-		"BlocksRenamed",
-		"BlocksReused",
-		"BlocksPulled",
-		"BlocksCopyOrigin",
-		"BlocksCopyOriginShifted",
-		"BlocksCopyElsewhere",
-
-		"IgnoreLines",
-		"IgnoreInverts",
-		"IgnoreFolded",
-		"IgnoreDeletable",
-		"IgnoreRooted",
-		"IgnoreIncludes",
-		"IgnoreEscapedIncludes",
-		"IgnoreDoubleStars",
-		"IgnoreStars",
-
-		// V3 added late in the RC
-		"WeakHashEnabled",
-		"Address",
-
-		// Receive only folders
-		"FolderRecvOnly",
-	}
-}
-
 func (r Report) Value() (driver.Value, error) {
 	// This needs to be string, yet we read back bytes..
 	bs, err := json.Marshal(r)

+ 18 - 0
lib/ur/usage_report.go

@@ -274,6 +274,18 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
 			if cfg.Type == config.FolderTypeReceiveEncrypted {
 				report.FolderUsesV3.ReceiveEncrypted++
 			}
+			if cfg.SendXattrs {
+				report.FolderUsesV3.SendXattrs++
+			}
+			if cfg.SyncXattrs {
+				report.FolderUsesV3.SyncXattrs++
+			}
+			if cfg.SendOwnership {
+				report.FolderUsesV3.SendOwnership++
+			}
+			if cfg.SyncOwnership {
+				report.FolderUsesV3.SyncOwnership++
+			}
 		}
 		sort.Ints(report.FolderUsesV3.FsWatcherDelays)
 
@@ -281,6 +293,12 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
 			if cfg.Untrusted {
 				report.DeviceUsesV3.Untrusted++
 			}
+			if cfg.MaxRecvKbps > 0 || cfg.MaxSendKbps > 0 {
+				report.DeviceUsesV3.UsesRateLimit++
+			}
+			if cfg.RawNumConnections > 1 {
+				report.DeviceUsesV3.MultipleConnections++
+			}
 		}
 
 		guiCfg := s.cfg.GUI()

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor