# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" .KEEP_STATE: SUBDIRS = rcapadm rcapd rcapstat MSGSUBDIRS = $(SUBDIRS) include Makefile.subdirs # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2025 Hans Rosenfeld # # Definitions for targets shared by some subdirs, which have # dependencies in rcap/common, the path to which must be the value of # COMMON_DIR. # include ../../Makefile.ctf LDFLAGS += $(MAPFILE.NGB:%=-Wl,-M%) CERRWARN += -Wno-unused-function CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-parentheses SMOFF += strcpy_overflow # Hammerhead: Use vpath to find sources in $(COMMON_DIR) instead of relying # on pattern rules with directory prefixes (which fail in GNU Make when # combined with pattern rules from Makefile.stat). vpath %.c $(COMMON_DIR) %.o: %.c $(COMPILE.c) $< $(POST_PROCESS_O) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" .KEEP_STATE: all : TARGET = all clean : TARGET = clean clobber : TARGET = clobber install : TARGET = install lint : TARGET = lint _msg : TARGET = _msg all clean clobber install lint: $(SUBDIRS) _msg: $(MSGSUBDIRS) $(filter-out .WAIT,$(SUBDIRS)): FRC @cd $@; pwd; VERSION='$(VERSION)' $(MAKE) $(TARGET) FRC: /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RCAPD_H #define _RCAPD_H #ifdef __cplusplus extern "C" { #endif #include #include #include "rcapd_conf.h" #define LC_NAME_LEN 32 #define RCAP_FMRI "svc:/system/rcap:default" #define CONFIG_PG "config" #define PRESSURE "pressure" #define RECONFIG_INT "reconfig_interval" #define REPORT_INT "report_interval" #define RSS_SAMPLE_INT "rss_sample_interval" #define WALK_INT "walk_interval" #define RCAPD_IGNORED_SET_FLUSH_IVAL 10 /* number of scans between */ /* flushes of the ignored set */ /* * set the buffer length for /proc-based path names based on the actual * length of the largest pid */ #define RCAPD__STR(a) #a #define RCAPD_STR(macro) RCAPD__STR(macro) #define PROC_PATH_MAX (sizeof ("/proc/" RCAPD_STR(PID_MAX) \ "/pagedata")) /* * lcollection_insert_update() result flags */ #define LCST_CAP_CHANGED (1<<0) #define LCST_CAP_REMOVED (1<<1) #define LCST_CAP_ZERO (1<<2) typedef enum { RCIDT_PROJECT, RCIDT_ZONE } rcid_type_t; typedef struct { /* * The following field could just be a rcid_type_t but it gets * written out to a file as binary data for communication between * 64-bit rcapd & 32-bit rcapstat, so we need to force a standard size * and alignment here. */ uint64_t rcid_type; int64_t rcid_val; } rcid_t; typedef enum { LCU_COMPLETE, /* an enumeration of all possible collections */ LCU_ACTIVE_ONLY /* an enumeration of only once-active collections */ } lcollection_update_type_t; struct lmapping; struct lprocess; struct lcollection; struct prxmap; struct psinfo; /* * Per-process data. */ typedef struct lprocess { struct lprocess *lpc_prev; /* global process list */ struct lprocess *lpc_next; pid_t lpc_pid; /* ID of this process */ int lpc_unscannable; /* flag indicating zombie or */ /* other unscannable process */ uint64_t lpc_rss; /* resident set size (kB) */ uint64_t lpc_unrm; /* scannable set size (kB) (est.) */ uint64_t lpc_size; /* process image size (kB) */ int lpc_mark; /* mark-and-sweep flag */ struct lcollection *lpc_collection; /* owning collection */ int lpc_psinfo_fd; /* cached psinfo fd */ int lpc_pgdata_fd; /* cached pagedata fd */ int lpc_xmap_fd; /* cached xmap fd */ struct prxmap *lpc_xmap; /* xmap corresponding to */ /* current pagedata */ int lpc_nxmap; /* number of mappings in xmap */ prpageheader_t *lpc_prpageheader; /* accumulated mask of */ /* process's ref/mod bits */ struct lmapping *lpc_ignore; /* empirically-unpageable mappings */ } lprocess_t; /* * Collection statistics. */ typedef struct { uint64_t lcols_scan; /* scan attempts */ uint64_t lcols_pg_att; /* kB attempted to page */ uint64_t lcols_pg_eff; /* kB paged out (est.) */ uint64_t lcols_rss_sample; /* RSS samplings */ uint64_t lcols_unenforced_cap; /* times cap could have been */ /* enforced, but wasn't (due to low */ /* global memory pressure, or global */ /* scanner being activated) */ uint64_t lcols_rss_sum; /* sum of sampled RSS values */ uint64_t lcols_rss_act_sum; /* sum of sampled, excess RSS values */ uint64_t lcols_min_rss; /* minimum RSS (kB), this interval */ uint64_t lcols_max_rss; /* maximum RSS (kB), this interval */ uint64_t lcols_proc_in; /* processes tracked */ uint64_t lcols_proc_out; /* processes freed */ hrtime_t lcols_scan_time; /* time spent scanning (ns) */ hrtime_t lcols_scan_time_complete; /* time spent scanning (ns) */ /* at last completion */ uint64_t lcols_scan_count; /* number of complete scans */ uint64_t lcols_scan_ineffective; /* number of uninterrupted */ /* revolutions of clock hand after */ /* which the excess was not */ /* completely reduced */ } lcollection_stat_t; /* * Collection. */ typedef struct lcollection { struct lcollection *lcol_prev; /* global collection list */ struct lcollection *lcol_next; rcid_t lcol_id; /* numerical ID for this collection */ char lcol_name[LC_NAME_LEN]; /* name of this collection, or */ /* "unknown" */ uint64_t lcol_rss; /* RSS of all processes (kB) */ uint64_t lcol_image_size; /* image size of all processes (kB) */ uint64_t lcol_rss_cap; /* RSS cap (kB) */ lcollection_stat_t lcol_stat; /* statistics */ lcollection_stat_t lcol_stat_old; /* previous interval's statistics */ lprocess_t *lcol_lprocess; /* member processes */ int lcol_mark; /* mark-and-sweep flag */ lprocess_t *lcol_victim; /* victim process to resume scanning */ void *lcol_resaddr; /* address to resume scanning from */ } lcollection_t; /* * Collection report. */ typedef struct lcollection_report { rcid_t lcol_id; /* numerical ID for this collection */ char lcol_name[LC_NAME_LEN]; /* name of this collection, or */ /* "unknown" */ uint64_t lcol_rss; /* RSS of all processes (kB) */ uint64_t lcol_image_size; /* image size of all processes (kB) */ uint64_t lcol_rss_cap; /* RSS limit (kB) */ lcollection_stat_t lcol_stat; /* statistics */ } lcollection_report_t; extern int get_psinfo(pid_t, struct psinfo *, int, int(*)(void *, int), void *, lprocess_t *); extern lcollection_t *lcollection_find(rcid_t *); extern void lcollection_freq_move(lprocess_t *); extern lcollection_t *lcollection_insert_update(rcid_t *, uint64_t, char *, int *changes); extern int lcollection_member(lcollection_t *, lprocess_t *); extern void lcollection_free(lcollection_t *); extern void lcollection_update(lcollection_update_type_t); extern void list_walk_collection(int (*)(lcollection_t *, void *), void *); extern int lprocess_update_psinfo_fd_cb(void *, int); extern void lprocess_free(lprocess_t *); extern void scan(lcollection_t *, int64_t); extern void scan_abort(void); extern void check_update_statistics(void); /* * Global (in rcapd only) variables. */ extern rcfg_t rcfg; extern uint64_t phys_total; extern int should_run; #ifdef __cplusplus } #endif #endif /* _RCAPD_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "rcapd.h" #include "rcapd_conf.h" #include "rcapd_stat.h" #include "utils.h" /* * Read configuration and set the fields of an rcfg_t correspondingly. * Verify that the statistics file is writable, with the optional * verify_stat_file_creation() callback. */ int rcfg_read(rcfg_t *_rcfg, int(*verify_stat_file_creation)(void)) { scf_simple_handle_t *simple_h; uint64_t count_val; int ret = E_ERROR; rcfg_init(_rcfg); if ((simple_h = scf_general_pg_setup(RCAP_FMRI, CONFIG_PG)) == NULL) { warn(gettext("SMF initialization problem: %s\n"), scf_strerror(scf_error())); goto err; } if (scf_read_count_property(simple_h, PRESSURE, &count_val) == SCF_FAILED) { warn(gettext("Configuration property '%s' " "not found. \n"), PRESSURE); goto err; } else { if (count_val > 100) _rcfg->rcfg_memory_cap_enforcement_pressure = 100; else _rcfg->rcfg_memory_cap_enforcement_pressure = count_val; debug("cap max pressure: %d%%\n", _rcfg->rcfg_memory_cap_enforcement_pressure); } if (scf_read_count_property(simple_h, RECONFIG_INT, &count_val) == SCF_FAILED) { warn(gettext("Configuration property '%s' " "not found. \n"), RECONFIG_INT); goto err; } else { _rcfg->rcfg_reconfiguration_interval = count_val; debug("reconfiguration interval: %d seconds\n", _rcfg->rcfg_reconfiguration_interval); } if (scf_read_count_property(simple_h, REPORT_INT, &count_val) == SCF_FAILED) { warn(gettext("Configuration property '%s' " "not found. \n"), REPORT_INT); goto err; } else { _rcfg->rcfg_report_interval = count_val; debug("report interval: %d seconds\n", _rcfg->rcfg_report_interval); } if (scf_read_count_property(simple_h, RSS_SAMPLE_INT, &count_val) == SCF_FAILED) { warn(gettext("Configuration property '%s' " "not found. \n"), RSS_SAMPLE_INT); goto err; } else { _rcfg->rcfg_rss_sample_interval = count_val; debug("RSS sample interval: %d seconds\n", _rcfg->rcfg_rss_sample_interval); } if (scf_read_count_property(simple_h, WALK_INT, &count_val) == SCF_FAILED) { warn(gettext("Configuration property '%s' " "not found. \n"), WALK_INT); goto err; } else { _rcfg->rcfg_proc_walk_interval = count_val; debug("proc_walk interval: %d seconds\n", _rcfg->rcfg_proc_walk_interval); } if (_rcfg->rcfg_mode_name == NULL) { /* * Set project mode, by default. */ _rcfg->rcfg_mode = rctype_project; _rcfg->rcfg_mode_name = "project"; debug("mode: %s\n", _rcfg->rcfg_mode_name); } if (verify_stat_file_creation != 0 && verify_stat_file_creation() != 0) { warn(gettext("cannot create statistics file, " "%s"), _rcfg->rcfg_stat_file); goto err; } debug("done parsing\n"); ret = E_SUCCESS; goto out; err: if (scf_error() != SCF_ERROR_NONE) { warn(gettext("Unexpected libscf error: %s. \n"), scf_strerror(scf_error())); } out: scf_simple_handle_destroy(simple_h); return (ret); } void rcfg_init(rcfg_t *rcfg) { bzero(rcfg, sizeof (*rcfg)); (void) strcpy(rcfg->rcfg_stat_file, STAT_FILE_DEFAULT); } /* * Modify configuration in repository given the rcfg_t structure. */ int modify_config(rcfg_t *conf) { scf_simple_handle_t *simple_h; scf_transaction_t *tx = NULL; int rval, ret = E_ERROR; if ((simple_h = scf_general_pg_setup(RCAP_FMRI, CONFIG_PG)) == NULL) { warn(gettext("SMF initialization problem: %s\n"), scf_strerror(scf_error())); goto out; } if ((tx = scf_transaction_setup(simple_h)) == NULL) { warn(gettext("SMF initialization problem: %s\n"), scf_strerror(scf_error())); goto out; } do { if (scf_set_count_property(tx, PRESSURE, conf->rcfg_memory_cap_enforcement_pressure, 0) != SCF_SUCCESS) { warn(gettext("Couldn't set '%s' property. \n"), PRESSURE); goto out; } if (scf_set_count_property(tx, RECONFIG_INT, conf->rcfg_reconfiguration_interval, 0) != SCF_SUCCESS) { warn(gettext("Couldn't set '%s' property. \n"), RECONFIG_INT); goto out; } if (scf_set_count_property(tx, RSS_SAMPLE_INT, conf->rcfg_rss_sample_interval, 0) != SCF_SUCCESS) { warn(gettext("Couldn't set '%s' property. \n"), RSS_SAMPLE_INT); goto out; } if (scf_set_count_property(tx, REPORT_INT, conf->rcfg_report_interval, 0) != SCF_SUCCESS) { warn(gettext("Couldn't set '%s' property. \n"), REPORT_INT); goto out; } if (scf_set_count_property(tx, WALK_INT, conf->rcfg_proc_walk_interval, 0) != SCF_SUCCESS) { warn(gettext("Couldn't set '%s' property. \n"), WALK_INT); goto out; } if ((rval = scf_transaction_commit(tx)) == -1) goto out; if (rval == 0) { if (scf_transaction_restart(simple_h, tx) != SCF_SUCCESS) { warn(gettext("SMF initialization problem: " "%s\n"), scf_strerror(scf_error())); goto out; } } } while (rval == 0); ret = E_SUCCESS; out: if (tx != NULL) { scf_transaction_destroy_children(tx); scf_transaction_destroy(tx); } scf_simple_handle_destroy(simple_h); return (ret); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RCAPD_CONF_H #define _RCAPD_CONF_H #include #include #ifdef __cplusplus extern "C" { #endif #define CFG_TEMPLATE_SUFFIX ".XXXXXX" /* suffix of mkstemp() arg */ /* * Operating modes */ typedef enum { rctype_project = 1 /* projects are the only collection type */ } rctype_t; /* * Configuration */ typedef struct { char rcfg_filename[MAXPATHLEN]; /* cfg filename */ int rcfg_fd; /* reserved cfg fd */ time_t rcfg_last_modification; /* last mod time */ rctype_t rcfg_mode; /* mode (collection type) */ char *rcfg_mode_name; /* mode name ("project" only) */ uint32_t rcfg_proc_walk_interval; /* /proc readdir() */ /* interval (s), */ /* undocumented */ uint32_t rcfg_reconfiguration_interval; /* lnode or project */ /* cap reconfig. */ /* interval (s) */ uint32_t rcfg_report_interval; /* report interval (s) */ int rcfg_memory_cap_enforcement_pressure; /* pressure */ /* above which memory caps are enforced */ char rcfg_stat_file[MAXPATHLEN]; /* statistics file */ uint32_t rcfg_rss_sample_interval; /* RSS sampling interval */ } rcfg_t; typedef enum { RCT_MODE_VAR = 257, RCT_PROC_WALK_INTERVAL_VAR, RCT_RECONFIGURATION_INTERVAL_VAR, RCT_REPORT_INTERVAL_VAR, RCT_RSS_SAMPLE_INTERVAL_VAR, RCT_MEMORY_CAP_ENFORCEMENT_PRESSURE_VAR, RCT_STAT_FILE_VAR, RCT_EQUALS, RCT_NUMBER, RCT_PROJECT, RCT_LNODE, RCT_ON, RCT_OFF, RCT_FILENAME, RCT_STATE, RCT_INVALID } rctoken_t; extern int rcfg_read(rcfg_t *, int(*)(void)); extern void rcfg_init(rcfg_t *); extern int modify_config(rcfg_t *); #ifdef __cplusplus } #endif #endif /* _RCAPD_CONF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include "rcapd_stat.h" /* * Return the pid of the writer of the statistics file. */ pid_t stat_get_rcapd_pid(char *file) { int fd; rcapd_stat_hdr_t hdr; char procfile[20]; struct stat st; if ((fd = open(file, O_RDONLY)) < 0) return (-1); if (read(fd, &hdr, sizeof (hdr)) != sizeof (hdr)) { (void) close(fd); return (-1); } (void) close(fd); (void) snprintf(procfile, 20, "/proc/%d/psinfo", (int)hdr.rs_pid); if (stat(procfile, &st) == 0) return (hdr.rs_pid); else return (-1); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RCAPD_STAT_H #define _RCAPD_STAT_H #ifdef __cplusplus extern "C" { #endif /* * Default path to statistics file */ #define STAT_FILE_DIR "/var/run/daemon" #define STAT_FILE_DEFAULT STAT_FILE_DIR "/rcap.stat" /* * Statistics file header. */ #define RC_MODE_LEN 16 typedef struct rcapd_stat_hdr { /* * sizeof pid_t can vary, so we use a fixed 64-bit quantity. */ uint64_t rs_pid; /* pid of producer */ hrtime_t rs_time; /* time recorded */ /* * Physical memory pressure statistics, in percent. */ uint32_t rs_pressure_cur; /* current memory pressure */ uint32_t rs_pressure_cap; /* minimum cap enforcement p. */ uint64_t rs_pressure_sample; /* count of pr. samplings */ char rs_mode[RC_MODE_LEN]; /* mode ("project" only) */ } rcapd_stat_hdr_t; extern pid_t stat_get_rcapd_pid(char *); #ifdef __cplusplus } #endif #endif /* _RCAPD_STAT_H */ /* * 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 #include #include #include #include #include #include #include #include #include "utils.h" static char ERRNO_FMT[] = ": %s"; static char *pname = NULL; static rcm_level_t message_priority = RCM_WARN; static rcm_dst_t message_dst = RCD_STD; static void dmesg(int level, char *msg); /*PRINTFLIKE2*/ void dprintfe(int level, char *format, ...) { va_list alist; va_start(alist, format); vdprintfe(level, format, alist); va_end(alist); } /*PRINTFLIKE2*/ void vdprintfe(int level, const char *format, va_list alist) { char buf[LINELEN]; char *c; int err = errno; *buf = 0; if ((strlen(buf) + 1) < LINELEN) (void) vsnprintf(buf + strlen(buf), LINELEN - 1 - strlen(buf), format, alist); if ((c = strchr(buf, '\n')) == NULL) { if ((strlen(buf) + 1) < LINELEN) (void) snprintf(buf + strlen(buf), LINELEN - 1 - strlen(buf), gettext(ERRNO_FMT), strerror(err)); } else *c = 0; dmesg(level, buf); } #ifdef DEBUG_MSG /*PRINTFLIKE1*/ void debug(char *format, ...) { va_list alist; if (get_message_priority() < RCM_DEBUG) return; va_start(alist, format); vdprintfe(RCM_DEBUG, format, alist); va_end(alist); } /*PRINTFLIKE1*/ void debug_high(char *format, ...) { va_list alist; if (get_message_priority() < RCM_DEBUG_HIGH) return; va_start(alist, format); vdprintfe(RCM_DEBUG_HIGH, format, alist); va_end(alist); } #endif /* DEBUG_MSG */ /*PRINTFLIKE1*/ void warn(const char *format, ...) { va_list alist; if (get_message_priority() < RCM_WARN) return; va_start(alist, format); vdprintfe(RCM_WARN, format, alist); va_end(alist); } /*PRINTFLIKE1*/ void die(char *format, ...) { va_list alist; if (get_message_priority() < RCM_ERR) return; va_start(alist, format); vdprintfe(RCM_ERR, format, alist); va_end(alist); exit(E_ERROR); } /*PRINTFLIKE1*/ void info(char *format, ...) { va_list alist; if (get_message_priority() < RCM_INFO) return; va_start(alist, format); vdprintfe(RCM_INFO, format, alist); va_end(alist); } char * setpname(char *arg0) { char *p = strrchr(arg0, '/'); if (p == NULL) p = arg0; else p++; pname = p; return (pname); } /* * Output a message to the controlling tty or log, depending on which is * configured. The message should contain no newlines. */ static void dmesg(int level, char *msg) { if (message_priority >= level) { FILE *fp; int syslog_severity = -1; switch (message_dst) { case RCD_STD: fp = level >= RCM_DEBUG ? stderr : stdout; if (pname != NULL) { (void) fputs(pname, fp); (void) fputs(": ", fp); } (void) fputs(msg, fp); (void) fputc('\n', fp); (void) fflush(fp); break; case RCD_SYSLOG: switch (level) { case RCM_ERR: syslog_severity = LOG_ERR; break; case RCM_WARN: syslog_severity = LOG_WARNING; break; case RCM_INFO: syslog_severity = LOG_INFO; break; case RCM_DEBUG: syslog_severity = LOG_DEBUG; break; } if (syslog_severity >= 0) (void) syslog(syslog_severity, "%s", msg); break; } } } rcm_level_t get_message_priority(void) { return (message_priority); } rcm_level_t set_message_priority(rcm_level_t new_priority) { rcm_level_t old_priority = message_priority; message_priority = new_priority; return (old_priority); } rcm_dst_t set_message_destination(rcm_dst_t new_dst) { rcm_dst_t old_dst = message_dst; if ((message_dst = new_dst) == RCD_SYSLOG) openlog(pname, LOG_ODELAY | LOG_PID, LOG_DAEMON); return (old_dst); } void hrt2ts(hrtime_t hrt, timestruc_t *tsp) { tsp->tv_sec = hrt / NANOSEC; tsp->tv_nsec = hrt % NANOSEC; } int xatoi(char *p) { int i; char *q; errno = 0; i = (int)strtol(p, &q, 10); if (errno != 0 || q == p || i < 0 || *q != '\0') { warn(gettext("illegal argument -- %s\n"), p); return (-1); } else { return (i); } } /* * get_running_zones() calls zone_list(2) to find out how many zones are * running. It then calls zone_list(2) again to fetch the list of running * zones (stored in *zents). */ int get_running_zones(uint_t *nzents, zone_entry_t **zents) { zoneid_t *zids; uint_t nzents_saved; int i; zone_entry_t *zentp; zone_state_t zstate; *zents = NULL; if (zone_list(NULL, nzents) != 0) { warn(gettext("could not get zoneid list\n")); return (E_ERROR); } again: if (*nzents == 0) return (E_SUCCESS); if ((zids = (zoneid_t *)calloc(*nzents, sizeof (zoneid_t))) == NULL) { warn(gettext("out of memory: zones will not be capped\n")); return (E_ERROR); } nzents_saved = *nzents; if (zone_list(zids, nzents) != 0) { warn(gettext("could not get zone list\n")); free(zids); return (E_ERROR); } if (*nzents != nzents_saved) { /* list changed, try again */ free(zids); goto again; } *zents = calloc(*nzents, sizeof (zone_entry_t)); if (*zents == NULL) { warn(gettext("out of memory: zones will not be capped\n")); free(zids); return (E_ERROR); } zentp = *zents; for (i = 0; i < *nzents; i++) { char name[ZONENAME_MAX]; if (getzonenamebyid(zids[i], name, sizeof (name)) < 0) { warn(gettext("could not get name for " "zoneid %d\n"), zids[i]); continue; } (void) strlcpy(zentp->zname, name, sizeof (zentp->zname)); zentp->zid = zids[i]; if (zone_get_state(name, &zstate) != Z_OK || zstate != ZONE_STATE_RUNNING) continue; zentp++; } *nzents = zentp - *zents; free(zids); return (E_SUCCESS); } /* * 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. */ #ifndef _UTILS_H #define _UTILS_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define E_SUCCESS 0 /* Exit status for success */ #define E_ERROR 1 /* Exit status for error */ #define E_USAGE 2 /* Exit status for usage error */ /* * Message filter levels by priority */ typedef enum rcm_level { RCM_NONE = 0, /* No messages */ RCM_ERR, /* Errors only */ RCM_WARN, /* Warnings */ RCM_INFO, /* Information */ RCM_DEBUG, /* Everything */ RCM_DEBUG_HIGH /* Fire hose */ } rcm_level_t; /* * Message destinations */ typedef enum rcm_dst { RCD_STD = 1, /* Standard output/error, depending */ /* on level */ RCD_SYSLOG /* syslog() daemon facility */ } rcm_dst_t; typedef struct zone_entry { zoneid_t zid; char zname[ZONENAME_MAX]; } zone_entry_t; #define LINELEN 256 /* max. message length */ #ifdef DEBUG #undef ASSERT #define ASSERT(x) (assert(x)) #else /* !DEBUG */ #undef ASSERT #define ASSERT(x) ((void)0) #endif /* DEBUG */ #ifdef DEBUG_MSG extern void debug(char *, ...); extern void debug_high(char *, ...); #else /* !DEBUG_MSG */ /*LINTED: static unused*/ static void debug(char *format, ...) /*ARGSUSED*/ {} /*LINTED: static unused*/ static void debug_high(char *format, ...) /*ARGSUSED*/ {} #endif /* DEBUG_MSG */ extern void die(char *, ...); extern void info(char *, ...); extern rcm_level_t get_message_priority(void); extern rcm_level_t set_message_priority(rcm_level_t); extern rcm_dst_t set_message_destination(rcm_dst_t); extern char *setpname(char *); extern void warn(const char *, ...); extern int valid_abspath(char *); extern void vdprintfe(int, const char *, va_list); extern void dprintfe(int, char *, ...); extern void hrt2ts(hrtime_t, timestruc_t *); extern int xatoi(char *); extern int get_running_zones(uint_t *, zone_entry_t **); #ifdef __cplusplus } #endif #endif /* _UTILS_H */ # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld .KEEP_STATE: .SUFFIXES: include ../../Makefile.cmd COMMON_DIR= ../common # for Makefile.com OBJS = rcapadm.o $(COMMON_OBJS) SRCS = $(OBJS:%.o=%.c) COMMON_OBJS = rcapd_stat.o utils.o rcapd_conf.o CLEANFILES = $(OBJS) $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG CPPFLAGS += -I$(COMMON_DIR) LDLIBS += -lumem -lscf -lzonecfg PROG = rcapadm POFILES = $(OBJS:%.o=%.po) POFILE = p$(PROG).po all: $(PROG) include ../Makefile.com $(PROG): $(OBJS) $(MAPFILE.NGB) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ $(CAT) $(POFILES) > $@ clean: $(RM) $(CLEANFILES) $(ROOTUSRSBIN): $(MKDIR) -p $(ROOTUSRSBIN) install: all $(ROOTUSRSBIN) $(ROOTUSRSBINPROG) include ../../Makefile.targ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "rcapd.h" #include "rcapd_conf.h" #include "rcapd_stat.h" static void usage() { (void) fprintf(stderr, gettext("usage: rcapadm\n" " [-E|-D] " "# enable/disable rcapd\n" " [-n] " "# don't start/stop rcapd\n" " [-i =value] " "# set intervals\n" " [-c ] " "# set memory cap\n" " " "# enforcement threshold\n" " [-z -m ] " "# update zone memory cap\n")); exit(E_USAGE); } static rcfg_t conf; static int enable = -1; static int disable = -1; static int pressure = -1; static int no_starting_stopping = -1; static int scan_interval = -1; static int report_interval = -1; static int config_interval = -1; static int sample_interval = -1; static char *subopt_v[] = { "scan", "sample", "report", "config", NULL }; typedef enum { OPT_SCAN = 0, OPT_SAMPLE, OPT_REPORT, OPT_CONFIG } subopt_idx_t; static void print_state(void) { scf_simple_prop_t *persistent_prop = NULL; scf_simple_prop_t *temporary_prop = NULL; uint8_t *persistent = NULL; uint8_t *temporary = NULL; scf_handle_t *h; /* LINTED: conditionally assigned and used in function */ ssize_t numvals; if ((h = scf_handle_create(SCF_VERSION)) == NULL || scf_handle_bind(h) != 0) goto out; if ((persistent_prop = scf_simple_prop_get(h, RCAP_FMRI, SCF_PG_GENERAL, SCF_PROPERTY_ENABLED)) != NULL && (numvals = scf_simple_prop_numvalues(persistent_prop)) > 0) persistent = scf_simple_prop_next_boolean(persistent_prop); if ((temporary_prop = scf_simple_prop_get(h, RCAP_FMRI, SCF_PG_GENERAL_OVR, SCF_PROPERTY_ENABLED)) != NULL && (numvals = scf_simple_prop_numvalues(temporary_prop)) > 0) temporary = scf_simple_prop_next_boolean(temporary_prop); out: if (!persistent) (void) printf(gettext(" " "state: unknown")); else if (temporary && *temporary != *persistent) (void) printf(gettext(" " "state: %s (%s at next boot)\n"), *temporary ? gettext("enabled") : gettext("disabled"), *persistent ? gettext("enabled") : gettext("disabled")); else (void) printf(gettext(" " "state: %s\n"), *persistent ? gettext("enabled") : gettext("disabled")); (void) printf(gettext(" memory cap enforcement" " threshold: %d%%\n"), conf.rcfg_memory_cap_enforcement_pressure); (void) printf(gettext(" process scan rate" " (sec): %d\n"), conf.rcfg_proc_walk_interval); (void) printf(gettext(" reconfiguration rate" " (sec): %d\n"), conf.rcfg_reconfiguration_interval); (void) printf(gettext(" report rate" " (sec): %d\n"), conf.rcfg_report_interval); (void) printf(gettext(" RSS sampling rate" " (sec): %d\n"), conf.rcfg_rss_sample_interval); scf_simple_prop_free(temporary_prop); scf_simple_prop_free(persistent_prop); scf_handle_destroy(h); } /* * Update the in-kernel memory cap for the specified zone. */ static int update_zone_mcap(char *zonename, char *maxrss) { zoneid_t zone_id; uint64_t num; if (getzoneid() != GLOBAL_ZONEID || zonecfg_in_alt_root()) return (E_SUCCESS); /* get the running zone from the kernel */ if ((zone_id = getzoneidbyname(zonename)) == -1) { (void) fprintf(stderr, gettext("zone '%s' must be running\n"), zonename); return (E_ERROR); } if (zonecfg_str_to_bytes(maxrss, &num) == -1) { (void) fprintf(stderr, gettext("invalid max-rss value\n")); return (E_ERROR); } if (zone_setattr(zone_id, ZONE_ATTR_PHYS_MCAP, &num, 0) == -1) { (void) fprintf(stderr, gettext("could not set memory " "cap for zone '%s'\n"), zonename); return (E_ERROR); } return (E_SUCCESS); } int main(int argc, char *argv[]) { char *subopts, *optval; int modified = 0; boolean_t refresh = B_FALSE; int opt; char *zonename; char *maxrss = NULL; (void) setpname("rcapadm"); (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); while ((opt = getopt(argc, argv, "DEc:i:m:nz:")) != EOF) { switch (opt) { case 'n': no_starting_stopping = 1; break; case 'c': if ((pressure = xatoi(optarg)) < 0 || pressure > 100 || errno == EINVAL) usage(); modified++; break; case 'E': enable = 1; disable = 0; break; case 'D': disable = 1; enable = 0; break; case 'i': subopts = optarg; while (*subopts != '\0') { switch (getsubopt(&subopts, subopt_v, &optval)) { case OPT_SCAN: if (optval == NULL || (scan_interval = xatoi(optval)) <= 0) usage(); break; case OPT_SAMPLE: if (optval == NULL || (sample_interval = xatoi(optval)) <= 0) usage(); break; case OPT_REPORT: if (optval == NULL || (report_interval = xatoi(optval)) < 0) usage(); break; case OPT_CONFIG: if (optval == NULL || (config_interval = xatoi(optval)) < 0) usage(); break; default: usage(); } } modified++; break; case 'm': maxrss = optarg; break; case 'z': refresh = B_TRUE; zonename = optarg; break; default: usage(); } } /* the -z & -m options must be used together */ if (argc > optind || (refresh && maxrss == NULL) || (!refresh && maxrss != NULL)) usage(); if (refresh && (no_starting_stopping > 0 || modified)) usage(); /* * disable/enable before reading configuration from the repository * which may fail and prevents the disabling/enabling to complete. */ if (disable > 0) { if (smf_disable_instance(RCAP_FMRI, no_starting_stopping > 0 ? SMF_AT_NEXT_BOOT : 0) != 0) die(gettext("cannot disable service: %s\n"), scf_strerror(scf_error())); } if (enable > 0) { if (smf_enable_instance(RCAP_FMRI, no_starting_stopping > 0 ? SMF_AT_NEXT_BOOT : 0) != 0) die(gettext("cannot enable service: %s\n"), scf_strerror(scf_error())); } if (rcfg_read(&conf, NULL) != E_SUCCESS) { /* * If instance is enabled, put it in maintenance since we * failed to read configuration from the repository or * create statistics file. */ if (strcmp(smf_get_state(RCAP_FMRI), SCF_STATE_STRING_DISABLED) != 0) (void) smf_maintain_instance(RCAP_FMRI, 0); die(gettext("resource caps not configured\n")); } else { /* Done reading configuration */ if (strcmp(conf.rcfg_mode_name, "project") != 0) { warn(gettext("%s mode specification ignored -- using" " project mode\n"), conf.rcfg_mode_name); conf.rcfg_mode_name = "project"; conf.rcfg_mode = rctype_project; } } if (refresh) return (update_zone_mcap(zonename, maxrss)); if (modified) { if (pressure >= 0) conf.rcfg_memory_cap_enforcement_pressure = pressure; if (config_interval >= 0) conf.rcfg_reconfiguration_interval = config_interval; if (scan_interval >= 0) conf.rcfg_proc_walk_interval = scan_interval; if (report_interval >= 0) conf.rcfg_report_interval = report_interval; if (sample_interval >= 0) conf.rcfg_rss_sample_interval = sample_interval; /* * Modify configuration with the new parameter(s). The * function will exit if it fails. */ if ((modify_config(&conf)) != 0) die(gettext("Error updating repository \n")); if (smf_refresh_instance(RCAP_FMRI) != 0) die(gettext("cannot refresh service: %s\n"), scf_strerror(scf_error())); } /* * Display current configuration */ print_state(); return (E_SUCCESS); } # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld include ../../Makefile.cmd include ../../Makefile.cmd.64 .KEEP_STATE: PROG = rcapd ROOTUSRLIBRCAP = $(ROOT)/usr/lib/rcap ROOTUSRLIBRCAPPROG = $(ROOTUSRLIBRCAP)/$(PROG) COMMON_DIR= ../common MANIFEST = rcap.xml ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) $(ROOTSVCSYSTEM)/rcap.xml : FILEMODE = 0444 SRCS = rcapd_main.c \ rcapd_collection.c \ rcapd_collection_project.c \ rcapd_collection_zone.c \ rcapd_mapping.c \ rcapd_rfd.c \ rcapd_scanner.c COMMON_SRCS = rcapd_stat.c utils.c rcapd_conf.c $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG CPPFLAGS += -DDEBUG_MSG CPPFLAGS += -I$(COMMON_DIR) LDLIBS += -lkstat -lproc -lproject -lzonecfg -lumem -lscf LDLIBS += $(EXTRA_LDLIBS) OBJS = $(SRCS:%.c=%.o) $(COMMON_SRCS:%.c=%.o) POFILES = $(OBJS:%.o=%.po) POFILE = $(PROG).po CLEANFILES = $(OBJS) include ../Makefile.com all: $(PROG) install: all $(ROOTUSRLIBRCAPPROG) $(ROOTUSRLIBRCAP) $(ROOTMANIFEST) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) check: $(CHKMANIFEST) clean: $(RM) $(CLEANFILES) $(POFILE): $(POFILES) $(RM) $@ $(CAT) $(POFILES) > $@ # Hammerhead: order-only prerequisite so $< is the file, not the directory $(ROOTUSRLIBRCAP)/%: % | $(ROOTUSRLIBRCAP) $(INS.file) include ../../Makefile.targ /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "rcapd.h" #include "utils.h" /* * An abstract "collection" of processes. Multiple types of collections can * exist, one of which is selected at run-time. Currently, the only one * defined corresponds to project(5)s. */ #define MAX(x, y) (((x) > (y)) ? (x) : (y)) typedef struct { rcid_t *lfa_colidp; lcollection_t *lfa_found; } lcollection_find_arg_t; extern void lcollection_update_project(lcollection_update_type_t, void(*)(char *, char *, int, uint64_t, int)); extern void lcollection_update_zone(lcollection_update_type_t, void(*)(char *, char *, int, uint64_t, int)); static void lcollection_update_notification_cb(char *, char *, int, uint64_t, int); rcid_t(*rc_getidbypsinfo)(psinfo_t *); uint64_t phys_total = 0; static lcollection_t *lcollection_head = NULL; void lcollection_update(lcollection_update_type_t ut) { lcollection_update_zone(ut, lcollection_update_notification_cb); lcollection_update_project(ut, lcollection_update_notification_cb); } /* * Inserts a collection with the supplied identity, or updates the caps of an * existing one. The return value will have these bits set, depending on the * previous and new cap values. If no cap was displaced, and the requested cap * is 0, no collection will be added, and the applicable *ZERO flags will be * set. * * LCST_CAP_CHANGED * LCST_CAP_REMOVED * LCSS_CAP_ZERO */ lcollection_t * lcollection_insert_update(rcid_t *colidp, uint64_t rss_cap, char *name, int *changes) { lcollection_t *lcol; *changes = 0; if (rss_cap == 0) *changes |= LCST_CAP_ZERO; lcol = lcollection_find(colidp); /* * If the specified collection is capped, add it to lcollection. */ if (lcol == NULL) { /* * If the cap has been zeroed and the collection doesn't exist, * don't create the collection just to remvoe the cap later. */ if (rss_cap == 0) return (NULL); *changes |= LCST_CAP_CHANGED; lcol = malloc(sizeof (*lcol)); if (lcol == NULL) { debug("not enough memory to monitor %s %s", (colidp->rcid_type == RCIDT_PROJECT ? "project" : "zone"), name); return (NULL); } (void) bzero(lcol, sizeof (*lcol)); lcol->lcol_id = *colidp; debug("added collection %s\n", name); lcol->lcol_prev = NULL; lcol->lcol_next = lcollection_head; lcol->lcol_stat.lcols_min_rss = (uint64_t)-1; if (lcollection_head != NULL) lcollection_head->lcol_prev = lcol; lcollection_head = lcol; } /* * Set/update the collection's name. */ (void) strlcpy(lcol->lcol_name, name, sizeof (lcol->lcol_name)); /* * Set cap flags. */ if (rss_cap != lcol->lcol_rss_cap) { *changes |= LCST_CAP_CHANGED; lcol->lcol_rss_cap = rss_cap; if (lcol->lcol_rss_cap == 0) *changes |= LCST_CAP_REMOVED; } if (rss_cap > 0) lcol->lcol_mark++; return (lcol); } static void lcollection_update_notification_cb(char *col_type, char *name, int changes, uint64_t rss_cap, int mark) { /* * Assume the collection has been updated redundantly if its mark count * exceeds 1, and that another notification is unnecessary. */ if (mark > 1) return; if (changes & LCST_CAP_ZERO) debug("%s %s: %s\n", col_type, name, (changes & LCST_CAP_REMOVED) ? "cap removed" : "uncapped"); else debug("%s %s: cap: %llukB\n", col_type, name, (unsigned long long)rss_cap); } /* * Function to walk list of collections and invoke the specified callback with * the specified argument. Callbacks are allowed to change the linkage of the * collection on which they act. */ void list_walk_collection(int (*cb)(lcollection_t *, void *), void *arg) { lcollection_t *lcol; lcollection_t *next; lcol = lcollection_head; while (lcol != NULL) { next = lcol->lcol_next; if (cb(lcol, arg) != 0) return; lcol = next; } } /* * Returns a nonzero value if an lprocess_t is still a valid member of a given * collection. */ int lcollection_member(lcollection_t *lcol, lprocess_t *lpc) { lprocess_t *cur = lcol->lcol_lprocess; while (cur != NULL) if (cur == lpc) return (1); else cur = cur->lpc_next; return (0); } static int lcollection_find_cb(lcollection_t *lcol, void *arg) { rcid_t *colidp = ((lcollection_find_arg_t *)arg)->lfa_colidp; if (lcol->lcol_id.rcid_type == colidp->rcid_type && lcol->lcol_id.rcid_val == colidp->rcid_val) { ((lcollection_find_arg_t *)arg)->lfa_found = lcol; return (1); } return (0); } lcollection_t * lcollection_find(rcid_t *colidp) { lcollection_find_arg_t lfa; lfa.lfa_colidp = colidp; lfa.lfa_found = NULL; list_walk_collection(lcollection_find_cb, &lfa); return (lfa.lfa_found); } /* * Unlinks a collection from lcollection. */ void lcollection_free(lcollection_t *lcol) { lprocess_t *lpc; lprocess_t *next; lpc = lcol->lcol_lprocess; while (lpc != NULL) { next = lpc->lpc_next; if (lpc->lpc_collection == lcol) lprocess_free(lpc); lpc = next; } /* * Unlink the collection. */ if (lcol->lcol_prev != NULL) lcol->lcol_prev->lcol_next = lcol->lcol_next; if (lcol->lcol_next != NULL) lcol->lcol_next->lcol_prev = lcol->lcol_prev; if (lcollection_head == lcol) lcollection_head = lcol->lcol_next; lcol->lcol_next = lcol->lcol_prev = NULL; free(lcol); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "rcapd.h" #include "utils.h" /* absolute cap name */ #define PJ_ABS_ATTR_NAME "rcap.max-rss" /* round up to next y = 2^n */ #define ROUNDUP(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) static int lcollection_update_project_cb(const struct project *proj, void *walk_data) { void(*update_notification_cb)(char *, char *, int, uint64_t, int) = (void(*)(char *, char *, int, uint64_t, int))walk_data; char *capattr_abs; char *end; int changes; int64_t max_rss; lcollection_t *lcol; rcid_t colid; capattr_abs = strstr(proj->pj_attr, PJ_ABS_ATTR_NAME "="); if (capattr_abs != NULL) { if (capattr_abs > proj->pj_attr) if (*(capattr_abs - 1) != ';') { /* * PJ_ABS_ATTR_NAME only matched part * of an attribute. */ return (0); } capattr_abs += strlen(PJ_ABS_ATTR_NAME "="); max_rss = ROUNDUP(strtoll(capattr_abs, &end, 10), 1024) / 1024; if (end == capattr_abs || *end != ';' && *end != 0) warn(gettext("project %s: malformed %s value '%s'\n"), proj->pj_name, PJ_ABS_ATTR_NAME, capattr_abs); } else max_rss = 0; colid.rcid_type = RCIDT_PROJECT; colid.rcid_val = proj->pj_projid; lcol = lcollection_insert_update(&colid, max_rss, proj->pj_name, &changes); if (update_notification_cb != NULL) update_notification_cb("project", proj->pj_name, changes, max_rss, (lcol != NULL) ? lcol->lcol_mark : 0); return (0); } static int lcollection_update_project_byid_cb(const projid_t id, void *walk_data) { char buf[PROJECT_BUFSZ]; struct project proj; if (getprojbyid(id, &proj, buf, sizeof (buf)) != NULL && proj.pj_attr != NULL) return (lcollection_update_project_cb(&proj, walk_data)); return (0); } static int lcollection_update_onceactive_cb(lcollection_t *lcol, void *walk_data) { void(*update_notification_cb)(char *, char *, int, uint64_t, int) = (void(*)(char *, char *, int, uint64_t, int))walk_data; if (lcol->lcol_id.rcid_type != RCIDT_PROJECT) return (0); return (lcollection_update_project_byid_cb(lcol->lcol_id.rcid_val, (void *)update_notification_cb)); } static int project_walk_all(int(*cb)(const struct project *, void *), void *walk_data) { char buf[PROJECT_BUFSZ]; struct project proj; int res = 0; setprojent(); while (getprojent(&proj, buf, sizeof (buf)) != NULL && res == 0) res = cb(&proj, walk_data); endprojent(); return (res); } void lcollection_update_project(lcollection_update_type_t ut, void(*update_notification_cb)(char *, char *, int, uint64_t, int)) { switch (ut) { case LCU_ACTIVE_ONLY: /* * Enumerate active projects. This is much faster than * enumerating all projects (as is done below, in the default * case), and is done to efficiently and incrementally update * lcollection with capped projects. The default case performs * the initialization. */ (void) project_walk(lcollection_update_project_byid_cb, (void *)update_notification_cb); /* * Enumerate once-active projects, including the active * projects just enumerated, meaning active projects will be * updated and marked twice. */ list_walk_collection(lcollection_update_onceactive_cb, (void *)update_notification_cb); break; default: /* * Enumerate all projects. */ (void) project_walk_all(lcollection_update_project_cb, (void *)update_notification_cb); } } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include "rcapd.h" #include "utils.h" extern boolean_t gz_capped; /* round up to next y = 2^n */ #define ROUNDUP(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) static void update_zone(zone_entry_t *zent, void *walk_data) { void(*update_notification_cb)(char *, char *, int, uint64_t, int) = (void(*)(char *, char *, int, uint64_t, int))walk_data; int changes; int64_t max_rss; uint64_t mcap; lcollection_t *lcol; rcid_t colid; if (zone_getattr(zent->zid, ZONE_ATTR_PHYS_MCAP, &mcap, sizeof (mcap)) != -1 && mcap != 0) max_rss = ROUNDUP(mcap, 1024) / 1024; else max_rss = 0; if (zent->zid == GLOBAL_ZONEID) { if (max_rss > 0) gz_capped = B_TRUE; else gz_capped = B_FALSE; } colid.rcid_type = RCIDT_ZONE; colid.rcid_val = zent->zid; lcol = lcollection_insert_update(&colid, max_rss, zent->zname, &changes); if (update_notification_cb != NULL) update_notification_cb("zone", zent->zname, changes, max_rss, (lcol != NULL) ? lcol->lcol_mark : 0); } /* ARGSUSED */ void lcollection_update_zone(lcollection_update_type_t ut, void(*update_notification_cb)(char *, char *, int, uint64_t, int)) { int i; uint_t nzents; zone_entry_t *zents; /* * Enumerate running zones. */ if (get_running_zones(&nzents, &zents) != 0) return; for (i = 0; i < nzents; i++) { update_zone(&zents[i], (void *)update_notification_cb); } free(zents); } /* * 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. */ /* * rcapd is a long-running daemon enforcing project-based resource caps (see * rcapd(8)). Each instance of a process aggregate (project or, generically, * "collection") may have a memory cap. A single thread monitors the resource * utilization of capped collections, enforces caps when they are exceeded (and * other conditions are met), and incorporates changes in configuration or * caps. Each of these actions occurs not more frequently than the rate * specified with rcapadm(8). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rcapd.h" #include "rcapd_mapping.h" #include "rcapd_rfd.h" #include "rcapd_stat.h" #include "utils.h" #define POSITIVE_MIN(x, y) \ (((x) <= 0) ? (y) : ((y) <= 0) ? (x) : MIN(x, y)) #define NEXT_EVENT_TIME(base, seconds) \ (((int)seconds > 0) ? (base + (hrtime_t)seconds * (hrtime_t)NANOSEC) \ : (hrtime_t)0) #define NEXT_REPORT_EVENT_TIME(base, seconds) \ ((rcfg.rcfg_stat_file[0] != 0) ? \ NEXT_EVENT_TIME(gethrtime(), seconds) : (hrtime_t)0) #define EVENT_TIME(time, eventtime) \ (((time) > (eventtime)) && (eventtime) != 0) #define STAT_TEMPLATE_SUFFIX ".XXXXXX" /* suffix of mkstemp() arg */ #define DAEMON_UID 1 /* uid to use */ #define CAPPED_PROJECT 0x01 #define CAPPED_ZONE 0x02 typedef struct soft_scan_arg { uint64_t ssa_sum_excess; int64_t ssa_scan_goal; boolean_t ssa_project_over_cap; } soft_scan_arg_t; typedef struct sample_col_arg { boolean_t sca_any_over_cap; boolean_t sca_project_over_cap; } sample_col_arg_t; static int debug_mode = 0; /* debug mode flag */ static pid_t rcapd_pid; /* rcapd's pid to ensure it's not */ /* scanned */ static kstat_ctl_t *kctl; /* kstat chain */ static int memory_pressure = 0; /* physical memory utilization (%) */ static int memory_pressure_sample = 0; /* count of samples */ static long page_size_kb = 0; /* system page size in KB */ static size_t nvmu_vals = 0; /* # of kernel RSS/swap vals in array */ static size_t vmu_vals_len = 0; /* size of RSS/swap vals array */ static vmusage_t *vmu_vals = NULL; /* snapshot of kernel RSS/swap values */ static hrtime_t next_report; /* time of next report */ static int termination_signal = 0; /* terminating signal */ static zoneid_t my_zoneid = (zoneid_t)-1; static lcollection_t *gz_col; /* global zone collection */ rcfg_t rcfg; /* * Updated when we re-read the collection configurations if this rcapd instance * is running in the global zone and the global zone is capped. */ boolean_t gz_capped = B_FALSE; /* * Flags. */ static int ever_ran; int should_run; static int should_reconfigure; static int verify_statistics(void); static int update_statistics(void); /* * Checks if a process is marked 'system'. Returns FALSE only when it is not. */ static boolean_t proc_issystem(pid_t pid) { char pc_clname[PC_CLNMSZ]; if (priocntl(P_PID, pid, PC_GETXPARMS, NULL, PC_KY_CLNAME, pc_clname, PC_KY_NULL) != -1) { return (strcmp(pc_clname, "SYS") == 0); } else { debug("cannot get class-specific scheduling parameters; " "assuming system process\n"); return (B_TRUE); } } static void lprocess_insert_mark(psinfo_t *psinfop) { pid_t pid = psinfop->pr_pid; /* flag indicating whether the process should be scanned. */ int unscannable = psinfop->pr_nlwp == 0; rcid_t colid; lcollection_t *lcol; lprocess_t *lproc; /* * Determine which collection to put this process into. We only have * to worry about tracking both zone and project capped processes if * this rcapd instance is running in the global zone, since we'll only * see processes in our own projects in a non-global zone. In the * global zone, if the process belongs to a non-global zone, we only * need to track it for the capped non-global zone collection. For * global zone processes, we first attempt to put the process into a * capped project collection. On the second pass into this function * the projid will be cleared so we will just track the process for the * global zone collection as a whole. */ if (psinfop->pr_zoneid == my_zoneid && psinfop->pr_projid != -1) { colid.rcid_type = RCIDT_PROJECT; colid.rcid_val = psinfop->pr_projid; } else { /* try to add to zone collection */ colid.rcid_type = RCIDT_ZONE; colid.rcid_val = psinfop->pr_zoneid; } if ((lcol = lcollection_find(&colid)) == NULL) return; /* * If the process is already being tracked, update the unscannable flag, * as determined by the caller, from the process's psinfo. */ lproc = lcol->lcol_lprocess; while (lproc != NULL) { if (lproc->lpc_pid == pid) { lproc->lpc_mark = 1; if (unscannable != 0 && lproc->lpc_unscannable == 0) { debug("process %d: became unscannable\n", (int)lproc->lpc_pid); lproc->lpc_unscannable = 1; } return; } lproc = lproc->lpc_next; } /* * We've fallen off the list without finding our current process; * insert it at the list head. */ if ((lproc = malloc(sizeof (*lproc))) == NULL) debug("insufficient memory to track new process %d", (int)pid); else { (void) bzero(lproc, sizeof (*lproc)); lproc->lpc_pid = pid; lproc->lpc_mark = 1; lproc->lpc_collection = lcol; lproc->lpc_psinfo_fd = -1; lproc->lpc_pgdata_fd = -1; lproc->lpc_xmap_fd = -1; /* * If the caller didn't flag this process as unscannable * already, do some more checking. */ lproc->lpc_unscannable = unscannable || proc_issystem(pid); #ifdef DEBUG /* * Verify the sanity of lprocess. It should not contain the * process we are about to prepend. */ if (lcollection_member(lcol, lproc)) { lprocess_t *cur = lcol->lcol_lprocess; debug("The collection %lld already has these members, " "including me, %d!\n", (long long)lcol->lcol_id.rcid_val, (int)lproc->lpc_pid); while (cur != NULL) { debug("\t%d\n", (int)cur->lpc_pid); cur = cur->lpc_next; } info(gettext("process already on lprocess\n")); abort(); } #endif /* DEBUG */ lproc->lpc_next = lcol->lcol_lprocess; if (lproc->lpc_next != NULL) lproc->lpc_next->lpc_prev = lproc; lproc->lpc_prev = NULL; lcol->lcol_lprocess = lproc; debug("tracking %s %ld %d %s%s\n", (colid.rcid_type == RCIDT_PROJECT ? "project" : "zone"), (long)colid.rcid_val, (int)pid, psinfop->pr_psargs, (lproc->lpc_unscannable != 0) ? " (not scannable)" : ""); lcol->lcol_stat.lcols_proc_in++; } } static int list_walk_process_cb(lcollection_t *lcol, void *arg) { int (*cb)(lcollection_t *, lprocess_t *) = (int(*)(lcollection_t *, lprocess_t *))arg; lprocess_t *member; lprocess_t *next; member = lcol->lcol_lprocess; while (member != NULL) { pid_t pid = member->lpc_pid; next = member->lpc_next; debug_high("list_walk_all lpc %d\n", (int)pid); if (cb(lcol, member) != 0) { debug_high("list_walk_all aborted at lpc %d\n", (int)pid); return (1); } member = next; } return (0); } /* * Invoke the given callback for each process in each collection. Callbacks * are allowed to change the linkage of the process on which they act. */ static void list_walk_all(int (*cb)(lcollection_t *, lprocess_t *)) { list_walk_collection(list_walk_process_cb, (void *)cb); } static void revoke_psinfo(rfd_t *rfd) { lprocess_t *lpc = (lprocess_t *)rfd->rfd_data; if (lpc != NULL) { debug("revoking psinfo fd for process %d\n", (int)lpc->lpc_pid); ASSERT(lpc->lpc_psinfo_fd != -1); lpc->lpc_psinfo_fd = -1; } else debug("revoking psinfo fd for unknown process\n"); } /* * Retrieve a process's psinfo via an already-opened or new file descriptor. * The supplied descriptor will be closed on failure. An optional callback * will be invoked with the last descriptor tried, and a supplied callback * argument, as its arguments, such that the new descriptor may be cached, or * an old one may be invalidated. If the result of the callback is zero, the * the caller is to assume responsibility for the file descriptor, to close it * with rfd_close(). * * On failure, a nonzero value is returned. */ int get_psinfo(pid_t pid, psinfo_t *psinfo, int cached_fd, int(*fd_update_cb)(void *, int), void *arg, lprocess_t *lpc) { int fd; int can_try_uncached; ASSERT(!(cached_fd > 0 && fd_update_cb == NULL)); do { if (cached_fd >= 0) { fd = cached_fd; can_try_uncached = 1; debug_high("%d/psinfo, trying cached fd %d\n", (int)pid, fd); } else { char pathbuf[PROC_PATH_MAX]; can_try_uncached = 0; (void) snprintf(pathbuf, sizeof (pathbuf), "/proc/%d/psinfo", (int)pid); if ((fd = rfd_open(pathbuf, 1, RFD_PSINFO, revoke_psinfo, lpc, O_RDONLY, 0000)) < 0) { debug("cannot open %s", pathbuf); break; } else debug_high("opened %s, fd %d\n", pathbuf, fd); } if (pread(fd, psinfo, sizeof (*psinfo), 0) == sizeof (*psinfo) && psinfo->pr_pid == pid) break; else { debug_high("closed fd %d\n", fd); if (rfd_close(fd) != 0) debug("could not close fd %d", fd); fd = cached_fd = -1; } } while (can_try_uncached == 1); if (fd_update_cb == NULL || fd_update_cb(arg, fd) != 0) if (fd >= 0) { debug_high("closed %s fd %d\n", fd_update_cb == NULL ? "uncached" : "cached", fd); if (rfd_close(fd) != 0) debug("could not close fd %d", fd); } debug_high("get_psinfo ret %d, fd %d, %s\n", ((fd >= 0) ? 0 : -1), fd, fd_update_cb != NULL ? "cached" : "uncached"); return ((fd >= 0) ? 0 : -1); } /* * Retrieve the collection membership of all processes and update the psinfo of * those non-system, non-zombie ones in collections. For global zone processes, * we first attempt to put the process into a capped project collection. We * also want to track the process for the global zone collection as a whole. */ static void proc_cb(const pid_t pid) { psinfo_t psinfo; if (get_psinfo(pid, &psinfo, -1, NULL, NULL, NULL) == 0) { lprocess_insert_mark(&psinfo); if (gz_capped && psinfo.pr_zoneid == GLOBAL_ZONEID) { /* * We also want to track this process for the global * zone as a whole so add it to the global zone * collection as well. */ psinfo.pr_projid = -1; lprocess_insert_mark(&psinfo); } } } /* * Cache the process' psinfo fd, taking responsibility for freeing it. */ int lprocess_update_psinfo_fd_cb(void *arg, int fd) { lprocess_t *lpc = arg; lpc->lpc_psinfo_fd = fd; return (0); } /* * Get the system pagesize. */ static void get_page_size(void) { page_size_kb = sysconf(_SC_PAGESIZE) / 1024; debug("physical page size: %luKB\n", page_size_kb); } static void tm_fmt(char *msg, hrtime_t t1, hrtime_t t2) { hrtime_t diff = t2 - t1; if (diff < MILLISEC) debug("%s: %lld nanoseconds\n", msg, diff); else if (diff < MICROSEC) debug("%s: %.2f microseconds\n", msg, (float)diff / MILLISEC); else if (diff < NANOSEC) debug("%s: %.2f milliseconds\n", msg, (float)diff / MICROSEC); else debug("%s: %.2f seconds\n", msg, (float)diff / NANOSEC); } /* * Get the zone's & project's RSS from the kernel. */ static void rss_sample(boolean_t my_zone_only, uint_t col_types) { size_t nres; size_t i; uint_t flags; hrtime_t t1, t2; if (my_zone_only) { flags = VMUSAGE_ZONE; } else { flags = 0; if (col_types & CAPPED_PROJECT) flags |= VMUSAGE_PROJECTS; if (col_types & CAPPED_ZONE && my_zoneid == GLOBAL_ZONEID) flags |= VMUSAGE_ALL_ZONES; } debug("vmusage sample flags 0x%x\n", flags); if (flags == 0) return; again: /* try the current buffer to see if the list will fit */ nres = vmu_vals_len; t1 = gethrtime(); if (getvmusage(flags, my_zone_only ? 0 : rcfg.rcfg_rss_sample_interval, vmu_vals, &nres) != 0) { if (errno != EOVERFLOW) { warn(gettext("can't read RSS from kernel\n")); return; } } t2 = gethrtime(); tm_fmt("getvmusage time", t1, t2); debug("kernel nres %lu\n", (ulong_t)nres); if (nres > vmu_vals_len) { /* array size is now too small, increase it and try again */ free(vmu_vals); if ((vmu_vals = (vmusage_t *)calloc(nres, sizeof (vmusage_t))) == NULL) { warn(gettext("out of memory: could not read RSS from " "kernel\n")); vmu_vals_len = nvmu_vals = 0; return; } vmu_vals_len = nres; goto again; } nvmu_vals = nres; debug("vmusage_sample\n"); for (i = 0; i < nvmu_vals; i++) { debug("%d: id: %d, type: 0x%x, rss_all: %llu (%lluKB), " "swap: %llu\n", (int)i, (int)vmu_vals[i].vmu_id, vmu_vals[i].vmu_type, (unsigned long long)vmu_vals[i].vmu_rss_all, (unsigned long long)vmu_vals[i].vmu_rss_all / 1024, (unsigned long long)vmu_vals[i].vmu_swap_all); } } static void update_col_rss(lcollection_t *lcol) { int i; lcol->lcol_rss = 0; lcol->lcol_image_size = 0; for (i = 0; i < nvmu_vals; i++) { if (vmu_vals[i].vmu_id != lcol->lcol_id.rcid_val) continue; if (vmu_vals[i].vmu_type == VMUSAGE_ZONE && lcol->lcol_id.rcid_type != RCIDT_ZONE) continue; if (vmu_vals[i].vmu_type == VMUSAGE_PROJECTS && lcol->lcol_id.rcid_type != RCIDT_PROJECT) continue; /* we found the right RSS entry, update the collection vals */ lcol->lcol_rss = vmu_vals[i].vmu_rss_all / 1024; lcol->lcol_image_size = vmu_vals[i].vmu_swap_all / 1024; break; } } /* * Sample the collection RSS, updating the collection's statistics with the * results. Also, sum the rss of all capped projects & return true if * the collection is over cap. */ static int rss_sample_col_cb(lcollection_t *lcol, void *arg) { int64_t excess; uint64_t rss; sample_col_arg_t *col_argp = (sample_col_arg_t *)arg; update_col_rss(lcol); lcol->lcol_stat.lcols_rss_sample++; rss = lcol->lcol_rss; excess = rss - lcol->lcol_rss_cap; if (excess > 0) { lcol->lcol_stat.lcols_rss_act_sum += rss; col_argp->sca_any_over_cap = B_TRUE; if (lcol->lcol_id.rcid_type == RCIDT_PROJECT) col_argp->sca_project_over_cap = B_TRUE; } lcol->lcol_stat.lcols_rss_sum += rss; if (lcol->lcol_stat.lcols_min_rss > rss) lcol->lcol_stat.lcols_min_rss = rss; if (lcol->lcol_stat.lcols_max_rss < rss) lcol->lcol_stat.lcols_max_rss = rss; return (0); } /* * Determine if we have capped projects, capped zones or both. */ static int col_type_cb(lcollection_t *lcol, void *arg) { uint_t *col_type = (uint_t *)arg; /* skip uncapped collections */ if (lcol->lcol_rss_cap == 0) return (1); if (lcol->lcol_id.rcid_type == RCIDT_PROJECT) *col_type |= CAPPED_PROJECT; else *col_type |= CAPPED_ZONE; /* once we know everything is capped, we can stop looking */ if ((*col_type & CAPPED_ZONE) && (*col_type & CAPPED_PROJECT)) return (1); return (0); } /* * Open /proc and walk entries. */ static void proc_walk_all(void (*cb)(const pid_t)) { DIR *pdir; struct dirent *dirent; pid_t pid; (void) rfd_reserve(1); if ((pdir = opendir("/proc")) == NULL) die(gettext("couldn't open /proc!")); while ((dirent = readdir(pdir)) != NULL) { if (strcmp(".", dirent->d_name) == 0 || strcmp("..", dirent->d_name) == 0) continue; pid = atoi(dirent->d_name); ASSERT(pid != 0 || strcmp(dirent->d_name, "0") == 0); if (pid == rcapd_pid) continue; else cb(pid); } (void) closedir(pdir); } /* * Clear unmarked callback. */ /*ARGSUSED*/ static int sweep_process_cb(lcollection_t *lcol, lprocess_t *lpc) { if (lpc->lpc_mark) { lpc->lpc_mark = 0; } else { debug("process %d finished\n", (int)lpc->lpc_pid); lprocess_free(lpc); } return (0); } /* * Print, for debugging purposes, a collection's recently-sampled RSS and * excess. */ /*ARGSUSED*/ static int excess_print_cb(lcollection_t *lcol, void *arg) { int64_t excess = lcol->lcol_rss - lcol->lcol_rss_cap; debug("%s %s rss/cap: %llu/%llu, excess = %lld kB\n", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), lcol->lcol_name, (unsigned long long)lcol->lcol_rss, (unsigned long long)lcol->lcol_rss_cap, (long long)excess); return (0); } /* * Scan those collections which have exceeded their caps. * * If we're running in the global zone it might have a cap. We don't want to * do any capping for the global zone yet since we might get under the cap by * just capping the projects in the global zone. */ /*ARGSUSED*/ static int scan_cb(lcollection_t *lcol, void *arg) { int64_t excess; /* skip over global zone collection for now but keep track for later */ if (lcol->lcol_id.rcid_type == RCIDT_ZONE && lcol->lcol_id.rcid_val == GLOBAL_ZONEID) { gz_col = lcol; return (0); } if ((excess = lcol->lcol_rss - lcol->lcol_rss_cap) > 0) { scan(lcol, excess); lcol->lcol_stat.lcols_scan++; } return (0); } /* * Scan the global zone collection and see if it still exceeds its cap. * We take into account the effects of capping any global zone projects here. */ static void scan_gz(lcollection_t *lcol, boolean_t project_over_cap) { int64_t excess; /* * If we had projects over their cap and the global zone was also over * its cap then we need to get the up-to-date global zone rss to * determine if we are still over the global zone cap. We might have * gone under while we scanned the capped projects. If there were no * projects over cap then we can use the rss value we already have for * the global zone. */ excess = lcol->lcol_rss - lcol->lcol_rss_cap; if (project_over_cap && excess > 0) { rss_sample(B_TRUE, CAPPED_ZONE); update_col_rss(lcol); excess = lcol->lcol_rss - lcol->lcol_rss_cap; } if (excess > 0) { debug("global zone excess %lldKB\n", (long long)excess); scan(lcol, excess); lcol->lcol_stat.lcols_scan++; } } /* * Do a soft scan of those collections which have excesses. A soft scan is one * in which the cap enforcement pressure is taken into account. The difference * between the utilized physical memory and the cap enforcement pressure will * be scanned-for, and each collection will be scanned proportionally by their * present excesses. */ static int soft_scan_cb(lcollection_t *lcol, void *a) { int64_t excess; soft_scan_arg_t *arg = a; /* skip over global zone collection for now but keep track for later */ if (lcol->lcol_id.rcid_type == RCIDT_ZONE && lcol->lcol_id.rcid_val == GLOBAL_ZONEID) { gz_col = lcol; return (0); } if ((excess = lcol->lcol_rss - lcol->lcol_rss_cap) > 0) { int64_t adjusted_excess = excess * arg->ssa_scan_goal / arg->ssa_sum_excess; debug("%s %ld excess %lld scan_goal %lld sum_excess %llu, " "scanning %lld\n", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), (long)lcol->lcol_id.rcid_val, (long long)excess, (long long)arg->ssa_scan_goal, (unsigned long long)arg->ssa_sum_excess, (long long)adjusted_excess); scan(lcol, adjusted_excess); lcol->lcol_stat.lcols_scan++; } return (0); } static void soft_scan_gz(lcollection_t *lcol, void *a) { int64_t excess; soft_scan_arg_t *arg = a; /* * If we had projects over their cap and the global zone was also over * its cap then we need to get the up-to-date global zone rss to * determine if we are still over the global zone cap. We might have * gone under while we scanned the capped projects. If there were no * projects over cap then we can use the rss value we already have for * the global zone. */ excess = lcol->lcol_rss - lcol->lcol_rss_cap; if (arg->ssa_project_over_cap && excess > 0) { rss_sample(B_TRUE, CAPPED_ZONE); update_col_rss(lcol); excess = lcol->lcol_rss - lcol->lcol_rss_cap; } if (excess > 0) { int64_t adjusted_excess = excess * arg->ssa_scan_goal / arg->ssa_sum_excess; debug("%s %ld excess %lld scan_goal %lld sum_excess %llu, " "scanning %lld\n", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), (long)lcol->lcol_id.rcid_val, (long long)excess, (long long)arg->ssa_scan_goal, (unsigned long long)arg->ssa_sum_excess, (long long)adjusted_excess); scan(lcol, adjusted_excess); lcol->lcol_stat.lcols_scan++; } } /* * When a scan could happen, but caps aren't enforced tick the * lcols_unenforced_cap counter. */ /*ARGSUSED*/ static int unenforced_cap_cb(lcollection_t *lcol, void *arg) { lcol->lcol_stat.lcols_unenforced_cap++; return (0); } /* * Update the count of physically installed memory. */ static void update_phys_total(void) { uint64_t old_phys_total; old_phys_total = phys_total; phys_total = (uint64_t)sysconf(_SC_PHYS_PAGES) * page_size_kb; if (phys_total != old_phys_total) debug("physical memory%s: %lluM\n", (old_phys_total == 0 ? "" : " adjusted"), (unsigned long long)(phys_total / 1024)); } /* * Unlink a process from its collection, updating relevant statistics, and * freeing its associated memory. */ void lprocess_free(lprocess_t *lpc) { pid_t pid; lpc->lpc_collection->lcol_stat.lcols_proc_out++; if (lpc->lpc_prev != NULL) lpc->lpc_prev->lpc_next = lpc->lpc_next; if (lpc->lpc_next != NULL) lpc->lpc_next->lpc_prev = lpc->lpc_prev; if (lpc->lpc_collection->lcol_lprocess == lpc) lpc->lpc_collection->lcol_lprocess = (lpc->lpc_next != lpc ? lpc->lpc_next : NULL); lpc->lpc_next = lpc->lpc_prev = NULL; if (lpc->lpc_prpageheader != NULL) free(lpc->lpc_prpageheader); if (lpc->lpc_xmap != NULL) free(lpc->lpc_xmap); if (lpc->lpc_psinfo_fd >= 0) { if (rfd_close(lpc->lpc_psinfo_fd) != 0) debug("could not close %d lpc_psinfo_fd %d", (int)lpc->lpc_pid, lpc->lpc_psinfo_fd); lpc->lpc_psinfo_fd = -1; } if (lpc->lpc_pgdata_fd >= 0) { if (rfd_close(lpc->lpc_pgdata_fd) != 0) debug("could not close %d lpc_pgdata_fd %d", (int)lpc->lpc_pid, lpc->lpc_pgdata_fd); lpc->lpc_pgdata_fd = -1; } if (lpc->lpc_xmap_fd >= 0) { if (rfd_close(lpc->lpc_xmap_fd) != 0) debug("could not close %d lpc_xmap_fd %d", (int)lpc->lpc_pid, lpc->lpc_xmap_fd); lpc->lpc_xmap_fd = -1; } if (lpc->lpc_ignore != NULL) lmapping_free(&lpc->lpc_ignore); pid = lpc->lpc_pid; free(lpc); debug_high("process %d freed\n", (int)pid); } /* * Collection clear callback. */ /*ARGSUSED*/ static int collection_clear_cb(lcollection_t *lcol, void *arg) { lcol->lcol_mark = 0; return (0); } /* * Respond to a terminating signal by setting a termination flag. */ /*ARGSUSED*/ static void terminate_signal(int signal) { if (termination_signal == 0) termination_signal = signal; should_run = 0; } /* * Handle any synchronous or asynchronous signals that would ordinarily cause a * process to abort. */ /*ARGSUSED*/ static void abort_signal(int signal) { /* * Allow the scanner to make a last-ditch effort to resume any stopped * processes. */ scan_abort(); abort(); } /* * Clean up collections which have been removed due to configuration. Unlink * the collection from lcollection and free it. */ /*ARGSUSED*/ static int collection_sweep_cb(lcollection_t *lcol, void *arg) { if (lcol->lcol_mark == 0) { debug("freeing %s %s\n", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), lcol->lcol_name); lcollection_free(lcol); } return (0); } /* * Set those variables which depend on the global configuration. */ static void finish_configuration(void) { /* * Warn that any lnode (or non-project) mode specification (by an SRM * 1.3 configuration file, for example) is ignored. */ if (strcmp(rcfg.rcfg_mode_name, "project") != 0) { warn(gettext("%s mode specification ignored -- using project" " mode\n"), rcfg.rcfg_mode_name); rcfg.rcfg_mode_name = "project"; rcfg.rcfg_mode = rctype_project; } } /* * Cause the configuration to be reread and applied. */ static void reread_configuration(void) { rcfg_t rcfg_new; if (rcfg_read(&rcfg_new, update_statistics) != E_SUCCESS) { warn(gettext("can't reread configuration \n")); exit(SMF_EXIT_ERR_CONFIG); } else { /* * Done reading configuration. Remove existing * collections in case there is a change in collection type. */ if (rcfg.rcfg_mode != rcfg_new.rcfg_mode) { list_walk_collection(collection_clear_cb, NULL); list_walk_collection(collection_sweep_cb, NULL); } /* * Make the newly-read configuration the global one, and update * any variables that depend on it. */ rcfg = rcfg_new; finish_configuration(); } } /* * First, examine changes, additions, and deletions to cap definitions. * Then, set the next event time. */ static void reconfigure(hrtime_t now, hrtime_t *next_configuration, hrtime_t *next_proc_walk, hrtime_t *next_rss_sample) { debug("reconfigure...\n"); /* * Walk the lcollection, marking active collections so inactive ones * can be freed. */ list_walk_collection(collection_clear_cb, NULL); lcollection_update(LCU_ACTIVE_ONLY); /* mark */ list_walk_collection(collection_sweep_cb, NULL); *next_configuration = NEXT_EVENT_TIME(now, rcfg.rcfg_reconfiguration_interval); /* * Reset each event time to the shorter of the previous and new * intervals. */ if (next_report == 0 && rcfg.rcfg_report_interval > 0) next_report = now; else next_report = POSITIVE_MIN(next_report, NEXT_REPORT_EVENT_TIME(now, rcfg.rcfg_report_interval)); if (*next_proc_walk == 0 && rcfg.rcfg_proc_walk_interval > 0) *next_proc_walk = now; else *next_proc_walk = POSITIVE_MIN(*next_proc_walk, NEXT_EVENT_TIME(now, rcfg.rcfg_proc_walk_interval)); if (*next_rss_sample == 0 && rcfg.rcfg_rss_sample_interval > 0) *next_rss_sample = now; else *next_rss_sample = POSITIVE_MIN(*next_rss_sample, NEXT_EVENT_TIME(now, rcfg.rcfg_rss_sample_interval)); } /* * Respond to SIGHUP by triggering the rereading the configuration and cap * definitions. */ /*ARGSUSED*/ static void sighup(int signal) { should_reconfigure = 1; } /* * Print, for debugging purposes, each collection's interval statistics. */ /*ARGSUSED*/ static int simple_report_collection_cb(lcollection_t *lcol, void *arg) { #define DELTA(field) \ (unsigned long long)( \ (lcol->lcol_stat.field - lcol->lcol_stat_old.field)) debug("%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 %llu ms\n", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), lcol->lcol_name, DELTA(lcols_pg_eff), DELTA(lcols_pg_att), DELTA(lcols_scan_ineffective), DELTA(lcols_scan), DELTA(lcols_unenforced_cap), DELTA(lcols_rss_sample), (unsigned long long)lcol->lcol_stat.lcols_min_rss, (unsigned long long)lcol->lcol_stat.lcols_max_rss, (unsigned long long)lcol->lcol_rss_cap, (unsigned long long)(lcol->lcol_stat.lcols_proc_in - lcol->lcol_stat.lcols_proc_out), DELTA(lcols_proc_out), DELTA(lcols_scan_count), NSEC2MSEC(DELTA(lcols_scan_time_complete))); #undef DELTA return (0); } /* * Record each collection's interval statistics in the statistics file. */ static int report_collection_cb(lcollection_t *lcol, void *arg) { lcollection_report_t dc; int fd = (intptr_t)arg; /* * Copy the relevant fields to the collection's record. */ bzero(&dc, sizeof (dc)); dc.lcol_id = lcol->lcol_id; (void) strcpy(dc.lcol_name, lcol->lcol_name); dc.lcol_rss = lcol->lcol_rss; dc.lcol_image_size = lcol->lcol_image_size; dc.lcol_rss_cap = lcol->lcol_rss_cap; dc.lcol_stat = lcol->lcol_stat; if (write(fd, &dc, sizeof (dc)) == sizeof (dc)) { lcol->lcol_stat_old = lcol->lcol_stat; } else { debug("can't write %s %s statistics", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), lcol->lcol_name); } return (0); } /* * Determine the count of pages scanned by the global page scanner, obtained * from the cpu_stat:*::scan kstats. Return zero on success. */ static int get_globally_scanned_pages(uint64_t *scannedp) { kstat_t *ksp; uint64_t scanned = 0; if (kstat_chain_update(kctl) == -1) { warn(gettext("can't update kstat chain")); return (0); } for (ksp = kctl->kc_chain; ksp != NULL; ksp = ksp->ks_next) { if (strcmp(ksp->ks_module, "cpu_stat") == 0) { if (kstat_read(kctl, ksp, NULL) != -1) { scanned += ((cpu_stat_t *) ksp->ks_data)->cpu_vminfo.scan; } else { return (-1); } } } *scannedp = scanned; return (0); } /* * Determine if the global page scanner is running, during which no memory * caps should be enforced, to prevent interference with the global page * scanner. */ static boolean_t is_global_scanner_running() { /* measure delta in page scan count */ static uint64_t new_sp = 0; static uint64_t old_sp = 0; boolean_t res = B_FALSE; if (get_globally_scanned_pages(&new_sp) == 0) { if (old_sp != 0 && (new_sp - old_sp) > 0) { debug("global memory pressure detected (%llu " "pages scanned since last interval)\n", (unsigned long long)(new_sp - old_sp)); res = B_TRUE; } old_sp = new_sp; } else { warn(gettext("unable to read cpu statistics")); new_sp = old_sp; } return (res); } /* * If soft caps are in use, determine if global memory pressure exceeds the * configured maximum above which soft caps are enforced. */ static boolean_t must_enforce_soft_caps() { /* * Check for changes to the amount of installed physical memory, to * compute the current memory pressure. */ update_phys_total(); memory_pressure = 100 - (int)((sysconf(_SC_AVPHYS_PAGES) * page_size_kb) * 100.0 / phys_total); memory_pressure_sample++; if (rcfg.rcfg_memory_cap_enforcement_pressure > 0 && memory_pressure > rcfg.rcfg_memory_cap_enforcement_pressure) { return (B_TRUE); } return (B_FALSE); } /* * Update the shared statistics file with each collection's current statistics. * Return zero on success. */ static int update_statistics(void) { int fd, res; static char template[LINELEN]; /* * Try to create a directory irrespective of whether it is existing * or not. If it is not there then it will create. Otherwise any way * it will fail at mkstemp call below. */ (void) mkdir(STAT_FILE_DIR, 0755); /* * Create a temporary file. */ if (sizeof (template) < (strlen(rcfg.rcfg_stat_file) + strlen(STAT_TEMPLATE_SUFFIX) + 1)) { debug("temporary file template size too small\n"); return (-1); } (void) strcpy(template, rcfg.rcfg_stat_file); (void) strcat(template, STAT_TEMPLATE_SUFFIX); (void) rfd_reserve(1); fd = mkstemp(template); /* * Write the header and per-collection statistics. */ if (fd >= 0) { rcapd_stat_hdr_t rs; rs.rs_pid = rcapd_pid; rs.rs_time = gethrtime(); ASSERT(sizeof (rs.rs_mode) > strlen(rcfg.rcfg_mode_name)); (void) strcpy(rs.rs_mode, rcfg.rcfg_mode_name); rs.rs_pressure_cur = memory_pressure; rs.rs_pressure_cap = rcfg.rcfg_memory_cap_enforcement_pressure; rs.rs_pressure_sample = memory_pressure_sample; if (fchmod(fd, 0644) == 0 && write(fd, &rs, sizeof (rs)) == sizeof (rs)) { list_walk_collection(report_collection_cb, (void *)(intptr_t)fd); /* * Replace the existing statistics file with this new * one. */ res = rename(template, rcfg.rcfg_stat_file); } else res = -1; (void) close(fd); } else res = -1; return (res); } /* * Verify the statistics file can be created and written to, and die if an * existing file may be in use by another rcapd. */ static int verify_statistics(void) { pid_t pid; /* * Warn if another instance of rcapd might be active. */ (void) rfd_reserve(1); pid = stat_get_rcapd_pid(rcfg.rcfg_stat_file); if (pid != rcapd_pid && pid != -1) die(gettext("%s exists; rcapd may already be active\n"), rcfg.rcfg_stat_file); return (update_statistics()); } static int sum_excess_cb(lcollection_t *lcol, void *arg) { uint64_t *sum_excess = arg; *sum_excess += MAX((int64_t)0, (int64_t)(lcol->lcol_rss - lcol->lcol_rss_cap)); return (0); } /* * Compute the quantity of memory (in kilobytes) above the cap enforcement * pressure. Set the scan goal to that quantity (or at most the excess). */ static void compute_soft_scan_goal(soft_scan_arg_t *argp) { /* * Compute the sum of the collections' excesses, which will be the * denominator. */ argp->ssa_sum_excess = 0; list_walk_collection(sum_excess_cb, &(argp->ssa_sum_excess)); argp->ssa_scan_goal = MIN((sysconf(_SC_PHYS_PAGES) * (100 - rcfg.rcfg_memory_cap_enforcement_pressure) / 100 - sysconf(_SC_AVPHYS_PAGES)) * page_size_kb, argp->ssa_sum_excess); } static void rcapd_usage(void) { info(gettext("usage: rcapd [-d]\n")); } void check_update_statistics(void) { hrtime_t now = gethrtime(); if (EVENT_TIME(now, next_report)) { debug("updating statistics...\n"); list_walk_collection(simple_report_collection_cb, NULL); if (update_statistics() != 0) debug("couldn't update statistics"); next_report = NEXT_REPORT_EVENT_TIME(now, rcfg.rcfg_report_interval); } } static void verify_and_set_privileges(void) { priv_set_t *required = priv_str_to_set("zone,sys_resource,proc_owner", ",", NULL); /* * Ensure the required privileges, suitable for controlling processes, * are possessed. */ if (setppriv(PRIV_SET, PRIV_PERMITTED, required) != 0 || setppriv( PRIV_SET, PRIV_EFFECTIVE, required) != 0) die(gettext("can't set requisite privileges")); /* * Ensure access to /var/run/daemon. */ if (setreuid(DAEMON_UID, DAEMON_UID) != 0) die(gettext("cannot become user daemon")); priv_freeset(required); } /* * This function does the top-level work to determine if we should do any * memory capping, and if so, it invokes the right call-backs to do the work. */ static void do_capping(hrtime_t now, hrtime_t *next_proc_walk) { boolean_t enforce_caps; /* soft cap enforcement flag, depending on memory pressure */ boolean_t enforce_soft_caps; /* avoid interference with kernel's page scanner */ boolean_t global_scanner_running; sample_col_arg_t col_arg; soft_scan_arg_t arg; uint_t col_types = 0; /* check what kind of collections (project/zone) are capped */ list_walk_collection(col_type_cb, &col_types); debug("collection types: 0x%x\n", col_types); /* no capped collections, skip checking rss */ if (col_types == 0) return; /* Determine if soft caps are enforced. */ enforce_soft_caps = must_enforce_soft_caps(); /* Determine if the global page scanner is running. */ global_scanner_running = is_global_scanner_running(); /* * Sample collections' member processes RSSes and recompute * collections' excess. */ rss_sample(B_FALSE, col_types); col_arg.sca_any_over_cap = B_FALSE; col_arg.sca_project_over_cap = B_FALSE; list_walk_collection(rss_sample_col_cb, &col_arg); list_walk_collection(excess_print_cb, NULL); debug("any collection/project over cap = %d, %d\n", col_arg.sca_any_over_cap, col_arg.sca_project_over_cap); if (enforce_soft_caps) debug("memory pressure %d%%\n", memory_pressure); /* * Cap enforcement is determined by the previous conditions. */ enforce_caps = !global_scanner_running && col_arg.sca_any_over_cap && (rcfg.rcfg_memory_cap_enforcement_pressure == 0 || enforce_soft_caps); debug("%senforcing caps\n", enforce_caps ? "" : "not "); /* * If soft caps are in use, determine the size of the portion from each * collection to scan for. */ if (enforce_caps && enforce_soft_caps) compute_soft_scan_goal(&arg); /* * Victimize offending collections. */ if (enforce_caps && (!enforce_soft_caps || (arg.ssa_scan_goal > 0 && arg.ssa_sum_excess > 0))) { /* * Since at least one collection is over its cap & needs * enforcing, check if it is at least time for a process walk * (we could be well past time since we only walk /proc when * we need to) and if so, update each collections process list * in a single pass through /proc. */ if (EVENT_TIME(now, *next_proc_walk)) { debug("scanning process list...\n"); proc_walk_all(proc_cb); /* insert & mark */ list_walk_all(sweep_process_cb); /* free dead procs */ *next_proc_walk = NEXT_EVENT_TIME(now, rcfg.rcfg_proc_walk_interval); } gz_col = NULL; if (enforce_soft_caps) { debug("scan goal is %lldKB\n", (long long)arg.ssa_scan_goal); list_walk_collection(soft_scan_cb, &arg); if (gz_capped && gz_col != NULL) { /* process global zone */ arg.ssa_project_over_cap = col_arg.sca_project_over_cap; soft_scan_gz(gz_col, &arg); } } else { list_walk_collection(scan_cb, NULL); if (gz_capped && gz_col != NULL) { /* process global zone */ scan_gz(gz_col, col_arg.sca_project_over_cap); } } } else if (col_arg.sca_any_over_cap) { list_walk_collection(unenforced_cap_cb, NULL); } } int main(int argc, char *argv[]) { int res; int should_fork = 1; /* fork flag */ hrtime_t now; /* current time */ hrtime_t next; /* time of next event */ int sig; /* signal iteration */ struct rlimit rl; hrtime_t next_proc_walk; /* time of next /proc scan */ hrtime_t next_configuration; /* time of next configuration */ hrtime_t next_rss_sample; /* (latest) time of next RSS sample */ (void) set_message_priority(RCM_INFO); (void) setpname("rcapd"); rcapd_pid = getpid(); (void) chdir("/"); should_run = 1; ever_ran = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * Parse command-line options. */ while ((res = getopt(argc, argv, "dF")) > 0) switch (res) { case 'd': should_fork = 0; if (debug_mode == 0) { debug_mode = 1; (void) set_message_priority(RCM_DEBUG); } else (void) set_message_priority(RCM_DEBUG_HIGH); break; case 'F': should_fork = 0; break; default: rcapd_usage(); return (E_USAGE); /*NOTREACHED*/ } /* * Read the configuration. */ if (rcfg_read(&rcfg, verify_statistics) != E_SUCCESS) { warn(gettext("resource caps not configured\n")); return (SMF_EXIT_ERR_CONFIG); } /* * If not debugging, fork and continue operating, changing the * destination of messages to syslog(). */ if (should_fork == 1) { pid_t child; debug("forking\n"); child = fork(); if (child == -1) die(gettext("cannot fork")); if (child > 0) return (0); else { rcapd_pid = getpid(); (void) set_message_destination(RCD_SYSLOG); (void) fclose(stdin); (void) fclose(stdout); (void) fclose(stderr); } /* * Start a new session and detatch from the controlling tty. */ if (setsid() == (pid_t)-1) debug(gettext("setsid() failed; cannot detach from " "terminal")); } finish_configuration(); should_reconfigure = 0; /* * Check that required privileges are possessed. */ verify_and_set_privileges(); now = next_report = next_proc_walk = next_rss_sample = gethrtime(); next_configuration = NEXT_EVENT_TIME(gethrtime(), rcfg.rcfg_reconfiguration_interval); /* * Open the kstat chain. */ kctl = kstat_open(); if (kctl == NULL) die(gettext("can't open kstats")); /* * Set RLIMIT_NOFILE as high as practical, so roughly 10K processes can * be effectively managed without revoking descriptors (at 3 per * process). */ rl.rlim_cur = 32 * 1024; rl.rlim_max = 32 * 1024; if (setrlimit(RLIMIT_NOFILE, &rl) != 0 && getrlimit(RLIMIT_NOFILE, &rl) == 0) { rl.rlim_cur = rl.rlim_max; (void) setrlimit(RLIMIT_NOFILE, &rl); } (void) enable_extended_FILE_stdio(-1, -1); if (getrlimit(RLIMIT_NOFILE, &rl) == 0) debug("fd limit: %lu\n", rl.rlim_cur); else debug("fd limit: unknown\n"); get_page_size(); my_zoneid = getzoneid(); /* * Handle those signals whose (default) exit disposition * prevents rcapd from finishing scanning before terminating. */ (void) sigset(SIGINT, terminate_signal); (void) sigset(SIGQUIT, abort_signal); (void) sigset(SIGILL, abort_signal); (void) sigset(SIGEMT, abort_signal); (void) sigset(SIGFPE, abort_signal); (void) sigset(SIGBUS, abort_signal); (void) sigset(SIGSEGV, abort_signal); (void) sigset(SIGSYS, abort_signal); (void) sigset(SIGPIPE, terminate_signal); (void) sigset(SIGALRM, terminate_signal); (void) sigset(SIGTERM, terminate_signal); (void) sigset(SIGUSR1, terminate_signal); (void) sigset(SIGUSR2, terminate_signal); (void) sigset(SIGPOLL, terminate_signal); (void) sigset(SIGVTALRM, terminate_signal); (void) sigset(SIGXCPU, abort_signal); (void) sigset(SIGXFSZ, abort_signal); for (sig = SIGRTMIN; sig <= SIGRTMAX; sig++) (void) sigset(sig, terminate_signal); /* * Install a signal handler for reconfiguration processing. */ (void) sigset(SIGHUP, sighup); /* * Determine which process collections to cap. */ lcollection_update(LCU_COMPLETE); /* * Loop forever, monitoring collections' resident set sizes and * enforcing their caps. Look for changes in caps as well as * responding to requests to reread the configuration. Update * per-collection statistics periodically. */ while (should_run != 0) { struct timespec ts; /* * Announce that rcapd is starting. */ if (ever_ran == 0) { info(gettext("starting\n")); ever_ran = 1; } /* * Check the configuration at every next_configuration interval. * Update the rss data once every next_rss_sample interval. * The condition of global memory pressure is also checked at * the same frequency, if strict caps are in use. */ now = gethrtime(); /* * Detect configuration and cap changes only when SIGHUP * is received. Call reconfigure to apply new configuration * parameters. */ if (should_reconfigure == 1) { reread_configuration(); should_reconfigure = 0; reconfigure(now, &next_configuration, &next_proc_walk, &next_rss_sample); } if (EVENT_TIME(now, next_configuration)) { reconfigure(now, &next_configuration, &next_proc_walk, &next_rss_sample); } /* * Do the main work for enforcing caps. */ if (EVENT_TIME(now, next_rss_sample)) { do_capping(now, &next_proc_walk); next_rss_sample = NEXT_EVENT_TIME(now, rcfg.rcfg_rss_sample_interval); } /* * Update the statistics file, if it's time. */ check_update_statistics(); /* * Sleep for some time before repeating. */ now = gethrtime(); next = next_configuration; next = POSITIVE_MIN(next, next_report); next = POSITIVE_MIN(next, next_rss_sample); if (next > now && should_run != 0) { debug("sleeping %-4.2f seconds\n", (float)(next - now) / (float)NANOSEC); hrt2ts(next - now, &ts); (void) nanosleep(&ts, NULL); } } if (termination_signal != 0) debug("exiting due to signal %d\n", termination_signal); if (ever_ran != 0) info(gettext("exiting\n")); /* * Unlink the statistics file before exiting. */ if (rcfg.rcfg_stat_file[0] != 0) (void) unlink(rcfg.rcfg_stat_file); return (E_SUCCESS); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "rcapd_mapping.h" #include "utils.h" /* * lmapping_t is a list of non-overlapping mappings, ordered by address. These * functions add, remove, and verify the existence of mappings in such a list. * rcapd_scanner.c is a consumer. */ typedef struct lmapping_find_cb_arg { uintptr_t lmfa_addr; size_t lmfa_size; lmapping_t *lmfa_prior; lmapping_t *lmfa_ret; } lmapping_find_cb_arg_t; #ifdef DEBUG /* * Verify a sublist is properly ordered. */ static void lmapping_verify(lmapping_t *lm) { while (lm != NULL) { if (lm->lm_next != NULL) ASSERT(lm->lm_next->lm_addr > lm->lm_addr); lm = lm->lm_next; } } #else /* !DEBUG */ #define lmapping_verify(x) ((void)0) #endif /* DEBUG */ /* * Determine the position of a mapping with the given address and size. Upon * return, lmfa_ret will be set to the actual mapping, if it exists, and * lmfa_prior will be set to the mapping which does or would precede one with * the given characteristics. */ static int lmapping_find_cb(lmapping_t *lm, void *arg) { lmapping_find_cb_arg_t *lmfa = arg; if (lm->lm_addr >= lmfa->lmfa_addr) { if (lmfa->lmfa_addr == lm->lm_addr && lmfa->lmfa_size == lm->lm_size) lmfa->lmfa_ret = lm; return (1); } else lmfa->lmfa_prior = lm; return (0); } static void lmapping_walk(lmapping_t *lm, int(*lmapping_walk_cb)(lmapping_t *, void *), void *arg) { lmapping_t *next; while (lm != NULL) { next = lm->lm_next; lmapping_verify(lm); if (lmapping_walk_cb(lm, arg) != 0) { lmapping_verify(lm); return; } lm = next; } } int lmapping_remove(lmapping_t **lm, uintptr_t addr, size_t size) { lmapping_find_cb_arg_t lmfa; lmfa.lmfa_addr = addr; lmfa.lmfa_size = size; lmfa.lmfa_prior = lmfa.lmfa_ret = NULL; lmapping_verify(*lm); lmapping_walk(*lm, lmapping_find_cb, &lmfa); if (lmfa.lmfa_ret == NULL) return (-1); if (lmfa.lmfa_prior != NULL) lmfa.lmfa_prior->lm_next = lmfa.lmfa_ret->lm_next; else if (*lm == lmfa.lmfa_ret) *lm = lmfa.lmfa_ret->lm_next; free(lmfa.lmfa_ret); lmapping_verify(*lm); return (0); } int lmapping_insert(lmapping_t **lm, uintptr_t addr, size_t size) { lmapping_find_cb_arg_t lmfa; lmapping_t *cur; cur = malloc(sizeof (*cur)); if (cur == NULL) return (-1); cur->lm_addr = addr; cur->lm_size = size; cur->lm_next = NULL; lmfa.lmfa_addr = addr; lmfa.lmfa_size = size; lmfa.lmfa_prior = lmfa.lmfa_ret = NULL; lmapping_verify(*lm); lmapping_walk(*lm, lmapping_find_cb, &lmfa); ASSERT(lmfa.lmfa_ret == NULL); if (lmfa.lmfa_prior != NULL) { cur->lm_next = lmfa.lmfa_prior->lm_next; lmfa.lmfa_prior->lm_next = cur; } else { cur->lm_next = *lm; *lm = cur; } lmapping_verify(*lm); return (0); } int lmapping_contains(lmapping_t *lm, uintptr_t addr, size_t size) { lmapping_find_cb_arg_t lmfa; lmfa.lmfa_addr = addr; lmfa.lmfa_size = size; lmfa.lmfa_ret = NULL; lmapping_walk(lm, lmapping_find_cb, &lmfa); return (lmfa.lmfa_ret != NULL); } /*ARGSUSED*/ static int lmapping_free_cb(lmapping_t *lm, void *arg) { free(lm); return (0); } void lmapping_free(lmapping_t **lm) { lmapping_walk(*lm, lmapping_free_cb, NULL); *lm = NULL; } #ifdef DEBUG int lmapping_dump_diff(lmapping_t *lm1, lmapping_t *lm2) { lmapping_t **lmv; int res = 0; int ch = 0; int label_printed = 0; #define OUTPUT_LABEL() \ if (label_printed == 0) { \ debug("changes in mappings:\n"); \ label_printed++; \ } while (lm1 != NULL && lm2 != NULL) { if ((lm1->lm_addr != lm2->lm_addr) || (lm1->lm_size != lm2->lm_size)) { res = -1; if (lm1->lm_addr == lm2->lm_addr && lm1->lm_size < lm2->lm_size || lm1->lm_addr < lm2->lm_addr) { lmv = &lm1; ch = '-'; } else { lmv = &lm2; ch = '+'; } OUTPUT_LABEL(); debug("%c%p+0x%llx\n", ch, (void *)(*lmv)->lm_addr, (long long)(*lmv)->lm_size); *lmv = (*lmv)->lm_next; } else { lm1 = lm1->lm_next; lm2 = lm2->lm_next; } } while (lm1 != NULL) { OUTPUT_LABEL(); debug("%c%p+0x%llx\n", '-', (void *)lm1->lm_addr, (unsigned long long)lm1->lm_size); lm1 = lm1->lm_next; res = 1; } while (lm2 != NULL) { OUTPUT_LABEL(); debug("%c%p+0x%llx\n", '+', (void *)lm2->lm_addr, (long long)lm2->lm_size); lm2 = lm2->lm_next; res = 1; } return (res); #undef OUTPUT_LABEL } #endif /* DEBUG */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RCAPD_MAPPING_H #define _RCAPD_MAPPING_H #ifdef __cplusplus extern "C" { #endif /* * List of mappings. */ typedef struct lmapping { uintptr_t lm_addr; size_t lm_size; struct lmapping *lm_next; } lmapping_t; extern int lmapping_contains(lmapping_t *, uintptr_t, size_t); extern void lmapping_free(lmapping_t **); extern int lmapping_insert(lmapping_t **, uintptr_t, size_t); extern int lmapping_remove(lmapping_t **, uintptr_t, size_t); #ifdef __cplusplus } #endif #endif /* _RCAPD_MAPPING_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * File descriptor usage * * The number of processes that can be effectively managed is limited to less * than half the number of descriptors available: one for each process's * psinfo, the other its pagedata. When managing more processes, file * descriptors are revoked as needed, in such a way as to maximize the * distribution of descriptors to pagedata which will be useful in meeting a * cap without paging out the process's working set, while retaining some * benefit from caching psinfo descriptors, and leaving enough available for * use by external consumers, such as are needed for project enumeration or * configuration file reading. * * Revokable file descriptors are opened and associated with a callback * function which can be invoked to revoke them later. pagedata and psinfo * descriptors are differentiated for the purposes of preferring pagedata over * psinfo, which effectively places the performance of rcapd behind the * importance of making good page selections. The one exception is that one * psinfo descriptor is guaranteed a place at any time, for the benefit of * psinfo updates of a presently currently-scanned process. Descriptors are * otherwise revoked in LIFO order. */ #include #include #include #include #include #include #include #include "rcapd_rfd.h" #include "utils.h" static rfd_t *tail; /* tail of global list */ static int rfd_revoke_next(rfd_class_t); /* * Return the previous rfd_t of the given class, starting at (and including) * the given rfd_t. */ static rfd_t * rfd_find_prev_class(rfd_t *rfd, rfd_class_t class) { while (rfd != NULL && rfd->rfd_class != class) rfd = rfd->rfd_prev; return (rfd); } /* * Revoke and free the given rfd_t, returning as close does. */ static int rfd_revoke_fd(rfd_t *rfd) { if (rfd->rfd_revoke != NULL) rfd->rfd_revoke(rfd); return (rfd_close(rfd->rfd_fd)); } /* * Revoke the next file descriptor according to the above constraints. Return * nonzero if there are none to revoke. */ static int rfd_revoke_next(rfd_class_t class) { rfd_t *rfd = NULL; if (tail == NULL) { debug("nothing to revoke\n"); return (-1); } /* * RESERVED-clsas descriptors are all equivalent and may not be revoked * to satisfy another request of the same clsas. rfd_reserve() uses * this to reserve descriptors by first allocating, then closing, these * descriptors. */ if (class != RFD_RESERVED) rfd = rfd_find_prev_class(tail, RFD_RESERVED); /* * Next try psinfo descriptors, leaving at least one open. Revoke the * second-last psinfo descriptor, if possible. */ if (rfd == NULL) { rfd = rfd_find_prev_class(tail, RFD_PSINFO); if (rfd != NULL) rfd = rfd->rfd_prev_class; } /* * Otherwise, revoke the last descriptor allocated, taking the same * care as above that it is not reserved, if the reserved kind is * sought. */ if (rfd == NULL) { rfd = tail; while (rfd != NULL && class == RFD_RESERVED && rfd->rfd_class == RFD_RESERVED) rfd = rfd->rfd_prev; } if (rfd != NULL) return (rfd_revoke_fd(rfd)); /* * Nothing but reserved-class descriptors are revocable, while a * reserved- class descriptor was sought. */ return (-1); } /* * Opens a file of the given class, which can later be revoked with the given * callback. Returns as open does. The callback should reset any state that * this caller establishes after the open, but should not close the descriptor, * which will be done when the caller explicitly does so with rfd_close(), or * the descriptor is revoked with rfd_revoke(). */ int rfd_open(char *name, int revoke_ok, rfd_class_t class, void(*revoke)(struct rfd *), void *data, int oflag, mode_t mode) { int fd; rfd_t *rfd; while ((fd = open(name, oflag, mode)) == -1 && (errno == ENFILE || errno == EMFILE)) { if (revoke_ok) { if (rfd_revoke_next(class) != 0) return (-1); } else break; } if (fd != -1) { /* * Create rfd_t and link into list. */ rfd = malloc(sizeof (*rfd)); if (rfd == NULL) { (void) close(fd); return (-1); } (void) bzero(rfd, sizeof (*rfd)); rfd->rfd_fd = fd; rfd->rfd_class = class; rfd->rfd_revoke = revoke; rfd->rfd_data = data; if (tail != NULL) rfd->rfd_prev_class = rfd_find_prev_class(tail, class); else rfd->rfd_prev_class = tail; rfd->rfd_prev = tail; if (tail != NULL) tail->rfd_next = rfd; tail = rfd; } return (fd); } /* * Close a given file descriptor, and return as close() does. */ int rfd_close(int fd) { rfd_t *nextclass; rfd_t *rfdprev; rfd_t *rfd; #ifdef DEBUG int freed = 0; #endif /* DEBUG */ rfd = tail; while (rfd != NULL) { rfdprev = rfd->rfd_prev; if (rfd->rfd_fd == fd) { if (rfd->rfd_prev != NULL) rfd->rfd_prev->rfd_next = rfd->rfd_next; if (rfd->rfd_next != NULL) rfd->rfd_next->rfd_prev = rfd->rfd_prev; if (tail == rfd) tail = rfd->rfd_prev; for (nextclass = rfd->rfd_next; nextclass != NULL; nextclass = nextclass->rfd_next) if (nextclass->rfd_class == rfd->rfd_class) { nextclass->rfd_prev_class = rfd->rfd_prev_class; break; } free(rfd); #ifdef DEBUG freed = 1; #endif /* DEBUG */ break; } rfd = rfdprev; } ASSERT(freed == 1); return (close(fd)); } /* * Makes sure at least n descriptors are available. Returns nonzero if * successful. */ int rfd_reserve(int n) { int i; int fd = 0; rfd_t *otail = NULL; rfd_t *rfdnext; for (i = 0; i < n && fd >= 0; i++) { /* * rfd_open() will append as many RFD_RESERVED-clsas * descriptors to the current tail as are requested, revoking * non-RFD_RESERVED-class descriptors until nothing else can be * revoked or the reservation is met. */ fd = rfd_open("/dev/null", 1, RFD_RESERVED, NULL, NULL, O_RDONLY, 0); if (otail == NULL) otail = tail; } if (fd == -1) debug("couldn't allocate %d descriptors\n", n); while (otail != NULL) { rfdnext = otail->rfd_next; (void) rfd_close(otail->rfd_fd); otail = rfdnext; } return (fd != -1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RCAPD_RFD_H #define _RCAPD_RFD_H #ifdef __cplusplus extern "C" { #endif #include #include "rcapd.h" /* * Classes of cached, revocable file descriptors */ typedef enum { RFD_PAGEDATA = 1, /* pagedata is always preferred over psinfo */ RFD_PSINFO, /* psinfo, at least one of which is */ /* cached at any time */ RFD_XMAP, /* xmap */ RFD_RESERVED /* opened for use by external consumers */ } rfd_class_t; /* * Revocable file descriptor */ typedef struct rfd { int rfd_fd; /* cached descriptor */ rfd_class_t rfd_class; /* class of descriptor */ void(*rfd_revoke)(struct rfd *); /* revocation function */ void *rfd_data; /* supplied data */ struct rfd *rfd_next; /* list */ struct rfd *rfd_prev; struct rfd *rfd_prev_class; /* link to previous of same */ /* class */ } rfd_t; int rfd_close(int); int rfd_open(char *, int, rfd_class_t, void(*)(struct rfd *), void *, int, mode_t); int rfd_reserve(int); #ifdef __cplusplus } #endif #endif /* _RCAPD_RFD_H */ /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rcapd.h" #include "rcapd_rfd.h" #include "rcapd_mapping.h" #include "utils.h" static int lpc_xmap_update(lprocess_t *); #ifdef DEBUG extern int lmapping_dump_diff(lmapping_t *lm1, lmapping_t *lm2); #endif /* DEBUG */ /* * The number of file descriptors required to grab a process and create an * agent in it. */ #define PGRAB_FD_COUNT 10 /* * Record a position in an address space as it corresponds to a prpageheader_t * and affiliated structures. */ typedef struct prpageheader_cur { int pr_nmap; /* number of mappings in address space */ int pr_map; /* number of this mapping */ uint64_t pr_pgoff; /* page offset into mapping */ uint64_t pr_npage; /* number of pages in mapping */ uint64_t pr_pagesize; /* page size of mapping */ uintptr_t pr_addr; /* base of mapping */ prpageheader_t *pr_prpageheader; /* associated page header */ void *pr_pdaddr; /* address of page's byte in pagedata */ prxmap_t *pr_xmap; /* array containing per-segment information */ int pr_nxmap; /* number of xmaps in array */ int64_t pr_rss; /* number of resident pages in mapping, */ /* or -1 if xmap is out of sync */ int64_t pr_pg_rss; /* number of pageable pages in mapping, or -1 */ } prpageheader_cur_t; static struct ps_prochandle *scan_pr; /* currently-scanned process's handle */ typedef enum { STDL_NORMAL, STDL_HIGH } st_debug_level_t; /* * Output a scanning-related debug message. */ /*PRINTFLIKE3*/ /*ARGSUSED*/ static void st_debug(st_debug_level_t level, lcollection_t *lcol, char *msg, ...) { #ifdef DEBUG_MSG va_list alist; char *buf; size_t len; if (get_message_priority() < ((level == STDL_HIGH) ? RCM_DEBUG_HIGH : RCM_DEBUG)) return; len = strlen(msg) + LINELEN; buf = malloc(len); if (buf == NULL) return; (void) snprintf(buf, len, "%s %s scanner %s", (lcol->lcol_id.rcid_type == RCIDT_PROJECT ? "project" : "zone"), lcol->lcol_name, msg); va_start(alist, msg); vdprintfe(RCM_DEBUG, buf, alist); va_end(alist); free(buf); #endif /* DEBUG_MSG */ } /* * Determine the collection's current victim, based on its last. The last will * be returned, or, if invalid, any other valid process, if the collection has * any. */ static lprocess_t * get_valid_victim(lcollection_t *lcol, lprocess_t *lpc) { if (lpc == NULL || !lcollection_member(lcol, lpc)) lpc = lcol->lcol_lprocess; /* * Find the next scannable process, and make it the victim. */ while (lpc != NULL && lpc->lpc_unscannable != 0) lpc = lpc->lpc_next; return (lpc); } /* * Get a process's combined current pagedata (per-page referenced and modified * bits) and set the supplied pointer to it. The caller is responsible for * freeing the data. If the pagedata is unreadable, a nonzero value is * returned, and errno is set. Otherwise, 0 is returned. */ static int get_pagedata(prpageheader_t **pghpp, int fd) { int res; struct stat st; redo: errno = 0; if (fstat(fd, &st) != 0) { debug("cannot stat pagedata\n"); return (-1); } errno = 0; *pghpp = malloc(st.st_size); if (*pghpp == NULL) { debug("cannot malloc() %ld bytes for pagedata", st.st_size); return (-1); } (void) bzero(*pghpp, st.st_size); errno = 0; if ((res = read(fd, *pghpp, st.st_size)) != st.st_size) { free(*pghpp); *pghpp = NULL; if (res > 0 || errno == E2BIG) { debug("pagedata changed size, retrying\n"); goto redo; } else { debug("cannot read pagedata"); return (-1); } } return (0); } /* * Return the count of kilobytes of pages represented by the given pagedata * which meet the given criteria, having pages which are in all of the states * specified by the mask, and in none of the states in the notmask. If the * CP_CLEAR flag is set, the pagedata will also be cleared. */ #define CP_CLEAR 1 static uint64_t count_pages(prpageheader_t *pghp, int flags, int mask, int notmask) { int map; caddr_t cur, end; prpageheader_t pgh = *pghp; prasmap_t *asmapp; uint64_t count = 0; cur = (caddr_t)pghp + sizeof (*pghp); for (map = 0; map < pgh.pr_nmap; map++) { asmapp = (prasmap_t *)(uintptr_t)cur; cur += sizeof (*asmapp); end = cur + asmapp->pr_npage; while (cur < end) { if ((*cur & mask) == mask && (*cur & notmask) == 0) count += asmapp->pr_pagesize / 1024; if ((flags & CP_CLEAR) != 0) *cur = 0; cur++; } /* * Skip to next 64-bit-aligned address to get the next * prasmap_t. */ cur = (caddr_t)((intptr_t)(cur + 7) & ~7); } return (count); } /* * Return the amount of memory (in kilobytes) that hasn't been referenced or * modified, which memory which will be paged out first. Should be written to * exclude nonresident pages when sufficient interfaces exist. */ static uint64_t unrm_size(lprocess_t *lpc) { return (count_pages(lpc->lpc_prpageheader, CP_CLEAR, 0, PG_MODIFIED | PG_REFERENCED)); } /* * Advance a prpageheader_cur_t to the address space's next mapping, returning * its address, or NULL if there is none. Any known nonpageable or nonresident * mappings will be skipped over. */ static uintptr_t advance_prpageheader_cur_nextmapping(prpageheader_cur_t *pcp) { prasmap_t *pap; int i; next: ASSERT(pcp->pr_map < pcp->pr_nmap); if ((pcp->pr_map + 1) == pcp->pr_nmap) return ((uintptr_t)NULL); pcp->pr_map++; if (pcp->pr_pgoff < pcp->pr_npage) { pcp->pr_pdaddr = (caddr_t)(uintptr_t) ((uintptr_t)pcp->pr_pdaddr + (pcp->pr_npage - pcp->pr_pgoff)); pcp->pr_pgoff = pcp->pr_npage; } /* * Skip to next 64-bit-aligned address to get the next prasmap_t. */ pcp->pr_pdaddr = (caddr_t)(((uintptr_t)pcp->pr_pdaddr + 7) & ~7); pap = (prasmap_t *)pcp->pr_pdaddr; pcp->pr_pgoff = 0; pcp->pr_npage = pap->pr_npage; pcp->pr_pagesize = pap->pr_pagesize; pcp->pr_addr = pap->pr_vaddr; pcp->pr_pdaddr = pap + 1; /* * Skip any known nonpageable mappings. Currently, the only one * detected is the schedctl page. */ if ((pap->pr_mflags ^ (MA_SHARED | MA_READ | MA_WRITE | MA_EXEC | MA_ANON)) == 0 && pap->pr_npage == 1) { debug("identified nonpageable schedctl mapping at %p\n", (void *)pcp->pr_addr); goto next; } /* * Skip mappings with no resident pages. If the xmap does not * correspond to the pagedata for any reason, it will be ignored. */ pcp->pr_rss = -1; pcp->pr_pg_rss = -1; for (i = 0; i < pcp->pr_nxmap; i++) { prxmap_t *xmap = &pcp->pr_xmap[i]; if (pcp->pr_addr == xmap->pr_vaddr && xmap->pr_size == (pcp->pr_npage * pcp->pr_pagesize)) { pcp->pr_rss = xmap->pr_rss; /* * Remove COW pages from the pageable RSS count. */ if ((xmap->pr_mflags & MA_SHARED) == 0) pcp->pr_pg_rss = xmap->pr_anon; break; } } if (pcp->pr_rss == 0) { debug("identified nonresident mapping at 0x%p\n", (void *)pcp->pr_addr); goto next; } else if (pcp->pr_pg_rss == 0) { debug("identified unpageable mapping at 0x%p\n", (void *)pcp->pr_addr); goto next; } return (pcp->pr_addr); } /* * Advance a prpageheader_cur_t to the mapping's next page, returning its * address, or NULL if there is none. */ static void * advance_prpageheader_cur(prpageheader_cur_t *pcp) { ASSERT(pcp->pr_pgoff < pcp->pr_npage); if ((pcp->pr_pgoff + 1) == pcp->pr_npage) return (NULL); pcp->pr_pdaddr = (caddr_t)pcp->pr_pdaddr + 1; pcp->pr_pgoff++; ASSERT((*(char *)pcp->pr_pdaddr & ~(PG_MODIFIED | PG_REFERENCED)) == 0); return ((caddr_t)pcp->pr_addr + pcp->pr_pgoff * pcp->pr_pagesize); } /* * Initialize a prpageheader_cur_t, positioned at the first page of the mapping * of an address space. */ static void * set_prpageheader_cur(prpageheader_cur_t *pcp, prpageheader_t *php, prxmap_t *xmap, int nxmap) { bzero(pcp, sizeof (*pcp)); pcp->pr_nmap = php->pr_nmap; pcp->pr_map = -1; pcp->pr_prpageheader = php; pcp->pr_xmap = xmap; pcp->pr_nxmap = nxmap; pcp->pr_pdaddr = (prpageheader_t *)php + 1; return ((void *)advance_prpageheader_cur_nextmapping(pcp)); } /* * Position a prpageheader_cur_t to the mapped address greater or equal to the * given value. */ static void * set_prpageheader_cur_addr(prpageheader_cur_t *pcp, prpageheader_t *php, prxmap_t *xmap, int nxmap, void *naddr) { void *addr = set_prpageheader_cur(pcp, php, xmap, nxmap); while (addr != NULL && addr <= naddr) if (naddr < (void *)((caddr_t)pcp->pr_addr + pcp->pr_pagesize * pcp->pr_npage)) { uint64_t pgdiff = ((uintptr_t)naddr - (uintptr_t)pcp->pr_addr) / pcp->pr_pagesize; pcp->pr_pgoff += pgdiff; pcp->pr_pdaddr = (caddr_t)pcp->pr_pdaddr + pgdiff; addr = (caddr_t)pcp->pr_addr + pcp->pr_pagesize * pcp->pr_pgoff; break; } else addr = (void *)advance_prpageheader_cur_nextmapping(pcp); return (addr); } static void revoke_pagedata(rfd_t *rfd) { lprocess_t *lpc = rfd->rfd_data; st_debug(STDL_NORMAL, lpc->lpc_collection, "revoking pagedata for" " process %d\n", (int)lpc->lpc_pid); ASSERT(lpc->lpc_pgdata_fd != -1); lpc->lpc_pgdata_fd = -1; } #ifdef DEBUG static void mklmapping(lmapping_t **lm, prpageheader_t *pgh) { prpageheader_cur_t cur; void *addr; addr = set_prpageheader_cur(&cur, pgh, NULL, -1); ASSERT(*lm == NULL); while (addr != NULL) { (void) lmapping_insert(lm, cur.pr_addr, cur.pr_npage * cur.pr_pagesize); addr = (void *)advance_prpageheader_cur_nextmapping(&cur); } } static void lmapping_dump(lmapping_t *lm) { debug("lm: %p\n", (void *)lm); while (lm != NULL) { debug("\t(%p, %llx\n", (void *)lm->lm_addr, (unsigned long long)lm->lm_size); lm = lm->lm_next; } } #endif /* DEBUG */ /* * OR two prpagedata_t which are supposedly snapshots of the same address * space. Intersecting mappings with different page sizes are tolerated but * not normalized (not accurate). If the mappings of the two snapshots differ * in any regard, the supplied mappings_changed flag will be set. */ static void OR_pagedata(prpageheader_t *src, prpageheader_t *dst, int *mappings_changedp) { prpageheader_cur_t src_cur; prpageheader_cur_t dst_cur; uintptr_t src_addr; uintptr_t dst_addr; int mappings_changed = 0; /* * OR source pagedata with the destination, for pages of intersecting * mappings. */ src_addr = (uintptr_t)set_prpageheader_cur(&src_cur, src, NULL, -1); dst_addr = (uintptr_t)set_prpageheader_cur(&dst_cur, dst, NULL, -1); while (src_addr != (uintptr_t)NULL && dst_addr != (uintptr_t)NULL) { while (src_addr == dst_addr && src_addr != (uintptr_t)NULL) { *(char *)dst_cur.pr_pdaddr |= *(char *)src_cur.pr_pdaddr; src_addr = (uintptr_t)advance_prpageheader_cur( &src_cur); dst_addr = (uintptr_t)advance_prpageheader_cur( &dst_cur); } if (src_addr != dst_addr) mappings_changed = 1; src_addr = advance_prpageheader_cur_nextmapping(&src_cur); dst_addr = advance_prpageheader_cur_nextmapping(&dst_cur); while (src_addr != dst_addr && src_addr != (uintptr_t)NULL && dst_addr != (uintptr_t)NULL) { mappings_changed = 1; if (src_addr < dst_addr) src_addr = advance_prpageheader_cur_nextmapping( &src_cur); else dst_addr = advance_prpageheader_cur_nextmapping( &dst_cur); } } *mappings_changedp = mappings_changed; } /* * Merge the current pagedata with that on hand. If the pagedata is * unretrievable for any reason, such as the process having exited or being a * zombie, a nonzero value is returned, the process should be marked * unscannable, and future attempts to scan it should be avoided, since the * symptom is probably permament. If the mappings of either pagedata * differ in any respect, the supplied callback will be invoked once. */ static int merge_current_pagedata(lprocess_t *lpc, void(*mappings_changed_cb) (lprocess_t *)) { prpageheader_t *pghp; int mappings_changed = 0; uint64_t cnt; if (lpc->lpc_pgdata_fd < 0 || get_pagedata(&pghp, lpc->lpc_pgdata_fd) != 0) { char pathbuf[PROC_PATH_MAX]; (void) snprintf(pathbuf, sizeof (pathbuf), "/proc/%d/pagedata", (int)lpc->lpc_pid); if ((lpc->lpc_pgdata_fd = rfd_open(pathbuf, 1, RFD_PAGEDATA, revoke_pagedata, lpc, O_RDONLY, 0)) < 0 || get_pagedata(&pghp, lpc->lpc_pgdata_fd) != 0) return (-1); debug("starting/resuming pagedata collection for %d\n", (int)lpc->lpc_pid); } cnt = count_pages(pghp, 0, PG_MODIFIED | PG_REFERENCED, 0); if (cnt != 0 || lpc->lpc_rss != 0) debug("process %d: %llu/%llukB rfd/mdfd since last read\n", (int)lpc->lpc_pid, (unsigned long long)cnt, (unsigned long long)lpc->lpc_rss); if (lpc->lpc_prpageheader != NULL) { /* * OR the two snapshots. */ #ifdef DEBUG lmapping_t *old = NULL; lmapping_t *new = NULL; mklmapping(&new, pghp); mklmapping(&old, lpc->lpc_prpageheader); #endif /* DEBUG */ OR_pagedata(lpc->lpc_prpageheader, pghp, &mappings_changed); #ifdef DEBUG if (((mappings_changed != 0) ^ (lmapping_dump_diff(old, new) != 0))) { debug("lmapping_changed inconsistent with lmapping\n"); debug("old\n"); lmapping_dump(old); debug("new\n"); lmapping_dump(new); debug("ignored\n"); lmapping_dump(lpc->lpc_ignore); ASSERT(0); } lmapping_free(&new); lmapping_free(&old); #endif /* DEBUG */ free(lpc->lpc_prpageheader); } else mappings_changed = 1; lpc->lpc_prpageheader = pghp; cnt = count_pages(pghp, 0, PG_MODIFIED | PG_REFERENCED, 0); if (cnt != 0 || lpc->lpc_rss != 0) debug("process %d: %llu/%llukB rfd/mdfd since hand swept\n", (int)lpc->lpc_pid, (unsigned long long)cnt, (unsigned long long)lpc->lpc_rss); if (mappings_changed != 0) { debug("process %d: mappings changed\n", (int)lpc->lpc_pid); if (mappings_changed_cb != NULL) mappings_changed_cb(lpc); } return (0); } /* * Attempt to page out a region of the given process's address space. May * return nonzero if not all of the pages may are pageable, for any reason. */ static int pageout(pid_t pid, struct ps_prochandle *Pr, caddr_t start, caddr_t end) { int res; if (end <= start) return (0); errno = 0; res = pr_memcntl(Pr, start, (end - start), MC_SYNC, (caddr_t)(MS_ASYNC | MS_INVALIDATE), 0, 0); debug_high("pr_memcntl [%p-%p): %d", (void *)start, (void *)end, res); /* * EBUSY indicates none of the pages have backing store allocated, or * some pages were locked, which are less interesting than other * conditions, which are noted. */ if (res != 0) if (errno == EBUSY) res = 0; else debug("%d: can't pageout %p+%llx (errno %d)", (int)pid, (void *)start, (long long)(end - start), errno); return (res); } /* * Compute the delta of the victim process's RSS since the last call. If the * psinfo cannot be obtained, no work is done, and no error is returned; it is * up to the caller to detect the process' termination via other means. */ static int64_t rss_delta(psinfo_t *new_psinfo, psinfo_t *old_psinfo, lprocess_t *vic) { int64_t d_rss = 0; if (get_psinfo(vic->lpc_pid, new_psinfo, vic->lpc_psinfo_fd, lprocess_update_psinfo_fd_cb, vic, vic) == 0) { d_rss = (int64_t)new_psinfo->pr_rssize - (int64_t)old_psinfo->pr_rssize; if (d_rss < 0) vic->lpc_collection->lcol_stat.lcols_pg_eff += (- d_rss); *old_psinfo = *new_psinfo; } return (d_rss); } static void unignore_mappings(lprocess_t *lpc) { lmapping_free(&lpc->lpc_ignore); } static void unignore_referenced_mappings(lprocess_t *lpc) { prpageheader_cur_t cur; void *vicaddr; vicaddr = set_prpageheader_cur(&cur, lpc->lpc_prpageheader, NULL, -1); while (vicaddr != NULL) { if (((*(char *)cur.pr_pdaddr) & (PG_REFERENCED | PG_MODIFIED)) != 0) { if (lmapping_remove(&lpc->lpc_ignore, cur.pr_addr, cur.pr_npage * cur.pr_pagesize) == 0) debug("removed mapping 0x%p+0t%llukB from" " ignored set\n", (void *)cur.pr_addr, (unsigned long long)(cur.pr_npage * cur.pr_pagesize / 1024)); vicaddr = (void *)advance_prpageheader_cur_nextmapping( &cur); } else if ((vicaddr = advance_prpageheader_cur(&cur)) == NULL) vicaddr = (void *)advance_prpageheader_cur_nextmapping( &cur); } } /* * Resume scanning, starting with the last victim, if it is still valid, or any * other one, otherwise. */ void scan(lcollection_t *lcol, int64_t excess) { lprocess_t *vic, *lpc; void *vicaddr, *endaddr, *nvicaddr; prpageheader_cur_t cur; psinfo_t old_psinfo, new_psinfo; hrtime_t scan_start; int res, resumed; uint64_t col_unrm_size; st_debug(STDL_NORMAL, lcol, "starting to scan, excess %lldk\n", (long long)excess); /* * Determine the address to start scanning at, depending on whether * scanning can be resumed. */ endaddr = NULL; if ((vic = get_valid_victim(lcol, lcol->lcol_victim)) == lcol->lcol_victim && lcol->lcol_resaddr != NULL) { vicaddr = lcol->lcol_resaddr; st_debug(STDL_NORMAL, lcol, "resuming process %d\n", (int)vic->lpc_pid); resumed = 1; } else { vicaddr = NULL; resumed = 0; } scan_start = gethrtime(); /* * Obtain the most current pagedata for the processes that might be * scanned, and remove from the ignored set any mappings which have * referenced or modified pages (in the hopes that the pageability of * the mapping's pages may have changed). Determine if the * unreferenced and unmodified portion is impossibly small to suffice * to reduce the excess completely. If so, ignore these bits so that * even working set will be paged out. */ col_unrm_size = 0; lpc = vic; while (lpc != NULL && should_run) { if (merge_current_pagedata(lpc, unignore_mappings) != 0) { st_debug(STDL_NORMAL, lcol, "process %d:" " exited/temporarily unscannable", (int)lpc->lpc_pid); goto next; } debug("process %d: %llu/%llukB scannable\n", (int)lpc->lpc_pid, (unsigned long long)(lpc->lpc_unrm = unrm_size(lpc)), (unsigned long long)lpc->lpc_size); col_unrm_size += lpc->lpc_unrm = unrm_size(lpc); if ((lcol->lcol_stat.lcols_scan_count % RCAPD_IGNORED_SET_FLUSH_IVAL) == 0) { /* * Periodically clear the set of ignored mappings. * This will allow processes whose ignored segments' * pageability have changed (without a corresponding * reference or modification to a page) to be * recognized. */ if (lcol->lcol_stat.lcols_scan_count > 0) unignore_mappings(lpc); } else { /* * Ensure mappings with referenced or modified pages * are not in the ignored set. Their usage might mean * the condition which made them unpageable is gone. */ unignore_referenced_mappings(lpc); } next: lpc = lpc->lpc_next != NULL ? get_valid_victim(lcol, lpc->lpc_next) : NULL; } if (col_unrm_size < excess) { lpc = vic; debug("will not reduce excess with only unreferenced pages\n"); while (lpc != NULL && should_run) { if (lpc->lpc_prpageheader != NULL) { (void) count_pages(lpc->lpc_prpageheader, CP_CLEAR, 0, 0); if (lpc->lpc_pgdata_fd >= 0) { if (rfd_close(lpc->lpc_pgdata_fd) != 0) debug("coud not close %d" " lpc_pgdata_fd %d", (int)lpc->lpc_pid, lpc->lpc_pgdata_fd); lpc->lpc_pgdata_fd = -1; } } lpc = lpc->lpc_next != NULL ? get_valid_victim(lcol, lpc->lpc_next) : NULL; } } /* * Examine each process for pages to remove until the excess is * reduced. */ while (vic != NULL && excess > 0 && should_run) { /* * Skip processes whose death was reported when the merging of * pagedata was attempted. */ if (vic->lpc_prpageheader == NULL) goto nextproc; /* * Obtain optional segment residency information. */ if (lpc_xmap_update(vic) != 0) st_debug(STDL_NORMAL, lcol, "process %d: xmap" " unreadable; ignoring", (int)vic->lpc_pid); #ifdef DEBUG_MSG { void *ovicaddr = vicaddr; #endif /* DEBUG_MSG */ vicaddr = set_prpageheader_cur_addr(&cur, vic->lpc_prpageheader, vic->lpc_xmap, vic->lpc_nxmap, vicaddr); #ifdef DEBUG_MSG st_debug(STDL_NORMAL, lcol, "trying to resume from" " 0x%p, next 0x%p\n", ovicaddr, vicaddr); } #endif /* DEBUG_MSG */ /* * Take control of the victim. */ if (get_psinfo(vic->lpc_pid, &old_psinfo, vic->lpc_psinfo_fd, lprocess_update_psinfo_fd_cb, vic, vic) != 0) { st_debug(STDL_NORMAL, lcol, "cannot get %d psinfo", (int)vic->lpc_pid); goto nextproc; } (void) rfd_reserve(PGRAB_FD_COUNT); if ((scan_pr = Pgrab(vic->lpc_pid, 0, &res)) == NULL) { st_debug(STDL_NORMAL, lcol, "cannot grab %d (%d)", (int)vic->lpc_pid, res); goto nextproc; } if (Pcreate_agent(scan_pr) != 0) { st_debug(STDL_NORMAL, lcol, "cannot control %d", (int)vic->lpc_pid); goto nextproc; } /* * Be very pessimistic about the state of the agent LWP -- * verify it's actually stopped. */ errno = 0; while (Pstate(scan_pr) == PS_RUN) (void) Pwait(scan_pr, 0); if (Pstate(scan_pr) != PS_STOP) { st_debug(STDL_NORMAL, lcol, "agent not in expected" " state (%d)", Pstate(scan_pr)); goto nextproc; } /* * Within the victim's address space, find contiguous ranges of * unreferenced pages to page out. */ st_debug(STDL_NORMAL, lcol, "paging out process %d\n", (int)vic->lpc_pid); while (excess > 0 && vicaddr != NULL && should_run) { /* * Skip mappings in the ignored set. Mappings get * placed in the ignored set when all their resident * pages are unreference and unmodified, yet unpageable * -- such as when they are locked, or involved in * asynchronous I/O. They will be scanned again when * some page is referenced or modified. */ if (lmapping_contains(vic->lpc_ignore, cur.pr_addr, cur.pr_npage * cur.pr_pagesize)) { debug("ignored mapping at 0x%p\n", (void *)cur.pr_addr); /* * Update statistics. */ lcol->lcol_stat.lcols_pg_att += cur.pr_npage * cur.pr_pagesize / 1024; vicaddr = (void *) advance_prpageheader_cur_nextmapping(&cur); continue; } /* * Determine a range of unreferenced pages to page out, * and clear the R/M bits in the preceding referenced * range. */ st_debug(STDL_HIGH, lcol, "start from mapping at 0x%p," " npage %llu\n", vicaddr, (unsigned long long)cur.pr_npage); while (vicaddr != NULL && *(caddr_t)cur.pr_pdaddr != 0) { *(caddr_t)cur.pr_pdaddr = 0; vicaddr = advance_prpageheader_cur(&cur); } st_debug(STDL_HIGH, lcol, "advance, vicaddr %p, pdaddr" " %p\n", vicaddr, cur.pr_pdaddr); if (vicaddr == NULL) { /* * The end of mapping was reached before any * unreferenced pages were seen. */ vicaddr = (void *) advance_prpageheader_cur_nextmapping(&cur); continue; } do endaddr = advance_prpageheader_cur(&cur); while (endaddr != NULL && *(caddr_t)cur.pr_pdaddr == 0 && (((intptr_t)endaddr - (intptr_t)vicaddr) / 1024) < excess) ; st_debug(STDL_HIGH, lcol, "endaddr %p, *cur %d\n", endaddr, *(caddr_t)cur.pr_pdaddr); /* * Page out from vicaddr to the end of the mapping, or * endaddr if set, then continue scanning after * endaddr, or the next mapping, if not set. */ nvicaddr = endaddr; if (endaddr == NULL) endaddr = (caddr_t)cur.pr_addr + cur.pr_pagesize * cur.pr_npage; if (pageout(vic->lpc_pid, scan_pr, vicaddr, endaddr) == 0) { int64_t d_rss, att; int willignore = 0; excess += (d_rss = rss_delta( &new_psinfo, &old_psinfo, vic)); /* * If this pageout attempt was unsuccessful * (the resident portion was not affected), and * was for the whole mapping, put it in the * ignored set, so it will not be scanned again * until some page is referenced or modified. */ if (d_rss >= 0 && (void *)cur.pr_addr == vicaddr && (cur.pr_pagesize * cur.pr_npage) == ((uintptr_t)endaddr - (uintptr_t)vicaddr)) { if (lmapping_insert( &vic->lpc_ignore, cur.pr_addr, cur.pr_pagesize * cur.pr_npage) != 0) debug("not enough memory to add" " mapping at %p to ignored" " set\n", (void *)cur.pr_addr); willignore = 1; } /* * Update statistics. */ lcol->lcol_stat.lcols_pg_att += (att = ((intptr_t)endaddr - (intptr_t)vicaddr) / 1024); st_debug(STDL_NORMAL, lcol, "paged out 0x%p" "+0t(%llu/%llu)kB%s\n", vicaddr, (unsigned long long)((d_rss < 0) ? - d_rss : 0), (unsigned long long)att, willignore ? " (will ignore)" : ""); } else { st_debug(STDL_NORMAL, lcol, "process %d: exited/unscannable\n", (int)vic->lpc_pid); vic->lpc_unscannable = 1; goto nextproc; } /* * Update the statistics file, if it's time. */ check_update_statistics(); vicaddr = (nvicaddr != NULL) ? nvicaddr : (void *)advance_prpageheader_cur_nextmapping(&cur); } excess += rss_delta(&new_psinfo, &old_psinfo, vic); st_debug(STDL_NORMAL, lcol, "done, excess %lld\n", (long long)excess); nextproc: /* * If a process was grabbed, release it, destroying its agent. */ if (scan_pr != NULL) { (void) Prelease(scan_pr, 0); scan_pr = NULL; } lcol->lcol_victim = vic; /* * Scan the collection at most once. Only if scanning was not * aborted for any reason, and the end of lprocess has not been * reached, determine the next victim and scan it. */ if (vic != NULL) { if (vic->lpc_next != NULL) { /* * Determine the next process to be scanned. */ if (excess > 0) { vic = get_valid_victim(lcol, vic->lpc_next); vicaddr = 0; } } else { /* * A complete scan of the collection was made, * so tick the scan counter and stop scanning * until the next request. */ lcol->lcol_stat.lcols_scan_count++; lcol->lcol_stat.lcols_scan_time_complete = lcol->lcol_stat.lcols_scan_time; /* * If an excess still exists, tick the * "ineffective scan" counter, signalling that * the cap may be uneforceable. */ if (resumed == 0 && excess > 0) lcol->lcol_stat .lcols_scan_ineffective++; /* * Scanning should start at the beginning of * the process list at the next request. */ if (excess > 0) vic = NULL; } } } lcol->lcol_stat.lcols_scan_time += (gethrtime() - scan_start); st_debug(STDL_HIGH, lcol, "done scanning; excess %lld\n", (long long)excess); lcol->lcol_resaddr = vicaddr; if (lcol->lcol_resaddr == NULL && lcol->lcol_victim != NULL) { lcol->lcol_victim = get_valid_victim(lcol, lcol->lcol_victim->lpc_next); } } /* * Abort the scan in progress, and destroy the agent LWP of any grabbed * processes. */ void scan_abort(void) { if (scan_pr != NULL) (void) Prelease(scan_pr, 0); } static void revoke_xmap(rfd_t *rfd) { lprocess_t *lpc = rfd->rfd_data; debug("revoking xmap for process %d\n", (int)lpc->lpc_pid); ASSERT(lpc->lpc_xmap_fd != -1); lpc->lpc_xmap_fd = -1; } /* * Retrieve the process's current xmap , which is used to determine the size of * the resident portion of its segments. Return zero if successful. */ static int lpc_xmap_update(lprocess_t *lpc) { int res; struct stat st; free(lpc->lpc_xmap); lpc->lpc_xmap = NULL; lpc->lpc_nxmap = -1; if (lpc->lpc_xmap_fd == -1) { char pathbuf[PROC_PATH_MAX]; (void) snprintf(pathbuf, sizeof (pathbuf), "/proc/%d/xmap", (int)lpc->lpc_pid); if ((lpc->lpc_xmap_fd = rfd_open(pathbuf, 1, RFD_XMAP, revoke_xmap, lpc, O_RDONLY, 0)) < 0) return (-1); } redo: errno = 0; if (fstat(lpc->lpc_xmap_fd, &st) != 0) { debug("cannot stat xmap\n"); (void) rfd_close(lpc->lpc_xmap_fd); lpc->lpc_xmap_fd = -1; return (-1); } if ((st.st_size % sizeof (*lpc->lpc_xmap)) != 0) { debug("xmap wrong size\n"); (void) rfd_close(lpc->lpc_xmap_fd); lpc->lpc_xmap_fd = -1; return (-1); } lpc->lpc_xmap = malloc(st.st_size); if (lpc->lpc_xmap == NULL) { debug("cannot malloc() %ld bytes for xmap", st.st_size); (void) rfd_close(lpc->lpc_xmap_fd); lpc->lpc_xmap_fd = -1; return (-1); } if ((res = pread(lpc->lpc_xmap_fd, lpc->lpc_xmap, st.st_size, 0)) != st.st_size) { free(lpc->lpc_xmap); lpc->lpc_xmap = NULL; if (res > 0) { debug("xmap changed size, retrying\n"); goto redo; } else { debug("cannot read xmap"); return (-1); } } lpc->lpc_nxmap = st.st_size / sizeof (*lpc->lpc_xmap); return (0); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld .KEEP_STATE: .SUFFIXES: include ../../Makefile.cmd COMMON_DIR= ../common # for Makefile.com PROG = rcapstat OBJS = rcapstat.o $(COMMON_OBJS) SRCS = $(OBJS:%.o=%.c) include $(SRC)/cmd/stat/Makefile.stat COMMON_OBJS = utils.o rcapd_conf.o # Hammerhead: Remove $(COMMON_OBJS:%.o=%.c) from CLEANFILES — we no longer # copy .c files locally (Makefile.stat compiles directly from source dir). CLEANFILES += $(OBJS) $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG CPPFLAGS += -I$(COMMON_DIR) LDLIBS += -lumem -lzonecfg -lscf POFILES = $(OBJS:%.o=%.po) POFILE = p$(PROG).po all: $(PROG) $(MAPFILE.NGB) include ../Makefile.com $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@ $(CAT) $(POFILES) > $@ clean: $(RM) $(CLEANFILES) $(ROOTBIN): $(MKDIR) -p $(ROOTBIN) install: all $(ROOTBIN) $(ROOTPROG) include ../../Makefile.targ /* * 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 #include #include #include #include #include #include #include #include #include #include #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); }