clndf -- disk free space tracking program

J. Deters jad at dayton.UUCP
Wed Feb 13 12:23:34 AEST 1991


[ I am posting this for a friend who does not have net access.  Please
  make sure that any replies, flames, kudos or stray $$$ are mailed to:
  joel at crystal.mn.org.  Thank you, -j ]

This is an update to clndf as posted about 2-6-91.  We added a disk over
the weekend, and I discovered the "-a" option didn't work properly.  It
mostly worked, but it trashed part of the data you'd already collected.

While I was at it, I made some other changes, which are commented in the
"Revision History" section of the code.

-Joe

#!/bin/sh
# This is a shell archive (shar 3.47)
# made 02/12/1991 21:17 UTC by joel at crystal
# Source directory /users/joel/work/clean/out
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  12262 -rw-r--r-- clndf.c
#   2172 -rw-r--r-- clndf.readme
#     82 -rw-rw-rw- math.h
#    148 -rw-r--r-- skip.h
#     75 -rw-r--r-- xerror.c
#
# ============= clndf.c ==============
if test -f 'clndf.c' -a X"$1" != X"-c"; then
	echo 'x - skipping clndf.c (File already exists)'
else
echo 'x - extracting clndf.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'clndf.c' &&
/*
X * clndf: perform "df" tracking.  Maintains a database of the df
X *          output and issues errors if:
X *
X *          drastic drop noticed
X *          long-term decline noticed
X *          low disk noticed
X *
X * Author:
X *
X *      Joseph P. Larson
X *      Crystal Farms
X *      Park Place West, Suite 200
X *      6465 Wayzata Blvd.
X *      St. Louis Park, Mn 55426
X *
X * Copyright 1991 by Crystal Farms.  Permission is granted to distribute
X *      freely provided distribution is free-of-charge beyond relatively
X *      minor media charges and this comment remains intact.  Also, if
X *      you make changes you then distribute, please comment those
X *      changes as being yours.
X *
X * Suggested enhancements:
X *
X *      o Modify -r to display only disk space or inodes as requested.
X *      o Modify -r to display only requested disks.
X *      o Modify -r to avoid 132-column mode if it's not needed.
X *      o Modify warning thresholds so they aren't hard-coded.
X *      o Add option to skip warnings, etc.
X *
X * Revision History
X *
X *      7-23-90     jpl.    Created.
X *      2-6-91      jpl.    Added "-l" option.
X */
X
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <usr/skip.h>
#include <usr/math.h>
X
#define TM struct tm
X
#define FN_MAIN     "/users/joel/.df.dnames"
#define FN_D_OLD    "/users/joel/.df.data.o"
#define FN_D_NEW    "/users/joel/.df.data.n"
X
#define MAXDIRS     20
#define FNSIZE      80
X
char    dnames[MAXDIRS][FNSIZE];    /* Names of disks returned by df        */
long    bfree[MAXDIRS],             /* Free space on each disk              */
X        ifree[MAXDIRS];             /* Free inodes on each disk             */
short   dirs,                       /* Number of disks in dnames.           */
X        ptrs[MAXDIRS];              /* Used to sort dnames.                 */
X
char    ldnames[MAXDIRS][FNSIZE];   /* Names of disks being tracked         */
short   ldirs,                      /* Number of entries in ldnames         */
X        dolast;                     /* If > 0  => # entries to report       */
X
long    block_warn = 10000,         /* Free space threshold                 */
X        inode_warn =  2000,         /* Free inodes threshold                */
X        bfree10[10][MAXDIRS],       /* Free space, last 10 entries          */
X        ifree10[10][MAXDIRS],       /* Free inodes, last 10 entries         */
X        lastc;                      /* Number of bfree10 entries filled     */
X
float   long_threshold  = 0.60,     /* Used in "dwindling free space" msg   */
X        short_threshold = 0.85;     /* Used in "chopped free space" msg     */
X
main(argc,argv)
int argc;
char **argv;
{
X    char    c,
X            quitnow,
X            do_inodes,
X            do_upd,
X            do_rpt;
X    extern char *optarg;
X
X    quitnow = do_inodes = do_upd = do_rpt = 0;
X    load_disks();
X    dolast = 0;
X    while ((c = getopt(argc, argv, "a:d:l:iruh")) != EOF)
X        switch (c)
X        {
X            case 'a':   add_disk(optarg); quitnow = 1; break;
X            case 'd':   del_disk(optarg); quitnow = 1; break;
X            case 'l':   dolast = atoi(optarg); break;
X            case 'i':   do_inodes = 1; break;
X            case 'u':   do_upd = 1; break;
X            case 'r':   do_rpt = 1; break;
X            case 'h':
X            default:    usage(argv[0]);
X        }
X    
X    if (quitnow) exit(0);
X    gather_df();
X    dump_warnings();
X    if (do_upd)
X        update_data();
X    if (do_rpt)
X        report_data(do_inodes);
}
X
X
load_disks()
{
X    FILE    *f1;
X    char    buf[132];
X    short   ilen;
X
X    ldirs = 0;
X    if (!(f1 = fopen(FN_MAIN, "r")))
X        xerror(FN_MAIN);
X    while (fgets(buf, 132, f1))
X    {
X        ilen = strlen(buf) - 1;
X        if (buf[ilen] == '\n') buf[ilen] = 0;
X        strcpy(ldnames[ldirs++], buf);
X    }
X    fclose(f1);
}
X
add_disk(dn)
char *dn;
{
X    FILE    *f1, *f2;
X    long    ibuf[60], dummy[2];
X    short   i, where, rsize, wsize1, wsize2, wsize3;
X
X    for (where = 0; where < ldirs; where++)
X    {
X        i = strcmp(ldnames[where], dn);
X        if (!i)
X        {
X            fprintf(stderr, "Disk %s is already in the database.\n");
X            return;
X        }
X        if (i > 0) break;
X    }
X
X    if (!(f1 = fopen(FN_D_OLD, "r")))
X        xerror(FN_D_OLD);
X    if (!(f2 = fopen(FN_D_NEW, "w")))
X        xerror(FN_D_NEW);
X    rsize = (1 + 2 * ldirs);
X    wsize1 = (1 + 2 * where);
X    wsize2 = 2;
X    wsize3 = rsize - wsize1;
X    dummy[0] = dummy[1] = 0;
X
X    while (fread(ibuf, rsize, sizeof(long), f1))
X    {
X        fwrite(ibuf, wsize1, sizeof(long), f2);
X        fwrite(dummy, wsize2, sizeof(long), f2);
X        fwrite(&ibuf[wsize1], wsize3, sizeof(long), f2);
X    }
X    fclose(f1);
X    fclose(f2);
X    if (!(f1 = fopen(FN_MAIN, "w")))
X        xerror(FN_MAIN);
X    for (i = 0; i < ldirs; i++)
X    {
X        if (i == where)
X            fprintf(f1, "%s\n", dn);
X        fprintf(f1, "%s\n", ldnames[i]);
X    }
X    if (i == where)
X        fprintf(f1, "%s\n", dn);
X    fclose(f1);
X
X    unlink(FN_D_OLD);
X    link(FN_D_NEW, FN_D_OLD);
X    unlink(FN_D_NEW);
X    chmod(FN_D_OLD, 0644);
}
X
del_disk(dn)
char *dn;
{
}
X
gather_df()
/*
X * Get all the statistics from a call to "df".
X */
{
X    FILE    *file;
X    char    *tname,
X            *cptr,
X            buf[132];
X    long    ibuf[60];
X    short   i, j, p,
X            count;
X    int     cmp_ptrs();
X
X    tname = tmpnam(0);
X    sprintf(buf, "df >%s", tname);
X    system(buf);
X
X    dirs = 0;
X    if (!(file = fopen(tname, "r")))
X        xerror(tname);
X    while (fgets(buf, 132, file))
X    {
X        cptr = buf;
X        for (i = 0; cptr[i] && (cptr[i] != ' ') && (cptr[i] != '('); i++)
X            dnames[dirs][i] = cptr[i];
X        dnames[dirs][i] = 0;
X        while (*cptr != ':') cptr++;
X        cptr++;
X        SKIPWHITE(cptr);
X        bfree[dirs] = atoi(cptr);
X        SKIPTOWHITE(cptr);
X        SKIPWHITE(cptr);
X        SKIPTOWHITE(cptr);
X        SKIPWHITE(cptr);
X        ifree[dirs] = atoi(cptr);
X        dirs++;
X    }
X    fclose(file);
X    unlink(tname);
X    for (i = 0; i < dirs; i++)
X        ptrs[i] = i;
X    qsort(ptrs, dirs, sizeof(short), cmp_ptrs);
X
X    if (!(file = fopen(FN_D_OLD, "r")))
X        xerror(FN_D_OLD);
X    count = (1 + ldirs * 2);
X    fseek(file, -10 * count * sizeof(long), 2);
X    for (lastc = 0; lastc < 10; lastc++)
X    {
X        if (fread(ibuf, count, sizeof(long), file) <= 0) break;
X        for (j = 0, p = 1; j < ldirs; j++)
X        {
X            bfree10[lastc][j] = ibuf[p++];
X            ifree10[lastc][j] = ibuf[p++];
X        }
X    }
X    fclose(file);
}
X
cmp_ptrs(p1, p2)
short *p1, *p2;
{
X    return(strcmp(dnames[*p1], dnames[*p2]));
}
X
X
dump_df()
/*
X * Dump the collected info.
X */
{
X    short   i, j;
X
X    for (j = 0; j < dirs; j++)
X    {
X        i = ptrs[j];
X        printf("%-24s  %8d blocks free  %8d inodes free\n",
X            dnames[i], bfree[i], ifree[i]);
X    }
}
X
dump_warnings()
/*
X * Go through the data.  Dump:
X *
X *      Any disks that are dangerously low on space.
X *      Any disks that suddenly dropped space over the last 3 updates.
X *      Any disks that have been dwindling severely over the last 10 updates.
X */
{
X    short   i, j, k, l, l3;
X    long    bfm, ifm, diddump;
X    char    line[132];
X
X    diddump = 0;
X    l3 = MAX(0, lastc - 3);
X    for (j = 0; j < dirs; j++)
X    {
X        i = ptrs[j];
X        if (bfree[i] < block_warn)
X        {
X            if (!diddump++) dump_df();
X            fprintf(stderr, "Low free space on : %s\n", dnames[i]);
X        }
X        if (ifree[i] < inode_warn)
X        {
X            if (!diddump++) dump_df();
X            fprintf(stderr, "Low inode count on: %s\n", dnames[i]);
X        }
X        for (k = 0; k < ldirs; k++)
X            if (!strcmp(ldnames[k], dnames[i]))
X            {
X                if (bfree10[0][k] * long_threshold > bfree[i])
X                {
X                    if (!diddump++) dump_df();
X                    fprintf(stderr, "Dwindling free space on: %s\n",dnames[i]);
X                    fprintf(stderr, "Was: %d.  Now: %d\n",
X                            bfree10[0][k], bfree[i]);
X                }
X                if (ifree10[0][k] * long_threshold > ifree[i])
X                {
X                    if (!diddump++) dump_df();
X                    fprintf(stderr, "Dwindling inodes on    : %s\n",dnames[i]);
X                    fprintf(stderr, "Was: %d.  Now: %d\n", 
X                            ifree10[0][k], ifree[i]);
X                }
X                bfm = ifm = 0;
X                for (l = l3; l < lastc; l++)
X                {
X                    bfm = MAX(bfm, bfree10[l][k]);
X                    ifm = MAX(ifm, ifree10[l][k]);
X                }
X                if (bfm * short_threshold > bfree[i])
X                {
X                    if (!diddump++) dump_df();
X                    fprintf(stderr, "Chopped free space on: %s\n",dnames[i]);
X                    fprintf(stderr, "Was: %d.  Now: %d\n", bfm, bfree[i]);
X                }
X                if (ifm * short_threshold > ifree[i])
X                {
X                    if (!diddump++) dump_df();
X                    fprintf(stderr, "Chopped inodes on    : %s\n",dnames[i]);
X                    fprintf(stderr, "Was: %d.  Now: %d\n", ifm, ifree[i]);
X                }
X                break;
X            }
X    }
}
X
update_data()
/*
X * Update the collected info.
X */
{
X    FILE    *f1;
X    int     now;
X    long    ibuf[50];
X    short   i, j, p;
X
X    if (!(f1 = fopen(FN_D_OLD, "a")))
X        xerror(FN_D_OLD);
X    time(&now);
X    ibuf[0] = now;
X    p = 1;
X    for (i = 0; i < ldirs; i++)
X    {
X        for (j = 0; j < dirs; j++)
X            if (!strcmp(ldnames[i], dnames[j]))
X            {
X                ibuf[p++] = bfree[j];
X                ibuf[p++] = ifree[j];
X            }
X    }
X    fwrite(ibuf, p, sizeof(long), f1);
X    fclose(f1);
}
X
report_data(do_inodes)
char *do_inodes;
/*
X * Generate a tracking report.
X */
{
X    FILE    *file;
X    TM      *tmptr;
X    long    ibuf[60], bfr, ifr;
X    int     count, then;
X    short   i, p, lcnt, width;
X    char    line[80];
X
X    if (isatty(1))
X    {
X        width = ldirs * 8 + 10;
X        if (do_inodes)
X            width = ldirs * 13 + 12;
X        if (width > 80)
X        {
X            printf("\nHit return to continue.\n");
X            gets(line);
X            printf("%c[?3h\n", 27);
X        }
X    }
X    head_dirs(do_inodes);
X
X    if (!(file = fopen(FN_D_OLD, "r")))
X        xerror(FN_D_OLD);
X    count = (1 + ldirs * 2);
X    lcnt = 0;
X    if (dolast)
X        fseek(file, -(sizeof(long) * count * dolast), 2);
X    while (1)
X    {
X        if (fread(ibuf, count, sizeof(long), file) <= 0) break;
X        if (!(++lcnt % 18)) head_dirs(do_inodes);
X        then = ibuf[0];
X        tmptr = localtime(&then);
X        printf("%2d-%2d-%2d",
X            tmptr->tm_mon+1, tmptr->tm_mday, tmptr->tm_year);
X        for (i = 0, p = 1; i < ldirs; i++)
X        {
X            bfr = ibuf[p++];
X            ifr = ibuf[p++];
X            if (!do_inodes)
X                printf("| %6d", bfr);
X            else
X                printf("|%6d %5d", bfr, ifr);
X        }
X        printf("|\n");
X    }
X    fclose(file);
X
X    if (isatty(1) && (width > 80))
X    {
X        printf("\nHit return to continue.\n");
X        gets(line);
X        printf("%c[?3l\n", 27);
X    }
}
X
head_dirs(do_inodes)
char do_inodes;
{
X    short   i;
X    char    *c1, *c2;
X
X    printf("        ");
X    for (i = 0; i < ldirs; i++)
X    {
X        for (c1 = c2 = ldnames[i]; *c2; c2++)
X            if (*c2 == '/')
X            {
X                c1 = c2;
X                if (*(c1+1)) c1++;
X            }
X        if (!do_inodes)
X            printf("|%7s", c1);
X        else
X            printf("|%9s   ", c1);
X    }
X    printf("|\n        ");
X    for (i = 0; i < ldirs; i++)
X        if (!do_inodes)
X            printf("| Blocks");
X        else
X            printf("|Blocks Inode");
X    printf("|\n        ");
X    for (i = 0; i < ldirs; i++)
X    {
X        if (!do_inodes)
X            printf("| ------");
X        else
X            printf("|------ -----");
X    }
X    printf("|\n");
}
X
usage(arg)
char *arg;
{
X    fprintf(stderr, "\
Usage: %s [-a disk] [-d disk] [-u] [-r]\n\n\
X       Errors imply -r.  -u updates the database.\n\
X       Use -a or -d to manage the list of disks by mount point.\n", arg);
X    exit(-1);
}
SHAR_EOF
chmod 0644 clndf.c ||
echo 'restore of clndf.c failed'
Wc_c="`wc -c < 'clndf.c'`"
test 12262 -eq "$Wc_c" ||
	echo 'clndf.c: original size 12262, current size' "$Wc_c"
fi
# ============= clndf.readme ==============
if test -f 'clndf.readme' -a X"$1" != X"-c"; then
	echo 'x - skipping clndf.readme (File already exists)'
else
echo 'x - extracting clndf.readme (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'clndf.readme' &&
Enclosed is clndf.  I use "cln" as the prefix for programs that help me keep
my system clean of problems.
X
This programs lets you monitor free disk space and free inodes by disk.
Each morning, I have a cron job that does a "clndf -u" (update).  This
updates the database file, keeping track of the date and amount of free
space on each of my disks.  Then, whenever I'm wondering what my free
space has been doing lately, I can do a "clndf -r" to get a report.
X
Other options are:
X
X	-a diskname		Add a disk.  (Uses the mount point...)
X	-l lastcount	When using -r, only list the last lastcount entries.
X
The "-d" option is not implemented.
X
To implement on your machine:
X
X	1. edit clndf.c, changing the path names for the FN_* #define variables
X	2. touch those files (create empty versions)
X	3. "make clndf".
X	4. clndf -a <dname1> -a <dname2> .. -a <dnamen> to tell clndf
X		which disks on your system you want to monitor.  Remember to
X		use the mount points.
X	5. Add a "clndf -u" to your crontab every morning (or whatever
X		granularity you wish -- you could run it every minute if you
X		*really* wanted to).
X
Below is the first two lines of a "df" command on my system.  If your system
is different, you'll need to modify function gather_df() as appropriate.
X
/         (/dev/dsk/10s1   ):    48416 blocks    47070 i-nodes
/data1    (/dev/dsk/20se   ):    39400 blocks    12272 i-nodes
X
clndf is not column-oriented, but instead skips white space as needed.
X
There may be bugs.  If you find any, let me know.  If you fix any, please
send me a note so I can fix my copy.  If you add features, ditto.
X
clndf assumes you are on a vt100-like terminal (it tosses your terminal
into and out of 132-column mode to do the report).  That's life.
X
clndf runs cleanly on an NCR Tower 32 running either SysVR2 or SysVR3.
I try to keep the code clean, so it should run on most SysV-like machines.
But be careful with chars -- some machines assume unsigned chars, so my
"while (( c = getopt..." loop never exits.
X
Note: to help those weenies who don't set their tabstops to 4, I've
detabbed the source.  If you want entab/detab source code, drop me a note.
X
-Joe Larson
February 6, 1991
SHAR_EOF
chmod 0644 clndf.readme ||
echo 'restore of clndf.readme failed'
Wc_c="`wc -c < 'clndf.readme'`"
test 2172 -eq "$Wc_c" ||
	echo 'clndf.readme: original size 2172, current size' "$Wc_c"
fi
# ============= math.h ==============
if test -f 'math.h' -a X"$1" != X"-c"; then
	echo 'x - skipping math.h (File already exists)'
else
echo 'x - extracting math.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'math.h' &&
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define MAX(a,b)	((a) > (b) ? (a) : (b))
SHAR_EOF
chmod 0666 math.h ||
echo 'restore of math.h failed'
Wc_c="`wc -c < 'math.h'`"
test 82 -eq "$Wc_c" ||
	echo 'math.h: original size 82, current size' "$Wc_c"
fi
# ============= skip.h ==============
if test -f 'skip.h' -a X"$1" != X"-c"; then
	echo 'x - skipping skip.h (File already exists)'
else
echo 'x - extracting skip.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'skip.h' &&
#define SKIPWHITE(c)	while (isspace(*c)) c++;
#define SKIPTOWHITE(c)	while (*c && !isspace(*c)) c++;
#define SKIPDIGITS(c)	while (isdigit(*c)) c++;
SHAR_EOF
chmod 0644 skip.h ||
echo 'restore of skip.h failed'
Wc_c="`wc -c < 'skip.h'`"
test 148 -eq "$Wc_c" ||
	echo 'skip.h: original size 148, current size' "$Wc_c"
fi
# ============= xerror.c ==============
if test -f 'xerror.c' -a X"$1" != X"-c"; then
	echo 'x - skipping xerror.c (File already exists)'
else
echo 'x - extracting xerror.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'xerror.c' &&
xerror(arg)
char *arg;
{
X	extern int errno;
X
X	perror(arg);
X	exit(errno);
}
SHAR_EOF
chmod 0644 xerror.c ||
echo 'restore of xerror.c failed'
Wc_c="`wc -c < 'xerror.c'`"
test 75 -eq "$Wc_c" ||
	echo 'xerror.c: original size 75, current size' "$Wc_c"
fi
exit 0
-- 
J. Deters
INTERNET:  jad at dayton.DHDSC.MN.ORG  ATT:   612-375-3116
UUCP:  ...!bungia!dayton!jad        USPS:  700 Nicollet Mall/MIS 1060
ICBM:  44^58'36"N by 93^16'14"W            Minneapolis, MN  55402



More information about the Alt.sources mailing list