Add rtstat-like per-cpu statistics to the neighbour cache core. This will add /proc/net/arp_cache_stat for ipv4 /proc/net/ndisc_cache_stat for ipv6 /proc/net/clip_arp_cache_stat for atm/clip /proc/net/decnet_neigh_stat for decnet The format is similar to rt_stat, one line per cpu where each line is: Field 1: Total number of entries in table Field 2: Total number of hash bucket grows Field 3: Total number of cache hits Field 4: Total number of neigh_allocs (should equal misses) Field 5: Total number of failed neighbour resolves Field 6: ? Field 7: ? Field 8: Total number of forced garbage collector runs Field 9: Total number of forced GC runs that missed their goal Signed-off-by: Harald Welte Index: linux-2.6.9-rc2-bk9-neigh1/include/net/neighbour.h =================================================================== --- linux-2.6.9-rc2-bk9-neigh1.orig/include/net/neighbour.h 2004-09-26 12:45:38.000000000 +0200 +++ linux-2.6.9-rc2-bk9-neigh1/include/net/neighbour.h 2004-09-26 23:31:13.000000000 +0200 @@ -7,6 +7,11 @@ * Authors: * Pedro Roque * Alexey Kuznetsov + * + * Changes: + * + * Harald Welte: + * - Add neighbour cache statistics like rtstat */ /* The following flags & states are exported to user space, @@ -92,10 +97,21 @@ { unsigned long allocs; unsigned long res_failed; + + unsigned int hits; /* number of cache hits */ + unsigned long rcv_probes_mcast; unsigned long rcv_probes_ucast; + + unsigned int hash_grows; /* total number of hash resizes */ + + unsigned int forced_gc_runs; /* total number of GC runs */ + unsigned int forced_gc_goal_miss;/* total number of gc goal misses */ }; +#define NEIGH_CACHE_STAT_INC(tbl, field) \ + (per_cpu_ptr((tbl)->stats, smp_processor_id())->field++) + struct neighbour { struct neighbour *next; @@ -172,12 +188,15 @@ unsigned long last_rand; struct neigh_parms *parms_list; kmem_cache_t *kmem_cachep; - struct neigh_statistics stats; + struct neigh_statistics *stats; struct neighbour **hash_buckets; unsigned int hash_mask; __u32 hash_rnd; unsigned int hash_chain_gc; struct pneigh_entry **phash_buckets; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *pde; +#endif }; /* flags for neigh_update() */ Index: linux-2.6.9-rc2-bk9-neigh1/net/core/neighbour.c =================================================================== --- linux-2.6.9-rc2-bk9-neigh1.orig/net/core/neighbour.c 2004-09-26 12:37:18.000000000 +0200 +++ linux-2.6.9-rc2-bk9-neigh1/net/core/neighbour.c 2004-09-27 10:54:35.934120048 +0200 @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONFIG_SYSCTL #include #endif @@ -59,6 +60,7 @@ static int neigh_glbl_allocs; static struct neigh_table *neigh_tables; +static struct file_operations neigh_stat_seq_fops; /* Neighbour hash table buckets are protected with rwlock tbl->lock. @@ -115,6 +117,8 @@ int shrunk = 0, num_incomplete = 0, reap_incomplete = 0; int i; + NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs); + write_lock_bh(&tbl->lock); rescan: for (i = 0; i <= tbl->hash_mask; i++) { @@ -161,6 +165,7 @@ if (reap_incomplete == 0 && shrunk < goal && (shrunk + num_incomplete) >= goal) { + NEIGH_CACHE_STAT_INC(tbl, forced_gc_goal_miss); reap_incomplete = 1; goto rescan; } @@ -299,7 +304,8 @@ init_timer(&n->timer); n->timer.function = neigh_timer_handler; n->timer.data = (unsigned long)n; - tbl->stats.allocs++; + + NEIGH_CACHE_STAT_INC(tbl, allocs); neigh_glbl_allocs++; tbl->entries++; n->tbl = tbl; @@ -341,6 +347,8 @@ struct neighbour **new_hash, **old_hash; unsigned int i, new_hash_mask, old_entries; + NEIGH_CACHE_STAT_INC(tbl, hash_grows); + BUG_ON(new_entries & (new_entries - 1)); new_hash = neigh_hash_alloc(new_entries); if (!new_hash) @@ -380,6 +388,7 @@ for (n = tbl->hash_buckets[hash_val]; n; n = n->next) { if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) { neigh_hold(n); + NEIGH_CACHE_STAT_INC(tbl, hits); break; } } @@ -397,6 +406,7 @@ for (n = tbl->hash_buckets[hash_val]; n; n = n->next) { if (!memcmp(n->primary_key, pkey, key_len)) { neigh_hold(n); + NEIGH_CACHE_STAT_INC(tbl, hits); break; } } @@ -787,7 +797,7 @@ neigh->nud_state = NUD_FAILED; notify = 1; - neigh->tbl->stats.res_failed++; + NEIGH_CACHE_STAT_INC(neigh->tbl, res_failed); NEIGH_PRINTK2("neigh %p is failed.\n", neigh); /* It is very thin place. report_unreachable is very complicated @@ -1336,6 +1346,30 @@ if (!tbl->kmem_cachep) panic("cannot create neighbour cache"); + tbl->stats = alloc_percpu(struct neigh_statistics); + if (!tbl->stats) + panic("cannot create neighbour cache statistics"); + +#ifdef CONFIG_PROC_FS +#define NC_STAT_SUFFIX "_stat" + { + char *proc_stat_name; + proc_stat_name = kmalloc(strlen(tbl->id) + + strlen(NC_STAT_SUFFIX) + 1, GFP_KERNEL); + if (!proc_stat_name) + panic("cannot allocate neighbour cache proc name buffer"); + strcpy(proc_stat_name, tbl->id); + strcat(proc_stat_name, NC_STAT_SUFFIX); + + /* FIXME: move this to seperate directory and add _stat postfix */ + tbl->pde = create_proc_entry(proc_stat_name, 0, proc_net); + if (!tbl->pde) + panic("cannot create neighbour proc dir entry"); + tbl->pde->proc_fops = &neigh_stat_seq_fops; + tbl->pde->data = tbl; + } +#endif + tbl->hash_mask = 0x1f; tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1); @@ -1882,6 +1916,94 @@ } EXPORT_SYMBOL(neigh_seq_stop); +/* statistics via seq_file */ + +static void *neigh_stat_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct proc_dir_entry *pde = seq->private; + struct neigh_table *tbl = pde->data; + int cpu; + + for (cpu = *pos; cpu < NR_CPUS; ++cpu) { + if (!cpu_possible(cpu)) + continue; + *pos = cpu; + return per_cpu_ptr(tbl->stats, cpu); + } + return NULL; +} + +static void *neigh_stat_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct proc_dir_entry *pde = seq->private; + struct neigh_table *tbl = pde->data; + int cpu; + + for (cpu = *pos + 1; cpu < NR_CPUS; ++cpu) { + if (!cpu_possible(cpu)) + continue; + *pos = cpu; + return per_cpu_ptr(tbl->stats, cpu); + } + return NULL; +} + +static void neigh_stat_seq_stop(struct seq_file *seq, void *v) +{ + +} + +static int neigh_stat_seq_show(struct seq_file *seq, void *v) +{ + struct proc_dir_entry *pde = seq->private; + struct neigh_table *tbl = pde->data; + struct neigh_statistics *st = v; + + seq_printf(seq, "%08x %08x %08x %08lx %08lx %08lx %08lx " + "%08x %08x\n", + tbl->entries, + + st->hash_grows, + st->hits, + st->allocs, + st->res_failed, + + st->rcv_probes_mcast, + st->rcv_probes_ucast, + + st->forced_gc_runs, + st->forced_gc_goal_miss + ); + + return 0; +} + +static struct seq_operations neigh_stat_seq_ops = { + .start = neigh_stat_seq_start, + .next = neigh_stat_seq_next, + .stop = neigh_stat_seq_stop, + .show = neigh_stat_seq_show, +}; + +static int neigh_stat_seq_open(struct inode *inode, struct file *file) +{ + int ret = seq_open(file, &neigh_stat_seq_ops); + + if (!ret) { + struct seq_file *sf = file->private_data; + sf->private = PDE(inode); + } + return ret; +}; + +static struct file_operations neigh_stat_seq_fops = { + .owner = THIS_MODULE, + .open = neigh_stat_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + #endif /* CONFIG_PROC_FS */ #ifdef CONFIG_ARPD Index: linux-2.6.9-rc2-bk9-neigh1/net/ipv6/ndisc.c =================================================================== --- linux-2.6.9-rc2-bk9-neigh1.orig/net/ipv6/ndisc.c 2004-09-26 12:29:03.000000000 +0200 +++ linux-2.6.9-rc2-bk9-neigh1/net/ipv6/ndisc.c 2004-09-26 23:30:56.000000000 +0200 @@ -802,9 +802,9 @@ } if (inc) - nd_tbl.stats.rcv_probes_mcast++; + NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast); else - nd_tbl.stats.rcv_probes_ucast++; + NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast); /* * update / create cache entry