ntp_monitor.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 
18 
19 import sys
20 import socket
21 from subprocess import Popen, PIPE
22 import time
23 import re
24 
25 import rospy
26 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
27 
28 class NtpMonitor():
29  def __init__(self, argv=sys.argv):
30  rospy.init_node("ntp_monitor")
31  self.parse_args(argv)
32 
33  stat = DiagnosticStatus()
34  stat.level = DiagnosticStatus.WARN
35  stat.name = '%s NTP Offset' % self.diag_hostname
36  stat.message = 'No Data'
37  stat.hardware_id = self.diag_hostname
38  stat.values = []
39  self.msg = DiagnosticArray()
40  self.msg.header.stamp = rospy.get_rostime()
41  self.msg.status = [stat]
42 
43  self.update_diagnostics()
44 
45  self.diag_pub = rospy.Publisher("/diagnostics", DiagnosticArray, queue_size=1)
46  self.diag_timer = rospy.Timer(rospy.Duration(1.0), self.publish_diagnostics)
47  self.monitor_timer = rospy.Timer(rospy.Duration(60.0), self.update_diagnostics)
48 
49  def update_diagnostics(self, event=None):
50  stat = DiagnosticStatus()
51  stat.level = DiagnosticStatus.WARN
52  stat.name = '%s NTP Offset' % self.diag_hostname
53  stat.message = 'No Data'
54  stat.hardware_id = self.diag_hostname
55  stat.values = []
56 
57  try:
58  for st,host,off in [(stat, self.ntp_server, self.offset)]:
59  p = Popen(["ntpdate", "-q", host], stdout=PIPE, stdin=PIPE, stderr=PIPE)
60  retcode = p.wait()
61  stdout, stderr = p.communicate()
62  try:
63  stdout = stdout.decode() #python3
64  except (UnicodeDecodeError, AttributeError):
65  pass
66 
67  if retcode != 0:
68  st.level = DiagnosticStatus.ERROR
69  st.message = 'ntpdate Error'
70  st.values = [ KeyValue(key = 'ntpdate Error', value = stderr),
71  KeyValue(key = 'Output', value = stdout) ]
72  continue
73 
74  measured_offset = float(re.search("offset (.*),", stdout).group(1))*1000000
75 
76  st.level = DiagnosticStatus.OK
77  st.message = "OK"
78  st.values = [ KeyValue("NTP Server" , self.ntp_server),
79  KeyValue("Offset (us)", str(measured_offset)),
80  KeyValue("Offset tolerance (us)", str(off)),
81  KeyValue("Offset tolerance (us) for Error", str(self.error_offset)) ]
82 
83  if (abs(measured_offset) > off):
84  st.level = DiagnosticStatus.WARN
85  st.message = "NTP Offset Too High"
86  if (abs(measured_offset) > self.error_offset):
87  st.level = DiagnosticStatus.ERROR
88  st.message = "NTP Offset Too High"
89 
90  except Exception as e:
91  stat.level = DiagnosticStatus.ERROR
92  stat.message = 'ntpdate Exception'
93  stat.values = [ KeyValue(key = 'Exception', value = str(e)) ]
94 
95  self.msg = DiagnosticArray()
96  self.msg.header.stamp = rospy.get_rostime()
97  self.msg.status = [stat]
98 
99  def publish_diagnostics(self, event):
100  self.diag_pub.publish(self.msg)
101 
102  def parse_args(self, argv=sys.argv):
103  import optparse
104  parser = optparse.OptionParser(usage="usage: ntp_monitor ntp-hostname []")
105  parser.add_option("--offset", dest="offset",
106  action="store", default=500,
107  help="Offset from NTP host", metavar="OFFSET")
108  parser.add_option("--error-offset", dest="error_offset",
109  action="store", default=5000000,
110  help="Offset from NTP host. Above this is error", metavar="OFFSET")
111  parser.add_option("--diag-hostname", dest="diag_hostname",
112  help="Computer name in diagnostics output (ex: 'c1')",
113  metavar="DIAG_HOSTNAME",
114  action="store", default=None)
115  options, args = parser.parse_args(rospy.myargv())
116 
117  if (len(args) != 2):
118  parser.error("Invalid arguments. Must have HOSTNAME [args]. %s" % args)
119  print('Invalid arguments.')
120  sys.exit(0)
121 
122  try:
123  offset = int(options.offset)
124  error_offset = int(options.error_offset)
125  except:
126  parser.error("Offsets must be numbers")
127  print('Offsets must be numbers')
128  sys.exit(0)
129 
130  self.ntp_server = args[1]
131  self.diag_hostname = options.diag_hostname
132  hostname = socket.gethostname()
133  if self.diag_hostname is None:
134  self.diag_hostname = hostname
135 
136  self.offset = rospy.get_param('~offset', offset)
137  self.error_offset = rospy.get_param('~error_offset', error_offset)
138 
139 
140 if __name__ == "__main__":
141  ntp = NtpMonitor(argv=sys.argv)
142  rospy.spin()
def publish_diagnostics(self, event)
Definition: ntp_monitor.py:99
def update_diagnostics(self, event=None)
Definition: ntp_monitor.py:49
def parse_args(self, argv=sys.argv)
Definition: ntp_monitor.py:102
def __init__(self, argv=sys.argv)
Definition: ntp_monitor.py:29


cob_monitoring
Author(s): Florian Weisshardt , Felix Messmer
autogenerated on Wed Apr 7 2021 03:03:11