text: /*
   * CDDL HEADER START
   *
   * The contents of this file are subject to the terms of the
   * Common Development and Distribution License (the "License").
   * You may not use this file except in compliance with the License.
   *
   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   * or http://www.opensolaris.org/os/licensing.
   * See the License for the specific language governing permissions
   * and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL HEADER in each
   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
   * If applicable, add the following below this CDDL HEADER, with the
   * fields enclosed by brackets "[]" replaced with your own identifying
   * information: Portions Copyright [yyyy] [name of copyright owner]
   *
   * CDDL HEADER END
   */
  
  /*
   * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
   */
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <stdlib.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <string.h>
  #include <strings.h>
  #include <libintl.h>
  #include <locale.h>
  
  #include "rcapd.h"
  #include "utils.h"
  #include "rcapd_stat.h"
  #include "statcommon.h"
  
  static char mode[RC_MODE_LEN];
  static rcapd_stat_hdr_t hdr;
  static int global;
  static int unformatted;
  static time_t stat_mod = 0;
  
  static uint_t timestamp_fmt = NODATE;
  
  typedef struct col {
  	rcid_t		col_id;
  	char		col_name[LC_NAME_LEN];
  	uint64_t	col_nproc;
  	uint64_t	col_vmsize;
  	uint64_t	col_rsssize;
  	uint64_t	col_rsslimit;
  	uint64_t	col_paged_eff;
  	uint64_t	col_paged_eff_old;
  	uint64_t	col_paged_eff_avg;
  	uint64_t	col_paged_att;
  	uint64_t	col_paged_att_old;
  	uint64_t	col_paged_att_avg;
  	uint64_t	col_count;
  	int		col_fresh;
  	struct col	*col_next;
  	struct col	*col_prev;
  	lcollection_stat_t	col_src_stat;
  	lcollection_stat_t	col_old_stat;
  } col_t;
  
  static col_t *col_head;
  static int ncol;
  
  static col_t *
  col_find(rcid_t id)
  {
  	col_t *col;
  	for (col = col_head; col != NULL; col = col->col_next)
  		if (col->col_id.rcid_type == id.rcid_type &&
  		    col->col_id.rcid_val == id.rcid_val)
  			return (col);
  	return (NULL);
  }
  
  static col_t *
  col_insert(rcid_t id)
  {
  	col_t *new_col;
  
  	new_col = malloc(sizeof (col_t));
  	if (new_col == NULL) {
  		(void) fprintf(stderr, gettext("rcapstat: malloc() failed\n"));
  		exit(E_ERROR);
  	}
  	(void) memset(new_col, 0, sizeof (col_t));
  	new_col->col_next = col_head;
  	new_col->col_id = id;
  	if (col_head != NULL)
  		col_head->col_prev = new_col;
  	col_head = new_col;
  	ncol++;
  	return (new_col);
  }
  
  static void
  col_remove(col_t *col)
  {
  	if (col->col_prev != NULL)
  		col->col_prev->col_next = col->col_next;
  	if (col->col_next != NULL)
  		col->col_next->col_prev = col->col_prev;
  	if (col_head == col)
  		col_head = col->col_next;
  	ncol--;
  	free(col);
  }
  
  static void
  usage()
  {
  	(void) fprintf(stderr,
  	    gettext("usage: rcapstat [-g] [-p | -z] [-T d|u] "
  	    "[interval [count]]\n"));
  	exit(E_USAGE);
  }
  
  static void
  format_size(char *str, uint64_t size, int length)
  {
  	char tag = 'K';
  	if (size >= 10000) {
  		size = (size + 512) / 1024;
  		tag = 'M';
  		if (size >= 10000) {
  			size = (size + 512) / 1024;
  			tag = 'G';
  		}
  	}
  	(void) snprintf(str, length, "%4lld%c", size, tag);
  }
  
  static int
  read_stats(rcid_type_t stat_type)
  {
  	int fd;
  	int proc_fd;
  	char procfile[20];
  	uint64_t pid;
  	col_t *col, *col_next;
  	lcollection_report_t report;
  	struct stat st;
  
  	if ((fd = open(STAT_FILE_DEFAULT, O_RDONLY)) < 0) {
  		warn(gettext("rcapd is not active\n"));
  		return (E_ERROR);
  	}
  
  	if (fstat(fd, &st) == 0)
  		stat_mod = st.st_mtime;
  
  	if (read(fd, &hdr, sizeof (hdr)) != sizeof (hdr)) {
  		(void) fprintf(stderr,
  		    gettext("rcapstat: can't read stat file header: %s\n"),
  		    strerror(errno));
  		(void) close(fd);
  		return (E_ERROR);
  	}
  
  	/*
  	 * Check if rcapd is running
  	 */
  	pid = hdr.rs_pid;
  	(void) snprintf(procfile, 20, "/proc/%lld/psinfo", pid);
  	if ((proc_fd = open(procfile, O_RDONLY)) < 0) {
  		warn(gettext("rcapd is not active\n"));
  		(void) close(fd);
  		return (E_ERROR);
  	}
  	(void) close(proc_fd);
  
  	(void) strncpy(mode, hdr.rs_mode, RC_MODE_LEN);
  	for (col = col_head; col != NULL; col = col->col_next) {
  		col->col_fresh = 0;
  		col->col_paged_eff = 0;
  		col->col_paged_att = 0;
  	}
  
  	while (read(fd, &report, sizeof (report)) == sizeof (report)) {
  		if (report.lcol_id.rcid_type != stat_type)
  			continue;
  
  		col = col_find(report.lcol_id);
  		if (col == NULL) {
  			col = col_insert(report.lcol_id);
  			col->col_paged_eff_old = col->col_paged_eff =
  			    report.lcol_stat.lcols_pg_eff;
  			col->col_paged_att_old = col->col_paged_att =
  			    report.lcol_stat.lcols_pg_att;
  			col->col_count = 0;
  		}
  		(void) strncpy(col->col_name, report.lcol_name, LC_NAME_LEN);
  		col->col_vmsize = report.lcol_image_size;
  		col->col_rsssize = report.lcol_rss;
  		col->col_rsslimit = report.lcol_rss_cap;
  		col->col_fresh = 1;
  		if (report.lcol_stat.lcols_pg_eff > col->col_paged_eff_old) {
  			col->col_paged_eff =
  			    report.lcol_stat.lcols_pg_eff -
  			    col->col_paged_eff_old;
  			if (report.lcol_stat.lcols_scan_count > col->col_count)
  				col->col_paged_eff_avg =
  				    col->col_paged_eff /
  				    (report.lcol_stat.lcols_scan_count -
  				    col->col_count);
  		} else {
  			col->col_paged_eff_avg = 0;
  		}
  		if (report.lcol_stat.lcols_pg_att > col->col_paged_att_old) {
  			col->col_paged_att =
  			    report.lcol_stat.lcols_pg_att -
  			    col->col_paged_att_old;
  			if (report.lcol_stat.lcols_scan_count > col->col_count)
  				col->col_paged_att_avg =
  				    col->col_paged_att /
  				    (report.lcol_stat.lcols_scan_count -
  				    col->col_count);
  		} else {
  			col->col_paged_att_avg = 0;
  		}
  		col->col_paged_eff_old = report.lcol_stat.lcols_pg_eff;
  		col->col_paged_att_old = report.lcol_stat.lcols_pg_att;
  		col->col_nproc =
  		    report.lcol_stat.lcols_proc_in -
  		    report.lcol_stat.lcols_proc_out;
  		col->col_count = report.lcol_stat.lcols_scan_count;
  		col->col_src_stat = report.lcol_stat;
  	}
  
  	/*
  	 * Remove stale data
  	 */
  	col = col_head;
  	while (col != NULL) {
  		col_next = col->col_next;
  		if (col->col_fresh == 0)
  			col_remove(col);
  		col = col_next;
  	}
  	(void) close(fd);
  	return (E_SUCCESS);
  }
  
  /*
   * Print each collection's interval statistics.
   */
  /*ARGSUSED*/
  static void
  print_unformatted_stats(void)
  {
  	col_t *col;
  
  #define	DELTA(field) \
  	(col->col_src_stat.field - col->col_old_stat.field)
  
  	col = col_head;
  	while (col != NULL) {
  		if (bcmp(&col->col_src_stat, &col->col_old_stat,
  		    sizeof (col->col_src_stat)) == 0) {
  			col = col->col_next;
  			continue;
  		}
  		(void) printf("%s %s status: succeeded/attempted (k): "
  		    "%llu/%llu, ineffective/scans/unenforced/samplings:  "
  		    "%llu/%llu/%llu/%llu, RSS min/max (k): %llu/%llu, cap %llu "
  		    "kB, processes/thpt: %llu/%llu, %llu scans over %lld ms\n",
  		    mode, col->col_name, DELTA(lcols_pg_eff),
  		    DELTA(lcols_pg_att), DELTA(lcols_scan_ineffective),
  		    DELTA(lcols_scan), DELTA(lcols_unenforced_cap),
  		    DELTA(lcols_rss_sample), col->col_src_stat.lcols_min_rss,
  		    col->col_src_stat.lcols_max_rss, col->col_rsslimit,
  		    (col->col_src_stat.lcols_proc_in -
  		    col->col_old_stat.lcols_proc_out), DELTA(lcols_proc_out),
  		    DELTA(lcols_scan_count),
  		    NSEC2MSEC(DELTA(lcols_scan_time_complete)));
  		col->col_old_stat = col->col_src_stat;
  
  		col = col->col_next;
  	}
  
  	if (global)
  		(void) printf(gettext("physical memory utilization: %3u%%   "
  		    "cap enforcement threshold: %3u%%\n"), hdr.rs_pressure_cur,
  		    hdr.rs_pressure_cap);
  #undef DELTA
  }
  
  static void
  print_stats(rcid_type_t stat_type)
  {
  	col_t *col;
  	char size[6];
  	char limit[6];
  	char rss[6];
  	char nproc[6];
  	char paged_att[6];
  	char paged_eff[6];
  	char paged_att_avg[6];
  	char paged_eff_avg[6];
  	static int count = 0;
  
  	/*
  	 * Print a header once every 20 times if we're only displaying reports
  	 * for one collection (10 times if -g is used).  Print a header every
  	 * interval otherwise.
  	 */
  	if (count == 0 || ncol != 1)
  		(void) printf("%6s %-15s %5s %5s %5s %5s %5s %5s %5s %5s\n",
  		    "id", (stat_type == RCIDT_PROJECT ?  "project" : "zone"),
  		    "nproc", "vm", "rss", "cap",
  		    "at", "avgat", "pg", "avgpg");
  	if (++count >= 20 || (count >= 10 && global != 0) || ncol != 1)
  		count = 0;
  
  	for (col = col_head; col != NULL; col = col->col_next) {
  		if (col->col_id.rcid_type != stat_type)
  			continue;
  
  		if (col->col_paged_att == 0)
  			(void) strlcpy(nproc, "-", sizeof (nproc));
  		else
  			(void) snprintf(nproc, sizeof (nproc), "%lld",
  			    col->col_nproc);
  		format_size(size, col->col_vmsize, 6);
  		format_size(rss, col->col_rsssize, 6);
  		format_size(limit, col->col_rsslimit, 6);
  		format_size(paged_att, col->col_paged_att, 6);
  		format_size(paged_eff, col->col_paged_eff, 6);
  		format_size(paged_att_avg, col->col_paged_att_avg, 6);
  		format_size(paged_eff_avg, col->col_paged_eff_avg, 6);
  		(void) printf("%6lld %-15s %5s %5s %5s %5s %5s %5s %5s %5s\n",
  		    col->col_id.rcid_val, col->col_name,
  		    nproc,
  		    size, rss, limit,
  		    paged_att, paged_att_avg,
  		    paged_eff, paged_eff_avg);
  	}
  	if (global)
  		(void) printf(gettext("physical memory utilization: %3u%%   "
  		    "cap enforcement threshold: %3u%%\n"), hdr.rs_pressure_cur,
  		    hdr.rs_pressure_cap);
  }
  
  int
  main(int argc, char *argv[])
  {
  	int interval = 5;
  	int count;
  	int always = 1;
  	int opt;
  	int projects = 0;
  	int zones = 0;
  	/* project reporting is the default if no option is specified */
  	rcid_type_t stat_type = RCIDT_PROJECT;
  
  	(void) setlocale(LC_ALL, "");
  	(void) textdomain(TEXT_DOMAIN);
  	(void) setpname("rcapstat");
  
  	global = unformatted = 0;
  	while ((opt = getopt(argc, argv, "gpuzT:")) != (int)EOF) {
  		switch (opt) {
  		case 'g':
  			global = 1;
  			break;
  		case 'p':
  			projects = 1;
  			stat_type = RCIDT_PROJECT;
  			break;
  		case 'u':
  			unformatted = 1;
  			break;
  		case 'z':
  			stat_type = RCIDT_ZONE;
  			zones = 1;
  			break;
  		case 'T':
  			if (optarg) {
  				if (*optarg == 'u')
  					timestamp_fmt = UDATE;
  				else if (*optarg == 'd')
  					timestamp_fmt = DDATE;
  				else
  					usage();
  			} else {
  				usage();
  			}
  			break;
  		default:
  			usage();
  		}
  	}
  
  	if (argc > optind)
  		if ((interval = xatoi(argv[optind++])) <= 0)
  			die(gettext("invalid interval specified\n"));
  	if (argc > optind) {
  		if ((count = xatoi(argv[optind++])) <= 0)
  			die(gettext("invalid count specified\n"));
  		always = 0;
  	}
  	if (argc > optind || (projects > 0 && zones > 0))
  		usage();
  
  	while (always || count-- > 0) {
  		if (read_stats(stat_type) != E_SUCCESS)
  			return (E_ERROR);
  		if (timestamp_fmt != NODATE)
  			print_timestamp(timestamp_fmt);
  		if (!unformatted) {
  			print_stats(stat_type);
  			(void) fflush(stdout);
  			if (count || always)
  				(void) sleep(interval);
  		} else {
  			struct stat st;
  
  			print_unformatted_stats();
  			(void) fflush(stdout);
  			while (stat(STAT_FILE_DEFAULT, &st) == 0 &&
  			    st.st_mtime == stat_mod)
  				(void) usleep((useconds_t)(0.2 * MICROSEC));
  		}
  	}
  
  	return (E_SUCCESS);
  }
  