mirror of https://github.com/asterisk/asterisk
This patch does the following: (1) It makes REF_DEBUG a meneselect item. Enabling REF_DEBUG now enables REF_DEBUG globally throughout Asterisk. (2) The ref debug log file is now created in the AST_LOG_DIR directory. Every run will now blow away the previous run (as large ref files sometimes caused issues). We now also no longer open/close the file on each write, instead relying on fflush to make sure data gets written to the file (in case the ao2 call being performed is about to cause a crash) (3) It goes with a comma delineated format for the ref debug file. This makes parsing much easier. This also now includes the thread ID of the thread that caused ref change. (4) A new python script instead for refcounting has been added in the contrib/scripts folder. (5) The old refcounter implementation in utils/ has been removed. Review: https://reviewboard.asterisk.org/r/3377/ ........ Merged revisions 412114 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 412115 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 412153 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@412154 65c4cc65-6c06-0410-ace0-fbb531ad65f3changes/97/197/1
parent
af95d8c1d2
commit
4f30c7e91f
@ -0,0 +1,171 @@
|
||||
#!/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>
|
||||
"""
|
||||
|
||||
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(filename):
|
||||
"""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
|
||||
- A list of objects whose lifetimes were not completed
|
||||
- A list of objects whose lifetimes are skewed
|
||||
"""
|
||||
|
||||
finished_objects = []
|
||||
leaked_objects = []
|
||||
skewed_objects = []
|
||||
current_objects = {}
|
||||
|
||||
with open(filename, 'r') as ref_file:
|
||||
for line in ref_file:
|
||||
parsed_line = parse_line(line)
|
||||
if not parsed_line:
|
||||
continue
|
||||
|
||||
obj = parsed_line['addr']
|
||||
|
||||
if obj not in current_objects:
|
||||
current_objects[obj] = []
|
||||
if 'constructor' not in parsed_line['state']:
|
||||
skewed_objects.append(current_objects[obj])
|
||||
current_objects[obj].append(parsed_line)
|
||||
|
||||
if 'destructor' in parsed_line['state']:
|
||||
lifetime = current_objects.get(obj)
|
||||
finished_objects.append(lifetime)
|
||||
del current_objects[obj]
|
||||
|
||||
leaked_objects = current_objects.values()
|
||||
return (finished_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]['addr'])
|
||||
for entry in obj:
|
||||
print "[%s] %s:%s %s: %s %s - [%s]" % (entry['thread_id'],
|
||||
entry['file'], entry['line'],
|
||||
entry['function'],
|
||||
entry['delta'], entry['tag'],
|
||||
entry['state'])
|
||||
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("-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 os.path.isfile(options.filepath):
|
||||
print >>sys.stderr, "File not found: %s" % options.filepath
|
||||
return -1
|
||||
|
||||
try:
|
||||
(finished_objects,
|
||||
leaked_objects,
|
||||
skewed_objects) = process_file(options.filepath)
|
||||
|
||||
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 >>sys.stderr, "File processing cancelled"
|
||||
return -1
|
||||
|
||||
return ret_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
@ -1,317 +0,0 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2008, Steve Murphy
|
||||
*
|
||||
* Steve Murphy <murf@digium.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief A program to read in the /tmp/refs file generated
|
||||
* by astobj2 code when the REF_DEBUG macro is defined.
|
||||
* It will read in the file line by line, and
|
||||
* sort the data out by object, and check to see
|
||||
* if the refcounts balance to zero, and the object
|
||||
* was destroyed just once. Any problems that are
|
||||
* found are reported to stdout and the objects
|
||||
* ref count history is printed out. If all is well,
|
||||
* this program reads in the /tmp/refs file and
|
||||
* generates no output. No news is good news.
|
||||
* The contents of the /tmp/refs file looks like this:
|
||||
* \verbatim
|
||||
* 0x84fd718 -1 astobj2.c:926:cd_cb_debug (deref object via container destroy) [@1]
|
||||
* 0x84fd718 =1 chan_sip.c:19760:build_user (allocate a user struct)
|
||||
* 0x84fd718 +1 chan_sip.c:21558:reload_config (link user into users table) [@1]
|
||||
* 0x84fd718 -1 chan_sip.c:2376:unref_user (Unref the result of build_user. Now, the table link is the only one left.) [@2]
|
||||
* 0x84fd718 **call destructor** astobj2.c:926:cd_cb_debug (deref object via container destroy)
|
||||
* \endverbatim
|
||||
*
|
||||
* \author Steve Murphy <murf@digium.com>
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<support_level>extended</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/hashtab.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
struct rc_hist
|
||||
{
|
||||
char *desc;
|
||||
struct rc_hist *next;
|
||||
};
|
||||
|
||||
struct rc_obj /* short for refcounted object */
|
||||
{
|
||||
unsigned int addr;
|
||||
unsigned int count; /* this plus addr makes each entry unique, starts at 1 */
|
||||
int last_count; /* count 1 objects will record how many other objects had the same addr */
|
||||
int destroy_count;
|
||||
int total_refcount;
|
||||
struct rc_hist *hist;
|
||||
struct rc_hist *last;
|
||||
};
|
||||
|
||||
void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int cp2_size, size_t *used);
|
||||
void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int cp2_size, size_t *used)
|
||||
{
|
||||
}
|
||||
|
||||
static unsigned int hashtab_hash_rc(const void *obj)
|
||||
{
|
||||
const struct rc_obj *rc = obj;
|
||||
return rc->addr + rc->count; /* it's addr will make a FINE hash */
|
||||
}
|
||||
|
||||
static int hashtab_compare_rc(const void *a, const void *b)
|
||||
{
|
||||
const struct rc_obj *rca = a;
|
||||
const struct rc_obj *rcb = b;
|
||||
if (rca->addr == rcb->addr && rca->count == rcb->count)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static struct rc_obj *alloc_obj(unsigned int addr, unsigned int count)
|
||||
{
|
||||
struct rc_obj *x = calloc(1,sizeof(struct rc_obj));
|
||||
x->addr = addr;
|
||||
x->count = count;
|
||||
x->last_count = 1;
|
||||
x->total_refcount = 1;
|
||||
return x;
|
||||
}
|
||||
|
||||
static void add_to_hist(char *buffer, struct rc_obj *obj)
|
||||
{
|
||||
struct rc_hist *y = calloc(1,sizeof(struct rc_hist));
|
||||
y->desc = strdup(buffer);
|
||||
if (obj->last) {
|
||||
obj->last->next = y;
|
||||
obj->last = y;
|
||||
} else {
|
||||
obj->hist = obj->last = y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
char linebuffer[300];
|
||||
FILE *ifile = fopen("/tmp/refs", "r");
|
||||
char *t;
|
||||
unsigned int un;
|
||||
struct rc_obj *curr_obj, *count1_obj;
|
||||
struct rc_obj lookup;
|
||||
struct ast_hashtab_iter *it;
|
||||
struct ast_hashtab *objhash;
|
||||
|
||||
if (!ifile) {
|
||||
printf("Sorry, Cannot open /tmp/refs!\n");
|
||||
exit(10);
|
||||
}
|
||||
|
||||
objhash = ast_hashtab_create(9000, hashtab_compare_rc, ast_hashtab_resize_java, ast_hashtab_newsize_java, hashtab_hash_rc, 1);
|
||||
|
||||
while (fgets(linebuffer, sizeof(linebuffer), ifile)) {
|
||||
/* collect data about the entry */
|
||||
un = strtoul(linebuffer, &t, 16);
|
||||
lookup.addr = un;
|
||||
lookup.count = 1;
|
||||
|
||||
count1_obj = ast_hashtab_lookup(objhash, &lookup);
|
||||
|
||||
if (count1_obj) {
|
||||
/* there IS a count1 obj, so let's see which one we REALLY want */
|
||||
if (*(t+1) == '=') {
|
||||
/* start a new object! */
|
||||
curr_obj = alloc_obj(un, ++count1_obj->last_count);
|
||||
/* put it in the hashtable */
|
||||
ast_hashtab_insert_safe(objhash, curr_obj);
|
||||
} else {
|
||||
if (count1_obj->last_count > 1) {
|
||||
lookup.count = count1_obj->last_count;
|
||||
curr_obj = ast_hashtab_lookup(objhash, &lookup);
|
||||
} else {
|
||||
curr_obj = count1_obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
/* NO obj at ALL? -- better make one! */
|
||||
if (*(t+1) != '=') {
|
||||
printf("BAD: object %x appears without previous allocation marker!\n", un);
|
||||
}
|
||||
curr_obj = count1_obj = alloc_obj(un, 1);
|
||||
/* put it in the hashtable */
|
||||
ast_hashtab_insert_safe(objhash, curr_obj);
|
||||
|
||||
}
|
||||
|
||||
if (*(t+1) == '+' || *(t+1) == '-' ) {
|
||||
curr_obj->total_refcount += strtol(t+1, NULL, 10);
|
||||
} else if (*(t+1) == '*') {
|
||||
curr_obj->destroy_count++;
|
||||
}
|
||||
|
||||
add_to_hist(linebuffer, curr_obj);
|
||||
}
|
||||
fclose(ifile);
|
||||
|
||||
/* traverse the objects and check for problems */
|
||||
it = ast_hashtab_start_traversal(objhash);
|
||||
while ((curr_obj = ast_hashtab_next(it))) {
|
||||
if (curr_obj->total_refcount != 0 || curr_obj->destroy_count != 1) {
|
||||
struct rc_hist *h;
|
||||
if (curr_obj->total_refcount != 0)
|
||||
printf("Problem: net Refcount not zero for object %x\n", curr_obj->addr);
|
||||
if (curr_obj->destroy_count > 1 )
|
||||
printf("Problem: Object %x destroyed more than once!\n", curr_obj->addr);
|
||||
printf("Object %x history:\n", curr_obj->addr);
|
||||
for(h=curr_obj->hist;h;h=h->next) {
|
||||
printf(" %s", h->desc);
|
||||
}
|
||||
printf("==============\n");
|
||||
}
|
||||
}
|
||||
ast_hashtab_end_traversal(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* stub routines to satisfy linking with asterisk subcomponents */
|
||||
|
||||
#ifndef LOW_MEMORY
|
||||
int ast_add_profile(const char *x, uint64_t scale)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int ast_loader_register(int (*updater)(void))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ast_loader_unregister(int (*updater)(void))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
void ast_module_register(const struct ast_module_info *x)
|
||||
{
|
||||
}
|
||||
|
||||
void ast_module_unregister(const struct ast_module_info *x)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef LOW_MEMORY
|
||||
void ast_register_file_version(const char *file, const char *version)
|
||||
{
|
||||
}
|
||||
|
||||
void ast_unregister_file_version(const char *file)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#undef ast_mark
|
||||
|
||||
int64_t ast_mark(int x, int start1_stop0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
|
||||
{
|
||||
va_list vars;
|
||||
va_start(vars,fmt);
|
||||
printf("LOG: lev:%d file:%s line:%d func: %s ",
|
||||
level, file, line, function);
|
||||
vprintf(fmt, vars);
|
||||
fflush(stdout);
|
||||
va_end(vars);
|
||||
}
|
||||
|
||||
void __ast_verbose(const char *file, int line, const char *func, int level, const char *fmt, ...)
|
||||
{
|
||||
va_list vars;
|
||||
va_start(vars,fmt);
|
||||
|
||||
printf("VERBOSE: ");
|
||||
vprintf(fmt, vars);
|
||||
fflush(stdout);
|
||||
va_end(vars);
|
||||
}
|
||||
|
||||
void ast_register_thread(char *name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ast_unregister_thread(void *id)
|
||||
{
|
||||
}
|
||||
#ifdef HAVE_BKTR
|
||||
struct ast_bt *__ast_bt_create(void);
|
||||
struct ast_bt *__ast_bt_create(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int __ast_bt_get_addresses(struct ast_bt *bt);
|
||||
int __ast_bt_get_addresses(struct ast_bt *bt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
char **__ast_bt_get_symbols(void **addresses, size_t num_frames);
|
||||
char **__ast_bt_get_symbols(void **addresses, size_t num_frames)
|
||||
{
|
||||
char **foo = calloc(num_frames, sizeof(char *) + 1);
|
||||
if (foo) {
|
||||
int i;
|
||||
for (i = 0; i < num_frames; i++) {
|
||||
foo[i] = (char *) foo + sizeof(char *) * num_frames;
|
||||
}
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
|
||||
void *__ast_bt_destroy(struct ast_bt *bt);
|
||||
void *__ast_bt_destroy(struct ast_bt *bt)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ast_log_backtrace(void)
|
||||
{
|
||||
}
|
||||
#endif
|
Loading…
Reference in new issue