Skip to content

Commit

Permalink
check_ntp_offset: implement any language parser (fixes #84)
Browse files Browse the repository at this point in the history
this parser is based on line numbers, so it may break with different windows systems.
Test cases welcome...
  • Loading branch information
sni committed Mar 14, 2024
1 parent c99e8ad commit 058fc51
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 14 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ issues:
- "mnd: Magic number: 6, in"
- "mnd: Magic number: 7, in"
- "mnd: Magic number: 10, in"
- "mnd: Magic number: 12, in"
- "mnd: Magic number: 14, in"
- "mnd: Magic number: 15, in"
- "mnd: Magic number: 16, in"
Expand Down
89 changes: 75 additions & 14 deletions pkg/snclient/check_ntp_offset.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,40 @@ func (l *CheckNTPOffset) addW32TM(ctx context.Context, check *CheckData, force b
return nil
}

valid := false
for _, line := range strings.Split(output, "\n") {
var valid bool
var source string
var offset string
var stratum string
var errorStr string
if strings.Contains(output, "Phase Offset") {
valid, source, offset, stratum, errorStr = l.parseW32English(output)
} else {
valid, source, offset, stratum, errorStr = l.parseW32AnyLang(output)
}

switch {
case errorStr != "":
entry["_error"] = errorStr
entry["_exit"] = "2"
case valid:
entry["server"] = source
entry["offset"] = offset
entry["offset_seconds"] = fmt.Sprintf("%f", convert.Float64(offset)/1e3)
entry["stratum"] = stratum

default:
entry["_error"] = fmt.Sprintf("cannot parse offset from w32tm: %s\n%s", output, stderr)
entry["_exit"] = "2"
}

check.listData = append(check.listData, entry)
l.addMetrics(check, entry)

return nil
}

func (l *CheckNTPOffset) parseW32English(text string) (valid bool, source, offset, stratum, errorStr string) {
for _, line := range strings.Split(text, "\n") {
cols := utils.TokenizeBy(line, ":", false, false)
if len(cols) < 2 {
continue
Expand All @@ -333,33 +365,62 @@ func (l *CheckNTPOffset) addW32TM(ctx context.Context, check *CheckData, force b
switch cols[0] {
case "Source":
servers := utils.TokenizeBy(cols[1], ",", false, false)
entry["server"] = servers[0]
source = servers[0]
case "Phase Offset":
value, _ := time.ParseDuration(cols[1])
entry["offset"] = fmt.Sprintf("%f", float64(value.Nanoseconds())/1e6)
entry["offset_seconds"] = fmt.Sprintf("%f", convert.Float64(entry["offset"])/1e3)
offset = fmt.Sprintf("%f", float64(value.Nanoseconds())/1e6)
valid = true
case "Stratum":
stratas := strings.Fields(cols[1])
entry["stratum"] = stratas[0]
stratum = stratas[0]
case "State Machine":
fields := strings.Fields(cols[1])
if fields[0] != "2" {
entry["_error"] = fmt.Sprintf("w32tm.exe: %s", line)
entry["_exit"] = "2"
errorStr = fmt.Sprintf("w32tm.exe: %s", line)
}
}
}

if !valid {
entry["_error"] = fmt.Sprintf("cannot parse offset from w32tm: %s\n%s", output, stderr)
entry["_exit"] = "2"
return valid, source, offset, stratum, errorStr
}

func (l *CheckNTPOffset) parseW32AnyLang(text string) (valid bool, source, offset, stratum, errorStr string) {
type attr struct {
key string
val string
line string
}
attributes := []attr{}
for _, line := range strings.Split(text, "\n") {
cols := utils.TokenizeBy(line, ":", false, false)
if len(cols) < 2 {
continue
}
cols[1] = strings.TrimSpace(cols[1])
attributes = append(attributes, attr{cols[0], cols[1], line})
}

check.listData = append(check.listData, entry)
l.addMetrics(check, entry)
if len(attributes) < 12 {
return false, "", "", "", ""
}

return nil
// assume output offsets stay sane across languages
servers := utils.TokenizeBy(attributes[7].val, ",", false, false)
source = servers[0]

phase, _ := time.ParseDuration(attributes[9].val)
offset = fmt.Sprintf("%f", float64(phase.Nanoseconds())/1e6)
valid = true

stratas := strings.Fields(attributes[1].val)
stratum = stratas[0]

fields := strings.Fields(attributes[11].val)
if fields[0] != "2" {
errorStr = fmt.Sprintf("w32tm.exe: %s", attributes[11].line)
}

return valid, source, offset, stratum, errorStr
}

// get offset on Mac OSX
Expand Down
75 changes: 75 additions & 0 deletions pkg/snclient/check_ntp_offset_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,81 @@ Time since Last Good Sync Time: 46.3012345s`,
assert.Equalf(t, "CRITICAL - w32tm.exe: State Machine: 1 (Hold)",
string(res.BuildPluginOutput()), "output matches")

// not synchronized
MockSystemUtilities(t, map[string]string{
"w32tm.exe": `Leap Indicator: 3(not synchronized)
Stratum: 0 (unspecified)
Precision: -23 (119.209ns per tick)
Root Delay: 0.0000000s
Root Dispersion: 0.0000000s
ReferenceId: 0x00000000 (unspecified)
Last Successful Sync Time: unspecified
Source: Free-running System Clock
Poll Interval: 10 (1024s)
Phase Offset: 0.0000000s
ClockRate: 0.0156250s
State Machine: 0 (Unset)
Time Source Flags: 0 (None)
Server Role: 0 (None)
Last Sync Error: 1 (The computer did not resync because no time data was available.)
Time since Last Good Sync Time: 1220.7932246s`,
})
res = snc.RunCheck("check_ntp_offset", []string{"source=w32tm"})
assert.Equalf(t, CheckExitCritical, res.State, "state Critical")
assert.Equalf(t, "CRITICAL - w32tm.exe: State Machine: 0 (Unset)",
string(res.BuildPluginOutput()), "output matches")

// german not synchronized
MockSystemUtilities(t, map[string]string{
"w32tm.exe": `Sprungindikator: 3(nicht synchronisiert)
Stratum: 0 (nicht angegeben)
Präzision: -23 (119.209ns pro Tick)
Stammverzögerung: 0.0000000s
Stammabweichung: 0.0000000s
Referenz-ID: 0x00000000 (nicht angegeben)
Letzte erfolgr. Synchronisierungszeit: nicht angegeben
Quelle: Free-running System Clock
Abrufintervall: 10 (1024s)
Phasendifferenz: 0.0000000s
Taktfrequenz: 0.0156250s
Statuscomputer: 0 (Löschen)
Zeitquellenkennzeichen: 0 (Keine)
Serverrolle: 0 (Keine)
Letzter Synchronierungsfehler: 1 (Der Computer wurde nicht synchronisiert, da keine Zeitdaten verfügbar waren.)
Zeit seit der letzten erfolgr. Synchronisierungszeit: 37103.3612781s`,
})
res = snc.RunCheck("check_ntp_offset", []string{"source=w32tm"})
assert.Equalf(t, CheckExitCritical, res.State, "state Critical")
assert.Equalf(t, "CRITICAL - w32tm.exe: Statuscomputer: 0 (Löschen)",
string(res.BuildPluginOutput()), "output matches")

// german synchronized
MockSystemUtilities(t, map[string]string{
"w32tm.exe": `Sprungindikator: 0(keine Warnung)
Stratum: 4 (Sekundärreferenz - synchr. über (S)NTP)
Präzision: -23 (119.209ns pro Tick)
Stammverzögerung: 0.0513098s
Stammabweichung: 0.1431416s
Referenz-ID: 0xC353D4CD (MD5-Hashbruchteil der IPv6-Adresse: )
Letzte erfolgr. Synchronisierungszeit: 14.03.2024 13:51:45
Quelle: ntp.company.lan
Abrufintervall: 12 (4096s)
Phasendifferenz: -0.0078433s
Taktfrequenz: 0.0156250s
Statuscomputer: 2 (Sync)
Zeitquellenkennzeichen: 12 (SignatureAuthenticated IPv6 )
Serverrolle: 0 (Keine)
Letzter Synchronierungsfehler: 0 (Der Befehl wurde erfolgreich ausgeführt.)
Zeit seit der letzten erfolgr. Synchronisierungszeit: 831.4054861s`,
})
res = snc.RunCheck("check_ntp_offset", []string{"source=w32tm"})
assert.Equalf(t, CheckExitOK, res.State, "state OK")
assert.Equalf(t, "OK - offset -7.842999ms from ntp.company.lan |'offset'=-7.8433ms;-50:50;-100:100 'stratum'=4;;;0",
string(res.BuildPluginOutput()), "output matches")

StopTestAgent(t, snc)
}

Expand Down

0 comments on commit 058fc51

Please sign in to comment.