master
go 180 lines 4.69 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nagios
4
5 import (
6 "math"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 )
12
13 const (
14 legacyDefaultDivisor = 1000
15 legacyTimeDivisor = 1_000_000_000
16 )
17
18 type legacyScale struct {
19 canonicalUnit string
20 divisor int
21 multiplier float64
22 }
23
24 func legacyDisplayValue(unit string, raw float64) float64 {
25 scale := legacyScaleFromUnit(unit)
26 scaled := int64(math.Round(raw * scale.multiplier))
27 return float64(scaled) / float64(scale.divisor)
28 }
29
30 func legacyScaleFromUnit(unit string) legacyScale {
31 trimmed := strings.TrimSpace(unit)
32 if trimmed == "" {
33 return legacyScale{canonicalUnit: "", divisor: legacyDefaultDivisor, multiplier: legacyDefaultDivisor}
34 }
35 lower := strings.ToLower(trimmed)
36 if scale, ok := legacyTimeScale(lower); ok {
37 return scale
38 }
39 if scale, ok := legacyByteScale(trimmed); ok {
40 return scale
41 }
42 if lower == "%" {
43 return legacyScale{canonicalUnit: "%", divisor: legacyDefaultDivisor, multiplier: legacyDefaultDivisor}
44 }
45 if lower == "c" {
46 return legacyScale{canonicalUnit: "c", divisor: 1, multiplier: 1}
47 }
48 return legacyScale{canonicalUnit: trimmed, divisor: legacyDefaultDivisor, multiplier: legacyDefaultDivisor}
49 }
50
51 func legacyTimeScale(unit string) (legacyScale, bool) {
52 switch unit {
53 case "s", "sec", "secs", "second", "seconds":
54 return legacyScale{canonicalUnit: "seconds", divisor: legacyTimeDivisor, multiplier: legacyTimeDivisor}, true
55 case "ms", "millisecond", "milliseconds":
56 return legacyScale{canonicalUnit: "seconds", divisor: legacyTimeDivisor, multiplier: 1_000_000}, true
57 case "us", "µs", "usec", "microsecond", "microseconds":
58 return legacyScale{canonicalUnit: "seconds", divisor: legacyTimeDivisor, multiplier: 1_000}, true
59 case "ns", "nanosecond", "nanoseconds":
60 return legacyScale{canonicalUnit: "seconds", divisor: legacyTimeDivisor, multiplier: 1}, true
61 default:
62 return legacyScale{}, false
63 }
64 }
65
66 func legacyByteScale(unit string) (legacyScale, bool) {
67 base, perSecond := legacySplitPerSecond(unit)
68 if base == "" {
69 return legacyScale{}, false
70 }
71 mult, kind, ok := legacyByteMultiplier(base)
72 if !ok {
73 return legacyScale{}, false
74 }
75 canonical := kind
76 if perSecond {
77 canonical += "/s"
78 }
79 return legacyScale{canonicalUnit: canonical, divisor: 1, multiplier: mult}, true
80 }
81
82 func legacySplitPerSecond(unit string) (string, bool) {
83 lower := strings.ToLower(unit)
84 switch {
85 case strings.HasSuffix(lower, "/s"):
86 return strings.TrimSpace(unit[:len(unit)-2]), true
87 case strings.HasSuffix(lower, "ps"):
88 return strings.TrimSpace(unit[:len(unit)-2]), true
89 default:
90 return strings.TrimSpace(unit), false
91 }
92 }
93
94 func legacyByteMultiplier(unit string) (float64, string, bool) {
95 unit = strings.TrimSpace(unit)
96 if unit == "" {
97 return 0, "", false
98 }
99 kind, prefix, ok := legacySplitByteUnit(unit)
100 if !ok {
101 return 0, "", false
102 }
103 mult, ok := legacyByteMagnitude(prefix)
104 if !ok {
105 return 0, "", false
106 }
107 return mult, kind, true
108 }
109
110 func legacySplitByteUnit(unit string) (string, string, bool) {
111 lower := strings.ToLower(unit)
112 switch {
113 case strings.HasSuffix(lower, "bytes"):
114 return "bytes", unit[:len(unit)-5], true
115 case strings.HasSuffix(lower, "byte"):
116 return "bytes", unit[:len(unit)-4], true
117 case strings.HasSuffix(lower, "bits"):
118 return "bits", unit[:len(unit)-4], true
119 case strings.HasSuffix(lower, "bit"):
120 return "bits", unit[:len(unit)-3], true
121 }
122 if len(unit) == 0 {
123 return "", "", false
124 }
125 last := unit[len(unit)-1]
126 switch last {
127 case 'B':
128 return "bytes", unit[:len(unit)-1], true
129 case 'b':
130 return "bits", unit[:len(unit)-1], true
131 }
132 return "", "", false
133 }
134
135 func legacyByteMagnitude(prefix string) (float64, bool) {
136 switch strings.ToLower(strings.TrimSpace(prefix)) {
137 case "":
138 return 1, true
139 case "k":
140 return 1_000, true
141 case "m":
142 return 1_000_000, true
143 case "g":
144 return 1_000_000_000, true
145 case "t":
146 return 1_000_000_000_000, true
147 default:
148 return 0, false
149 }
150 }
151
152 func TestLegacyScaleDistinguishesBitsAndBytes(t *testing.T) {
153 tests := map[string]struct {
154 unit string
155 value float64
156 canonicalUnit string
157 want float64
158 }{
159 "megabytes per second": {
160 unit: "MBps", value: 1.5, canonicalUnit: "bytes/s", want: 1_500_000,
161 },
162 "megabits per second": {
163 unit: "Mbps", value: 1.5, canonicalUnit: "bits/s", want: 1_500_000,
164 },
165 "megabytes": {
166 unit: "MB", value: 2.5, canonicalUnit: "bytes", want: 2_500_000,
167 },
168 "megabits": {
169 unit: "Mb", value: 2.5, canonicalUnit: "bits", want: 2_500_000,
170 },
171 }
172
173 for name, tc := range tests {
174 t.Run(name, func(t *testing.T) {
175 scale := legacyScaleFromUnit(tc.unit)
176 assert.Equal(t, tc.canonicalUnit, scale.canonicalUnit)
177 assert.Equal(t, tc.want, legacyDisplayValue(tc.unit, tc.value))
178 })
179 }
180 }