Asterisk - The Open Source Telephony Project GIT-master-f36a736
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 <mjordan@digium.com>
19"""
20
21from __future__ import print_function
22import sys
23import os
24
25from optparse import OptionParser
26
27
28def 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
55def 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
157def 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
175def 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
243if __name__ == "__main__":
244 sys.exit(main(sys.argv))
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 main(argv=None)
Definition: refcounter.py:175
def process_file(options)
Definition: refcounter.py:55
def print_objects(objects, prefix="")
Definition: refcounter.py:157