1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-25 18:50:18 +03:00

Merge pull request #3481 from poettering/relative-memcg

various changes, most importantly regarding memory metrics
This commit is contained in:
Lennart Poettering 2016-06-16 13:56:23 +02:00 committed by GitHub
commit 616aab6085
15 changed files with 296 additions and 95 deletions

6
TODO
View File

@ -33,7 +33,10 @@ Janitorial Clean-ups:
Features:
* use phyical_memory() to allow MemoryLimit= configuration based on available system memory
* resolved: make sure when we get an ip address with ifindex suffix, we handle
it nicely
* resolved: maybe add a switch to disable any local caching
* ProtectKernelLogs= (drops CAP_SYSLOG, add seccomp for syslog() syscall, and DeviceAllow to /dev/kmsg) in service files
@ -227,7 +230,6 @@ Features:
- resolved should optionally register additional per-interface LLMNR
names, so that for the container case we can establish the same name
(maybe "host") for referencing the server, everywhere.
- enable DNSSEC by default
- allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?)
* refcounting in sd-resolve is borked

View File

@ -92,16 +92,14 @@
<refsect1>
<title>Automatic Dependencies</title>
<para>Units with the <varname>Slice=</varname> setting set get
automatic <varname>Requires=</varname> and
<varname>After=</varname> dependencies on the specified slice
unit.</para>
<para>Units with the <varname>Slice=</varname> setting set automatically acquire <varname>Requires=</varname> and
<varname>After=</varname> dependencies on the specified slice unit.</para>
</refsect1>
<refsect1>
<title>Unified and Legacy Control Group Hierarchies</title>
<para>Unified control group hierarchy is the new version of kernel control group interface. Depending on the
<para>The unified control group hierarchy is the new version of kernel control group interface. Depending on the
resource type, there are differences in resource control capabilities. Also, because of interface changes, some
resource types have a separate set of options on the unified hierarchy.</para>
@ -117,8 +115,8 @@
<varlistentry>
<term><option>Memory</option></term>
<listitem>
<para><varname>MemoryMax</varname> replaces <varname>MemoryLimit</varname>. <varname>MemoryLow</varname>
and <varname>MemoryHigh</varname> are effective only on unified hierarchy.</para>
<para><varname>MemoryMax=</varname> replaces <varname>MemoryLimit=</varname>. <varname>MemoryLow=</varname>
and <varname>MemoryHigh=</varname> are effective only on unified hierarchy.</para>
</listitem>
</varlistentry>
</variablelist>
@ -228,9 +226,11 @@
reclaimed as long as memory can be reclaimed from unprotected units.</para>
<para>Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. This controls the
<literal>memory.low</literal> control group attribute. For details about this control group attribute, see
<ulink url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a
percentage value may be specified, which is taken relative to the installed physical memory on the
system. This controls the <literal>memory.low</literal> control group attribute. For details about this
control group attribute, see <ulink
url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
<para>Implies <literal>MemoryAccounting=true</literal>.</para>
@ -247,7 +247,9 @@
aggressively in such cases. This is the main mechanism to control memory usage of a unit.</para>
<para>Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a
percentage value may be specified, which is taken relative to the installed physical memory on the
system. If assigned the
special value <literal>infinity</literal>, no memory limit is applied. This controls the
<literal>memory.high</literal> control group attribute. For details about this control group attribute, see
<ulink url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
@ -268,8 +270,9 @@
last line of defense.</para>
<para>Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the
special value <literal>infinity</literal>, no memory limit is applied. This controls the
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a
percentage value may be specified, which is taken relative to the installed physical memory on the system. If
assigned the special value <literal>infinity</literal>, no memory limit is applied. This controls the
<literal>memory.max</literal> control group attribute. For details about this control group attribute, see
<ulink url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
@ -284,17 +287,14 @@
<term><varname>MemoryLimit=<replaceable>bytes</replaceable></varname></term>
<listitem>
<para>Specify the limit on maximum memory usage of the
executed processes. The limit specifies how much process and
kernel memory can be used by tasks in this unit. Takes a
memory size in bytes. If the value is suffixed with K, M, G
or T, the specified memory size is parsed as Kilobytes,
Megabytes, Gigabytes, or Terabytes (with the base 1024),
respectively. If assigned the special value
<literal>infinity</literal>, no memory limit is applied. This
controls the <literal>memory.limit_in_bytes</literal>
control group attribute. For details about this control
group attribute, see <ulink
<para>Specify the limit on maximum memory usage of the executed processes. The limit specifies how much
process and kernel memory can be used by tasks in this unit. Takes a memory size in bytes. If the value is
suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or
Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is
taken relative to the installed physical memory on the system. If assigned the special value
<literal>infinity</literal>, no memory limit is applied. This controls the
<literal>memory.limit_in_bytes</literal> control group attribute. For details about this control group
attribute, see <ulink
url="https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt">memory.txt</ulink>.</para>
<para>Implies <literal>MemoryAccounting=true</literal>.</para>

View File

@ -532,3 +532,22 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
return 0;
}
int parse_percent(const char *p) {
const char *pc, *n;
unsigned v;
int r;
pc = endswith(p, "%");
if (!pc)
return -EINVAL;
n = strndupa(p, pc - p);
r = safe_atou(n, &v);
if (r < 0)
return r;
if (v > 100)
return -ERANGE;
return (int) v;
}

View File

@ -105,3 +105,5 @@ static inline int safe_atozu(const char *s, size_t *ret_u) {
int safe_atod(const char *s, double *ret_d);
int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
int parse_percent(const char *p);

View File

@ -36,6 +36,7 @@
#include "alloc-util.h"
#include "build.h"
#include "cgroup-util.h"
#include "def.h"
#include "dirent-util.h"
#include "fd-util.h"
@ -771,15 +772,64 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
}
uint64_t physical_memory(void) {
long mem;
_cleanup_free_ char *root = NULL, *value = NULL;
uint64_t mem, lim;
size_t ps;
long sc;
/* We return this as uint64_t in case we are running as 32bit
* process on a 64bit kernel with huge amounts of memory */
/* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
* memory.
*
* In order to support containers nicely that have a configured memory limit we'll take the minimum of the
* physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
mem = sysconf(_SC_PHYS_PAGES);
assert(mem > 0);
sc = sysconf(_SC_PHYS_PAGES);
assert(sc > 0);
return (uint64_t) mem * (uint64_t) page_size();
ps = page_size();
mem = (uint64_t) sc * (uint64_t) ps;
if (cg_get_root_path(&root) < 0)
return mem;
if (cg_get_attribute("memory", root, "memory.limit_in_bytes", &value))
return mem;
if (safe_atou64(value, &lim) < 0)
return mem;
/* Make sure the limit is a multiple of our own page size */
lim /= ps;
lim *= ps;
return MIN(mem, lim);
}
uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
uint64_t p, m, ps, r;
assert(max > 0);
/* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
* the result is a multiple of the page size (rounds down). */
ps = page_size();
assert(ps > 0);
p = physical_memory() / ps;
assert(p > 0);
m = p * v;
if (m / p != v)
return UINT64_MAX;
m /= max;
r = m * ps;
if (r / ps != m)
return UINT64_MAX;
return r;
}
int update_reboot_parameter_and_warn(const char *param) {

View File

@ -184,6 +184,7 @@ int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
uint64_t physical_memory(void);
uint64_t physical_memory_scale(uint64_t v, uint64_t max);
int update_reboot_parameter_and_warn(const char *param);

View File

@ -641,7 +641,7 @@ int bus_cgroup_set_property(
return 1;
} else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) {
} else if (STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
const char *path;
bool read = true;
unsigned n = 0;
@ -835,6 +835,8 @@ int bus_cgroup_set_property(
r = sd_bus_message_read(message, "t", &v);
if (r < 0)
return r;
if (v <= 0)
return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name);
if (mode != UNIT_CHECK) {
if (streq(name, "MemoryLow"))
@ -847,19 +849,53 @@ int bus_cgroup_set_property(
unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
if (v == CGROUP_LIMIT_MAX)
unit_write_drop_in_private_format(u, mode, name, "%s=max", name);
unit_write_drop_in_private_format(u, mode, name, "%s=infinity", name);
else
unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, v);
}
return 1;
} else if (STR_IN_SET(name, "MemoryLowByPhysicalMemory", "MemoryHighByPhysicalMemory", "MemoryMaxByPhysicalMemory")) {
uint32_t raw;
uint64_t v;
r = sd_bus_message_read(message, "u", &raw);
if (r < 0)
return r;
v = physical_memory_scale(raw, UINT32_MAX);
if (v <= 0 || v == UINT64_MAX)
return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name);
if (mode != UNIT_CHECK) {
const char *e;
/* Chop off suffix */
assert_se(e = endswith(name, "ByPhysicalMemory"));
name = strndupa(name, e - name);
if (streq(name, "MemoryLow"))
c->memory_low = v;
else if (streq(name, "MemoryHigh"))
c->memory_high = v;
else
c->memory_max = v;
unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu32 "%%", name, (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100, (uint64_t) UINT32_MAX)));
}
return 1;
} else if (streq(name, "MemoryLimit")) {
uint64_t limit;
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
if (limit <= 0)
return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name);
if (mode != UNIT_CHECK) {
c->memory_limit = limit;
@ -873,6 +909,26 @@ int bus_cgroup_set_property(
return 1;
} else if (streq(name, "MemoryLimitByPhysicalMemory")) {
uint64_t limit;
uint32_t raw;
r = sd_bus_message_read(message, "u", &raw);
if (r < 0)
return r;
limit = physical_memory_scale(raw, UINT32_MAX);
if (limit <= 0 || limit == UINT64_MAX)
return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name);
if (mode != UNIT_CHECK) {
c->memory_limit = limit;
unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
unit_write_drop_in_private_format(u, mode, "MemoryLimit", "MemoryLimit=%" PRIu32 "%%", (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100, (uint64_t) UINT32_MAX)));
}
return 1;
} else if (streq(name, "DevicePolicy")) {
const char *policy;
CGroupDevicePolicy p;

View File

@ -2774,7 +2774,7 @@ int config_parse_cpu_quota(
void *userdata) {
CGroupContext *c = data;
double percent;
int r;
assert(filename);
assert(lvalue);
@ -2785,18 +2785,13 @@ int config_parse_cpu_quota(
return 0;
}
if (!endswith(rvalue, "%")) {
log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. Ignoring.", rvalue);
r = parse_percent(rvalue);
if (r <= 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "CPU quota '%s' invalid. Ignoring.", rvalue);
return 0;
}
if (sscanf(rvalue, "%lf%%", &percent) != 1 || percent <= 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' invalid. Ignoring.", rvalue);
return 0;
}
c->cpu_quota_per_sec_usec = (usec_t) (percent * USEC_PER_SEC / 100);
c->cpu_quota_per_sec_usec = ((usec_t) r * USEC_PER_SEC) / 100U;
return 0;
}
@ -2817,9 +2812,19 @@ int config_parse_memory_limit(
int r;
if (!isempty(rvalue) && !streq(rvalue, "infinity")) {
r = parse_size(rvalue, 1024, &bytes);
if (r < 0 || bytes < 1) {
log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue);
r = parse_percent(rvalue);
if (r < 0) {
r = parse_size(rvalue, 1024, &bytes);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue);
return 0;
}
} else
bytes = physical_memory_scale(r, 100U);
if (bytes < 1) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Memory limit '%s' too small. Ignoring.", rvalue);
return 0;
}
}

View File

@ -3375,7 +3375,7 @@ int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, co
return -EINVAL;
wrapped = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\"\n"
"or an equivalent operation. Do not edit.\n",
"# or an equivalent operation. Do not edit.\n",
data,
"\n");

View File

@ -307,7 +307,7 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMP &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
triple_timestamp_from_realtime(&rt->timestamp, timeval_load(CMSG_DATA(cmsg)));
triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
}
if (!triple_timestamp_is_set(&rt->timestamp))

View File

@ -843,7 +843,6 @@ int config_parse_tmpfs_size(
void *userdata) {
size_t *sz = data;
const char *e;
int r;
assert(filename);
@ -851,29 +850,17 @@ int config_parse_tmpfs_size(
assert(rvalue);
assert(data);
e = endswith(rvalue, "%");
if (e) {
unsigned long ul;
char *f;
errno = 0;
ul = strtoul(rvalue, &f, 10);
if (errno > 0 || f != e) {
log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue);
return 0;
}
if (ul <= 0 || ul >= 100) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Percentage value out of range, ignoring: %s", rvalue);
return 0;
}
*sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) ul) / (uint64_t) 100));
} else {
/* First, try to parse as percentage */
r = parse_percent(rvalue);
if (r > 0 && r < 100)
*sz = physical_memory_scale(r, 100U);
else {
uint64_t k;
/* If the passed argument was not a percentage, or out of range, parse as byte size */
r = parse_size(rvalue, 1024, &k);
if (r < 0 || (uint64_t) (size_t) k != k) {
if (r < 0 || k <= 0 || (uint64_t) (size_t) k != k) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
return 0;
}

View File

@ -61,7 +61,7 @@ static void manager_reset_config(Manager *m) {
m->idle_action_usec = 30 * USEC_PER_MINUTE;
m->idle_action = HANDLE_IGNORE;
m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */
m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */
m->user_tasks_max = 12288;
m->sessions_max = 8192;
m->inhibitors_max = 8192;

View File

@ -83,18 +83,14 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
if (isempty(eq))
r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
else if (endswith(eq, "%")) {
double percent;
if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) {
log_error("CPU quota '%s' invalid.", eq);
else {
r = parse_percent(eq);
if (r <= 0) {
log_error_errno(r, "CPU quota '%s' invalid.", eq);
return -EINVAL;
}
r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100);
} else {
log_error("CPU quota needs to be in percent.");
return -EINVAL;
r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) r * USEC_PER_SEC / 100U);
}
goto finish;
@ -110,6 +106,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
char *n;
usec_t t;
size_t l;
r = parse_sec(eq, &t);
if (r < 0)
return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
@ -123,6 +120,34 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
strcpy(mempcpy(n, field, l - 3), "USec");
r = sd_bus_message_append(m, "sv", n, "t", t);
goto finish;
} else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
uint64_t bytes;
if (isempty(eq) || streq(eq, "infinity"))
bytes = CGROUP_LIMIT_MAX;
else {
r = parse_percent(eq);
if (r >= 0) {
char *n;
/* When this is a percentage we'll convert this into a relative value in the range
* 0UINT32_MAX and pass it in the MemoryLowByPhysicalMemory property (and related
* ones). This way the physical memory size can be determined server-side */
n = strjoina(field, "ByPhysicalMemory");
r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
goto finish;
} else {
r = parse_size(eq, 1024, &bytes);
if (r < 0)
return log_error_errno(r, "Failed to parse bytes specification %s", assignment);
}
}
r = sd_bus_message_append(m, "sv", field, "t", bytes);
goto finish;
}
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
@ -166,21 +191,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "v", "b", r);
} else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
uint64_t bytes;
if (isempty(eq) || streq(eq, "infinity"))
bytes = CGROUP_LIMIT_MAX;
else {
r = parse_size(eq, 1024, &bytes);
if (r < 0) {
log_error("Failed to parse bytes specification %s", assignment);
return -EINVAL;
}
}
r = sd_bus_message_append(m, "v", "t", bytes);
} else if (streq(field, "TasksMax")) {
uint64_t n;

View File

@ -475,6 +475,24 @@ static void test_safe_atod(void) {
assert_se(*e == ',');
}
static void test_parse_percent(void) {
assert_se(parse_percent("") == -EINVAL);
assert_se(parse_percent("foo") == -EINVAL);
assert_se(parse_percent("0") == -EINVAL);
assert_se(parse_percent("50") == -EINVAL);
assert_se(parse_percent("100") == -EINVAL);
assert_se(parse_percent("-1") == -EINVAL);
assert_se(parse_percent("0%") == 0);
assert_se(parse_percent("55%") == 55);
assert_se(parse_percent("100%") == 100);
assert_se(parse_percent("-7%") == -ERANGE);
assert_se(parse_percent("107%") == -ERANGE);
assert_se(parse_percent("%") == -EINVAL);
assert_se(parse_percent("%%") == -EINVAL);
assert_se(parse_percent("%1") == -EINVAL);
assert_se(parse_percent("1%%") == -EINVAL);
}
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@ -488,6 +506,7 @@ int main(int argc, char *argv[]) {
test_safe_atou16();
test_safe_atoi16();
test_safe_atod();
test_parse_percent();
return 0;
}

View File

@ -26,6 +26,7 @@
#include "def.h"
#include "fileio.h"
#include "fs-util.h"
#include "parse-util.h"
#include "raw-clone.h"
#include "rm-rf.h"
#include "string-util.h"
@ -263,6 +264,53 @@ static void test_raw_clone(void) {
}
}
static void test_physical_memory(void) {
uint64_t p;
char buf[FORMAT_BYTES_MAX];
p = physical_memory();
assert_se(p > 0);
assert_se(p < UINT64_MAX);
assert_se(p % page_size() == 0);
log_info("Memory: %s (%" PRIu64 ")", format_bytes(buf, sizeof(buf), p), p);
}
static void test_physical_memory_scale(void) {
uint64_t p;
p = physical_memory();
assert_se(physical_memory_scale(0, 100) == 0);
assert_se(physical_memory_scale(100, 100) == p);
log_info("Memory original: %" PRIu64, physical_memory());
log_info("Memory scaled by 50%%: %" PRIu64, physical_memory_scale(50, 100));
log_info("Memory divided by 2: %" PRIu64, physical_memory() / 2);
log_info("Page size: %zu", page_size());
/* There might be an uneven number of pages, hence permit these calculations to be half a page off... */
assert_se(page_size()/2 + physical_memory_scale(50, 100) - p/2 <= page_size());
assert_se(physical_memory_scale(200, 100) == p*2);
assert_se(physical_memory_scale(0, 1) == 0);
assert_se(physical_memory_scale(1, 1) == p);
assert_se(physical_memory_scale(2, 1) == p*2);
assert_se(physical_memory_scale(0, 2) == 0);
assert_se(page_size()/2 + physical_memory_scale(1, 2) - p/2 <= page_size());
assert_se(physical_memory_scale(2, 2) == p);
assert_se(physical_memory_scale(4, 2) == p*2);
assert_se(physical_memory_scale(0, UINT32_MAX) == 0);
assert_se(physical_memory_scale(UINT32_MAX, UINT32_MAX) == p);
/* overflow */
assert_se(physical_memory_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX);
}
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@ -277,6 +325,8 @@ int main(int argc, char *argv[]) {
test_log2i();
test_execute_directory();
test_raw_clone();
test_physical_memory();
test_physical_memory_scale();
return 0;
}