/*-
 * Test for ru_maxrss field of getrusage().
 *
 * Copyright 2007 Google Inc. (Frank Mayhar <fmayhar@google.com>)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notices, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notices, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE AUTHORS BE LIABLE.
 *
 * What it's for:
 *	While the ru_maxrss field of struct rusage is not mandated or
 *	specified by POSIX, it is implemented in several Unix derivatives and
 *	is a useful (and relatively lightweight) way to get that information.
 *	Most implementations define the value of the field in units of
 *	kilobytes; some may use pages.  This interface test assumes that the
 *	units are kilobytes, not pages, and verifies that it correctly reports
 *	the memory usage of terminated children and grandchildren.  It does
 *	not yet verify whether shared pages are included in the count, nor
 *	does it test its behavior in multithreaded applications.
 *
 *	As this program uses mlockall(), it must be run as root.
 *
 *      For reference, here are links to various Unices' definitions of
 *	ru_maxrss:
 *	    AIX: http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.doc/libs/basetrf1/getrusage_64.htm
 *	    Irix:  http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?cmd=getdoc&coll=0650&db=man&fname=3%20getrusage
 *	    FreeBSD: http://www.freebsd.org/cgi/man.cgi?query=getrusage&manpath=FreeBSD+6.2-RELEASE
 *
 *	The test has been run against the following operating systems:
 *		FreeBSD 6.2		PASS
 *		Linux 2.6 (unpatched)	FAIL (unimplemented)
 *		Linux 2.6 (patched)	PASS
 *		MacOSX/Darwin 8.10.1	FAIL (unimplemented)
 *		Solaris/SunOS 5.10	FAIL (unimplemented)
 *
 *	The patch to Linux 2.6 to implement ru_maxrss can be found at
 *	http://www.exit.com/Archives/Linux/.
 *
 *	TODO:  Run this test against systems running 32-bit binaries and
 *	64-bit kernels.  Currently Linux appears to merely truncate the
 *	64-bit fields in that case, which results in garbage if the value
 *	has exceeded 32 bits.
 *
 * To build and run:
 *	cc getrusage-test.c -o getrusage-test
 *	./getrusage-test
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/mman.h>

#define BUFSIZE 10240*4

static struct timespec sleeptime = { 0, 200000000 }; /* About 2/10 second.   */

static int childsigflag = 0;
static int childfailflag = 0;

/*
 * childsignalled()
 *	Signal handler for RUSAGE_CHILDREN tests.
 *
 * Description:
 *	This is used by a parent to field signals from child processes.  It
 *	handles SIGUSR1, which informs the parent that the signalling child
 *	has reached the next expected state of execution, and SIGHUP, which
 *	informs the parent that the child has failed.
 */
void
childsignalled(int sig)
{
	if (sig == SIGUSR1)
		childsigflag++;
	if (sig == SIGHUP)
		childfailflag++;
	signal(SIGUSR1, childsignalled);
}

/*
 * getabuffer()
 *	Get a buffer of "size" kilobytes.
 *
 * Description:
 *	Gets the buffer, zeros it.
 */
char *
getabuffer(int size)
{
	char *buf;

	size <<= 10;			/* Convert to bytes (from kb).        */
	if ((buf = malloc(size)) == NULL) {
		perror("malloc(): ");
		return(NULL);
	}
	memset(buf, 0xba, size);	/* Fill the memory.                   */
	return(buf);
}
	
/*
 * rusage_self()
 *	Tests the RUSAGE_SELF parameter.
 *
 * Description:
 *	Uses RUSAGE_SELF to get ru_maxrss for the current process, allocates
 *	a buffer of the passed size (in kilobytes), then gets the field again.
 *	Passes if the new value will be at least 'bufsize' greater than the
 *	old value, fails otherwise.
 */
void
rusage_self(int testnum, int bufsize)
{
	struct rusage r;
	char *buf;
	int rc;
	int old_maxrss;

	if (mlockall(MCL_FUTURE) < 0) {
		perror("mlockall");
		printf("%d:  rusage_self:  UNRESOLVED\n", testnum);
		exit(1);
	}
	if ((rc = getrusage(RUSAGE_SELF, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_self:  UNRESOLVED\n", testnum);
		return;
	}
	old_maxrss = r.ru_maxrss;
	buf = getabuffer(bufsize);
	if ((rc = getrusage(RUSAGE_SELF, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_self:  UNRESOLVED\n", testnum);
		return;
	}
	printf("before %d after: %d\n", old_maxrss, r.ru_maxrss);
	if (r.ru_maxrss >= old_maxrss + bufsize)
		printf("%d:  rusage_self:  PASS\n", testnum);
	else
		printf("%d:  rusage_self:  FAIL\n", testnum);
	free(buf);
}

/*
 * rusage_children()
 *	Tests the RUSAGE_CHILDREN parameter for a child.
 *
 * Description:
 *	This routine uses RUSAGE_CHILDREN to get the ru_maxrss field for
 *	a terminated, waited-for child.  It forks a child, then uses
 *	signalling to coordinate between the parent that child.  The parent
 *	waits for a signal from the child that it reached the next stage of
 *	processing; when it receives the signal the parent calls getrusage()
 *	to collect the ru_maxrss value at that stage and does any other
 *	necessary processing (such as calling wait()).  The child first sleeps
 *	in order to allow the parent to go to sleep, then it alternately
 *	signals the parent and sleeps or performs the appropriate operation.
 *	Using this process, values are gathered before the child does its
 *	allocation, after the allocation but before the child exits, after he
 *	has told us he is exiting but before we wait() for him and after we
 *	have wait()ed for him.  The test passes if all accumulated values but
 *	the last are less than the passed bufsize and the last is at least as
 *	large as bufsize.
 */
void
rusage_children(int testnum, int bufsize)
{
	struct rusage r;
	pid_t pid;
	int rc;
	int dad_maxrss, kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4;
	int dad_minflt, kid_minflt1, kid_minflt2, kid_minflt3, kid_minflt4;
	int dad_nvcsw, kid_nvcsw1, kid_nvcsw2, kid_nvcsw3, kid_nvcsw4;
	char *buf;

	if ((rc = getrusage(RUSAGE_SELF, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	dad_maxrss = r.ru_maxrss;	/* Before fork.                       */
	dad_minflt = r.ru_minflt;	/* Before fork.                       */
	dad_nvcsw = r.ru_nvcsw;	/* Before fork.                       */
	childsigflag = 0;
	signal(SIGUSR1, childsignalled);
	signal(SIGHUP, childsignalled);
	pid = fork();
	if (pid < 0) {
		perror("fork: ");
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	if (pid == 0) {			/* Child is us.                       */
		pid_t dad = getppid();

		if (mlockall(MCL_FUTURE) < 0) {
			perror("mlockall");
			kill(dad, SIGHUP);
			exit(1);
		}
		nanosleep(&sleeptime, NULL);
		kill(dad, SIGUSR1);	/* Tell Dad we're ready.              */
		nanosleep(&sleeptime, NULL);
		buf = getabuffer(bufsize);
		if (buf == NULL) {
			kill(dad, SIGHUP);
			exit(1);
		}
		kill(dad, SIGUSR1);	/* Tell Dad we got memory.            */
		nanosleep(&sleeptime, NULL);
		kill(dad, SIGUSR1);	/* Tell Dad we're about to exit.      */
		exit(0);
	}
	/* Wait for the kid to tell us he's ready. */
	while (childsigflag != 1)
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss1 = r.ru_maxrss;
	kid_minflt1 = r.ru_minflt;
	kid_nvcsw1 = r.ru_nvcsw;
	/* Wait for the kid to tell us he did the allocate. */
	while (childsigflag != 2) 
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss2 = r.ru_maxrss;
	kid_minflt2 = r.ru_minflt;
	kid_nvcsw2 = r.ru_nvcsw;
	/* Wait for the kid to tell us he's exiting. */
	while (childsigflag != 3)
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_children:  UNRESOLVED\n", testnum);
		return;
	}
	nanosleep(&sleeptime, NULL);	/* He should exit while we sleep.     */
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_children:  UNRESOLVED\n");
		return;
	}
	kid_maxrss3 = r.ru_maxrss;
	/*kid_minflt3 = r.ru_minflt;*/
	/*kid_nvcsw3 = r.ru_nvcsw;*/
	wait(&rc);			/* Reap him.                          */
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_children:  UNRESOLVED\n");
		return;
	}
	kid_maxrss4 = r.ru_maxrss;
	kid_minflt4 = r.ru_minflt;
	kid_nvcsw4 = r.ru_nvcsw;
	printf("flag %d dad %d, kid1 %d, kid2 %d, kid3 %d kid4 %d sz %d\n", childsigflag, dad_maxrss,
		kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4, bufsize);
	/*
	printf("\tdad %x, kid1 %x, kid2 %x, kid3 %x kid4 %x\n", dad_minflt,
		kid_minflt1, kid_minflt2, kid_minflt3, kid_minflt4);
	printf("\tdad %x, kid1 %x, kid2 %x, kid3 %x kid4 %x\n", dad_nvcsw,
		kid_nvcsw1, kid_nvcsw2, kid_nvcsw3, kid_nvcsw4);
	*/
	if (kid_maxrss1 == kid_maxrss2 &&
	    kid_maxrss2 == kid_maxrss3 && kid_maxrss4 > kid_maxrss3 &&
	    kid_maxrss4 >= bufsize)
		printf("%d:  rusage_children:  PASS\n", testnum);
	else
		printf("%d:  rusage_children:  FAIL\n", testnum);
}

/*
 * rusage_grandchildren()
 *	Tests the RUSAGE_CHILDREN parameter for a grandchild.
 *
 * Description:
 *	This routine uses RUSAGE_CHILDREN to get the ru_maxrss field for
 *	a terminated, waited-for grandchild.  Like the above routine, it uses
 *	signalling to control execution and samples the ru_maxrss field at
 *	various points of the process.  Also like the above routine, the test
 *	passes if all accumulated values but the last are less than the passed
 *	bufsize and the last is at least as large as bufsize.
 */
void
rusage_grandchildren(int testnum, int bufsize)
{
	struct rusage r;
	pid_t pid;
	pid_t granddad;
	int rc;
	int granddad_maxrss, kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4, kid_maxrss5;
	char *buf;

	if ((rc = getrusage(RUSAGE_SELF, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	granddad_maxrss = r.ru_maxrss;	/* Before fork.                       */
	childsigflag = 0;
	childfailflag = 0;
	granddad = getpid();
	signal(SIGUSR1, childsignalled);
	signal(SIGHUP, childsignalled);
	pid = fork();
	if (pid < 0) {
		perror("fork: ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n");
		return;
	}
	if (pid == 0) {			/* Child is us.                       */
		pid_t dad = getpid();

		if (mlockall(MCL_FUTURE) < 0) {
			perror("mlockall");
			kill(granddad, SIGHUP);
			exit(1);
		}
		nanosleep(&sleeptime, NULL);
		kill(granddad, SIGUSR1); /* Tell our dad we're about to fork. */
		nanosleep(&sleeptime, NULL);
		pid = fork();
		if (pid < 0) {		/* Fork failed.                       */
			kill(granddad, SIGHUP);
			exit(1);
		}
		if (pid != 0) {		/* We're dad.                         */
			/* Wait for the kid to tell us he's finished. */
			while (childsigflag != 1)
				pause();
			if (childfailflag != 0)
				exit(1);
			kill(granddad, SIGUSR1); /* Kid exited, no reap.      */
			nanosleep(&sleeptime, NULL);
			wait(&rc);
			kill(granddad, SIGUSR1); /* Kid reaped.               */
			nanosleep(&sleeptime, NULL);
			kill(granddad, SIGUSR1); /* About to exit.            */
			exit(0);
			/*NOTREACHED*/
		}
		/* We're the child. */
		if (mlockall(MCL_FUTURE) < 0) {
			perror("mlockall");
			kill(dad, SIGHUP);
			exit(1);
		}
		nanosleep(&sleeptime, NULL);
		kill(granddad, SIGUSR1); /* Tell granddad we're ready.        */
		nanosleep(&sleeptime, NULL);
		buf = getabuffer(bufsize);
		kill(granddad, SIGUSR1); /* Tell granddad we got memory.      */
		nanosleep(&sleeptime, NULL);
		kill(dad, SIGUSR1);	/* Tell dad we're about to exit.     */
		exit(0);
	}
	/* Wait for the grandkid to tell us he's ready. */
	while (childsigflag != 1)
		pause();
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss1 = r.ru_maxrss;
	/* Wait for the grandkid to tell us he did the allocate. */
	while (childsigflag != 2) 
		pause();
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss2 = r.ru_maxrss;
	/* Wait for the kid to tell us his kid exited. */
	while (childsigflag != 3)
		pause();
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss3 = r.ru_maxrss;
	/* Wait for the kid to tell us he reaped his kid. */
	while (childsigflag != 4)
		pause();
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss4 = r.ru_maxrss;
	/* Wait for the kid to tell us he's exiting. */
	while (childsigflag != 5)
		pause();
	wait(&rc);			/* Reap him.                          */
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_grandchildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss5 = r.ru_maxrss;
	printf("flag %d granddad %d, kid1 %d, kid2 %d, kid3 %d kid4 %d kid5 %d sz %d\n",
		childsigflag, granddad_maxrss,
		kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4, kid_maxrss5,
		bufsize);
	if (kid_maxrss1 == kid_maxrss2 &&
	    kid_maxrss2 == kid_maxrss3 && kid_maxrss3 == kid_maxrss4 &&
	    kid_maxrss5 > kid_maxrss4 && kid_maxrss5 >= bufsize)
		printf("%d:  rusage_grandchildren:  PASS\n", testnum);
	else
		printf("%d:  rusage_grandchildren:  FAIL\n", testnum);
}

/*
 * rusage_ignorechildren()
 *	Tests the RUSAGE_CHILDREN parameter for a child when SIGCHLD is
 *	ignored.
 *
 * Description:
 *	This routine uses RUSAGE_CHILDREN to verify that the ru_maxrss field
 *	doesn't change when a child exits while SIGCHLD is ignored.  Like the
 *	previous routines, it uses signalling to control execution and samples
 *	the ru_maxrss field at various points of the process.  The test
 *	passes if all accumulated values are less than the passed bufsize.
 */
void
rusage_ignorechildren(int testnum, int bufsize)
{
	struct rusage r;
	pid_t pid;
	int rc;
	int dad_maxrss, kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4;
	int dad_minflt, kid_minflt1, kid_minflt2, kid_minflt3;
	int dad_nvcsw, kid_nvcsw1, kid_nvcsw2, kid_nvcsw3;
	char *buf;

	if ((rc = getrusage(RUSAGE_SELF, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	dad_maxrss = r.ru_maxrss;	/* Before fork.                       */
	/*dad_minflt = r.ru_minflt;*/	/* Before fork.                       */
	/*dad_nvcsw = r.ru_nvcsw;*/	/* Before fork.                       */
	childsigflag = 0;
	signal(SIGUSR1, childsignalled);
	signal(SIGHUP, childsignalled);
	signal(SIGCHLD, SIG_IGN);
	pid = fork();
	if (pid < 0) {
		perror("fork: ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	if (pid == 0) {			/* Child is us.                       */
		pid_t dad = getppid();

		if (mlockall(MCL_FUTURE) < 0) {
			perror("mlockall");
			kill(dad, SIGHUP);
			exit(1);
		}
		nanosleep(&sleeptime, NULL);
		kill(dad, SIGUSR1);	/* Tell Dad we're ready.              */
		nanosleep(&sleeptime, NULL);
		buf = getabuffer(bufsize);
		if (buf == NULL) {
			kill(dad, SIGHUP);
			exit(1);
		}
		kill(dad, SIGUSR1);	/* Tell Dad we got memory.            */
		nanosleep(&sleeptime, NULL);
		kill(dad, SIGUSR1);	/* Tell Dad we're about to exit.      */
		exit(0);
	}
	/* Wait for the kid to tell us he's ready. */
	while (childsigflag != 1)
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss1 = r.ru_maxrss;
	/*kid_minflt1 = r.ru_minflt;*/
	/*kid_nvcsw1 = r.ru_nvcsw;*/
	/* Wait for the kid to tell us he did the allocate. */
	while (childsigflag != 2) 
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	kid_maxrss2 = r.ru_maxrss;
	/*kid_minflt2 = r.ru_minflt;*/
	/*kid_nvcsw2 = r.ru_nvcsw;*/
	/* Wait for the kid to tell us he's exiting. */
	while (childsigflag != 3)
		pause();
	if (childfailflag != 0) {
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n", testnum);
		return;
	}
	nanosleep(&sleeptime, NULL);	/* He should exit while we sleep.     */
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n");
		return;
	}
	kid_maxrss3 = r.ru_maxrss;
	/*kid_minflt3 = r.ru_minflt;*/
	/*kid_nvcsw3 = r.ru_nvcsw;*/
	wait(&rc);			/* Reap him.                          */
	if ((rc = getrusage(RUSAGE_CHILDREN, &r)) < 0) {
		perror("getrusage(): ");
		printf("%d:  rusage_ignorechildren:  UNRESOLVED\n");
		return;
	}
	kid_maxrss4 = r.ru_maxrss;
	/*kid_minflt4 = r.ru_minflt;*/
	/*kid_nvcsw4 = r.ru_nvcsw;*/
	printf("flag %d dad %d, kid1 %d, kid2 %d, kid3 %d kid4 %d sz %d\n", childsigflag, dad_maxrss,
		kid_maxrss1, kid_maxrss2, kid_maxrss3, kid_maxrss4, bufsize);
	/*
	printf("\tdad %d, kid1 %d, kid2 %d, kid3 %d \n", dad_minflt,
		kid_minflt1, kid_minflt2, kid_minflt3);
	printf("\tdad %d, kid1 %d, kid2 %d, kid3 %d \n", dad_nvcsw,
		kid_nvcsw1, kid_nvcsw2, kid_nvcsw3);
	*/
	if (kid_maxrss1 == kid_maxrss2 &&
	    kid_maxrss2 == kid_maxrss3 && kid_maxrss3 == kid_maxrss4)
		printf("%d:  rusage_ignorechildren:  PASS\n", testnum);
	else
		printf("%d:  rusage_ignorechildren:  FAIL\n", testnum);
}

int
main(void)
{
	int testnum;

	testnum = 1;
	/*
	 * Call each test using successively larger buffers, so that earlier
	 * allocations don't eclipse later ones.
	 */
	rusage_self(testnum++, BUFSIZE/4);
	rusage_grandchildren(testnum++, BUFSIZE/3);
	rusage_children(testnum++, BUFSIZE/2);
	rusage_ignorechildren(testnum++, BUFSIZE);
	return 0;
}
