diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 844ae396a0..f03ad93b43 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -109,6 +109,8 @@ int quota_nb_perms_per_node = 5; int quota_trans_nodes = 1024; int quota_max_path_len = XENSTORE_REL_PATH_MAX; int quota_req_outstanding = 20; +int quota_memory_per_domain_soft = 2 * 1024 * 1024; /* 2 MB */ +int quota_memory_per_domain_hard = 2 * 1024 * 1024 + 512 * 1024; /* 2.5 MB */ unsigned int timeout_watch_event_msec = 20000; @@ -2390,7 +2392,14 @@ static void usage(void) " quotas are:\n" " transaction-nodes: number of accessed node per\n" " transaction\n" +" memory: total used memory per domain for nodes,\n" +" transactions, watches and requests, above\n" +" which Xenstore will stop talking to domain\n" " outstanding: number of outstanding requests\n" +" -q, --quota-soft = set a soft quota to the value ,\n" +" causing a warning to be issued via syslog() if the\n" +" limit is violated, allowed quotas are:\n" +" memory: see above\n" " -w, --timeout = set the timeout in seconds for ,\n" " allowed timeout candidates are:\n" " watch-event: time a watch-event is kept pending\n" @@ -2417,6 +2426,7 @@ static struct option options[] = { { "perm-nb", 1, NULL, 'A' }, { "path-max", 1, NULL, 'M' }, { "quota", 1, NULL, 'Q' }, + { "quota-soft", 1, NULL, 'q' }, { "timeout", 1, NULL, 'w' }, { "no-recovery", 0, NULL, 'R' }, { "internal-db", 0, NULL, 'I' }, @@ -2464,7 +2474,7 @@ static void set_timeout(const char *arg) barf("unknown timeout \"%s\"\n", arg); } -static void set_quota(const char *arg) +static void set_quota(const char *arg, bool soft) { const char *eq = strchr(arg, '='); int val; @@ -2472,11 +2482,16 @@ static void set_quota(const char *arg) if (!eq) barf("quotas must be specified via =\n"); val = get_optval_int(eq + 1); - if (what_matches(arg, "outstanding")) + if (what_matches(arg, "outstanding") && !soft) quota_req_outstanding = val; - else if (what_matches(arg, "transaction-nodes")) + else if (what_matches(arg, "transaction-nodes") && !soft) quota_trans_nodes = val; - else + else if (what_matches(arg, "memory")) { + if (soft) + quota_memory_per_domain_soft = val; + else + quota_memory_per_domain_hard = val; + } else barf("unknown quota \"%s\"\n", arg); } @@ -2494,7 +2509,7 @@ int main(int argc, char *argv[]) orig_argc = argc; orig_argv = argv; - while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:T:RVW:w:U", + while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:q:T:RVW:w:U", options, NULL)) != -1) { switch (opt) { case 'D': @@ -2545,7 +2560,10 @@ int main(int argc, char *argv[]) quota_max_path_len); break; case 'Q': - set_quota(optarg); + set_quota(optarg, false); + break; + case 'q': + set_quota(optarg, true); break; case 'w': set_timeout(optarg); diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h index a772f3b8ea..ec52d8d3ff 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -268,6 +268,8 @@ extern int priv_domid; extern int quota_nb_entry_per_domain; extern int quota_req_outstanding; extern int quota_trans_nodes; +extern int quota_memory_per_domain_soft; +extern int quota_memory_per_domain_hard; extern unsigned int timeout_watch_event_msec; diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c index 5c79eed3dc..4242380886 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -76,6 +76,13 @@ struct domain /* number of entry from this domain in the store */ int nbentry; + /* Amount of memory allocated for this domain. */ + int memory; + bool soft_quota_reported; + bool hard_quota_reported; + time_t mem_last_msg; +#define MEM_WARN_MINTIME_SEC 10 + /* number of watch for this domain */ int nbwatch; @@ -192,6 +199,9 @@ static bool domain_can_read(struct connection *conn) return false; if (conn->domain->nboutstanding >= quota_req_outstanding) return false; + if (conn->domain->memory >= quota_memory_per_domain_hard && + quota_memory_per_domain_hard) + return false; } return (intf->req_cons != intf->req_prod); @@ -982,6 +992,89 @@ int domain_entry(struct connection *conn) : 0; } +static bool domain_chk_quota(struct domain *domain, int mem) +{ + time_t now; + + if (!domain || !domid_is_unprivileged(domain->domid) || + (domain->conn && domain->conn->is_ignored)) + return false; + + now = time(NULL); + + if (mem >= quota_memory_per_domain_hard && + quota_memory_per_domain_hard) { + if (domain->hard_quota_reported) + return true; + syslog(LOG_ERR, "Domain %u exceeds hard memory quota, Xenstore interface to domain stalled\n", + domain->domid); + domain->mem_last_msg = now; + domain->hard_quota_reported = true; + return true; + } + + if (now - domain->mem_last_msg >= MEM_WARN_MINTIME_SEC) { + if (domain->hard_quota_reported) { + domain->mem_last_msg = now; + domain->hard_quota_reported = false; + syslog(LOG_INFO, "Domain %u below hard memory quota again\n", + domain->domid); + } + if (mem >= quota_memory_per_domain_soft && + quota_memory_per_domain_soft && + !domain->soft_quota_reported) { + domain->mem_last_msg = now; + domain->soft_quota_reported = true; + syslog(LOG_WARNING, "Domain %u exceeds soft memory quota\n", + domain->domid); + } + if (mem < quota_memory_per_domain_soft && + domain->soft_quota_reported) { + domain->mem_last_msg = now; + domain->soft_quota_reported = false; + syslog(LOG_INFO, "Domain %u below soft memory quota again\n", + domain->domid); + } + + } + + return false; +} + +int domain_memory_add(unsigned int domid, int mem, bool no_quota_check) +{ + struct domain *domain; + + domain = find_domain_struct(domid); + if (domain) { + /* + * domain_chk_quota() will print warning and also store whether + * the soft/hard quota has been hit. So check no_quota_check + * *after*. + */ + if (domain_chk_quota(domain, domain->memory + mem) && + !no_quota_check) + return ENOMEM; + domain->memory += mem; + } else { + /* + * The domain the memory is to be accounted for should always + * exist, as accounting is done either for a domain related to + * the current connection, or for the domain owning a node + * (which is always existing, as the owner of the node is + * tested to exist and replaced by domid 0 if not). + * So not finding the related domain MUST be an error in the + * data base. + */ + errno = ENOENT; + corrupt(NULL, "Accounting called for non-existing domain %u\n", + domid); + return ENOENT; + } + + return 0; +} + void domain_watch_inc(struct connection *conn) { if (!conn || !conn->domain) diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h index 5937931314..d342e5e867 100644 --- a/tools/xenstore/xenstored_domain.h +++ b/tools/xenstore/xenstored_domain.h @@ -62,6 +62,26 @@ int domain_entry_inc(struct connection *conn, struct node *); void domain_entry_dec(struct connection *conn, struct node *); int domain_entry_fix(unsigned int domid, int num, bool update); int domain_entry(struct connection *conn); +int domain_memory_add(unsigned int domid, int mem, bool no_quota_check); + +/* + * domain_memory_add_chk(): to be used when memory quota should be checked. + * Not to be used when specifying a negative mem value, as lowering the used + * memory should always be allowed. + */ +static inline int domain_memory_add_chk(unsigned int domid, int mem) +{ + return domain_memory_add(domid, mem, false); +} +/* + * domain_memory_add_nochk(): to be used when memory quota should not be + * checked, e.g. when lowering memory usage, or in an error case for undoing + * a previous memory adjustment. + */ +static inline void domain_memory_add_nochk(unsigned int domid, int mem) +{ + domain_memory_add(domid, mem, true); +} void domain_watch_inc(struct connection *conn); void domain_watch_dec(struct connection *conn); int domain_watch(struct connection *conn);