#!mkcmd
# $Id: unixstats-HPUX11.m,v 1.9 2008/07/10 18:07:33 ksb Exp $
# unixstats for
# HP-UX * B.11.11 U 9000/800 * unlimited-user license
#
# $Compile: %b -mMkcmd -d hpux11 %f && %b -m%m hpux11.c
# $Mkcmd(*): : ${BUD:=../../bin/rrdup} && ${mkcmd-mkcmd} -n %s -I ${BUD} %f && rsync $BUD/machine.h machine.h && perl -pi -e "s/UL,/L,/" %s.c
# $Clean: ${rm-rm} hpux11 hpux11.[ch]
#
comment "%c%kCompile: $%{cc-cc%} %%f -o %%F"
from '<sys/types.h>'
from '<sys/param.h>'
from '<sys/pstat.h>'
from '<sys/unistd.h>'
from '<sysexits.h>'
# KI instrumentation interface, poorly documented
from '<sys/dk.h>'
from '<stdlib.h>'
from '<string.h>'
from '<stdio.h>'
from '<errno.h>'

# See the mkcmd -I option for the path to this module, ususally
# ../../bin/rrdup or in mkcmd's util directory.
require "rrrdupdate.m"
require "std_help.m" "std_version.m"

basename "unixstats" ""
%i
static char rcsid[] =
	"$Id: unixstats-HPUX11.m,v 1.9 2008/07/10 18:07:33 ksb Exp $";
%%

type "seconds" 'p' {
	named "iDelay" "pcDelay"
	track "fPersist"
	init "0"
	param "delay"
	help "be persistent, rather than running only once, sleeping delay seconds between updates"
}

%i
#if !defined(CP_USER)
#define CP_USER 	0	/* user time of normal process */
#endif
#if !defined(CP_NICE)
#define CP_NICE 	1	/* user time at nice prio */
#endif
#if !defined(CP_SYS)
#define CP_SYS		2	/* kernel calls */
#endif
#if !defined(CP_IDLE)
#define CP_IDLE 	3	/* idle time */
#endif
#if !defined(CP_WAIT)
#define CP_WAIT 	4	/* I/O wait */
#endif
#if !defined(CP_INTR)
#define CP_INTR 	7	/*  time spent in interrupt code  XXX == 0*/
#endif
#if !defined(CPUSTATES)
#define CPUSTATES	9
#endif

#if !defined(MAX_CPU_POLL)
#define MAX_CPU_POLL	128	/* super done */
#endif

#if !defined(MASK_S)
#define MASK_S(Mx)	(0x3fffffff & (Mx))
#endif
%%

integer named 'iErr' {
	hidden
}

char* name "pcDest" {
	init '"peg.sac.fedex.com:31415"'
	param "[peg:]port"
	after "iErr = rrrdupdate(%n, 0, 0, 0, 0);if (iErr < 0) {fprintf(stderr, \"%%s: rrrdupdate: %%s: %%d\\n\", %b, %n, iErr);exit(1);}"
	help "host receiving RRD samples (also allows :port)"
}
# for compatibility with the perl version
char* 'H' {
	hidden local
	update "pcDest = %N;"
}

char* 'N' {
	named "pcName"
	param "node"
	help "override gethostname to force our node's hostname"
}

boolean 's' {
	named "fSplit"
	help "split CPU stats into unix-0.rrd, unix-1.rrd, etc"
}
char* 'S' {
	named "pcSuffix"
	param "suffix"
	help "add this suffix to the default name"
}
char* 'O' {
	named "pcAdminDept"
	init '"sac"'
	param "admin"
	help "remove this suffix after toplevel, if present, default %qi"
}
char* 'T' {
	named "pcTopLevel"
	init '"fedex.com"'
	param "toplevel"
	help "remove this from the end of our hostname, default %qi"
}

augment action 'V' {
	user "Version();"
}
left ["pcDest"] {
}
exit {
	named "UnixStats"
	update "%n();"
}

init 2 "FixPath();"

%c
/* We are run from cron, and might not have a PATH that makes us	(ksb)
 * happy: try to add the 2 common components we might be missing.
 */
static void
FixPath()
{
	register char *pcPath, *pcMem, *pcHome;
	register int iLen;
	static char acAdd[] = ":/usr/local/bin:/usr/local/libexec";
	static char acHomeBin[] = "/bin";
	static char acSet[] = "PATH";
	if ((char *)0 == (pcPath = getenv(acSet)) || '\000' == *pcPath) {
		pcPath = "/usr/bin:/usr/sbin:/usr/ccs/bin:/sbin";
	}
	/* Build "PATH = $PATH $ADD [ :$HOME/bin]"
	 * (N.B. I know sizeof("") includes the \000 on the end,
	 * but too big is not an issue here. -- ksb)
	 */
	iLen = sizeof(acSet)+1+strlen(pcPath)+sizeof(acAdd);
	if ((char *)0 == (pcHome = getenv("HOME"))) {
		/* don't bother to find our home with getpwuid(getuid())
		 */
	} else if ('\000' == *pcHome) {
		pcHome = (char *)0;
	} else {
		iLen += 1+strlen(pcHome)+sizeof(acHomeBin);
	}
	iLen |=7;
	if ((char *)0 == (pcMem = malloc(iLen+9))) {
		return;
	}
	sprintf(pcMem, "%s%s", pcPath, acAdd);
	if ((char *)0 != pcHome) {
		sprintf(strchr(pcMem, '\000'), ":%s%s", pcHome, acHomeBin);
	}
	putenv(pcMem);
}

/* some clue about where we expect rrdup				(ksb)
 */
static void
Version()
{
	printf("%s: rrdup to \"%s\"\n", progname, pcDest);
}

/* update unix stats for HPUX11						(ksb)
 */
static void
UnixStats()
{
	auto char acMyName[MAXHOSTNAMELEN+2];
	static char acRRDFmt[] = "host/%s/unix%s.rrd";
	auto char acRRDName[((5+MAXHOSTNAMELEN+4+32+4)|7)+1];
	static char acTemplate[] = "sysload:cpu_user:cpu_nice:cpu_sys:cpu_wait:cpu_idle:cpu_int:fork:intr:scall:cswitch:pagein:pageout";
	static char acFmt[] = "N:%lf:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu:%lu";
	auto struct pst_processor aoProc[MAX_CPU_POLL];
	auto struct pst_processor oSum;
	auto struct pst_vminfo VM;
	auto char acPegCmd[256+1024+1024+32];
	register int j, i, iMax;
	register char *pcDot;
	register FILE *fpSend;

	if (CPUSTATES > sizeof(oSum.psp_cpu_time)/sizeof(oSum.psp_cpu_time[0])) {
		fprintf(stderr, "%s: assert: tick vector too short\n", progname);
		exit(EX_UNAVAILABLE);
	}
	if (-1 == gethostname(acMyName, sizeof(acMyName))) {
		fprintf(stderr, "%s: gethostname: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	/* whittle off "{sac.}{fedex.com}", if we match them
	 */
	if ((char *)0 != pcTopLevel && '\000' != *pcTopLevel && strlen(pcTopLevel) < strlen(acMyName)) {
		register char *pcX;
		pcX = strchr(acMyName, '\000')-strlen(pcTopLevel);
		if ('.' == pcX[-1] && 0 == strcasecmp(pcTopLevel, pcX))
			*--pcX = '\000';
	}
	if ((char *)0 != pcAdminDept && '\000' != *pcAdminDept && strlen(pcAdminDept) < strlen(acMyName) && !((char *)0 != pcSuffix && 0 == strcmp(pcSuffix, pcAdminDept))) {
		register char *pcX;
		pcX = strchr(acMyName, '\000')-strlen(pcAdminDept);
		if ('.' == pcX[-1] && 0 == strcasecmp(pcAdminDept, pcX))
			*--pcX = '\000';
	}
	if ((char *)0 != pcSuffix) {
		register unsigned u;
		u = strlen(acMyName);
		snprintf(acMyName+u, sizeof(acMyName)-u, ".%s", pcSuffix);
	}
	if ((char *)0 == pcName) {
		pcName = acMyName;
	}
	do {
		(void)memset((void *)aoProc, '\000', sizeof(aoProc));
		(void)memset((void *)& oSum, '\000', sizeof(oSum));
		oSum.psp_avg_1_min = 0.0;
		oSum.psp_sysexec = 0;
		if (-1 == pstat_getvminfo(& VM, sizeof(VM), 1, 0)) {
			fprintf(stderr, "%s: pstat_getvminfo: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		if (-1 == (i = pstat_getprocessor(aoProc, sizeof(aoProc[0]), (size_t)MAX_CPU_POLL, 0))) {
			fprintf(stderr, "%s: pstat_getstatic: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		/* The first CPU with 0 idle is the end of the list
		 */
		for (iMax = 0; iMax < MAX_CPU_POLL; ++iMax) {
			if (0 == aoProc[iMax].psp_cpu_time[CP_IDLE]) {
				break;
			}
		}
		if (0 == iMax) {
			fprintf(stderr, "%s: no processors on-line?\n", progname);
			exit(EX_IOERR);
		}
		for (i = 0; i < iMax; ++i) {
			auto char acWhich[64];
			if (0 == aoProc[i].psp_cpu_time[CP_IDLE]) {
				break;
			}
			oSum.psp_avg_1_min += aoProc[i].psp_avg_1_min;
			oSum.psp_sysexec += aoProc[i].psp_sysexec;
			for (j = 0; j < CPUSTATES; ++j) {
				oSum.psp_cpu_time[j] += aoProc[i].psp_cpu_time[j];
			}
			if (!fSplit) {
				continue;
			}
			snprintf(acWhich, sizeof(acWhich), "-%d", i);
			snprintf(acRRDName, sizeof(acRRDName), acRRDFmt, pcName, acWhich);
			iErr = rrrdupdate(NULL, acRRDName, acTemplate, acFmt, aoProc[i].psp_avg_1_min, MASK_S(aoProc[i].psp_cpu_time[CP_USER]), MASK_S(aoProc[i].psp_cpu_time[CP_NICE]), MASK_S(aoProc[i].psp_cpu_time[CP_SYS]), MASK_S(aoProc[i].psp_cpu_time[CP_WAIT]), MASK_S(aoProc[i].psp_cpu_time[CP_IDLE]), MASK_S(aoProc[i].psp_cpu_time[CP_INTR]), VM.psv_cntfork/iMax, VM.psv_sintr/iMax, VM.psv_ssyscall/iMax, VM.psv_sswtch/iMax, VM.psv_spgin/iMax, VM.psv_spgout/iMax);
			if (iErr < 0) {
				fprintf(stderr, "%s: %s: send thread: %d\n", progname, pcDest, iErr);
				exit(1);
			}
		}
		oSum.psp_avg_1_min /= iMax;
#if HACK
		if (oSum.psp_cpu_time[CP_USER] < 0) oSum.psp_cpu_time[CP_USER] = 0;
		if (oSum.psp_cpu_time[CP_NICE] < 0) oSum.psp_cpu_time[CP_NICE] = 0;
		if (oSum.psp_cpu_time[CP_SYS] < 0) oSum.psp_cpu_time[CP_SYS] = 0;
		if (oSum.psp_cpu_time[CP_WAIT] < 0) oSum.psp_cpu_time[CP_WAIT] = 0;
		if (oSum.psp_cpu_time[CP_IDLE] < 0) oSum.psp_cpu_time[CP_IDLE] = 0;
		if (oSum.psp_cpu_time[CP_INTR] < 0) oSum.psp_cpu_time[CP_INTR] = 0;
#endif
		snprintf(acRRDName, sizeof(acRRDName), acRRDFmt, pcName, "");
		iErr = rrrdupdate(NULL, acRRDName, acTemplate, acFmt, oSum.psp_avg_1_min, MASK_S(oSum.psp_cpu_time[CP_USER]), MASK_S(oSum.psp_cpu_time[CP_NICE]), MASK_S(oSum.psp_cpu_time[CP_SYS]), MASK_S(oSum.psp_cpu_time[CP_WAIT]), MASK_S(oSum.psp_cpu_time[CP_IDLE]), MASK_S(oSum.psp_cpu_time[CP_INTR]), VM.psv_cntfork, VM.psv_sintr, VM.psv_ssyscall, VM.psv_sswtch, VM.psv_spgin, VM.psv_spgout);
		if (iErr < 0) {
			fprintf(stderr, "%s: %s: send consolidated: %d\n", progname, pcDest, iErr);
			exit(1);
		}
	} while (fPersist && 0 == sleep(iDelay));
	exit(EX_OK);
}
