00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 import roslib
00036 roslib.load_manifest('diagnostic_common_diagnostics')
00037 import rospy
00038 import diagnostic_updater as DIAG
00039
00040 import sys
00041 import threading
00042 import socket
00043 from subprocess import Popen, PIPE
00044 import time
00045 import re
00046
00047
00048 def ntp_diag(st, host, off, error_offset):
00049 try:
00050 p = Popen(["ntpdate", "-q", host], stdout=PIPE, stdin=PIPE, stderr=PIPE)
00051 res = p.wait()
00052 (o,e) = p.communicate()
00053 except OSError, (errno, msg):
00054 if errno == 4:
00055 return None
00056 else:
00057 raise
00058 if (res == 0):
00059 measured_offset = float(re.search("offset (.*),", o).group(1))*1000000
00060
00061 st.level = DIAG.DiagnosticStatus.OK
00062 st.message = "OK"
00063 st.values = [ DIAG.KeyValue("Offset (us)", str(measured_offset)),
00064 DIAG.KeyValue("Offset tolerance (us)", str(off)),
00065 DIAG.KeyValue("Offset tolerance (us) for Error", str(error_offset)) ]
00066
00067 if (abs(measured_offset) > off):
00068 st.level = DIAG.DiagnosticStatus.WARN
00069 st.message = "NTP Offset Too High"
00070 if (abs(measured_offset) > error_offset):
00071 st.level = DIAG.DiagnosticStatus.ERROR
00072 st.message = "NTP Offset Too High"
00073
00074 else:
00075 st.level = DIAG.DiagnosticStatus.ERROR
00076 st.message = "Error Running ntpdate. Returned %d" % res
00077 st.values = [ DIAG.KeyValue("Offset (us)", "N/A"),
00078 DIAG.KeyValue("Offset tolerance (us)", str(off)),
00079 DIAG.KeyValue("Offset tolerance (us) for Error", str(error_offset)),
00080 DIAG.KeyValue("Output", o),
00081 DIAG.KeyValue("Errors", e) ]
00082
00083 return st
00084
00085
00086 class NTPMonitor:
00087
00088 def __init__(self, ntp_hostname, offset=500, self_offset=500,
00089 diag_hostname = None, error_offset = 5000000,
00090 do_self_test=True):
00091
00092 self.ntp_hostname = ntp_hostname
00093 self.offset = offset
00094 self.self_offset = self_offset
00095 self.diag_hostname = diag_hostname
00096 self.error_offset = error_offset
00097 self.do_self_test = do_self_test
00098
00099 self.hostname = socket.gethostname()
00100 if self.diag_hostname is None:
00101 self.diag_hostname = self.hostname
00102
00103 self.stat = DIAG.DiagnosticStatus()
00104 self.stat.level = DIAG.DiagnosticStatus.OK
00105 self.stat.name = "NTP offset from "+ self.diag_hostname + " to " + self.ntp_hostname
00106 self.stat.message = "OK"
00107 self.stat.hardware_id = self.hostname
00108 self.stat.values = []
00109
00110 self.self_stat = DIAG.DiagnosticStatus()
00111 self.self_stat.level = DIAG.DiagnosticStatus.OK
00112 self.self_stat.name = "NTP self-offset for "+ self.diag_hostname
00113 self.self_stat.message = "OK"
00114 self.self_stat.hardware_id = self.hostname
00115 self.self_stat.values = []
00116
00117 self.mutex = threading.Lock()
00118 self.pub = rospy.Publisher("/diagnostics", DIAG.DiagnosticArray, queue_size=10)
00119
00120
00121 self.current_msg = None
00122 self.pubtimer = rospy.Timer(rospy.Duration(.1), self.pubCB)
00123 self.checktimer = rospy.Timer(rospy.Duration(.1), self.checkCB, True)
00124
00125 def pubCB(self, ev):
00126 with self.mutex:
00127 if self.current_msg:
00128 self.pub.publish(self.current_msg)
00129
00130 def checkCB(self, ev):
00131 new_msg = DIAG.DiagnosticArray()
00132 new_msg.header.stamp = rospy.get_rostime()
00133
00134 st = ntp_diag(self.stat, self.ntp_hostname, self.offset, self.error_offset)
00135 if st is not None:
00136 new_msg.status.append(st)
00137
00138 if self.do_self_test:
00139 st = ntp_diag(self.self_stat, self.hostname, self.self_offset, self.error_offset)
00140 if st is not None:
00141 new_msg.status.append(st)
00142
00143 with self.mutex:
00144 self.current_msg = new_msg
00145
00146 self.checktimer = rospy.Timer(rospy.Duration(10), self.checkCB, True)
00147
00148
00149 def ntp_monitor_main(argv=sys.argv):
00150 import optparse
00151 parser = optparse.OptionParser(usage="usage: ntp_monitor ntp-hostname []")
00152 parser.add_option("--offset-tolerance", dest="offset_tol",
00153 action="store", default=500,
00154 help="Offset from NTP host", metavar="OFFSET-TOL")
00155 parser.add_option("--error-offset-tolerance", dest="error_offset_tol",
00156 action="store", default=5000000,
00157 help="Offset from NTP host. Above this is error", metavar="OFFSET-TOL")
00158 parser.add_option("--self_offset-tolerance", dest="self_offset_tol",
00159 action="store", default=500,
00160 help="Offset from self", metavar="SELF_OFFSET-TOL")
00161 parser.add_option("--diag-hostname", dest="diag_hostname",
00162 help="Computer name in diagnostics output (ex: 'c1')",
00163 metavar="DIAG_HOSTNAME",
00164 action="store", default=None)
00165 parser.add_option("--no-self-test", dest="do_self_test",
00166 help="Disable self test",
00167 action="store_false", default=True)
00168 options, args = parser.parse_args(rospy.myargv())
00169
00170 if (len(args) != 2):
00171 parser.error("Invalid arguments. Must have HOSTNAME [args]. %s" % args)
00172
00173
00174 try:
00175 offset = int(options.offset_tol)
00176 self_offset = int(options.self_offset_tol)
00177 error_offset = int(options.error_offset_tol)
00178 except:
00179 parser.error("Offsets must be numbers")
00180
00181 ntp_monitor = NTPMonitor(args[1], offset, self_offset,
00182 options.diag_hostname, error_offset,
00183 options.do_self_test)
00184
00185 rospy.spin()
00186
00187 if __name__ == "__main__":
00188 rospy.init_node("ntp_monitor", anonymous=True)
00189 try:
00190 ntp_monitor_main(rospy.myargv())
00191 except KeyboardInterrupt: pass
00192 except SystemExit: pass
00193 except:
00194 import traceback
00195 traceback.print_exc()