((mask) & GFP_ATOMIC)) != 0); \
} while (0)
-#define LIBCFS_ALLOC_POST(ptr, size) \
+/* message format here needs to match regexp in lustre/tests/leak_finder.pl */
+#define LIBCFS_MEM_MSG(ptr, size, name) \
+ CDEBUG(D_MALLOC, name " '" #ptr "': %d at %p.\n", (int)(size), ptr)
+
+#define LIBCFS_ALLOC_POST(ptr, size, name) \
do { \
if (unlikely((ptr) == NULL)) { \
CERROR("LNET: out of memory at %s:%d (tried to alloc '" \
#ptr "' = %d)\n", __FILE__, __LINE__, (int)(size)); \
CERROR("LNET: %lld total bytes allocated by lnet\n", \
- libcfs_kmem_read()); \
+ libcfs_kmem_read()); \
} else { \
libcfs_kmem_inc((ptr), (size)); \
- CDEBUG(D_MALLOC, "alloc '" #ptr "': %d at %p (tot %lld).\n", \
- (int)(size), (ptr), libcfs_kmem_read()); \
+ LIBCFS_MEM_MSG(ptr, size, name); \
} \
} while (0)
+#define LIBCFS_FREE_PRE(ptr, size, name) \
+ libcfs_kmem_dec((ptr), (size)); \
+ LIBCFS_MEM_MSG(ptr, size, name)
+
/**
* allocate memory with GFP flags @mask
* The allocated memory is zeroed-out.
LIBCFS_ALLOC_PRE((size), (mask)); \
(ptr) = (size) <= LIBCFS_VMALLOC_SIZE ? \
kzalloc((size), (mask)) : vzalloc(size); \
- LIBCFS_ALLOC_POST((ptr), (size)); \
+ LIBCFS_ALLOC_POST((ptr), (size), "alloc"); \
} while (0)
/**
(ptr) = (size) <= LIBCFS_VMALLOC_SIZE ? \
cfs_cpt_malloc((cptab), (cpt), (size), (mask) | __GFP_ZERO) : \
cfs_cpt_vzalloc((cptab), (cpt), (size)); \
- LIBCFS_ALLOC_POST((ptr), (size)); \
+ LIBCFS_ALLOC_POST((ptr), (size), "alloc"); \
} while (0)
/** default numa allocator */
"%s:%d\n", s, __FILE__, __LINE__); \
break; \
} \
- libcfs_kmem_dec((ptr), s); \
- CDEBUG(D_MALLOC, "kfreed '" #ptr "': %d at %p (tot %lld).\n", \
- s, (ptr), libcfs_kmem_read()); \
+ LIBCFS_FREE_PRE(ptr, size, "kfreed"); \
if (unlikely(s > LIBCFS_VMALLOC_SIZE)) \
libcfs_vfree_atomic(ptr); \
else \
size = offsetof(struct lnet_libmd, md_kiov[md->md_niov]);
if (size <= LNET_SMALL_MD_SIZE) {
- CDEBUG(D_MALLOC, "slab-freed 'md' at %p.\n", md);
+ LIBCFS_MEM_MSG(md, size, "slab-freed");
kmem_cache_free(lnet_small_mds_cachep, md);
} else {
LIBCFS_FREE(md, size);
the_lnet.ln_counters[cpt]->lct_health.lch_rst_alloc++;
lnet_net_unlock(cpt);
}
- CDEBUG(D_MALLOC, "rspt alloc %p\n", rspt);
+ LIBCFS_ALLOC_POST(rspt, sizeof(*rspt), "alloc");
return rspt;
}
static inline void
lnet_rspt_free(struct lnet_rsp_tracker *rspt, int cpt)
{
- CDEBUG(D_MALLOC, "rspt free %p\n", rspt);
-
+ LIBCFS_FREE_PRE(rspt, sizeof(*rspt), "free");
kmem_cache_free(lnet_rspt_cachep, rspt);
lnet_net_lock(cpt);
the_lnet.ln_counters[cpt]->lct_health.lch_rst_alloc--;
else
ret = __ll_vmalloc(size, __GFP_HIGHMEM | GFP_NOIO | __GFP_ZERO);
- LIBCFS_ALLOC_POST(ret, size);
+ LIBCFS_ALLOC_POST(ret, size, "alloc");
return ret;
}
static inline void kgnilnd_vfree(void *ptr, int size)
{
- libcfs_kmem_dec(ptr, size);
+ LIBCFS_FREE_PRE(ptr, size, "vfree");
vfree(ptr);
}
/* we only allocate this if we need to */
if (tx->tx_phys != NULL) {
kmem_cache_free(kgnilnd_data.kgn_tx_phys_cache, tx->tx_phys);
- CDEBUG(D_MALLOC, "slab-freed 'tx_phys': %lu at %p.\n",
- GNILND_MAX_IOV * sizeof(gni_mem_segment_t), tx->tx_phys);
+ LIBCFS_MEM_MSG(tx->tx_phys,
+ GNILND_MAX_IOV * sizeof(gni_mem_segment_t),
+ "slab-freed");
}
/* Only free the buffer if we used it */
if (tx->tx_buffer_copy != NULL) {
+ LIBCFS_MEM_MSG(tx->tx_buffer_copy, tx->tx_rdma_desc.length,
+ "vfreed");
kgnilnd_vfree(tx->tx_buffer_copy, tx->tx_rdma_desc.length);
tx->tx_buffer_copy = NULL;
- CDEBUG(D_MALLOC, "vfreed buffer2\n");
}
#if 0
KGNILND_POISON(tx, 0x5a, sizeof(kgn_tx_t));
#endif
- CDEBUG(D_MALLOC, "slab-freed 'tx': %lu at %p.\n", sizeof(*tx), tx);
+ LIBCFS_MEM_MSG(tx, sizeof(*tx), "slab-freed");
kmem_cache_free(kgnilnd_data.kgn_tx_cache, tx);
}
CERROR("failed to allocate tx\n");
return NULL;
}
- CDEBUG(D_MALLOC, "slab-alloced 'tx': %lu at %p.\n",
- sizeof(*tx), tx);
+ LIBCFS_MEM_MSG(tx, sizeof(*tx), "slab-alloced");
/* setup everything here to minimize time under the lock */
tx->tx_buftype = GNILND_BUF_NONE;
GOTO(error, rc);
}
- CDEBUG(D_MALLOC, "slab-alloced 'tx->tx_phys': %lu at %p.\n",
- GNILND_MAX_IOV * sizeof(gni_mem_segment_t), tx->tx_phys);
+ LIBCFS_MEM_MSG(tx->tx_phys,
+ GNILND_MAX_IOV * sizeof(gni_mem_segment_t),
+ "slab-alloced");
/* if loops changes, please change kgnilnd_cksum_kiov
* and kgnilnd_setup_immediate_buffer */
error:
if (tx->tx_phys != NULL) {
kmem_cache_free(kgnilnd_data.kgn_tx_phys_cache, tx->tx_phys);
- CDEBUG(D_MALLOC, "slab-freed 'tx_phys': %lu at %p.\n",
- sizeof(*tx->tx_phys), tx->tx_phys);
+ LIBCFS_MEM_MSG(tx->tx_phys, sizeof(*tx->tx_phys), "slab-freed");
tx->tx_phys = NULL;
}
return rc;
CERROR("failed to allocate rx\n");
return NULL;
}
- CDEBUG(D_MALLOC, "slab-alloced 'rx': %lu at %p.\n",
- sizeof(*rx), rx);
+ LIBCFS_MEM_MSG(rx, sizeof(*rx), "slab-alloced");
/* no memset to zero, we'll always fill all members */
return rx;
kgnilnd_release_msg(conn);
}
+ LIBCFS_MEM_MSG(rx, sizeof(*rx), "slab-freed");
kmem_cache_free(kgnilnd_data.kgn_rx_cache, rx);
- CDEBUG(D_MALLOC, "slab-freed 'rx': %lu at %p.\n",
- sizeof(*rx), rx);
}
int
* purgatory holds. While we have purgatory holds, we might check the conn
* RX mailbox during the CLOSING process. It is possible that kgni might
* try to look into the RX side for credits when sending the CLOSE msg too */
- CDEBUG(D_MALLOC, "fmablk %p free buffer %p mbox_size %d\n",
- fma_blk, fma_blk->gnm_block, fma_blk->gnm_mbox_size);
-
if (fma_blk->gnm_state == GNILND_FMABLK_PHYS) {
+ LIBCFS_MEM_MSG(fma_blk->gnm_block, fma_blk->gnm_mbox_size, "free");
kmem_cache_free(kgnilnd_data.kgn_mbox_cache, fma_blk->gnm_block);
} else {
kgnilnd_vfree(fma_blk->gnm_block, fma_blk->gnm_blk_size);
if (size <= LNET_SMALL_MD_SIZE) {
lmd = kmem_cache_zalloc(lnet_small_mds_cachep, GFP_NOFS);
if (lmd) {
- CDEBUG(D_MALLOC,
- "slab-alloced 'md' of size %u at %p.\n",
- size, lmd);
+ LIBCFS_MEM_MSG(lmd, size, "slab-alloced");
} else {
CDEBUG(D_MALLOC, "failed to allocate 'md' of size %u\n",
size);
CDEBUG(D_MALLOC, "failed to allocate 'me'\n");
return ERR_PTR(-ENOMEM);
}
- CDEBUG(D_MALLOC, "slab-alloced 'me' at %p.\n", me);
+ LIBCFS_ALLOC_POST(me, sizeof(*me), "slab-alloced");
lnet_res_lock(mtable->mt_cpt);
lnet_md_unlink(md);
}
- CDEBUG(D_MALLOC, "slab-freed 'me' at %p.\n", me);
+ LIBCFS_FREE_PRE(me, sizeof(*me), "slab-freed");
kmem_cache_free(lnet_mes_cachep, me);
}
me_list)) != NULL) {
CERROR("Active ME %p on exit\n", me);
list_del(&me->me_list);
- CDEBUG(D_MALLOC,
- "slab-freed 'me' at %p in cleanup.\n",
- me);
+ LIBCFS_FREE_PRE(me, sizeof(*me), "slab-freed");
kmem_cache_free(lnet_mes_cachep, me);
}
}
INIT_LIST_HEAD(&udsp->udsp_rte.ud_addr_range);
INIT_LIST_HEAD(&udsp->udsp_rte.ud_net_id.udn_net_num_range);
- CDEBUG(D_MALLOC, "udsp alloc %p\n", udsp);
+ LIBCFS_ALLOC_POST(udsp, sizeof(*udsp), "alloc");
return udsp;
}
lnet_udsp_nid_descr_free(&udsp->udsp_dst);
lnet_udsp_nid_descr_free(&udsp->udsp_rte);
- CDEBUG(D_MALLOC, "udsp free %p\n", udsp);
+ LIBCFS_FREE_PRE(udsp, sizeof(*udsp), "kfreed");
kmem_cache_free(lnet_udsp_cachep, udsp);
}
#define OBD_DEBUG_MEMUSAGE (1)
#if OBD_DEBUG_MEMUSAGE
-#define OBD_ALLOC_POST(ptr, size, name) \
- obd_memory_add(size); \
- CDEBUG(D_MALLOC, name " '" #ptr "': %d at %p.\n", \
- (int)(size), ptr)
-
-#define OBD_FREE_PRE(ptr, size, name) \
- LASSERT(ptr); \
- obd_memory_sub(size); \
- CDEBUG(D_MALLOC, name " '" #ptr "': %d at %p.\n", \
- (int)(size), ptr);
+/* message format here needs to match regexp in lustre/tests/leak_finder.pl */
+#define OBD_ALLOC_POST(ptr, size, name) \
+ obd_memory_add(size); \
+ LIBCFS_MEM_MSG(ptr, size, name)
+
+/* message format here needs to match regexp in lustre/tests/leak_finder.pl */
+#define OBD_FREE_PRE(ptr, size, name) \
+ LASSERT(ptr); \
+ obd_memory_sub(size); \
+ LIBCFS_MEM_MSG(ptr, size, name)
#else /* !OBD_DEBUG_MEMUSAGE */
{
LASSERT(refcount_read(&mfd->mfd_open_handle.h_ref) == 1);
LASSERT(list_empty(&mfd->mfd_list));
- OBD_FREE_PRE(mfd, sizeof(*mfd), "rcu");
+ OBD_FREE_PRE(mfd, sizeof(*mfd), "kfree_rcu");
kfree_rcu(mfd, mfd_open_handle.h_rcu);
}
if (exp != obd->obd_self_export)
class_decref(obd, "export", exp);
- OBD_FREE_PRE(exp, sizeof(*exp), "rcu");
+ OBD_FREE_PRE(exp, sizeof(*exp), "kfree_rcu");
kfree_rcu(exp, exp_handle.h_rcu);
EXIT;
}
my $start_time = 0;
if (!defined($ARGV[0])) {
- print "No log file specified\n";
- exit
+ print "No log file specified\n";
+ print "Usage: leak_finder.pl <debug_file> [--option]\n";
+ print " --by_func show leak logs by function name in ascending order.\n";
+ print " --summary implies --by_func, print a summary report by \n";
+ print " the number of total leak bytes of each function \n";
+ print " in ascending order in YAML format.\n";
+ exit
}
open(INFILE, $ARGV[0]);
while ($line = <INFILE>) {
$debug_line++;
my ($file, $func, $lno, $name, $size, $addr, $type);
- if ($line =~ m/^(.*)\((.*):(\d+):(.*)\(\)\) (k|v|slab-)(.*) '(.*)': (\d+) at ([\da-f]+)/){
+ # message format here needs to match OBD_ALLOC_POST()/OBD_FREE_PRE()
+ # mask:subs:cpu:epoch second.usec:?:pid:?:(filename:line:function_name())
+ # alloc-type 'var_name': size at memory_address.
+ if ($line =~ m/^(.*)\((.*):(\d+):(.*)\(\)\) (k[m]?|v[m]?|slab-|)(alloc(ed)?|free[d]?(_rcu)?) '(.*)': (\d+) at ([\da-f]+)/) {
@parsed = split(":", $1);
if ($parsed[3] <= $start_time) {
next;
}
-
+
$file = $2;
$lno = $3;
$func = $4;
$type = $6;
- $name = $7;
- $size = $8;
- $addr = $9;
+ $name = $9;
+ $size = $10;
+ $addr = $11;
# we can't dump the log after portals has exited, so skip "leaks"
# from memory freed in the portals module unloading.
}
printf("%8s %6d bytes at %s called %s (%s:%s:%d)\n", $type, $size,
$addr, $name, $file, $func, $lno);
+ } elsif ($line =~ m/(alloc(ed)?|free[d]?).*at [0-9a-f]*/) {
+ # alloc/free line that didn't match regexp, notify user of missed line
+ print STDERR "Couldn't parse line $debug_line, script needs to be fixed:\n$line";
+ next;
} else {
+ # line not related to alloc/free, skip it silently
+ #print STDERR "Couldn't parse $line";
next;
}
- if (index($type, 'alloced') >= 0) {
+ if (index($type, 'alloc') >= 0) {
if (defined($memory->{$addr})) {
print STDOUT "*** Two allocs with the same address ($size bytes at $addr, $file:$func:$lno)\n";
print STDOUT " first malloc at $memory->{$addr}->{file}:$memory->{$addr}->{func}:$memory->{$addr}->{lno}, second at $file:$func:$lno\n";
}
close(INFILE);
-# Sort leak output by allocation time
+my $aa;
+my $bb;
my @sorted = sort {
- return $memory->{$a}->{debug_line} <=> $memory->{$b}->{debug_line};
+ if (defined($ARGV[1])) {
+ if ($ARGV[1] eq "--by_func" or $ARGV[1] eq "--summary") {
+ # Sort leak output by source code position
+ $aa = "$memory->{$a}->{func}:$memory->{$a}->{lno}:$memory->{$a}->{name}:$memory->{$a}->{size}";
+ $bb = "$memory->{$b}->{func}:$memory->{$b}->{lno}:$memory->{$b}->{name}:$memory->{$b}->{size}";
+ $aa cmp $bb;
+ }
+ } else {
+ # Sort leak output by allocation time
+ $memory->{$a}->{debug_line} <=> $memory->{$b}->{debug_line};
+ }
} keys(%{$memory});
+$aa = "";
+$bb = "";
my $key;
+my $leak_count = 1;
+my @records;
+my $leak_size = 0;
+my $leak_func;
+my $i = 0;
foreach $key (@sorted) {
- my ($oldname, $oldsize, $oldfile, $oldfunc, $oldlno) = $memory->{$key};
- print STDOUT "*** Leak: $memory->{$key}->{size} bytes allocated at $key ($memory->{$key}->{file}:$memory->{$key}->{func}:$memory->{$key}->{lno}, debug file line $memory->{$key}->{debug_line})\n";
+ if (defined($ARGV[1]) and $ARGV[1] eq "--summary") {
+ $aa = "$memory->{$key}->{func}:$memory->{$key}->{lno}:$memory->{$key}->{name}:$memory->{$key}->{size}";
+ if ($bb eq $aa) {
+ $leak_count++;
+ } elsif ($bb ne ""){
+ $records[$i]->{func} = $leak_func;
+ $records[$i]->{size} = $leak_size;
+ $records[$i]->{count} = $leak_count;
+ $records[$i]->{total} = $leak_count * $leak_size;;
+ $bb = $aa;
+ $i++;
+ $leak_count = 1;
+ } else {
+ $bb = $aa;
+ }
+ $leak_func = "$memory->{$key}->{func}:$memory->{$key}->{lno}:$memory->{$key}->{name}";
+ $leak_size = $memory->{$key}->{size};
+ } else {
+ print STDOUT "*** Leak: $memory->{$key}->{size} bytes allocated at $key ($memory->{$key}->{file}:$memory->{$key}->{func}:$memory->{$key}->{lno}:$memory->{$key}->{name}, debug file line $memory->{$key}->{debug_line})\n";
+ }
}
+if (defined($ARGV[1]) and $ARGV[1] eq "--summary") {
+ # print a summary report by total leak bytes in ASC order
+ my @sorted_records = sort {
+ $a->{total} <=> $b->{total};
+ } @records;
+ foreach $key (@sorted_records) {
+ printf("- { func: %-58s, alloc_bytes: %-8d, leak_count: %-6d, leak_bytes: %-8d },\n",
+ $key->{func}, $key->{size}, $key->{count}, $key->{total});
+ }
+}
print STDOUT "maximum used: $max, amount leaked: $total\n";