/*
 * dump-metrics.c --
 *
 *      Operations to compute and dump some MIB metrics.
 *
 * Copyright (c) 2000 Frank Strauss, Technical University of Braunschweig.
 * Copyright (c) 2000 J. Schoenwaelder, Technical University of Braunschweig.
 * Copyright (c) 2002 J. Schoenwaelder, University of Osnabrueck.
 * Copyright (c) 2004 J. Schoenwaelder, International University Bremen.
 *
 * See the file "COPYING" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: dump-metrics.c 1734 2011-12-14 08:02:20Z schoenw $
 */

/*
  # revisions
  # imports
  # row creations:
  # count node references in notification definitions
 */

#include <config.h>

#include <stdio.h>
#include <string.h>

#include "smi.h"
#include "smidump.h"


static int raw = 0;

static int silent = 0;


typedef struct BasetypeCounter {
    unsigned long total;
    unsigned long unknown;
    unsigned long integer32;
    unsigned long octetstring;
    unsigned long objectidentifier;
    unsigned long unsigned32;
    unsigned long integer64;
    unsigned long unsigned64;
    unsigned long float32;
    unsigned long float64;
    unsigned long float128;
    unsigned long enums;
    unsigned long bits;
    unsigned long pointer;
} BasetypeCounter;


typedef struct StatusCounter {
    unsigned long total;
    unsigned long current;
    unsigned long deprecated;
    unsigned long obsolete;
} StatusCounter;



typedef struct AccessCounter {
    unsigned long total;
    unsigned long noaccess;
    unsigned long notify;
    unsigned long readonly;
    unsigned long readwrite;
} AccessCounter;



typedef struct IndexCounter {
    unsigned long total;
    unsigned long index;
    unsigned long augment;
    unsigned long reorder;
    unsigned long sparse;
    unsigned long expand;
} IndexCounter;



typedef struct IndexLenCounter {
    unsigned long total;
    unsigned long length[11];
} IndexLenCounter;



typedef struct TableLenCounter {
    unsigned long total;
    unsigned long length[81];
} TableLenCounter;



typedef struct ScalarLenCounter {
    unsigned long total;
    unsigned long length[81];
} ScalarLenCounter;



typedef struct IndexComplexityCounter {
    unsigned long total;
    unsigned long complexity[100];
} IndexComplexityCounter;



typedef struct LengthCounter {
    unsigned long total;
    unsigned long descr;
    unsigned long descr_len;
    unsigned long reference;
    unsigned long reference_len;
    unsigned long units;
    unsigned long units_len;
    unsigned long format;
    unsigned long format_len;
} LengthCounter;



typedef struct RowStatusCounter {
    unsigned long basetables;
    unsigned long rowstatus;
    unsigned long storagetype;
} RowStatusCounter;



typedef struct Metrics {
    BasetypeCounter basetypesColumns;
    BasetypeCounter basetypesScalars;
    BasetypeCounter basetypesAll;
    StatusCounter   statusTypes;
    StatusCounter   statusTables;
    StatusCounter   statusColumns;
    StatusCounter   statusScalars;
    StatusCounter   statusNotifications;
    StatusCounter   statusGroups;
    StatusCounter   statusCompliances;
    StatusCounter   statusAll;
    AccessCounter   accessColumns;
    AccessCounter   accessScalars;
    AccessCounter   accessAll;
    IndexCounter    indexTables;
    IndexLenCounter indexLenTables;
    IndexComplexityCounter indexComplexity;
    TableLenCounter tableLength;
    ScalarLenCounter scalarLength;
    LengthCounter   lengthTypes;
    LengthCounter   lengthTables;
    LengthCounter   lengthRows;
    LengthCounter   lengthColumns;
    LengthCounter   lengthScalars;
    LengthCounter   lengthNotifications;
    LengthCounter   lengthAll;
} Metrics;



typedef struct UsageCounter {
    char     *module;
    char     *name;
    unsigned count;
    struct UsageCounter *nextPtr;
} UsageCounter;


static UsageCounter *typeList = NULL;
static UsageCounter *extTypeList = NULL;
static UsageCounter *extNodeList = NULL;
static UsageCounter *extModuleList = NULL;
static UsageCounter *indexComplexityList = NULL;

#define INCR_NODE 0x01
#define INCR_TYPE 0x02


static char*
getDateString(time_t t)
{
    static char   *s = NULL;
    struct tm	  *tm;

    if (s) xfree(s);
    
    tm = gmtime(&t);
    smiAsprintf(&s, "%04d-%02d-%02d",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
    return s;
}



static char*
language(SmiLanguage language)
{
    return
	(language == SMI_LANGUAGE_UNKNOWN)    ? "-" :
	(language == SMI_LANGUAGE_SMIV1)      ? "SMIv1" :
	(language == SMI_LANGUAGE_SMIV2)      ? "SMIv2" :
	(language == SMI_LANGUAGE_SMING)      ? "SMIng" :
						"-";
}



static int
calcSize(SmiModule *smiModule)
{
    SmiNode *smiNode;
    SmiType *smiType;
    int size = 0;
    
    for (smiType = smiGetFirstType(smiModule);
	 smiType;
	 smiType = smiGetNextType(smiType)) {
	if (smiType->name) {
	    size++;
	}
    }
    
    for (smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_ANY);
	 smiNode;
	 smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_ANY)) {

	switch (smiNode->nodekind) {
	case SMI_NODEKIND_SCALAR:
	case SMI_NODEKIND_COLUMN:
	case SMI_NODEKIND_NOTIFICATION:
	    size++;
	    break;
	default:
	    break;
	}
    }

    return size;
}



typedef void	(*ForEachIndexFunc)	(FILE *f, SmiNode *groupNode, SmiNode *smiNode, void *data);

static void
foreachIndexDo(FILE *f, SmiNode *smiNode, ForEachIndexFunc func, void *data)
{
    SmiNode *indexNode = NULL, *iNode;
    SmiElement *smiElement;
    
    switch (smiNode->indexkind) {
    case SMI_INDEX_INDEX:
    case SMI_INDEX_REORDER:
	indexNode = smiNode;
	break;
    case SMI_INDEX_EXPAND:	/* TODO: we have to do more work here! */
	break;
    case SMI_INDEX_AUGMENT:
    case SMI_INDEX_SPARSE:
	indexNode = smiGetRelatedNode(smiNode);
	break;
    case SMI_INDEX_UNKNOWN:
	break;
    }
    if (indexNode) {
	for (smiElement = smiGetFirstElement(indexNode);
	     smiElement; smiElement = smiGetNextElement(smiElement)) {
	    iNode = smiGetElementNode(smiElement);
	    if (iNode) {
		(func) (f, smiNode, iNode, data);
	    }
	}
    }
}



static UsageCounter*
incrUsageCounter(UsageCounter *uCntList, char *module, char *name, int incr)
{
    UsageCounter *uCnt;

    for (uCnt = uCntList; uCnt; uCnt = uCnt->nextPtr) {
	if (strcmp(uCnt->module, module) == 0
	    && (! name || strcmp(uCnt->name, name) == 0)) {
	    break;
	}
    }

    if (! uCnt) {
	uCnt = (UsageCounter *) xmalloc(sizeof(UsageCounter));
	uCnt->module = xstrdup(module);
	uCnt->name = name ? xstrdup(name) : NULL;
	uCnt->count = 0;
	uCnt->nextPtr = uCntList;
	uCntList = uCnt;
    }

    uCnt->count += incr;
    return uCntList;
}



static void
incrTypeAndNodeUsageCounter(SmiModule *smiModule, SmiNode *smiNode, int flags)
{
    SmiType *smiType;
    char *extModule;

    /*
     * First check whether the node is external. If yes, increment the
     * external node counter and we are done.
     */

    extModule = smiGetNodeModule(smiNode)->name;
    if (extModule
	&& strcmp(extModule, smiModule->name) != 0) {
	if (flags & INCR_NODE) {
	    extNodeList = incrUsageCounter(extNodeList,
					   extModule, smiNode->name, 1);
	    extModuleList = incrUsageCounter(extModuleList, extModule, NULL, 1);
	}
	return;
    }
    
    /*
     * Next, check whether the type of the node is external. If yes,
     * increment the external type counter and we are done. Do not
     * count base types (that is types that have no parent type).
     */

    smiType = smiGetNodeType(smiNode);
    if (! smiType) {
	return;
    }

    if (smiType->name && smiGetParentType(smiType)) {
	char *extModule = smiGetTypeModule(smiType)->name;
	if (extModule /* && *extModule */
	    && strcmp(extModule, smiModule->name) != 0) {
	    if (flags & INCR_TYPE) {
		extTypeList = incrUsageCounter(extTypeList,
					       extModule, smiType->name, 1);
		extModuleList = incrUsageCounter(extModuleList, extModule, NULL, 1);
	    }
	}
    }

    /*
     * Finally, count the type name (whether external or not does not
     * matter here nor does it matter whether it is a base type or
     * not).
     */

    if (! smiType->name && smiGetParentType(smiType)) {
	smiType = smiGetParentType(smiType);
    }
    typeList = incrUsageCounter(typeList, smiGetTypeModule(smiType)->name,
				smiType->name, 1);
}



static void
incrIndexComplexityCounter(SmiModule *smiModule, SmiNode *smiNode, int complexity)
{
    indexComplexityList = incrUsageCounter(indexComplexityList,
					   smiModule->name, smiNode->name, complexity);
}



static int
cmp(const void *va, const void *vb)
{
    UsageCounter **a = (UsageCounter **) va;
    UsageCounter **b = (UsageCounter **) vb;

    if ((*a)->count > (*b)->count) return -1;
    if ((*a)->count < (*b)->count) return 1;

    if ((*a)->module && (*b)->module) {
	int x = strcmp((*a)->module,  (*b)->module);
	if (x) return x;
    }

    if ((*a)->name && (*b)->name) {
	int x = strcmp((*a)->name,  (*b)->name);
	if (x) return x;
    }

    return 0;
}



static int
fprintRevisions(FILE *f, int modLen, SmiRevision *smiRevision,
		SmiModule *smiModule, int size)
{
    int n = 0;
    
    if (smiRevision) {
	n = fprintRevisions(f, modLen,
			    smiGetNextRevision(smiRevision), smiModule, -1);
	fprintf(f, "%-*s %7s  ", modLen, smiModule->name,
		(size >= 0) ? language(smiModule->language) : "-");
 	if (size >= 0) {
	    fprintf(f, "%4d", size);
	} else {
	    fprintf(f, "   -");
	}
	fprintf(f, "   %3d    %s\n", n, getDateString(smiRevision->date));
	n++;
    }

    if (!smiRevision && size >= 0) {
	fprintf(f, "%-*s %7s  ", modLen, smiModule->name,
		language(smiModule->language));
	fprintf(f, "%4d", size);
	fprintf(f, "     -    ----------\n");
    }
    
    return n;
}



static void
fprintRevision(FILE *f, int modc, SmiModule **modv)
{
    int i;
    int modLen = 8;

    for (i = 0; i < modc; i++) {
	if (modLen < strlen(modv[i]->name)) {
	    modLen = strlen(modv[i]->name);
	}
    }

    fprintf(f, "%-*s LANGUAGE SIZE REVISION DATE\n", modLen, "MODULE");

    for (i = 0; i < modc; i++) {
	fprintRevisions(f, modLen, smiGetFirstRevision(modv[i]),
			modv[i], calcSize(modv[i]));
    }
    fprintf(f, "\n");
 
}



static void
fprintTypeUsage(FILE *f, UsageCounter *typeUsageList)
{
    UsageCounter *uCnt;
    int modLen = 8, nameLen = 8;
    unsigned total = 0;
    int i, cnt = 0;
    UsageCounter **sortCnt;

    /* should be sorted */

    for (uCnt = typeUsageList, cnt = 0; uCnt; uCnt = uCnt->nextPtr, cnt++) {
	if (modLen < strlen(uCnt->module)) {
	    modLen = strlen(uCnt->module);
	}
	if (nameLen < strlen(uCnt->name)) {
	    nameLen = strlen(uCnt->name);
	}
	total += uCnt->count;
    }

    if (cnt == 0) {
	return;
    }

    /* create an array for a quick qsort */

    sortCnt = (UsageCounter **) xmalloc(cnt * sizeof(UsageCounter *));
    memset(sortCnt, 0, cnt * sizeof(UsageCounter *));
    for (uCnt = typeUsageList, i = 0; uCnt; uCnt = uCnt->nextPtr, i++) {
	sortCnt[i] = uCnt;
    }
    qsort(sortCnt, cnt, sizeof(UsageCounter *), cmp);
    
    if (! silent) {
	fputs(
"# The following table shows the distribution of the number of references\n"
"# to defined types (including base types) in the set of loaded MIB\n"
"# modules.\n"
"\n", f);
    }
    fprintf(f, "%-*s %-*s   USAGE\n", modLen, "MODULE", nameLen, "TYPE");

    for (i = 0; i < cnt; i++) {
	fprintf(f, "%-*s %-*s ",
		modLen, sortCnt[i]->module, nameLen, sortCnt[i]->name);
	if (raw) {
	    fprintf(f, "%8u\n", sortCnt[i]->count);
	} else {
	    fprintf(f, "%6.1f%%\n", (double) sortCnt[i]->count * 100 / total);
	}
    }

    xfree(sortCnt);
}



static void
fprintExtTypeUsage(FILE *f, UsageCounter *typeUsageList)
{
    UsageCounter *uCnt;
    int modLen = 8, nameLen = 8;
    unsigned total = 0;
    int i, cnt = 0;
    UsageCounter **sortCnt;

    /* should be sorted */

    for (uCnt = typeUsageList, cnt = 0; uCnt; uCnt = uCnt->nextPtr, cnt++) {
	if (modLen < strlen(uCnt->module)) {
	    modLen = strlen(uCnt->module);
	}
	if (nameLen < strlen(uCnt->name)) {
	    nameLen = strlen(uCnt->name);
	}
	total += uCnt->count;
    }

    if (cnt == 0) {
	return;
    }

    /* create an array for a quick qsort */

    sortCnt = (UsageCounter **) xmalloc(cnt * sizeof(UsageCounter *));
    memset(sortCnt, 0, cnt * sizeof(UsageCounter *));
    for (uCnt = typeUsageList, i = 0; uCnt; uCnt = uCnt->nextPtr, i++) {
	sortCnt[i] = uCnt;
    }
    qsort(sortCnt, cnt, sizeof(UsageCounter *), cmp);
    
    if (! silent) {
	fputs(
"# The following table shows the distribution of the number of references\n"
"# to externally defined types (excluding base types) in the set of loaded\n"
"# MIB modules.\n"
"\n", f);
    }
    fprintf(f, "%-*s %-*s EXT-USAGE\n", modLen, "MODULE", nameLen, "TYPE");

    for (i = 0; i < cnt; i++) {
	fprintf(f, "%-*s %-*s ",
		modLen, sortCnt[i]->module, nameLen, sortCnt[i]->name);
	if (raw) {
	    fprintf(f, "%8u\n", sortCnt[i]->count);
	} else {
	    fprintf(f, "%6.1f%%\n", (double) sortCnt[i]->count * 100 / total);
	}
    }

    xfree(sortCnt);
}



static void
fprintExtNodeUsage(FILE *f, UsageCounter *typeUsageList)
{
    UsageCounter *uCnt;
    int modLen = 8, nameLen = 8;
    unsigned total = 0;
    int i, cnt = 0;
    UsageCounter **sortCnt;

    /* should be sorted */

    for (uCnt = typeUsageList, cnt = 0; uCnt; uCnt = uCnt->nextPtr, cnt++) {
	if (modLen < strlen(uCnt->module)) {
	    modLen = strlen(uCnt->module);
	}
	if (nameLen < strlen(uCnt->name)) {
	    nameLen = strlen(uCnt->name);
	}
	total += uCnt->count;
    }

    if (cnt == 0) {
	return;
    }

    /* create an array for a quick qsort */

    sortCnt = (UsageCounter **) xmalloc(cnt * sizeof(UsageCounter *));
    memset(sortCnt, 0, cnt * sizeof(UsageCounter *));
    for (uCnt = typeUsageList, i = 0; uCnt; uCnt = uCnt->nextPtr, i++) {
	sortCnt[i] = uCnt;
    }
    qsort(sortCnt, cnt, sizeof(UsageCounter *), cmp);
    
    if (! silent) {
	fputs(
"# The following table shows the distribution of the number of references\n"
"# to externally defined nodes in the set of loaded MIB modules.\n"
"\n", f);
    }
    fprintf(f, "%-*s %-*s EXT-USAGE\n", modLen, "MODULE", nameLen, "NODE");

    for (i = 0; i < cnt; i++) {
	fprintf(f, "%-*s %-*s ",
		modLen, sortCnt[i]->module, nameLen, sortCnt[i]->name);
	if (raw) {
	    fprintf(f, "%8u\n", sortCnt[i]->count);
	} else {
	    fprintf(f, "%6.1f%%\n", (double) sortCnt[i]->count * 100 / total);
	}
    }

    xfree(sortCnt);
}



static void
fprintModuleUsage(FILE *f, UsageCounter *modUsageList)
{
    UsageCounter *uCnt;
    int modLen = 8;
    unsigned total = 0;
    int i, cnt = 0;
    UsageCounter **sortCnt;

    /* should be sorted */

    for (uCnt = modUsageList, cnt = 0; uCnt; uCnt = uCnt->nextPtr, cnt++) {
	if (modLen < strlen(uCnt->module)) {
	    modLen = strlen(uCnt->module);
	}
	total += uCnt->count;
    }

    if (cnt == 0) {
	return;
    }

    /* create an array for a quick qsort */

    sortCnt = (UsageCounter **) xmalloc(cnt * sizeof(UsageCounter *));
    memset(sortCnt, 0, cnt * sizeof(UsageCounter *));
    for (uCnt = modUsageList, i = 0; uCnt; uCnt = uCnt->nextPtr, i++) {
	sortCnt[i] = uCnt;
    }
    qsort(sortCnt, cnt, sizeof(UsageCounter *), cmp);

    if (! silent) {
	fputs(
"# The following table shows the distribution of the number of references\n"
"# to externally defined items (such as types or objects) accumulated by\n"
"# the defining MIB module in the set of loaded MIB modules.\n"
"\n", f);
    }
    fprintf(f, "%-*s EXT-USAGE\n", modLen, "MODULE");

    for (i = 0; i < cnt; i++) {
	fprintf(f, "%-*s ", modLen, sortCnt[i]->module);
	if (raw) {
	    fprintf(f, "%8u\n", sortCnt[i]->count);
	} else {
	    fprintf(f, "%6.1f%%\n", (double) sortCnt[i]->count * 100 / total);
	}
    }

    xfree(sortCnt);
}



static void
fprintIndexComplexity(FILE *f, UsageCounter *modUsageList)
{
    UsageCounter *uCnt;
    int modLen = 8;
    int nameLen = 8;
    unsigned total = 0;
    int i, cnt = 0;
    UsageCounter **sortCnt;

    /* should be sorted */

    for (uCnt = modUsageList, cnt = 0; uCnt; uCnt = uCnt->nextPtr, cnt++) {
	if (modLen < strlen(uCnt->module)) {
	    modLen = strlen(uCnt->module);
	}
	if (nameLen < strlen(uCnt->name)) {
	    nameLen = strlen(uCnt->name);
	}
	total += uCnt->count;
    }

    if (cnt == 0) {
	return;
    }

    /* create an array for a quick qsort */

    sortCnt = (UsageCounter **) xmalloc(cnt * sizeof(UsageCounter *));
    memset(sortCnt, 0, cnt * sizeof(UsageCounter *));
    for (uCnt = modUsageList, i = 0; uCnt; uCnt = uCnt->nextPtr, i++) {
	sortCnt[i] = uCnt;
    }
    qsort(sortCnt, cnt, sizeof(UsageCounter *), cmp);

    if (! silent) {
	fputs(
"# The following table shows the distribution of the index complexity\n"
"# in the set of loaded MIB modules.\n"
"\n", f);
    }
    fprintf(f, "%-*s %-*s COMPLEXITY\n", modLen, "MODULE", nameLen, "TABLE");

    for (i = 0; i < cnt; i++) {
	fprintf(f, "%-*s %-*s ", modLen, sortCnt[i]->module, nameLen, sortCnt[i]->name);
	if (raw) {
	    fprintf(f, "%8u\n", sortCnt[i]->count);
	} else {
	    fprintf(f, "%6.1f%%\n", (double) sortCnt[i]->count);
	}
    }

    xfree(sortCnt);
}



static void
freeUsageCounter(UsageCounter *usageCounterList)
{
    UsageCounter *uCnt, *p;
    
    for (uCnt = usageCounterList; uCnt; ) {
	p = uCnt, uCnt = uCnt->nextPtr;
	xfree(p->module);
	xfree(p->name);
	xfree(p);
    }
}



static void
incrBasetypeCounter(BasetypeCounter *basetypeCounter, SmiNode *smiNode)
{
    SmiType *smiType;

    smiType = smiGetNodeType(smiNode);
    if (smiType) {
	basetypeCounter->total++;
	switch (smiType->basetype) {
	case SMI_BASETYPE_UNKNOWN:
	    basetypeCounter->unknown++;
	    break;
	case SMI_BASETYPE_INTEGER32:
	    basetypeCounter->integer32++;
	    break;
	case SMI_BASETYPE_OCTETSTRING:
	    basetypeCounter->octetstring++;
	    break;
	case SMI_BASETYPE_OBJECTIDENTIFIER:
	    basetypeCounter->objectidentifier++;
	    break;
	case SMI_BASETYPE_UNSIGNED32:
	    basetypeCounter->unsigned32++;
	    break;
	case SMI_BASETYPE_INTEGER64:
	    basetypeCounter->integer64++;
	    break;
	case SMI_BASETYPE_UNSIGNED64:
	    basetypeCounter->unsigned64++;
	    break;
	case SMI_BASETYPE_FLOAT32:
	    basetypeCounter->float32++;
	    break;
	case SMI_BASETYPE_FLOAT64:
	    basetypeCounter->float64++;
	    break;
	case SMI_BASETYPE_FLOAT128:
	    basetypeCounter->float128++;
	    break;
	case SMI_BASETYPE_ENUM:
	    basetypeCounter->enums++;
	    break;
	case SMI_BASETYPE_BITS:
	    basetypeCounter->bits++;
	    break;
	case SMI_BASETYPE_POINTER:
	    basetypeCounter->pointer++;
	    break;
	}
    }
}



static void
incrStatusCounter(StatusCounter *cnt, SmiStatus smiStatus)
{
    cnt->total++;
    switch (smiStatus) {
    case SMI_STATUS_CURRENT:
    case SMI_STATUS_MANDATORY:
    case SMI_STATUS_OPTIONAL:
	cnt->current++;
	break;
    case SMI_STATUS_DEPRECATED:
	cnt->deprecated++;
	break;
    case SMI_STATUS_OBSOLETE:
	cnt->obsolete++;
	break;
    case SMI_STATUS_UNKNOWN:
	break;
    }
}



static void
incrAccessCounter(AccessCounter *cnt, SmiAccess smiAccess)
{
    cnt->total++;
    switch (smiAccess) {
    case SMI_ACCESS_NOT_ACCESSIBLE:
	cnt->noaccess++;
	break;
    case SMI_ACCESS_NOTIFY:
	cnt->notify++;
	break;
    case SMI_ACCESS_READ_ONLY:
	cnt->readonly++;
	break;
    case SMI_ACCESS_READ_WRITE:
	cnt->readwrite++;
	break;
    case SMI_ACCESS_INSTALL:
    case SMI_ACCESS_INSTALL_NOTIFY:
    case SMI_ACCESS_REPORT_ONLY:
    case SMI_ACCESS_UNKNOWN:
    case SMI_ACCESS_NOT_IMPLEMENTED:
    case SMI_ACCESS_EVENT_ONLY:
	break;
    }
}



static void
incrIndexCounter(IndexCounter *cnt, SmiIndexkind indexkind)
{
    cnt->total++;
    switch (indexkind) {
    case SMI_INDEX_INDEX:
	cnt->index++;
	break;
    case SMI_INDEX_AUGMENT:
	cnt->augment++;
	break;
    case SMI_INDEX_REORDER:
	cnt->reorder++;
	break;
    case SMI_INDEX_SPARSE:
	cnt->sparse++;
	break;
    case SMI_INDEX_EXPAND:
	cnt->expand++;
	break;
    case SMI_INDEX_UNKNOWN:
	break;
    }
}



static void
incrIndexLenCounter(IndexLenCounter *cnt, int len)
{
    cnt->total++;
    if (len < sizeof(cnt->length)/sizeof(cnt->length[0])) {
	cnt->length[len]++;
    } else {
	fprintf(stderr, "smidump: index len overflow: %d\n", len);
    }
}



static void
incrTableLenCounter(TableLenCounter *cnt, int len)
{
    cnt->total++;
    if (len < sizeof(cnt->length)/sizeof(cnt->length[0])) {
	cnt->length[len]++;
    } else {
	fprintf(stderr, "smidump: table len overflow: %d\n", len);
    }
}



static void
incrIndexComplexityMetric(IndexComplexityCounter *cnt, int cmplx)
{
    cnt->total++;
    if (cmplx < sizeof(cnt->complexity)/sizeof(cnt->complexity[0])) {
	cnt->complexity[cmplx]++;
    } else {
	fprintf(stderr, "smidump: index complexity overflow: %d\n", cmplx);
    }
}



static void
incrLengthCounter(LengthCounter *cnt, char *description, char *reference,
		  char *units, char *format)
{
    cnt->total++;
    if (description) {
	cnt->descr++;
	cnt->descr_len += strlen(description);
    }

    if (reference) {
	cnt->reference++;
	cnt->reference_len += strlen(reference);
    }
    if (units) {
	cnt->units++;
	cnt->units_len += strlen(units);
    }
    if (format) {
	cnt->format++;
	cnt->format_len += strlen(format);
    }
}



static void
incrRowStatusCounter(SmiNode *rowNode)
{
    SmiNode *smiNode;
    SmiType *smiType;
    SmiModule *smiModule;

    for (smiNode = smiGetFirstChildNode(rowNode);
	 smiNode;
	 smiNode = smiGetNextChildNode(smiNode)) {
	smiType = smiGetNodeType(smiNode);
	if (smiType && smiType->name) {
	    smiModule = smiGetTypeModule(smiType);
	    if (smiModule && smiModule->name
		&& strcmp(smiType->name, "RowStatus") == 0
		&& strcmp(smiModule->name, "SNMPv2-TC") == 0) {
		break;
	    }
	}
    }

    if (smiNode) {
#if 0
	fprintf(stderr, "** %s\t%s\t%s\n", rowNode->name,
		smiNode->name, smiType->name);
	/* xxx count rows indexed by ifIndex, InterfaceIndex, InterfaceIndexOrZero, ... */
#endif
    }
}



static void
count(FILE *f, SmiNode *row, SmiNode *col, void *data)
{
    int *cnt = (int *) data;

    (*cnt)++;
}



static void
complexity(FILE *f, SmiNode *row, SmiNode *col, void *data)
{
    int *cmplx = (int *) data;
    SmiType *smiType;
    unsigned long min, max;

    smiType = smiGetNodeType(col);
    if (! smiType) {
	return;
    }

    switch (smiType->basetype) {
    case SMI_BASETYPE_INTEGER32:
    case SMI_BASETYPE_UNSIGNED32:
    case SMI_BASETYPE_ENUM:
	*cmplx += 1;
	break;
    case SMI_BASETYPE_OCTETSTRING:
    case SMI_BASETYPE_OBJECTIDENTIFIER:
    case SMI_BASETYPE_BITS:
	*cmplx += 2;
	min = smiGetMinSize(smiType);
	max = smiGetMaxSize(smiType);
	if (min != max) {
	    *cmplx += 1;
	}
	break;
    default:				/* ignore everything else */
	break;
    }
}



static void
yadayada(FILE *f, SmiNode *row, SmiNode *col, void *data)
{
    SmiModule *smiModule = (SmiModule *) data;
    int flags = 0;

    if (col->access == SMI_ACCESS_NOT_ACCESSIBLE) {
	flags |= INCR_TYPE;
    }
    flags |= INCR_NODE;

    incrTypeAndNodeUsageCounter(smiModule, col, flags);
}



static void
addMetrics(Metrics *metrics, SmiModule *smiModule)
{
    SmiNode *smiNode;
    SmiType *smiType;

    for (smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_ANY);
	 smiNode;
	 smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_ANY)) {
	switch (smiNode->nodekind) {
	case SMI_NODEKIND_TABLE:
	    incrStatusCounter(&metrics->statusTables, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    incrLengthCounter(&metrics->lengthTables,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrLengthCounter(&metrics->lengthAll,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    break;
	case SMI_NODEKIND_ROW:
	    incrIndexCounter(&metrics->indexTables, smiNode->indexkind);
	    incrLengthCounter(&metrics->lengthRows,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrLengthCounter(&metrics->lengthAll,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrRowStatusCounter(smiNode);
	    {
		int cnt = 0;
		foreachIndexDo(NULL, smiNode, count, &cnt);
		incrIndexLenCounter(&metrics->indexLenTables, cnt);
		foreachIndexDo(NULL, smiNode, yadayada, smiModule);
	    }
	    {
		int cmplx = 0;
		foreachIndexDo(NULL, smiNode, complexity, &cmplx);
		incrIndexComplexityCounter(smiModule, smiNode, cmplx);
		incrIndexComplexityMetric(&metrics->indexComplexity, cmplx);
	    }
	    /* count the childs ... */
	    {
		    SmiModule *smiModule = smiGetModule("SNMPv2-TC");
		    SmiNode *childNode;
		    SmiType *rowStatus = smiGetType(smiModule, "RowStatus");
		    SmiType *storageType = smiGetType(smiModule, "StorageType");
		    /* include index elements not in table */
		    int n = 0;
		    for (childNode = smiGetFirstChildNode(smiNode);
			 childNode;
			 childNode = smiGetNextChildNode(childNode)) {
			    n++;
			    if (rowStatus == smiGetNodeType(childNode)) {
				    fprintf(stderr, "**** GEEEEEE - ROWSTATUS\n");
			    }
			    if (storageType == smiGetNodeType(childNode)) {
				    fprintf(stderr, "**** GEEEEEE - STORAGETYPE\n");
			    }
		    }
		    incrTableLenCounter(&metrics->tableLength, n);
	    }
	    break;
	case SMI_NODEKIND_COLUMN:
	    incrBasetypeCounter(&metrics->basetypesColumns, smiNode);
	    incrBasetypeCounter(&metrics->basetypesAll, smiNode);
	    incrStatusCounter(&metrics->statusColumns, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    incrAccessCounter(&metrics->accessColumns, smiNode->access);
	    incrAccessCounter(&metrics->accessAll, smiNode->access);
	    incrLengthCounter(&metrics->lengthColumns,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrLengthCounter(&metrics->lengthAll,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrTypeAndNodeUsageCounter(smiModule, smiNode, INCR_TYPE);
	    break;
	case SMI_NODEKIND_SCALAR:
	    incrBasetypeCounter(&metrics->basetypesScalars, smiNode);
	    incrBasetypeCounter(&metrics->basetypesAll, smiNode);
	    incrStatusCounter(&metrics->statusScalars, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    incrAccessCounter(&metrics->accessScalars, smiNode->access);
	    incrAccessCounter(&metrics->accessAll, smiNode->access);
	    incrLengthCounter(&metrics->lengthScalars,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrLengthCounter(&metrics->lengthAll,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrTypeAndNodeUsageCounter(smiModule, smiNode, INCR_TYPE);
	    break;
	case SMI_NODEKIND_NOTIFICATION:
	    incrStatusCounter(&metrics->statusNotifications, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    incrLengthCounter(&metrics->lengthNotifications,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    incrLengthCounter(&metrics->lengthAll,
			      smiNode->description, smiNode->reference,
			      smiNode->units, smiNode->format);
	    break;
	case SMI_NODEKIND_GROUP:
	    incrStatusCounter(&metrics->statusGroups, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    break;
	case SMI_NODEKIND_COMPLIANCE:
	    incrStatusCounter(&metrics->statusCompliances, smiNode->status);
	    incrStatusCounter(&metrics->statusAll, smiNode->status);
	    break;
	}
    }

    for (smiType = smiGetFirstType(smiModule);
	 smiType;
	 smiType = smiGetNextType(smiType)) {

	/*
	 * Ignore all types with empty descriptions coming from the
	 * "SNMPv2-SMI" module since they are not really defined
	 * types but part of the language itself.
	 */

	if (! smiType->description) {
	    SmiModule *m = smiGetTypeModule(smiType);
	    if (m && strcmp(m->name, "SNMPv2-SMI") == 0) {
		continue;
	    }
	}
    
	incrStatusCounter(&metrics->statusTypes, smiType->status);
	incrStatusCounter(&metrics->statusAll, smiType->status);
	incrLengthCounter(&metrics->lengthTypes,
			  smiType->description, smiType->reference,
			  smiType->units, smiType->format);
	incrLengthCounter(&metrics->lengthAll,
			  smiType->description, smiType->reference,
			  smiType->units, smiType->format);
    }
}



static void
fprintBasetypeCounter(FILE *f, BasetypeCounter *cnt, const char *s)
{
    if (!s && ! cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the basetype usage distribution in the\n"
"# set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-10s Int32 Uns32 Int64 Uns64 OctSt ObjId Enums  Bits Flo32 Flo64 Flo128\n",
		"CATEGORY");
	return;
    }

    if (raw) {
	fprintf(f, "%-10s %5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu\n", s,
		cnt->integer32, cnt->unsigned32,
		cnt->integer64, cnt->unsigned64,
		cnt->octetstring, cnt->objectidentifier,
		cnt->enums, cnt->bits,
		cnt->float32, cnt->float64, cnt->float128);
    } else {
	fprintf(f, "%-10s %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%% %4.1f%%\n", s,
		cnt->total ? (double) cnt->integer32 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->unsigned32 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->integer64 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->unsigned64 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->octetstring * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->objectidentifier * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->enums * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->bits * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->float32 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->float64 * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->float128 * 100 / cnt->total : 0);
    }
}




static void
fprintStatusCounter(FILE *f, StatusCounter *cnt, char *s)
{
    if (!s || !cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the status distribution of various\n"
"# definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-14s %8s %8s %11s %9s\n", "CATEGORY",
		"TOTAL", "CURRENT", "DEPRECATED", "OBSOLETE");
	return;
    }

    if (raw) {
	fprintf(f, "%-14s %8lu %8lu %11lu %9lu\n", s,
		cnt->total, cnt->current, cnt->deprecated, cnt->obsolete);
    } else {
	fprintf(f, "%-14s %8lu %7.1f%% %10.1f%% %8.1f%%\n", s,
		cnt->total,
		cnt->total ? (double) cnt->current * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->deprecated * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->obsolete * 100 / cnt->total : 0);
    }
}



static void
fprintAccessCounter(FILE *f, AccessCounter *cnt, char *s)
{
    if (!s || !cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the access mode distribution of all scalar\n"
"# or column definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-14s %8s %10s %9s %7s %8s\n", "CATEGORY",
		"TOTAL", "READWRITE", "READONLY", "NOTIFY", "NOACCES");
	return;
    }

    if (raw) {
	fprintf(f, "%-14s %8lu %10lu %9lu %7lu %8lu\n", s,
		cnt->total, cnt->readwrite, cnt->readonly,
		cnt->notify, cnt->noaccess);
    } else {
	fprintf(f, "%-14s %8lu %9.1f%% %8.1f%% %6.1f%% %7.1f%%\n", s,
		cnt->total,
		cnt->total ? (double) cnt->readwrite * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->readonly * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->notify * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->noaccess * 100 / cnt->total : 0);
    }
}



static void
fprintIndexCounter(FILE *f, IndexCounter *cnt, char *s)
{
    if (! s || ! cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the table index kind distribution of\n"
"# table definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	fprintf(f, "%-14s %8s %8s %8s %8s %8s %8s\n", "CATEGORY",
		"TOTAL", "INDEX", "AUGMENT", "REORDER", "SPARSE", "EXPAND");
	}
	return;
    }
    
    if (raw) {
	fprintf(f, "%-14s %8lu %8lu %8lu %8lu %8lu %8lu\n", s,
		cnt->total, cnt->index, cnt->augment,
		cnt->reorder, cnt->sparse, cnt->expand);
    } else {
	fprintf(f, "%-14s %8lu %7.1f%% %7.1f%% %7.1f%% %7.1f%% %7.1f%%\n", s,
		cnt->total,
		cnt->total ? (double) cnt->index * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->augment * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->reorder * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->sparse * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->expand * 100 / cnt->total : 0);
    }
}



static void
fprintIndexLenCounter(FILE *f, IndexLenCounter *cnt, char *s)
{
    int i;
    int n = sizeof(cnt->length)/sizeof(cnt->length[0]);
    char buf[42];
    
    if (! s || ! cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the table index length distribution of\n"
"# table definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-10s %6s ", "CATEGORY", "TOTAL");
	for (i = 1; i < n; i++) {
	    sprintf(buf, "[%d]", i);
	    fprintf(f, " %5s", buf);
	}
	fprintf(f, "\n");
	return;
    }

    fprintf(f, "%-10s %6lu ", s, cnt->total);
    if (raw) {
	for (i = 1; i < n; i++) {
	    fprintf(f, " %5lu", cnt->length[i]);
	}
    } else {
	for (i = 1; i < n; i++) {
	    fprintf(f, " %4.1f%%", (double) cnt->length[i] * 100 / cnt->total);
	}
    }
    
    fprintf(f, "\n");
}



static void
fprintTableLenCounter(FILE *f, TableLenCounter *cnt, char *s)
{
    int i;
    int n = sizeof(cnt->length)/sizeof(cnt->length[0]);
    char buf[42];
    
    if (! s || ! cnt) {
	if (! silent) {
	    fputs(
"# The following table shows the table length distribution of\n"
"# table definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-10s %6s ", "CATEGORY", "TOTAL");
	for (i = 1; i < n; i++) {
	    sprintf(buf, "[%d]", i);
	    fprintf(f, " %5s", buf);
	}
	fprintf(f, "\n");
	return;
    }

    fprintf(f, "%-10s %6lu ", s, cnt->total);
    if (raw) {
	for (i = 1; i < n; i++) {
	    fprintf(f, " %5lu", cnt->length[i]);
	}
    } else {
	for (i = 1; i < n; i++) {
	    fprintf(f, " %4.1f%%", (double) cnt->length[i] * 100 / cnt->total);
	}
    }
    
    fprintf(f, "\n");
}



static void
fprintLengthCounter(FILE *f, LengthCounter *cnt, char *s)
{
    if (! s) {
	if (! silent) {
	    fputs(
"# The following table shows the text clause usage distribution of all\n"
"# definitions contained in the set of loaded MIB modules.\n"
"\n", f);
	}
	fprintf(f, "%-14s %8s %12s %10s %8s %8s\n", "CATEGORY",
		"TOTAL", "DESCRIPTION", "REFERENCE", "UNIT", "FORMAT");
	return;
    }
    
    if (raw) {
	fprintf(f, "%-14s %8lu %12lu %10lu %8lu %8lu\n", s,
		cnt->total, cnt->descr, cnt->reference,
		cnt->units, cnt->format);
    } else {
	fprintf(f, "%-14s %8lu %11.1f%% %9.1f%% %7.1f%% %7.1f%%\n", s,
		cnt->total,
		cnt->total ? (double) cnt->descr * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->reference * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->units * 100 / cnt->total : 0,
		cnt->total ? (double) cnt->format * 100 / cnt->total : 0);
    }
}



static void
fprintLengthCounter2(FILE *f, LengthCounter *cnt, char *s)
{
    if (! s) {
	if (! silent) {
	    fprintf(f,
"# The following table shows the %s text length distribution (in\n"
"# bytes) of all definitions contained in the set of loaded MIB modules.\n"
"\n", raw ? "total" : "average");
	}
	fprintf(f, "%-14s %8s %12s %10s %8s %8s\n", "CATEGORY",
		"TOTAL", "DESCRIPTION", "REFERENCE", "UNIT", "FORMAT");
	return;
    }
    
    if (raw) {
	fprintf(f, "%-14s %8lu %12lu %10lu %8lu %8lu\n", s,
		cnt->total, cnt->descr_len, cnt->reference_len,
		cnt->units_len, cnt->format_len);
    } else {
	fprintf(f, "%-14s %8lu %12.1f %10.1f %8.1f %8.1f\n", s,
		cnt->total,
		cnt->descr ? (double) cnt->descr_len / cnt->descr : 0,
		cnt->reference ? (double) cnt->reference_len / cnt->reference : 0,
		cnt->units ? (double) cnt->units_len / cnt->units : 0,
		cnt->format ? (double) cnt->format_len / cnt->format : 0);
    }
}



static void
fprintfComplexity(FILE *f, Metrics *metrics)
{
    unsigned long cmplx = 0, fctrs = 0;
    unsigned long total_cmplx = 0, total_fctrs = 0;

    if (! silent) {
	fputs(
"# The following table shows the complexity metrics of the set of loaded\n"
"# MIB modules.\n"
"\n", f);
    }
    fprintf(f, "%-14s %8s %8s %8s %8s\n", "CATEGORY", "TOTAL",
	    "RAW", "WEIGHT", "COMPLEXITY");
    
    cmplx = metrics->accessScalars.readonly  * 1;
    fctrs = metrics->accessScalars.readonly;
    fprintf(f, "%-14s %8lu %8lu\n", "Scalars (ro):", fctrs, cmplx);
    total_cmplx += cmplx;
    total_fctrs += fctrs;
	    
    cmplx = metrics->accessScalars.readwrite * 2;
    fctrs = metrics->accessScalars.readwrite;
    fprintf(f, "%-14s %8lu %8lu\n", "Scalars (rw):", fctrs, cmplx);
    total_cmplx += cmplx;
    total_fctrs += fctrs;

    cmplx = metrics->accessColumns.readonly * 2;
    fctrs = metrics->accessColumns.readonly;
    fprintf(f, "%-14s %8lu %8lu\n", "Columns (ro):", fctrs, cmplx);
    total_cmplx += cmplx;
    total_fctrs += fctrs;

    cmplx = metrics->accessColumns.readwrite * 3;
    fctrs = metrics->accessColumns.readwrite;
    fprintf(f, "%-14s %8lu %8lu\n", "Columns (rw):", fctrs, cmplx);
    total_cmplx += cmplx;
    total_fctrs += fctrs;

    /* readcreate tables ? */

    /* table index complexity ? */

    {
	int i;
	cmplx = 0;
	for (i = 0; i < 100; i++) {
	    cmplx += 3 * i * metrics->indexComplexity.complexity[i];
	}
	fprintf(f, "%-14s %8lu %8lu\n", "Indexes:", metrics->indexComplexity.total, cmplx);
    }

    fprintf(f, "%-14s %8lu %8lu\n", "Summary:", total_fctrs, total_cmplx);
}



static void
fprintMetrics(FILE *f, Metrics *metrics)
{
    fprintStatusCounter(f, NULL, NULL);
    fprintStatusCounter(f, &metrics->statusTypes, "Types:");
    fprintStatusCounter(f, &metrics->statusTables, "Tables:");
    fprintStatusCounter(f, &metrics->statusColumns, "Columns:");
    fprintStatusCounter(f, &metrics->statusScalars, "Scalars:");
    fprintStatusCounter(f, &metrics->statusNotifications, "Notifications:");
    fprintStatusCounter(f, &metrics->statusGroups, "Groups:");
    fprintStatusCounter(f, &metrics->statusCompliances, "Compliances:");
    fprintStatusCounter(f, &metrics->statusAll, "Summary:");
    fprintf(f, "\n");
    fprintAccessCounter(f, NULL, NULL);
    fprintAccessCounter(f, &metrics->accessColumns, "Columns:");
    fprintAccessCounter(f, &metrics->accessScalars, "Scalars:");
    fprintAccessCounter(f, &metrics->accessAll, "Summary:");
    fprintf(f, "\n");
    fprintIndexCounter(f, NULL, NULL);
    fprintIndexCounter(f, &metrics->indexTables, "Tables:");
    fprintf(f, "\n");
    fprintIndexLenCounter(f, NULL, NULL);
    fprintIndexLenCounter(f, &metrics->indexLenTables, "Tables:");
    fprintf(f, "\n");
    fprintTableLenCounter(f, NULL, NULL);
    fprintTableLenCounter(f, &metrics->tableLength, "Tables:");
    fprintf(f, "\n");
    fprintLengthCounter(f, NULL, NULL);
    fprintLengthCounter(f, &metrics->lengthTypes, "Types:");
    fprintLengthCounter(f, &metrics->lengthTables, "Tables:");
    fprintLengthCounter(f, &metrics->lengthColumns, "Columns:");
    fprintLengthCounter(f, &metrics->lengthScalars, "Scalars:");
    fprintLengthCounter(f, &metrics->lengthNotifications, "Notifications:");
    fprintLengthCounter(f, &metrics->lengthAll, "Summary:");
    fprintf(f, "\n");
    fprintLengthCounter2(f, NULL, NULL);
    fprintLengthCounter2(f, &metrics->lengthTypes, "Types:");
    fprintLengthCounter2(f, &metrics->lengthTables, "Tables:");
    fprintLengthCounter2(f, &metrics->lengthColumns, "Columns:");
    fprintLengthCounter2(f, &metrics->lengthScalars, "Scalars:");
    fprintLengthCounter2(f, &metrics->lengthNotifications, "Notifications:");
    fprintLengthCounter2(f, &metrics->lengthAll, "Summary:");
    fprintf(f, "\n");
    fprintBasetypeCounter(f, NULL, NULL);
    fprintBasetypeCounter(f, &metrics->basetypesColumns, "Columns:");
    fprintBasetypeCounter(f, &metrics->basetypesScalars, "Scalars:");
    fprintBasetypeCounter(f, &metrics->basetypesAll, "Summary:");
    fprintf(f, "\n");
    fprintfComplexity(f, metrics);
    fprintf(f, "\n");
    fprintTypeUsage(f, typeList);
    freeUsageCounter(typeList), typeList = NULL;
    fprintf(f, "\n");
    fprintExtTypeUsage(f, extTypeList);
    freeUsageCounter(extTypeList), extTypeList = NULL;
    fprintf(f, "\n");
    fprintExtNodeUsage(f, extNodeList);
    freeUsageCounter(extNodeList), extNodeList = NULL;
    fprintf(f, "\n");
    fprintModuleUsage(f, extModuleList);
    freeUsageCounter(extModuleList), extModuleList = NULL;
    fprintf(f, "\n");
    fprintIndexComplexity(f, indexComplexityList);
    freeUsageCounter(indexComplexityList), indexComplexityList = NULL;
    fprintf(f, "\n");
}



static void
dumpMetrics(int modc, SmiModule **modv, int flags, char *output)
{
    Metrics   metrics;
    int       i;
    FILE      *f = stdout;

    silent = (flags & SMIDUMP_FLAG_SILENT);

    if (output) {
	f = fopen(output, "w");
	if (!f) {
	    fprintf(stderr, "smidump: cannot open %s for writing: ", output);
	    perror(NULL);
	    exit(1);
	}
    }

    if (flags & SMIDUMP_FLAG_UNITE) {
	if (! silent) {
	    int pos = 8888;
	    fprintf(f, "# united module metrics [%d modules] "
		    "(generated by smidump " SMI_VERSION_STRING ")\n", modc);
	    fprintf(f, "#\n# smidump -u -f metrics");
	    if (raw) fprintf(f, " --metrics-raw");
	    for (i = 0; i < modc; i++) {
		int len = strlen(modv[i]->name);
		if (pos + len > 70) {
		    fprintf(f, " \\\n#\t"), pos = 8;
		}
		fprintf(f, "%s ", modv[i]->name);
		pos += len + 1;
	    }
	    fprintf(f, "%s\n", (pos == 8) ? "" : "\n");
	}

	fprintRevision(f, modc, modv);
	    
	for (i = 0; i < modc; i++) {
	    memset(&metrics, 0, sizeof(Metrics));
	}
	for (i = 0; i < modc; i++) {
	    addMetrics(&metrics, modv[i]);
	}
	fprintMetrics(f, &metrics);
    } else {
	for (i = 0; i < modc; i++) {
	    if (! silent) {
		fprintf(f, "# %s module metrics (generated by smidump "
			SMI_VERSION_STRING ")\n\n", modv[i]->name);
	    }

	    fprintRevision(f, 1, modv+i);
    
	    memset(&metrics, 0, sizeof(Metrics));
	    addMetrics(&metrics, modv[i]);
	    fprintMetrics(f, &metrics);
	}
    }

    if (fflush(f) || ferror(f)) {
	perror("smidump: write error");
	exit(1);
    }

    if (output) {
	fclose(f);
    }
}



void
initMetrics()
{
    static SmidumpDriverOption opt[] = {
	{ "raw", OPT_FLAG, &raw, 0,
	  "generate raw statistics (no percentages)"},
        { 0, OPT_END, 0, 0 }
    };

    static SmidumpDriver driver = {
	"metrics",
	dumpMetrics,
	0,
	SMIDUMP_DRIVER_CANT_YANG,
	"metrics characterizing MIB modules",
	opt,
	NULL
    };
    
    smidumpRegisterDriver(&driver);
}
