master
go 107 lines 2.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package metrix
4
5 import (
6 "fmt"
7 "maps"
8 "sort"
9 "strings"
10 )
11
12 // HostScope identifies the target host/vnode partition for metric series.
13 //
14 // The zero value is the default scope and is equivalent to unscoped writes.
15 // Non-default scopes must have a stable ScopeKey plus host metadata.
16 type HostScope struct {
17 ScopeKey string
18 GUID string
19 Hostname string
20 Labels map[string]string
21 }
22
23 // IsDefault reports whether this scope is the default unscoped partition.
24 func (s HostScope) IsDefault() bool {
25 return strings.TrimSpace(s.ScopeKey) == ""
26 }
27
28 func normalizeHostScope(scope HostScope) (HostScope, error) {
29 out := HostScope{
30 ScopeKey: strings.TrimSpace(scope.ScopeKey),
31 GUID: strings.TrimSpace(scope.GUID),
32 Hostname: strings.TrimSpace(scope.Hostname),
33 }
34 if strings.ContainsAny(out.ScopeKey, "\xfe\xff") {
35 return HostScope{}, fmt.Errorf("metrix: host scope key contains reserved separator")
36 }
37 if out.ScopeKey == "" {
38 if out.GUID != "" || out.Hostname != "" || len(scope.Labels) > 0 {
39 return HostScope{}, fmt.Errorf("metrix: default host scope cannot carry vnode metadata")
40 }
41 return HostScope{}, nil
42 }
43 if out.GUID == "" {
44 return HostScope{}, fmt.Errorf("metrix: host scope guid is required")
45 }
46 if out.Hostname == "" {
47 return HostScope{}, fmt.Errorf("metrix: host scope hostname is required")
48 }
49 if len(scope.Labels) > 0 {
50 out.Labels = make(map[string]string, len(scope.Labels))
51 for key, value := range scope.Labels {
52 k := strings.TrimSpace(key)
53 if k == "" {
54 return HostScope{}, fmt.Errorf("metrix: host scope label key is required")
55 }
56 if _, ok := out.Labels[k]; ok {
57 return HostScope{}, fmt.Errorf("metrix: duplicate host scope label key %q", k)
58 }
59 out.Labels[k] = strings.TrimSpace(value)
60 }
61 }
62 return out, nil
63 }
64
65 func mustNormalizeHostScope(scope HostScope) HostScope {
66 out, err := normalizeHostScope(scope)
67 if err != nil {
68 panic(err)
69 }
70 return out
71 }
72
73 func cloneHostScope(scope HostScope) HostScope {
74 if scope.Labels != nil {
75 scope.Labels = maps.Clone(scope.Labels)
76 }
77 return scope
78 }
79
80 func hostScopeEqual(a, b HostScope) bool {
81 if a.ScopeKey != b.ScopeKey || a.GUID != b.GUID || a.Hostname != b.Hostname {
82 return false
83 }
84 return maps.Equal(a.Labels, b.Labels)
85 }
86
87 func sortedHostScopes(scopes map[string]HostScope) []HostScope {
88 if len(scopes) == 0 {
89 return nil
90 }
91 keys := make([]string, 0, len(scopes))
92 for key := range scopes {
93 keys = append(keys, key)
94 }
95 sort.Strings(keys)
96 out := make([]HostScope, 0, len(keys))
97 if scope, ok := scopes[""]; ok {
98 out = append(out, cloneHostScope(scope))
99 }
100 for _, key := range keys {
101 if key == "" {
102 continue
103 }
104 out = append(out, cloneHostScope(scopes[key]))
105 }
106 return out
107 }