@cryptotaxi247 / netdata-1 / commits / eb93428eb

go.d chrony add serverstats query (disabled for now) (#18055)

Ilya Mashchenko committed Jul 1, 2024 at 22:29 UTC eb93428ebac1bc71a61705cd6960e332a0ba2612
5 files changed +449 -101
src/go/collectors/go.d.plugin/modules/chrony/charts.go
+266 -92
@@ -3,140 +3,211 @@
3 package chrony
4
5 import (
6 + "errors"
7 +
8 "github.com/netdata/netdata/go/go.d.plugin/agent/module"
9 )
10
11 +const (
12 + prioStratum = module.Priority + iota
13 + prioCurrentCorrection
14 + prioRootDelay
15 + prioRootDispersion
16 + prioLastOffset
17 + prioRmsOffset
18 + prioFrequency
19 + prioResidualFrequency
20 + prioSkew
21 + prioUpdateInterval
22 + prioRefMeasurementTime
23 + prioLeapStatus
24 + prioActivity
25 + prioNTPPackets
26 + prioCommandPackets
27 + prioNKEConnections
28 + prioClientLogRecords
29 +)
30 +
31 var charts = module.Charts{
10 - {
11 - ID: "stratum",
12 - Title: "Distance to the reference clock",
13 - Units: "level",
14 - Fam: "stratum",
15 - Ctx: "chrony.stratum",
32 + stratumChart.Copy(),
33 +
34 + currentCorrectionChart.Copy(),
35 +
36 + rootDelayChart.Copy(),
37 + rootDispersionChart.Copy(),
38 +
39 + lastOffsetChart.Copy(),
40 + rmsOffsetChart.Copy(),
41 +
42 + frequencyChart.Copy(),
43 + residualFrequencyChart.Copy(),
44 +
45 + skewChart.Copy(),
46 +
47 + updateIntervalChart.Copy(),
48 + refMeasurementTimeChart.Copy(),
49 +
50 + leapStatusChart.Copy(),
51 +
52 + activityChart.Copy(),
53 +}
54 +
55 +// Tracking charts
56 +var (
57 + stratumChart = module.Chart{
58 + ID: "stratum",
59 + Title: "Distance to the reference clock",
60 + Units: "level",
61 + Fam: "stratum",
62 + Ctx: "chrony.stratum",
63 + Priority: prioStratum,
64 Dims: module.Dims{
65 {ID: "stratum", Name: "stratum"},
66 },
19 - },
20 - {
21 - ID: "current_correction",
22 - Title: "Current correction",
23 - Units: "seconds",
24 - Fam: "correction",
25 - Ctx: "chrony.current_correction",
67 + }
68 +
69 + currentCorrectionChart = module.Chart{
70 + ID: "current_correction",
71 + Title: "Current correction",
72 + Units: "seconds",
73 + Fam: "correction",
74 + Ctx: "chrony.current_correction",
75 + Priority: prioCurrentCorrection,
76 Dims: module.Dims{
77 {ID: "current_correction", Div: scaleFactor},
78 },
29 - },
30 - {
31 - ID: "root_delay",
32 - Title: "Network path delay to stratum-1",
33 - Units: "seconds",
34 - Fam: "root",
35 - Ctx: "chrony.root_delay",
79 + }
80 +
81 + rootDelayChart = module.Chart{
82 + ID: "root_delay",
83 + Title: "Network path delay to stratum-1",
84 + Units: "seconds",
85 + Fam: "root",
86 + Ctx: "chrony.root_delay",
87 + Priority: prioRootDelay,
88 Dims: module.Dims{
89 {ID: "root_delay", Div: scaleFactor},
90 },
39 - },
40 - {
41 - ID: "root_dispersion",
42 - Title: "Dispersion accumulated back to stratum-1",
43 - Units: "seconds",
44 - Fam: "root",
45 - Ctx: "chrony.root_dispersion",
91 + }
92 + rootDispersionChart = module.Chart{
93 + ID: "root_dispersion",
94 + Title: "Dispersion accumulated back to stratum-1",
95 + Units: "seconds",
96 + Fam: "root",
97 + Ctx: "chrony.root_dispersion",
98 + Priority: prioRootDispersion,
99 Dims: module.Dims{
100 {ID: "root_dispersion", Div: scaleFactor},
101 },
49 - },
50 - {
51 - ID: "last_offset",
52 - Title: "Offset on the last clock update",
53 - Units: "seconds",
54 - Fam: "offset",
55 - Ctx: "chrony.last_offset",
102 + }
103 +
104 + lastOffsetChart = module.Chart{
105 + ID: "last_offset",
106 + Title: "Offset on the last clock update",
107 + Units: "seconds",
108 + Fam: "offset",
109 + Ctx: "chrony.last_offset",
110 + Priority: prioLastOffset,
111 Dims: module.Dims{
112 {ID: "last_offset", Name: "offset", Div: scaleFactor},
113 },
59 - },
60 - {
61 - ID: "rms_offset",
62 - Title: "Long-term average of the offset value",
63 - Units: "seconds",
64 - Fam: "offset",
65 - Ctx: "chrony.rms_offset",
114 + }
115 + rmsOffsetChart = module.Chart{
116 + ID: "rms_offset",
117 + Title: "Long-term average of the offset value",
118 + Units: "seconds",
119 + Fam: "offset",
120 + Ctx: "chrony.rms_offset",
121 + Priority: prioRmsOffset,
122 Dims: module.Dims{
123 {ID: "rms_offset", Name: "offset", Div: scaleFactor},
124 },
69 - },
70 - {
71 - ID: "frequency",
72 - Title: "Frequency",
73 - Units: "ppm",
74 - Fam: "frequency",
75 - Ctx: "chrony.frequency",
125 + }
126 +
127 + frequencyChart = module.Chart{
128 + ID: "frequency",
129 + Title: "Frequency",
130 + Units: "ppm",
131 + Fam: "frequency",
132 + Ctx: "chrony.frequency",
133 + Priority: prioFrequency,
134 Dims: module.Dims{
135 {ID: "frequency", Div: scaleFactor},
136 },
79 - },
80 - {
81 - ID: "residual_frequency",
82 - Title: "Residual frequency",
83 - Units: "ppm",
84 - Fam: "frequency",
85 - Ctx: "chrony.residual_frequency",
137 + }
138 + residualFrequencyChart = module.Chart{
139 + ID: "residual_frequency",
140 + Title: "Residual frequency",
141 + Units: "ppm",
142 + Fam: "frequency",
143 + Ctx: "chrony.residual_frequency",
144 + Priority: prioResidualFrequency,
145 Dims: module.Dims{
146 {ID: "residual_frequency", Div: scaleFactor},
147 },
89 - },
90 - {
91 - ID: "skew",
92 - Title: "Skew",
93 - Units: "ppm",
94 - Fam: "frequency",
95 - Ctx: "chrony.skew",
148 + }
149 +
150 + skewChart = module.Chart{
151 + ID: "skew",
152 + Title: "Skew",
153 + Units: "ppm",
154 + Fam: "frequency",
155 + Ctx: "chrony.skew",
156 + Priority: prioSkew,
157 Dims: module.Dims{
158 {ID: "skew", Div: scaleFactor},
159 },
99 - },
100 - {
101 - ID: "update_interval",
102 - Title: "Interval between the last two clock updates",
103 - Units: "seconds",
104 - Fam: "updates",
105 - Ctx: "chrony.update_interval",
160 + }
161 +
162 + updateIntervalChart = module.Chart{
163 + ID: "update_interval",
164 + Title: "Interval between the last two clock updates",
165 + Units: "seconds",
166 + Fam: "updates",
167 + Ctx: "chrony.update_interval",
168 + Priority: prioUpdateInterval,
169 Dims: module.Dims{
170 {ID: "update_interval", Div: scaleFactor},
171 },
109 - },
110 - {
111 - ID: "ref_measurement_time",
112 - Title: "Time since the last measurement",
113 - Units: "seconds",
114 - Fam: "updates",
115 - Ctx: "chrony.ref_measurement_time",
172 + }
173 + refMeasurementTimeChart = module.Chart{
174 + ID: "ref_measurement_time",
175 + Title: "Time since the last measurement",
176 + Units: "seconds",
177 + Fam: "updates",
178 + Ctx: "chrony.ref_measurement_time",
179 + Priority: prioRefMeasurementTime,
180 Dims: module.Dims{
181 {ID: "ref_measurement_time"},
182 },
119 - },
120 - {
121 - ID: "leap_status",
122 - Title: "Leap status",
123 - Units: "status",
124 - Fam: "leap status",
125 - Ctx: "chrony.leap_status",
183 + }
184 +
185 + leapStatusChart = module.Chart{
186 + ID: "leap_status",
187 + Title: "Leap status",
188 + Units: "status",
189 + Fam: "leap status",
190 + Ctx: "chrony.leap_status",
191 + Priority: prioLeapStatus,
192 Dims: module.Dims{
193 {ID: "leap_status_normal", Name: "normal"},
194 {ID: "leap_status_insert_second", Name: "insert_second"},
195 {ID: "leap_status_delete_second", Name: "delete_second"},
196 {ID: "leap_status_unsynchronised", Name: "unsynchronised"},
197 },
132 - },
133 - {
134 - ID: "activity",
135 - Title: "Peers activity",
136 - Units: "sources",
137 - Fam: "activity",
138 - Ctx: "chrony.activity",
139 - Type: module.Stacked,
198 + }
199 +)
200 +
201 +// Activity charts
202 +var (
203 + activityChart = module.Chart{
204 + ID: "activity",
205 + Title: "Peers activity",
206 + Units: "sources",
207 + Fam: "activity",
208 + Ctx: "chrony.activity",
209 + Type: module.Stacked,
210 + Priority: prioActivity,
211 Dims: module.Dims{
212 {ID: "online_sources", Name: "online"},
213 {ID: "offline_sources", Name: "offline"},
@@ -144,5 +215,108 @@ var charts = module.Charts{
215 {ID: "burst_offline_sources", Name: "burst_offline"},
216 {ID: "unresolved_sources", Name: "unresolved"},
217 },
147 - },
218 + }
219 +)
220 +
221 +var serverStatsVer1Charts = module.Charts{
222 + ntpPacketsChart.Copy(),
223 + commandPacketsChart.Copy(),
224 + clientLogRecordsChart.Copy(),
225 +}
226 +
227 +var serverStatsVer2Charts = module.Charts{
228 + ntpPacketsChart.Copy(),
229 + commandPacketsChart.Copy(),
230 + clientLogRecordsChart.Copy(),
231 + nkeConnectionChart.Copy(),
232 +}
233 +
234 +var serverStatsVer3Charts = module.Charts{
235 + ntpPacketsChart.Copy(),
236 + commandPacketsChart.Copy(),
237 + clientLogRecordsChart.Copy(),
238 + nkeConnectionChart.Copy(),
239 +}
240 +
241 +var serverStatsVer4Charts = module.Charts{
242 + ntpPacketsChart.Copy(),
243 + commandPacketsChart.Copy(),
244 + clientLogRecordsChart.Copy(),
245 + nkeConnectionChart.Copy(),
246 +}
247 +
248 +// ServerStats charts
249 +var (
250 + ntpPacketsChart = module.Chart{
251 + ID: "ntp_packets",
252 + Title: "NTP packets",
253 + Units: "packets/s",
254 + Fam: "client requests",
255 + Ctx: "chrony.ntp_packets",
256 + Type: module.Stacked,
257 + Priority: prioNTPPackets,
258 + Dims: module.Dims{
259 + {ID: "ntp_packets_received", Name: "received", Algo: module.Incremental},
260 + {ID: "ntp_packets_dropped", Name: "dropped", Algo: module.Incremental},
261 + },
262 + }
263 + commandPacketsChart = module.Chart{
264 + ID: "command_packets",
265 + Title: "Command packets",
266 + Units: "packets/s",
267 + Fam: "client requests",
268 + Ctx: "chrony.command_packets",
269 + Type: module.Stacked,
270 + Priority: prioCommandPackets,
271 + Dims: module.Dims{
272 + {ID: "command_packets_received", Name: "received", Algo: module.Incremental},
273 + {ID: "command_packets_dropped", Name: "dropped", Algo: module.Incremental},
274 + },
275 + }
276 + nkeConnectionChart = module.Chart{
277 + ID: "nke_connections",
278 + Title: "NTS-KE connections",
279 + Units: "connections/s",
280 + Fam: "client requests",
281 + Ctx: "chrony.nke_connections",
282 + Type: module.Stacked,
283 + Priority: prioNKEConnections,
284 + Dims: module.Dims{
285 + {ID: "nke_connections_accepted", Name: "accepted", Algo: module.Incremental},
286 + {ID: "nke_connections_dropped", Name: "dropped", Algo: module.Incremental},
287 + },
288 + }
289 + clientLogRecordsChart = module.Chart{
290 + ID: "client_log_records",
291 + Title: "Client log records",
292 + Units: "records/s",
293 + Fam: "client requests",
294 + Ctx: "chrony.client_log_records",
295 + Type: module.Stacked,
296 + Priority: prioClientLogRecords,
297 + Dims: module.Dims{
298 + {ID: "client_log_records_dropped", Name: "dropped", Algo: module.Incremental},
299 + },
300 + }
301 +)
302 +
303 +func (c *Chrony) addServerStatsCharts(stats *serverStats) {
304 + var err error
305 +
306 + switch {
307 + case stats.v1 != nil:
308 + err = c.Charts().Add(*serverStatsVer1Charts.Copy()...)
309 + case stats.v2 != nil:
310 + err = c.Charts().Add(*serverStatsVer2Charts.Copy()...)
311 + case stats.v3 != nil:
312 + err = c.Charts().Add(*serverStatsVer3Charts.Copy()...)
313 + case stats.v4 != nil:
314 + err = c.Charts().Add(*serverStatsVer4Charts.Copy()...)
315 + default:
316 + err = errors.New("unknown stats chart")
317 + }
318 +
319 + if err != nil {
320 + c.Warning(err)
321 + }
322 }
src/go/collectors/go.d.plugin/modules/chrony/chrony.go
+9 -4
@@ -5,11 +5,13 @@ package chrony
5 import (
6 _ "embed"
7 "errors"
8 + "sync"
9 "time"
10
10 - "github.com/facebook/time/ntp/chrony"
11 "github.com/netdata/netdata/go/go.d.plugin/agent/module"
12 "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
13 +
14 + "github.com/facebook/time/ntp/chrony"
15 )
16
17 //go:embed "config_schema.json"
@@ -29,8 +31,9 @@ func New() *Chrony {
31 Address: "127.0.0.1:323",
32 Timeout: web.Duration(time.Second),
33 },
32 - charts: charts.Copy(),
33 - newClient: newChronyClient,
34 + charts: charts.Copy(),
35 + addStatsChartsOnce: &sync.Once{},
36 + newClient: newChronyClient,
37 }
38 }
39
@@ -45,7 +48,8 @@ type (
48 module.Base
49 Config `yaml:",inline" json:""`
50
48 - charts *module.Charts
51 + charts *module.Charts
52 + addStatsChartsOnce *sync.Once
53
54 client chronyClient
55 newClient func(c Config) (chronyClient, error)
@@ -53,6 +57,7 @@ type (
57 chronyClient interface {
58 Tracking() (*chrony.ReplyTracking, error)
59 Activity() (*chrony.ReplyActivity, error)
60 + ServerStats() (*serverStats, error)
61 Close()
62 }
63 )
src/go/collectors/go.d.plugin/modules/chrony/chrony_test.go
+28 -3
@@ -240,9 +240,10 @@ func prepareChronyWithMock(m *mockClient) *Chrony {
240 }
241
242 type mockClient struct {
243 - errOnTracking bool
244 - errOnActivity bool
245 - closeCalled bool
243 + errOnTracking bool
244 + errOnActivity bool
245 + errOnServerStats bool
246 + closeCalled bool
247 }
248
249 func (m *mockClient) Tracking() (*chrony.ReplyTracking, error) {
@@ -286,6 +287,30 @@ func (m *mockClient) Activity() (*chrony.ReplyActivity, error) {
287 return &reply, nil
288 }
289
290 +func (m *mockClient) ServerStats() (*serverStats, error) {
291 + if m.errOnServerStats {
292 + return nil, errors.New("mockClient.ServerStats call error")
293 + }
294 +
295 + reply := serverStats{
296 + v3: &chrony.ServerStats3{
297 + NTPHits: 10,
298 + NKEHits: 10,
299 + CMDHits: 10,
300 + NTPDrops: 1,
301 + NKEDrops: 1,
302 + CMDDrops: 1,
303 + LogDrops: 1,
304 + NTPAuthHits: 10,
305 + NTPInterleavedHits: 10,
306 + NTPTimestamps: 0,
307 + NTPSpanSeconds: 0,
308 + },
309 + }
310 +
311 + return &reply, nil
312 +}
313 +
314 func (m *mockClient) Close() {
315 m.closeCalled = true
316 }
src/go/collectors/go.d.plugin/modules/chrony/client.go
+85 -2
@@ -19,6 +19,7 @@ func newChronyClient(c Config) (chronyClient, error) {
19 conn: conn,
20 client: &chrony.Client{Connection: conn},
21 }
22 +
23 return client, nil
24 }
25
@@ -28,7 +29,9 @@ type simpleClient struct {
29 }
30
31 func (sc *simpleClient) Tracking() (*chrony.ReplyTracking, error) {
31 - reply, err := sc.client.Communicate(chrony.NewTrackingPacket())
32 + req := chrony.NewTrackingPacket()
33 +
34 + reply, err := sc.client.Communicate(req)
35 if err != nil {
36 return nil, err
37 }
@@ -41,7 +44,9 @@ func (sc *simpleClient) Tracking() (*chrony.ReplyTracking, error) {
44 }
45
46 func (sc *simpleClient) Activity() (*chrony.ReplyActivity, error) {
44 - reply, err := sc.client.Communicate(chrony.NewActivityPacket())
47 + req := chrony.NewActivityPacket()
48 +
49 + reply, err := sc.client.Communicate(req)
50 if err != nil {
51 return nil, err
52 }
@@ -53,6 +58,84 @@ func (sc *simpleClient) Activity() (*chrony.ReplyActivity, error) {
58 return activity, nil
59 }
60
61 +type serverStats struct {
62 + v1 *chrony.ServerStats
63 + v2 *chrony.ServerStats2
64 + v3 *chrony.ServerStats3
65 + v4 *chrony.ServerStats4
66 +}
67 +
68 +func (sc *simpleClient) ServerStats() (*serverStats, error) {
69 + req := chrony.NewServerStatsPacket()
70 +
71 + reply, err := sc.client.Communicate(req)
72 + if err != nil {
73 + return nil, err
74 + }
75 +
76 + var stats serverStats
77 +
78 + switch v := reply.(type) {
79 + case *chrony.ReplyServerStats:
80 + stats.v1 = &chrony.ServerStats{
81 + NTPHits: v.NTPHits,
82 + CMDHits: v.CMDHits,
83 + NTPDrops: v.NTPDrops,
84 + CMDDrops: v.CMDDrops,
85 + LogDrops: v.LogDrops,
86 + }
87 + case *chrony.ReplyServerStats2:
88 + stats.v2 = &chrony.ServerStats2{
89 + NTPHits: v.NTPHits,
90 + NKEHits: v.NKEHits,
91 + CMDHits: v.CMDHits,
92 + NTPDrops: v.NTPDrops,
93 + NKEDrops: v.NKEDrops,
94 + CMDDrops: v.CMDDrops,
95 + LogDrops: v.LogDrops,
96 + NTPAuthHits: v.NTPAuthHits,
97 + }
98 + case *chrony.ReplyServerStats3:
99 + stats.v3 = &chrony.ServerStats3{
100 + NTPHits: v.NTPHits,
101 + NKEHits: v.NKEHits,
102 + CMDHits: v.CMDHits,
103 + NTPDrops: v.NTPDrops,
104 + NKEDrops: v.NKEDrops,
105 + CMDDrops: v.CMDDrops,
106 + LogDrops: v.LogDrops,
107 + NTPAuthHits: v.NTPAuthHits,
108 + NTPInterleavedHits: v.NTPInterleavedHits,
109 + NTPTimestamps: v.NTPTimestamps,
110 + NTPSpanSeconds: v.NTPSpanSeconds,
111 + }
112 + case *chrony.ReplyServerStats4:
113 + stats.v4 = &chrony.ServerStats4{
114 + NTPHits: v.NTPHits,
115 + NKEHits: v.NKEHits,
116 + CMDHits: v.CMDHits,
117 + NTPDrops: v.NTPDrops,
118 + NKEDrops: v.NKEDrops,
119 + CMDDrops: v.CMDDrops,
120 + LogDrops: v.LogDrops,
121 + NTPAuthHits: v.NTPAuthHits,
122 + NTPInterleavedHits: v.NTPInterleavedHits,
123 + NTPTimestamps: v.NTPTimestamps,
124 + NTPSpanSeconds: v.NTPSpanSeconds,
125 + NTPDaemonRxtimestamps: v.NTPDaemonRxtimestamps,
126 + NTPDaemonTxtimestamps: v.NTPDaemonTxtimestamps,
127 + NTPKernelRxtimestamps: v.NTPKernelRxtimestamps,
128 + NTPKernelTxtimestamps: v.NTPKernelTxtimestamps,
129 + NTPHwRxTimestamps: v.NTPHwRxTimestamps,
130 + NTPHwTxTimestamps: v.NTPHwTxTimestamps,
131 + }
132 + default:
133 + return nil, fmt.Errorf("unexpected reply type, want=ReplyServerStats, got=%T", reply)
134 + }
135 +
136 + return &stats, nil
137 +}
138 +
139 func (sc *simpleClient) Close() {
140 if sc.conn != nil {
141 _ = sc.conn.Close()
src/go/collectors/go.d.plugin/modules/chrony/collect.go
+61
@@ -3,7 +3,9 @@
3 package chrony
4
5 import (
6 + "errors"
7 "fmt"
8 + "strings"
9 "time"
10 )
11
@@ -26,6 +28,14 @@ func (c *Chrony) collect() (map[string]int64, error) {
28 if err := c.collectActivity(mx); err != nil {
29 return mx, err
30 }
31 + if strings.HasPrefix(c.Address, "/") {
32 + // TODO: Allowed only through the Unix domain socket (requires "_chrony" group membership).
33 + // See https://github.com/facebook/time/blob/18207c5d8ddc7242e8d4192985898b6dbe66932c/cmd/ntpcheck/checker/chrony.go#L38
34 + // ^^ For some reason doesn't work, Chrony doesn't respond. Additional configuration needed?
35 + //if err := c.collectServerStats(mx); err != nil {
36 + // return mx, err
37 + //}
38 + }
39
40 return mx, nil
41 }
@@ -82,6 +92,57 @@ func (c *Chrony) collectActivity(mx map[string]int64) error {
92 return nil
93 }
94
95 +func (c *Chrony) collectServerStats(mx map[string]int64) error {
96 + stats, err := c.client.ServerStats()
97 + if err != nil {
98 + return fmt.Errorf("error on collecting server stats: %v", err)
99 + }
100 +
101 + switch {
102 + case stats.v4 != nil:
103 + mx["ntp_packets_received"] = int64(stats.v4.NTPHits)
104 + mx["ntp_packets_dropped"] = int64(stats.v4.NTPDrops)
105 + mx["command_packets_received"] = int64(stats.v4.CMDHits)
106 + mx["command_packets_dropped"] = int64(stats.v4.CMDDrops)
107 + mx["client_log_records_dropped"] = int64(stats.v4.LogDrops)
108 + mx["nke_connections_accepted"] = int64(stats.v4.NKEHits)
109 + mx["nke_connections_dropped"] = int64(stats.v4.NKEDrops)
110 + mx["authenticated_ntp_packets"] = int64(stats.v4.NTPAuthHits)
111 + mx["interleaved_ntp_packets"] = int64(stats.v4.NTPInterleavedHits)
112 + case stats.v3 != nil:
113 + mx["ntp_packets_received"] = int64(stats.v3.NTPHits)
114 + mx["ntp_packets_dropped"] = int64(stats.v3.NTPDrops)
115 + mx["command_packets_received"] = int64(stats.v3.CMDHits)
116 + mx["command_packets_dropped"] = int64(stats.v3.CMDDrops)
117 + mx["client_log_records_dropped"] = int64(stats.v3.LogDrops)
118 + mx["nke_connections_accepted"] = int64(stats.v3.NKEHits)
119 + mx["nke_connections_dropped"] = int64(stats.v3.NKEDrops)
120 + mx["authenticated_ntp_packets"] = int64(stats.v3.NTPAuthHits)
121 + mx["interleaved_ntp_packets"] = int64(stats.v3.NTPInterleavedHits)
122 + case stats.v2 != nil:
123 + mx["ntp_packets_received"] = int64(stats.v2.NTPHits)
124 + mx["ntp_packets_dropped"] = int64(stats.v2.NTPDrops)
125 + mx["command_packets_received"] = int64(stats.v2.CMDHits)
126 + mx["command_packets_dropped"] = int64(stats.v2.CMDDrops)
127 + mx["client_log_records_dropped"] = int64(stats.v2.LogDrops)
128 + mx["nke_connections_accepted"] = int64(stats.v2.NKEHits)
129 + mx["nke_connections_dropped"] = int64(stats.v2.NKEDrops)
130 + mx["authenticated_ntp_packets"] = int64(stats.v2.NTPAuthHits)
131 + case stats.v1 != nil:
132 + mx["ntp_packets_received"] = int64(stats.v1.NTPHits)
133 + mx["ntp_packets_dropped"] = int64(stats.v1.NTPDrops)
134 + mx["command_packets_received"] = int64(stats.v1.CMDHits)
135 + mx["command_packets_dropped"] = int64(stats.v1.CMDDrops)
136 + mx["client_log_records_dropped"] = int64(stats.v1.LogDrops)
137 + default:
138 + return errors.New("invalid server stats reply")
139 + }
140 +
141 + c.addStatsChartsOnce.Do(func() { c.addServerStatsCharts(stats) })
142 +
143 + return nil
144 +}
145 +
146 func boolToInt(v bool) int64 {
147 if v {
148 return 1