clndf - device free space history tracker
J. Deters
jad at dayton.UUCP
Fri Feb 8 02:44:05 AEST 1991
[ I am posting this for a friend. Please re-direct replies, comments,
flames, etc. to joel at crystal.mn.org. You may continue to mail money,
jewelry, deeds to property, etc. to me, however. Joe won't know, he
doesn't have net access! ;-) -j ]
---------------------------------------------------------------
When was the last time you were wondering what happened to all the
free disk space you had just last week? How often have you wanted
to tell your boss you *really* needed that neat 1 Gig disk Sea Change
is selling, but just didn't have the ammunition.
Well, enclosed is clndf, a program that will help you track your free
disk space. Disk space getting low and you want to estimate when
you'll run out, just do a "clndf -r" and see what the trend is.
Coulda sworn your had more space on your machine yesterday, just ask
clndf what it was.
To use clndf, unpack the archive, build as per the directions in the
readme, and add a line to your crontab to start tracking your space.
Next time a disk starts to get a little low, just ask clndf for a
report.
Comments to joel at crystal.mn.org.
#!/bin/sh
# This is a shell archive (shar 3.47)
# made 02/06/1991 23:20 UTC by joel at crystal
# Source directory /users/mpls/mis/joel/work/clean/out
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 2172 -rw-r--r-- clndf.readme
# 11724 -rw-r--r-- clndr.c
# 82 -rw-rw-rw- math.h
# 148 -rw-r--r-- skip.h
#
# ============= 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
# ============= clndr.c ==============
if test -f 'clndr.c' -a X"$1" != X"-c"; then
echo 'x - skipping clndr.c (File already exists)'
else
echo 'x - extracting clndr.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'clndr.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/mpls/mis/joel/.df.dnames"
#define FN_D_OLD "/users/mpls/mis/joel/.df.data.o"
#define FN_D_NEW "/users/mpls/mis/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_upd,
X do_rpt;
X extern char *optarg;
X
X quitnow = do_upd = do_rpt = 0;
X load_disks();
X dolast = 0;
X while ((c = getopt(argc, argv, "a:d:l:ruh")) != 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 '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();
}
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 = (2 * (ldirs - where));
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[2 + where*2], 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 if (diddump)
X {
X printf("\nHit return to continue.\n");
X gets(line);
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()
/*
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;
X char line[80];
X
X if (isatty(1))
X printf("%c[?3h\n", 27);
X head_dirs();
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();
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 printf("|%6d %5d", bfr, ifr);
X }
X printf("|\n");
X }
X fclose(file);
X
X if (isatty(1))
X {
X printf("\nHit return to continue.\n");
X gets(line);
X printf("%c[?3l\n", 27);
X }
}
X
head_dirs()
{
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 printf("|%9s ", c1);
X }
X printf("|\n ");
X for (i = 0; i < ldirs; i++)
X printf("|Blocks Inode");
X printf("|\n ");
X for (i = 0; i < ldirs; i++)
X printf("|------ -----");
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);
}
X
xerror(arg)
char *arg;
{
X extern int errno;
X
X perror(arg)
X exit(errno);
}
SHAR_EOF
chmod 0644 clndr.c ||
echo 'restore of clndr.c failed'
Wc_c="`wc -c < 'clndr.c'`"
test 11724 -eq "$Wc_c" ||
echo 'clndr.c: original size 11724, 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
exit 0
----
Joe Larson joel at crystal.mn.org 612-542-1245(w)
Crystal Foods 612-591-1037(h)
6465 Wayzata Blvd
St. Louis Park, Mn 55426
--
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