ntp_monitor.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2008, Willow Garage, Inc.
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 #  * Redistributions of source code must retain the above copyright
00013 #    notice, this list of conditions and the following disclaimer.
00014 #  * Redistributions in binary form must reproduce the above
00015 #    copyright notice, this list of conditions and the following
00016 #    disclaimer in the documentation and/or other materials provided
00017 #    with the distribution.
00018 #  * Neither the name of the Willow Garage nor the names of its
00019 #    contributors may be used to endorse or promote products derived
00020 #    from this software without specific prior written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
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 #ctrl-c interrupt
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         # we need to periodically republish this
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()


diagnostic_common_diagnostics
Author(s): Brice Rebsamen
autogenerated on Mon Aug 14 2017 02:52:26