123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- #!/usr/bin/env python
- """Process a ref debug log
- This file will process a log file created by the REF_DEBUG
- build option in Asterisk.
- See http://www.asterisk.org for more information about
- the Asterisk project. Please do not directly contact
- any of the maintainers of this project for assistance;
- the project provides a web site, mailing lists and IRC
- channels for your use.
- This program is free software, distributed under the terms of
- the GNU General Public License Version 2. See the LICENSE file
- at the top of the source tree.
- Copyright (C) 2014, Digium, Inc.
- Matt Jordan <mjordan@digium.com>
- """
- from __future__ import print_function
- import sys
- import os
- from optparse import OptionParser
- def parse_line(line):
- """Parse out a line into its constituent parts.
- Keyword Arguments:
- line The line from a ref debug log to parse out
- Returns:
- A dictionary containing the options, or None
- """
- tokens = line.strip().split(',', 7)
- if len(tokens) < 8:
- print("ERROR: ref debug line '%s' contains fewer tokens than "
- "expected: %d" % (line.strip(), len(tokens)))
- return None
- processed_line = {'addr': tokens[0],
- 'delta': tokens[1],
- 'thread_id': tokens[2],
- 'file': tokens[3],
- 'line': tokens[4],
- 'function': tokens[5],
- 'state': tokens[6],
- 'tag': tokens[7],
- }
- return processed_line
- def process_file(options):
- """The routine that kicks off processing a ref file
- Keyword Arguments:
- filename The full path to the file to process
- Returns:
- A tuple containing:
- - A list of objects whose lifetimes were completed
- (i.e., finished objects)
- - A list of objects referenced after destruction
- (i.e., invalid objects)
- - A list of objects whose lifetimes were not completed
- (i.e., leaked objects)
- - A list of objects whose lifetimes are skewed
- (i.e., Object history starting with an unusual ref count)
- """
- finished_objects = []
- invalid_objects = []
- leaked_objects = []
- skewed_objects = []
- current_objects = {}
- filename = options.filepath
- with open(filename, 'r') as ref_file:
- for line in ref_file:
- parsed_line = parse_line(line)
- if not parsed_line:
- continue
- invalid = False
- obj = parsed_line['addr']
- if obj not in current_objects:
- current_objects[obj] = {'log': [], 'curcount': 1}
- if 'constructor' in parsed_line['state']:
- # This is the normal expected case
- pass
- elif 'invalid' in parsed_line['state']:
- invalid = True
- current_objects[obj]['curcount'] = 0
- if options.invalid:
- invalid_objects.append((obj, current_objects[obj]))
- elif 'destructor' in parsed_line['state']:
- current_objects[obj]['curcount'] = 0
- if options.skewed:
- skewed_objects.append((obj, current_objects[obj]))
- else:
- current_objects[obj]['curcount'] = int(
- parsed_line['state'])
- if options.skewed:
- skewed_objects.append((obj, current_objects[obj]))
- else:
- current_objects[obj]['curcount'] += int(parsed_line['delta'])
- current_objects[obj]['log'].append(
- "[%s] %s:%s %s: %s %s - [%s]" % (
- parsed_line['thread_id'],
- parsed_line['file'],
- parsed_line['line'],
- parsed_line['function'],
- parsed_line['delta'],
- parsed_line['tag'],
- parsed_line['state']))
- # It is possible for curcount to go below zero if someone
- # unrefs an object by two or more when there aren't that
- # many refs remaining. This condition abnormally finishes
- # the object.
- if current_objects[obj]['curcount'] <= 0:
- if current_objects[obj]['curcount'] < 0:
- current_objects[obj]['log'].append(
- "[%s] %s:%s %s: %s %s - [%s]" % (
- parsed_line['thread_id'],
- parsed_line['file'],
- parsed_line['line'],
- parsed_line['function'],
- "+0",
- "Object abnormally finalized",
- "**implied destructor**"))
- # Highlight the abnormally finished object in the
- # invalid section as well as reporting it in the normal
- # finished section.
- if options.invalid:
- invalid_objects.append((obj, current_objects[obj]))
- if not invalid and options.normal:
- finished_objects.append((obj, current_objects[obj]))
- del current_objects[obj]
- if options.leaks:
- for (key, lines) in current_objects.items():
- leaked_objects.append((key, lines))
- return (finished_objects, invalid_objects, leaked_objects, skewed_objects)
- def print_objects(objects, prefix=""):
- """Prints out the objects that were processed
- Keyword Arguments:
- objects A list of objects to print
- prefix A prefix to print that specifies something about
- this object
- """
- print("======== %s Objects ========" % prefix)
- print("\n")
- for obj in objects:
- print("==== %s Object %s history ====" % (prefix, obj[0]))
- for line in obj[1]['log']:
- print(line)
- print("\n")
- def main(argv=None):
- """Main entry point for the script"""
- ret_code = 0
- if argv is None:
- argv = sys.argv
- parser = OptionParser()
- parser.add_option("-f", "--file", action="store", type="string",
- dest="filepath", default="/var/log/asterisk/refs",
- help="The full path to the refs file to process")
- parser.add_option("-i", "--suppress-invalid", action="store_false",
- dest="invalid", default=True,
- help="If specified, don't output invalid object "
- "references")
- parser.add_option("-l", "--suppress-leaks", action="store_false",
- dest="leaks", default=True,
- help="If specified, don't output leaked objects")
- parser.add_option("-n", "--suppress-normal", action="store_false",
- dest="normal", default=True,
- help="If specified, don't output objects with a "
- "complete lifetime")
- parser.add_option("-s", "--suppress-skewed", action="store_false",
- dest="skewed", default=True,
- help="If specified, don't output objects with a "
- "skewed lifetime")
- (options, args) = parser.parse_args(argv)
- if not options.invalid and not options.leaks and not options.normal \
- and not options.skewed:
- print("All options disabled", file=sys.stderr)
- return -1
- if not os.path.isfile(options.filepath):
- print("File not found: %s" % options.filepath, file=sys.stderr)
- return -1
- try:
- (finished_objects,
- invalid_objects,
- leaked_objects,
- skewed_objects) = process_file(options)
- if options.invalid and len(invalid_objects):
- print_objects(invalid_objects, "Invalid Referenced")
- ret_code |= 4
- if options.leaks and len(leaked_objects):
- print_objects(leaked_objects, "Leaked")
- ret_code |= 1
- if options.skewed and len(skewed_objects):
- print_objects(skewed_objects, "Skewed")
- ret_code |= 2
- if options.normal:
- print_objects(finished_objects, "Finalized")
- except (KeyboardInterrupt, SystemExit, IOError):
- print("File processing cancelled", file=sys.stderr)
- return -1
- return ret_code
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
|