app_voicemail/IMAP: check mailstream not NULL in leave_voicemail
[asterisk/asterisk.git] / contrib / scripts / refcounter.py
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 <mjordan@digium.com>
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))