ntp_monitor.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Software License Agreement (BSD License)
4 #
5 # Copyright (c) 2008, Willow Garage, Inc.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials provided
17 # with the distribution.
18 # * Neither the name of the Willow Garage nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # POSSIBILITY OF SUCH DAMAGE.
34 
35 import roslib
36 roslib.load_manifest('diagnostic_common_diagnostics')
37 import rospy
38 import diagnostic_updater as DIAG
39 
40 import sys
41 import threading
42 import socket
43 from subprocess import Popen, PIPE
44 import time
45 import re
46 
47 
48 def ntp_diag(st, host, off, error_offset):
49  try:
50  p = Popen(["ntpdate", "-q", host], stdout=PIPE, stdin=PIPE, stderr=PIPE)
51  res = p.wait()
52  (o,e) = p.communicate()
53  except OSError, (errno, msg):
54  if errno == 4:
55  return None #ctrl-c interrupt
56  else:
57  raise
58  if (res == 0):
59  measured_offset = float(re.search("offset (.*),", o).group(1))*1000000
60 
61  st.level = DIAG.DiagnosticStatus.OK
62  st.message = "OK"
63  st.values = [ DIAG.KeyValue("Offset (us)", str(measured_offset)),
64  DIAG.KeyValue("Offset tolerance (us)", str(off)),
65  DIAG.KeyValue("Offset tolerance (us) for Error", str(error_offset)) ]
66 
67  if (abs(measured_offset) > off):
68  st.level = DIAG.DiagnosticStatus.WARN
69  st.message = "NTP Offset Too High"
70  if (abs(measured_offset) > error_offset):
71  st.level = DIAG.DiagnosticStatus.ERROR
72  st.message = "NTP Offset Too High"
73 
74  else:
75  st.level = DIAG.DiagnosticStatus.ERROR
76  st.message = "Error Running ntpdate. Returned %d" % res
77  st.values = [ DIAG.KeyValue("Offset (us)", "N/A"),
78  DIAG.KeyValue("Offset tolerance (us)", str(off)),
79  DIAG.KeyValue("Offset tolerance (us) for Error", str(error_offset)),
80  DIAG.KeyValue("Output", o),
81  DIAG.KeyValue("Errors", e) ]
82 
83  return st
84 
85 
86 class NTPMonitor:
87 
88  def __init__(self, ntp_hostname, offset=500, self_offset=500,
89  diag_hostname = None, error_offset = 5000000,
90  do_self_test=True):
91 
92  self.ntp_hostname = ntp_hostname
93  self.offset = offset
94  self.self_offset = self_offset
95  self.diag_hostname = diag_hostname
96  self.error_offset = error_offset
97  self.do_self_test = do_self_test
98 
99  self.hostname = socket.gethostname()
100  if self.diag_hostname is None:
101  self.diag_hostname = self.hostname
102 
103  self.stat = DIAG.DiagnosticStatus()
104  self.stat.level = DIAG.DiagnosticStatus.OK
105  self.stat.name = "NTP offset from "+ self.diag_hostname + " to " + self.ntp_hostname
106  self.stat.message = "OK"
107  self.stat.hardware_id = self.hostname
108  self.stat.values = []
109 
110  self.self_stat = DIAG.DiagnosticStatus()
111  self.self_stat.level = DIAG.DiagnosticStatus.OK
112  self.self_stat.name = "NTP self-offset for "+ self.diag_hostname
113  self.self_stat.message = "OK"
114  self.self_stat.hardware_id = self.hostname
115  self.self_stat.values = []
116 
117  self.mutex = threading.Lock()
118  self.pub = rospy.Publisher("/diagnostics", DIAG.DiagnosticArray, queue_size=10)
119 
120  # we need to periodically republish this
121  self.current_msg = None
122  self.pubtimer = rospy.Timer(rospy.Duration(.1), self.pubCB)
123  self.checktimer = rospy.Timer(rospy.Duration(.1), self.checkCB, True)
124 
125  def pubCB(self, ev):
126  with self.mutex:
127  if self.current_msg:
128  self.pub.publish(self.current_msg)
129 
130  def checkCB(self, ev):
131  new_msg = DIAG.DiagnosticArray()
132  new_msg.header.stamp = rospy.get_rostime()
133 
134  st = ntp_diag(self.stat, self.ntp_hostname, self.offset, self.error_offset)
135  if st is not None:
136  new_msg.status.append(st)
137 
138  if self.do_self_test:
139  st = ntp_diag(self.self_stat, self.hostname, self.self_offset, self.error_offset)
140  if st is not None:
141  new_msg.status.append(st)
142 
143  with self.mutex:
144  self.current_msg = new_msg
145 
146  self.checktimer = rospy.Timer(rospy.Duration(10), self.checkCB, True)
147 
148 
149 def ntp_monitor_main(argv=sys.argv):
150  import optparse
151  parser = optparse.OptionParser(usage="usage: ntp_monitor ntp-hostname []")
152  parser.add_option("--offset-tolerance", dest="offset_tol",
153  action="store", default=500,
154  help="Offset from NTP host", metavar="OFFSET-TOL")
155  parser.add_option("--error-offset-tolerance", dest="error_offset_tol",
156  action="store", default=5000000,
157  help="Offset from NTP host. Above this is error", metavar="OFFSET-TOL")
158  parser.add_option("--self_offset-tolerance", dest="self_offset_tol",
159  action="store", default=500,
160  help="Offset from self", metavar="SELF_OFFSET-TOL")
161  parser.add_option("--diag-hostname", dest="diag_hostname",
162  help="Computer name in diagnostics output (ex: 'c1')",
163  metavar="DIAG_HOSTNAME",
164  action="store", default=None)
165  parser.add_option("--no-self-test", dest="do_self_test",
166  help="Disable self test",
167  action="store_false", default=True)
168  options, args = parser.parse_args(rospy.myargv())
169 
170  if (len(args) != 2):
171  parser.error("Invalid arguments. Must have HOSTNAME [args]. %s" % args)
172 
173 
174  try:
175  offset = int(options.offset_tol)
176  self_offset = int(options.self_offset_tol)
177  error_offset = int(options.error_offset_tol)
178  except:
179  parser.error("Offsets must be numbers")
180 
181  ntp_monitor = NTPMonitor(args[1], offset, self_offset,
182  options.diag_hostname, error_offset,
183  options.do_self_test)
184 
185  rospy.spin()
186 
187 if __name__ == "__main__":
188  rospy.init_node("ntp_monitor", anonymous=True)
189  try:
190  ntp_monitor_main(rospy.myargv())
191  except KeyboardInterrupt: pass
192  except SystemExit: pass
193  except:
194  import traceback
195  traceback.print_exc()
def __init__(self, ntp_hostname, offset=500, self_offset=500, diag_hostname=None, error_offset=5000000, do_self_test=True)
Definition: ntp_monitor.py:90
def ntp_diag(st, host, off, error_offset)
Definition: ntp_monitor.py:48


diagnostic_common_diagnostics
Author(s): Brice Rebsamen
autogenerated on Mon Feb 28 2022 22:18:19