Asterisk - The Open Source Telephony Project  18.5.0
refcounter.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """Process a ref debug log
3 
4  This file will process a log file created by enabling
5  the refdebug config option in asterisk.conf.
6 
7  See http://www.asterisk.org for more information about
8  the Asterisk project. Please do not directly contact
9  any of the maintainers of this project for assistance;
10  the project provides a web site, mailing lists and IRC
11  channels for your use.
12 
13  This program is free software, distributed under the terms of
14  the GNU General Public License Version 2. See the LICENSE file
15  at the top of the source tree.
16 
17  Copyright (C) 2014, Digium, Inc.
18  Matt Jordan <[email protected]>
19 """
20 
21 from __future__ import print_function
22 import sys
23 import os
24 
25 from optparse import OptionParser
26 
27 
28 def parse_line(line):
29  """Parse out a line into its constituent parts.
30 
31  Keyword Arguments:
32  line The line from a ref debug log to parse out
33 
34  Returns:
35  A dictionary containing the options, or None
36  """
37  tokens = line.strip().split(',', 7)
38  if len(tokens) < 8:
39  print("ERROR: ref debug line '%s' contains fewer tokens than "
40  "expected: %d" % (line.strip(), len(tokens)))
41  return None
42 
43  processed_line = {'addr': tokens[0],
44  'delta': tokens[1],
45  'thread_id': tokens[2],
46  'file': tokens[3],
47  'line': tokens[4],
48  'function': tokens[5],
49  'state': tokens[6],
50  'tag': tokens[7],
51  }
52  return processed_line
53 
54 
55 def process_file(options):
56  """The routine that kicks off processing a ref file
57 
58  Keyword Arguments:
59  filename The full path to the file to process
60 
61  Returns:
62  A tuple containing:
63  - A list of objects whose lifetimes were completed
64  (i.e., finished objects)
65  - A list of objects referenced after destruction
66  (i.e., invalid objects)
67  - A list of objects whose lifetimes were not completed
68  (i.e., leaked objects)
69  - A list of objects whose lifetimes are skewed
70  (i.e., Object history starting with an unusual ref count)
71  """
72 
73  finished_objects = []
74  invalid_objects = []
75  leaked_objects = []
76  skewed_objects = []
77  current_objects = {}
78  filename = options.filepath
79 
80  with open(filename, 'r') as ref_file:
81  for line in ref_file:
82  parsed_line = parse_line(line)
83  if not parsed_line:
84  continue
85 
86  invalid = False
87  obj = parsed_line['addr']
88 
89  if obj not in current_objects:
90  current_objects[obj] = {'log': [], 'curcount': 1}
91  if 'constructor' in parsed_line['state']:
92  # This is the normal expected case
93  pass
94  elif 'invalid' in parsed_line['state']:
95  invalid = True
96  current_objects[obj]['curcount'] = 0
97  if options.invalid:
98  invalid_objects.append((obj, current_objects[obj]))
99  elif 'destructor' in parsed_line['state']:
100  current_objects[obj]['curcount'] = 0
101  if options.skewed:
102  skewed_objects.append((obj, current_objects[obj]))
103  else:
104  current_objects[obj]['curcount'] = int(
105  parsed_line['state'])
106  if options.skewed:
107  skewed_objects.append((obj, current_objects[obj]))
108  else:
109  current_objects[obj]['curcount'] += int(parsed_line['delta'])
110 
111  # Suppress object sizes and lock-state from output logs.
112  if 'constructor' in parsed_line['state']:
113  parsed_line['state'] = '**constructor**'
114  elif 'destructor' in parsed_line['state']:
115  parsed_line['state'] = '**destructor**'
116 
117  current_objects[obj]['log'].append(
118  "[%s] %s:%s %s: %s %s - [%s]" % (
119  parsed_line['thread_id'],
120  parsed_line['file'],
121  parsed_line['line'],
122  parsed_line['function'],
123  parsed_line['delta'],
124  parsed_line['tag'],
125  parsed_line['state']))
126 
127  # It is possible for curcount to go below zero if someone
128  # unrefs an object by two or more when there aren't that
129  # many refs remaining. This condition abnormally finishes
130  # the object.
131  if current_objects[obj]['curcount'] <= 0:
132  if current_objects[obj]['curcount'] < 0:
133  current_objects[obj]['log'].append(
134  "[%s] %s:%s %s: %s %s - [%s]" % (
135  parsed_line['thread_id'],
136  parsed_line['file'],
137  parsed_line['line'],
138  parsed_line['function'],
139  "+0",
140  "Object abnormally finalized",
141  "**implied destructor**"))
142  # Highlight the abnormally finished object in the
143  # invalid section as well as reporting it in the normal
144  # finished section.
145  if options.invalid:
146  invalid_objects.append((obj, current_objects[obj]))
147  if not invalid and options.normal:
148  finished_objects.append((obj, current_objects[obj]))
149  del current_objects[obj]
150 
151  if options.leaks:
152  for (key, lines) in current_objects.items():
153  leaked_objects.append((key, lines))
154  return (finished_objects, invalid_objects, leaked_objects, skewed_objects)
155 
156 
157 def print_objects(objects, prefix=""):
158  """Prints out the objects that were processed
159 
160  Keyword Arguments:
161  objects A list of objects to print
162  prefix A prefix to print that specifies something about
163  this object
164  """
165 
166  print("======== %s Objects ========" % prefix)
167  print("\n")
168  for obj in objects:
169  print("==== %s Object %s history ====" % (prefix, obj[0]))
170  for line in obj[1]['log']:
171  print(line)
172  print("\n")
173 
174 
175 def main(argv=None):
176  """Main entry point for the script"""
177 
178  ret_code = 0
179 
180  if argv is None:
181  argv = sys.argv
182 
183  parser = OptionParser()
184 
185  parser.add_option("-f", "--file", action="store", type="string",
186  dest="filepath", default="/var/log/asterisk/refs",
187  help="The full path to the refs file to process")
188  parser.add_option("-i", "--suppress-invalid", action="store_false",
189  dest="invalid", default=True,
190  help="If specified, don't output invalid object "
191  "references")
192  parser.add_option("-l", "--suppress-leaks", action="store_false",
193  dest="leaks", default=True,
194  help="If specified, don't output leaked objects")
195  parser.add_option("-n", "--suppress-normal", action="store_false",
196  dest="normal", default=True,
197  help="If specified, don't output objects with a "
198  "complete lifetime")
199  parser.add_option("-s", "--suppress-skewed", action="store_false",
200  dest="skewed", default=True,
201  help="If specified, don't output objects with a "
202  "skewed lifetime")
203 
204  (options, args) = parser.parse_args(argv)
205 
206  if not options.invalid and not options.leaks and not options.normal \
207  and not options.skewed:
208  print("All options disabled", file=sys.stderr)
209  return -1
210 
211  if not os.path.isfile(options.filepath):
212  print("File not found: %s" % options.filepath, file=sys.stderr)
213  return -1
214 
215  try:
216  (finished_objects,
217  invalid_objects,
218  leaked_objects,
219  skewed_objects) = process_file(options)
220 
221  if options.invalid and len(invalid_objects):
222  print_objects(invalid_objects, "Invalid Referenced")
223  ret_code |= 4
224 
225  if options.leaks and len(leaked_objects):
226  print_objects(leaked_objects, "Leaked")
227  ret_code |= 1
228 
229  if options.skewed and len(skewed_objects):
230  print_objects(skewed_objects, "Skewed")
231  ret_code |= 2
232 
233  if options.normal:
234  print_objects(finished_objects, "Finalized")
235 
236  except (KeyboardInterrupt, SystemExit, IOError):
237  print("File processing cancelled", file=sys.stderr)
238  return -1
239 
240  return ret_code
241 
242 
243 if __name__ == "__main__":
244  sys.exit(main(sys.argv))
def main(argv=None)
Definition: refcounter.py:175
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
def parse_line(line)
Definition: refcounter.py:28
def process_file(options)
Definition: refcounter.py:55
def print_objects(objects, prefix="")
Definition: refcounter.py:157