hz_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 import sys
19 
20 import rospy
21 import rostopic
22 import copy
23 
24 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
25 
26 class HzTest():
27  def __init__(self):
28  rospy.init_node("hz_monitor")
29 
30  # get parameters
31  try:
32  # topic to test
33  self.topics = rospy.get_param('~topics')
34  # expected publishing rate
35  self.hz = rospy.get_param('~hz')
36  # margin of error allowed
37  self.hzerror = rospy.get_param('~hzerror')
38  # length of test
39  self.window_size = float(rospy.get_param('~window_size', 100))
40  # name for diagnostic message
41  self.diagnostics_name = rospy.get_param('~diagnostics_name',"")
42  self.diagnostics_name = self.diagnostics_name.replace('/','_')
43  self.diagnostics_period = rospy.Duration(1.0)
44  except KeyError as e:
45  rospy.logerr('hztest not initialized properly. Parameter [{}] not set. debug[{}] debug[{}]'.format(e, rospy.get_caller_id(), rospy.resolve_name(e.args[0])))
46  sys.exit(1)
47 
48  self.rt_HZ_store = dict()
49  self.missing_topics = copy.deepcopy(self.topics)
50  self.pub_diagnostics = rospy.Publisher('~diagnostics', DiagnosticArray, queue_size = 1)
52 
53 
54  def run(self):
55  # wait for first message
56  while len(self.missing_topics) != 0 and not rospy.is_shutdown():
57  for topic in self.topics:
58  msg_class, real_topic, msg_eval = rostopic.get_topic_class(topic, blocking=False) #pause hz until topic is published
59  if real_topic:
60  if real_topic in self.missing_topics:
61  self.missing_topics.remove(real_topic)
62 
63  try:
64  rospy.logdebug("hz monitor is waiting for type of topics {}.".format(self.missing_topics))
65  rospy.sleep(1.0)
66  except rospy.exceptions.ROSInterruptException:
67  pass
68 
69  # call rostopic hz
70  self.rt_HZ_store = dict()
71  for topic in self.topics:
72  rt = rostopic.ROSTopicHz(self.window_size)
73  rospy.Subscriber(topic, rospy.AnyMsg, rt.callback_hz)
74  self.rt_HZ_store[topic] = rt
75  rospy.loginfo("subscribed to [{}]".format(topic))
76 
77  def publish_diagnostics(self, event):
78  # set desired rates
79  if isinstance(self.hzerror, float) or isinstance(self.hzerror, int):
80  if self.hzerror < 0:
81  rospy.logerr("hzerror cannot be negative")
82  sys.exit(1)
83  min_rate = self.hz - self.hzerror
84  max_rate = self.hz + self.hzerror
85  if min_rate < 0 or max_rate < 0:
86  rospy.logerr("min_rate/max_rate cannot be negative")
87  sys.exit(1)
88  min_duration = 1/min_rate if min_rate > 0 else float('inf')
89  max_duration = 1/max_rate if max_rate > 0 else float('inf')
90  else:
91  rospy.logerr("hzerror not float or int")
92  sys.exit(1)
93 
94  # create diagnostic message
95  array = DiagnosticArray()
96  array.header.stamp = rospy.Time.now()
97  hz_status = DiagnosticStatus()
98  hz_status.name = self.diagnostics_name
99  hz_status.level = DiagnosticStatus.OK
100  hz_status.message = 'all publishing rates are ok'
101  hz_status.values.append(KeyValue("topics", str(self.topics)))
102  hz_status.values.append(KeyValue("desired_rate", str(self.hz)))
103  hz_status.values.append(KeyValue("min_rate", str(min_rate)))
104  hz_status.values.append(KeyValue("max_rate", str(max_rate)))
105  hz_status.values.append(KeyValue("window_size", str(self.window_size)))
106  rates = []
107 
108  consolidated_error_messages = {} ## store and display consolidated erros messages for all the topics
109  consolidated_error_messages.setdefault("never received message for topics", [])
110  consolidated_error_messages.setdefault("no messages anymore for topics", [])
111  consolidated_error_messages.setdefault("publishing rate is too low for topics", [])
112  consolidated_error_messages.setdefault("publishing rate is too high for topics", [])
113 
114  if not all (k in self.rt_HZ_store for k in self.topics):
115  hz_status.level = DiagnosticStatus.WARN
116  hz_status.message = "could not determine type for topics {}. Probably never published on that topics.".format(self.missing_topics)
117  array.status.append(hz_status)
118  self.pub_diagnostics.publish(array)
119  return
120 
121  # calculate actual rates
122  for topic, rt in self.rt_HZ_store.items():
123  #print("rt.times {}".format(rt.times))
124  #print("rt.msg_tn {}".format(rt.msg_tn))
125  #print("rt.last_printed_tn {}".format(rt.last_printed_tn))
126  #print("rt.delta: {}".format(rt.msg_tn - rt.last_printed_tn))
127  #print("event.current_real: {}".format(event.current_real.to_sec()))
128  #print("event.last_real: {}".format(event.last_real.to_sec()))
129  #print("event.delta: {}".format((event.current_real - event.last_real).to_sec()))
130  if not rt or not rt.times:
131  hz_status.level = DiagnosticStatus.ERROR
132  rates.append(0.0)
133  consolidated_error_messages["never received message for topics"].append(topic)
134  elif rt.msg_tn == rt.last_printed_tn \
135  and not (event.current_real.to_sec() < rt.msg_tn + min_duration): # condition to prevent WARN for topics with hz<diagnostics_rate, allow 1/min_rte to pass before reporting error
136  hz_status.level = DiagnosticStatus.ERROR
137  rates.append(0.0)
138  consolidated_error_messages["no messages anymore for topics"].append(topic)
139  else:
140  with rt.lock: # calculation taken from /opt/ros/indigo/lib/python2.7/dist-packages/rostopic/__init__.py
141  n = len(rt.times)
142  mean = sum(rt.times) / n
143  rate = 1./mean if mean > 0. else 0
144  rt.last_printed_tn = rt.msg_tn
145  rates.append(round(rate, 2))
146  if min_rate and rate < min_rate:
147  hz_status.level = DiagnosticStatus.WARN
148  consolidated_error_messages["publishing rate is too low for topics"].append(topic)
149 
150  if max_rate and rate > max_rate:
151  hz_status.level = DiagnosticStatus.WARN
152  consolidated_error_messages["publishing rate is too high for topics"].append(topic)
153 
154  if hz_status.level != DiagnosticStatus.OK:
155  message = ""
156  key = "never received message for topics"
157  if len(consolidated_error_messages[key]) > 0:
158  message += key + " " + str(consolidated_error_messages[key]) + ", "
159  key = "no messages anymore for topics"
160  if len(consolidated_error_messages[key]) > 0:
161  message += key + " " + str(consolidated_error_messages[key]) + ", "
162  key = "publishing rate is too low for topics"
163  if len(consolidated_error_messages[key]) > 0:
164  message += key + " " + str(consolidated_error_messages[key]) + ", "
165  key = "publishing rate is too high for topics"
166  if len(consolidated_error_messages[key]) > 0:
167  message += key + " " + str(consolidated_error_messages[key])
168  hz_status.message = message
169 
170  hz_status.values.append(KeyValue("rates", str(rates)))
171  array.status.append(hz_status)
172  self.pub_diagnostics.publish(array)
173 
174 if __name__ == '__main__':
175  hzt = HzTest()
176  hzt.run()
177  rospy.spin()
def __init__(self)
Definition: hz_monitor.py:27
def run(self)
Definition: hz_monitor.py:54
def publish_diagnostics(self, event)
Definition: hz_monitor.py:77


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