From 0550cead9f7e62ffe47c64a853d200f75b93ff5d Mon Sep 17 00:00:00 2001 From: William Brown Date: Apr 03 2017 23:59:36 +0000 Subject: Ticket 48864 - Cleanup memory detection before we add cgroup support Bug Description: Our old memory detection code was really spaghetti like, and not very nice. Make a cleaner better interface. Fix Description: Add a new slapi platform abstraction. We will start to move pieces of abstraction in here as needed. This contains a struct of system information such an memory infomation. due to the design, we can now test not only the memory detection, but because we pass mi as a parameter to util_cachesize_issane, we can now test our cache checking function too. https://pagure.io/389-ds-base/issue/48864 Author: wibrown Review by: mreynolds (Thanks!) --- diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c index 739a8b8..26a96f6 100644 --- a/ldap/servers/slapd/back-ldbm/dblayer.c +++ b/ldap/servers/slapd/back-ldbm/dblayer.c @@ -1391,7 +1391,7 @@ dblayer_start(struct ldbminfo *li, int dbmode) /* Oops---looks like the admin misconfigured, let's warn them */ slapi_log_err(SLAPI_LOG_WARNING,"dblayer_start", "Likely CONFIGURATION ERROR -" "dbcachesize is configured to use more than the available " - "physical memory, decreased to the largest available size (%lu bytes).\n", + "physical memory, decreased to the largest available size (%"PRIu64" bytes).\n", priv->dblayer_cachesize); li->li_dbcachesize = priv->dblayer_cachesize; } @@ -1692,9 +1692,6 @@ dblayer_start(struct ldbminfo *li, int dbmode) * nsslapd-import-cache-autosize: 0 * get the nsslapd-import-cachesize. * Calculate the memory size left after allocating the import cache size. - * If the size is less than the hard limit, it issues an error and quit. - * If the size is greater than the hard limit and less than the soft limit, - * it issues a warning, but continues the import task. * * Note: this function is called only if the import is executed as a stand * alone command line (ldif2db). @@ -1702,27 +1699,17 @@ dblayer_start(struct ldbminfo *li, int dbmode) int check_and_set_import_cache(struct ldbminfo *li) { - size_t import_pages = 0; - size_t pagesize, pages, procpages, availpages; - size_t soft_limit = 0; - size_t hard_limit = 0; - size_t page_delta = 0; + uint64_t import_cache = 0; char s[64]; /* big enough to hold %ld */ + /* Get our platform memory values. */ + slapi_pal_meminfo *mi = spal_meminfo_get(); - if (util_info_sys_pages(&pagesize, &pages, &procpages, &availpages) != 0 || 0 == pagesize || 0 == pages) { - slapi_log_err(SLAPI_LOG_ERR, "check_and_set_import_cache", - "Failed to get pagesize: %ld or pages: %ld\n", - pagesize, pages); + if (mi == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "check_and_set_import_cache", "Failed to get system memory infomation\n"); return ENOENT; } - slapi_log_err(SLAPI_LOG_INFO, "check_and_set_import_cache", - "pagesize: %ld, pages: %ld, procpages: %ld\n", - pagesize, pages, procpages); + slapi_log_err(SLAPI_LOG_INFO, "check_and_set_import_cache", "pagesize: %"PRIu64", available bytes %"PRIu64", process usage %"PRIu64" \n", mi->pagesize_bytes, mi->system_available_bytes, mi->process_consumed_bytes); - /* Soft limit: pages equivalent to 1GB (defined in dblayer.h) */ - soft_limit = (DBLAYER_IMPORTCACHESIZE_SL*1024) / (pagesize/1024); - /* Hard limit: pages equivalent to 100MB (defined in dblayer.h) */ - hard_limit = (DBLAYER_IMPORTCACHESIZE_HL*1024) / (pagesize/1024); /* * default behavior for ldif2db import cache, * nsslapd-import-cache-autosize==-1, @@ -1743,48 +1730,29 @@ check_and_set_import_cache(struct ldbminfo *li) if (li->li_import_cache_autosize == 0) { /* user specified importCache */ - import_pages = li->li_import_cachesize / pagesize; + import_cache = li->li_import_cachesize; } else { /* autosizing importCache */ /* ./125 instead of ./100 is for adjusting the BDB overhead. */ -#ifdef LINUX - /* On linux, availpages is correct so we should use it! */ - import_pages = (li->li_import_cache_autosize * availpages) / 125; -#else - import_pages = (li->li_import_cache_autosize * pages) / 125; -#endif + import_cache = (li->li_import_cache_autosize * mi->system_available_bytes) / 125; } - page_delta = pages - import_pages; - if (page_delta < hard_limit) { - slapi_log_err(SLAPI_LOG_ERR, - "check_and_set_import_cache", "After allocating import cache %ldKB, " - "the available memory is %ldKB, " - "which is less than the hard limit %ldKB. " - "Please decrease the import cache size and rerun import.\n", - import_pages*(pagesize/1024), page_delta*(pagesize/1024), - hard_limit*(pagesize/1024)); + if (util_is_cachesize_sane(mi, &import_cache) == UTIL_CACHESIZE_ERROR) { + + slapi_log_err(SLAPI_LOG_INFO, "check_and_set_import_cache", "Import failed to run: unable to validate system memory limits.\n"); + spal_meminfo_destroy(mi); return ENOMEM; } - if (page_delta < soft_limit) { - slapi_log_err(SLAPI_LOG_WARNING, - "check_and_set_import_cache", "After allocating import cache %ldKB, " - "the available memory is %ldKB, " - "which is less than the soft limit %ldKB. " - "You may want to decrease the import cache size and " - "rerun import.\n", - import_pages*(pagesize/1024), page_delta*(pagesize/1024), - soft_limit*(pagesize/1024)); - } - slapi_log_err(SLAPI_LOG_INFO, "check_and_set_import_cache", "Import allocates %ldKB import cache.\n", - import_pages*(pagesize/1024)); - if (li->li_import_cache_autosize > 0) { /* import cache autosizing */ + slapi_log_err(SLAPI_LOG_INFO, "check_and_set_import_cache", "Import allocates %"PRIu64"KB import cache.\n", import_cache / 1024); + if (li->li_import_cache_autosize > 0) { + /* import cache autosizing */ /* set the calculated import cache size to the config */ - sprintf(s, "%lu", (unsigned long)(import_pages * pagesize)); + sprintf(s, "%"PRIu64, import_cache); ldbm_config_internal_set(li, CONFIG_IMPORT_CACHESIZE, s); } + spal_meminfo_destroy(mi); return 0; } diff --git a/ldap/servers/slapd/back-ldbm/dblayer.h b/ldap/servers/slapd/back-ldbm/dblayer.h index e4307fc..816c943 100644 --- a/ldap/servers/slapd/back-ldbm/dblayer.h +++ b/ldap/servers/slapd/back-ldbm/dblayer.h @@ -68,14 +68,6 @@ #define DB_REGION_NAME 25 /* DB: named regions, no backing file. */ #endif -/* Used in check_and_set_import_cache */ -/* After allocating the import cache, free memory must be left more than - * the hard limit to run import. */ -/* If the free memory size left is greater than hard limit and less than - * soft limit, the import utility issues a warning, but it runs */ -#define DBLAYER_IMPORTCACHESIZE_HL 100 /* import cache hard limit 100MB */ -#define DBLAYER_IMPORTCACHESIZE_SL 1024 /* import cache soft limit 1GB */ - struct dblayer_private_env { DB_ENV *dblayer_DB_ENV; Slapi_RWLock * dblayer_env_lock; diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h index 6349c8f..0c76580 100644 --- a/ldap/servers/slapd/slapi-private.h +++ b/ldap/servers/slapd/slapi-private.h @@ -1366,19 +1366,6 @@ long long slapi_parse_duration_longlong(const char *value); int slapi_is_duration_valid(const char *value); /** - * Populate the pointers with the system memory information. - * At this time, Linux is the only "reliable" system for returning these values - * - * \param pagesize Will return the system page size in bytes. - * \param pages The total number of memory pages on the system. May include swap pages depending on OS. - * \param procpages Number of memory pages our current process is consuming. May not be accurate on all platforms as this could be the VMSize rather than the actual number of consumed pages. - * \param availpages Number of available pages of memory on the system. Not all operating systems set this correctly. - * - * \return 0 on success, non-zero on failure to determine memory sizings. - */ -int util_info_sys_pages(size_t *pagesize, size_t *pages, size_t *procpages, size_t *availpages); - -/** * Possible results of a cachesize check */ typedef enum _util_cachesize_result { diff --git a/ldap/servers/slapd/slapi_pal.c b/ldap/servers/slapd/slapi_pal.c new file mode 100644 index 0000000..4bafd4b --- /dev/null +++ b/ldap/servers/slapd/slapi_pal.c @@ -0,0 +1,250 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2017 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +/* + * Implementation of functions to abstract from platform + * specific issues. + */ + +/* Provide ch_malloc etc. */ +#include +/* Provide slapi_log_err macro wrapper */ +#include +#include + +/* Assert macros */ +#include +/* Access errno */ +#include + +/* For getpagesize */ +#include + +/* For rlimit */ +#include +#include + +#ifdef OS_solaris +#include +#endif + +#if defined ( hpux ) +#include +#endif + +static int_fast32_t +_spal_rlimit_get(int resource, uint64_t *soft_limit, uint64_t *hard_limit) { + struct rlimit rl = {0}; + + if (getrlimit(resource, &rl) != 0) { + int errsrv = errno; + slapi_log_err(SLAPI_LOG_ERR, "_spal_rlimit_mem_get", "Failed to access system resource limits %d\n", errsrv); + return 1; + } + + if (rl.rlim_cur != RLIM_INFINITY) { + *soft_limit = (uint64_t)rl.rlim_cur; + } + if (rl.rlim_max != RLIM_INFINITY) { + *hard_limit = (uint64_t)rl.rlim_max; + } + + return 0; +} + + +#ifdef LINUX +static int_fast32_t +_spal_uint64_t_file_get(char *name, char *prefix, uint64_t *dest) { + FILE *f; + char s[40] = {0}; + size_t prefix_len = strlen(prefix); + + /* Make sure we can fit into our buffer */ + assert((prefix_len + 20) < 39); + + f = fopen(name, "r"); + if (!f) { /* fopen failed */ + int errsrv = errno; + slapi_log_err(SLAPI_LOG_ERR,"_spal_get_uint64_t_file", "Unable to open file \"%s\". errno=%d\n", name, errsrv); + return 1; + } + + int_fast32_t retval = 0; + while (! feof(f)) { + if (!fgets(s, 39, f)) { + retval = 1; + break; /* error or eof */ + } + if (feof(f)) { + retval = 1; + break; + } + if (strncmp(s, prefix, prefix_len) == 0) { + sscanf(s + prefix_len, "%"SCNu64, dest); + break; + } + } + fclose(f); + return retval; +} + + + +slapi_pal_meminfo * +spal_meminfo_get() { + slapi_pal_meminfo *mi = (slapi_pal_meminfo *)slapi_ch_calloc(1, sizeof(slapi_pal_meminfo)); + + mi->pagesize_bytes = getpagesize(); + + /* + * We have to compare values from a number of sources to ensure we have + * the correct result. + */ + + char f_proc_status[30] = {0}; + sprintf(f_proc_status, "/proc/%d/status", getpid()); + char *p_vmrss = "VmRSS:"; + uint64_t vmrss = 0; + + if (_spal_uint64_t_file_get(f_proc_status, p_vmrss, &vmrss)) { + slapi_log_err(SLAPI_LOG_ERR, "spal_meminfo_get", "Unable to retrieve vmrss\n"); + } + + /* vmrss is in kb, so convert to bytes */ + vmrss = vmrss * 1024; + + uint64_t rl_mem_soft = 0; + uint64_t rl_mem_hard = 0; + uint64_t rl_mem_soft_avail = 0; + + if (_spal_rlimit_get(RLIMIT_AS, &rl_mem_soft, &rl_mem_hard)) { + slapi_log_err(SLAPI_LOG_ERR, "spal_meminfo_get", "Unable to retrieve memory rlimit\n"); + } + + if (rl_mem_soft != 0 && rl_mem_soft > vmrss) { + rl_mem_soft_avail = rl_mem_soft - vmrss; + } + + char *f_meminfo = "/proc/meminfo"; + char *p_memtotal = "MemTotal:"; + char *p_memavail = "MemAvailable:"; + + uint64_t memtotal = 0; + uint64_t memavail = 0; + + if (_spal_uint64_t_file_get(f_meminfo, p_memtotal, &memtotal)) { + slapi_log_err(SLAPI_LOG_ERR, "spal_meminfo_get", "Unable to retrieve %s : %s\n", f_meminfo, p_memtotal); + } + + if (_spal_uint64_t_file_get(f_meminfo, p_memavail, &memavail)) { + slapi_log_err(SLAPI_LOG_ERR, "spal_meminfo_get", "Unable to retrieve %s : %s\n", f_meminfo, p_memavail); + } + + /* Both memtotal and memavail are in kb */ + memtotal = memtotal * 1024; + memavail = memavail * 1024; + + /* Now, compare the values and make a choice to which is provided */ + + /* Process consumed memory */ + mi->process_consumed_bytes = vmrss; + mi->process_consumed_pages = vmrss / mi->pagesize_bytes; + + /* System Total memory */ + /* If we have a memtotal, OR if no memtotal but rlimit */ + if (rl_mem_hard != 0 && ((memtotal != 0 && rl_mem_hard < memtotal) || memtotal == 0)) { + mi->system_total_bytes = rl_mem_hard; + mi->system_total_pages = rl_mem_hard / mi->pagesize_bytes; + } else if (memtotal != 0) { + mi->system_total_bytes = memtotal; + mi->system_total_pages = memtotal / mi->pagesize_bytes; + } else { + slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "Unable to determine system total memory!\n"); + spal_meminfo_destroy(mi); + return NULL; + } + + /* System Available memory */ + + if (rl_mem_soft_avail != 0 && ((memavail != 0 && (rl_mem_soft_avail) < memavail) || memavail == 0)) { + mi->system_available_bytes = rl_mem_soft_avail; + mi->system_available_pages = rl_mem_soft_avail / mi->pagesize_bytes; + } else if (rl_mem_soft != 0 && ((memavail != 0 && (rl_mem_soft) < memavail) || memavail == 0)) { + mi->system_available_bytes = rl_mem_soft; + mi->system_available_pages = rl_mem_soft / mi->pagesize_bytes; + } else if (memavail != 0) { + mi->system_available_bytes = memavail; + mi->system_available_pages = memavail / mi->pagesize_bytes; + } else { + slapi_log_err(SLAPI_LOG_CRIT, "spal_meminfo_get", "Unable to determine system available memory!\n"); + spal_meminfo_destroy(mi); + return NULL; + } + + slapi_log_err(SLAPI_LOG_TRACE, "spal_meminfo_get", "{pagesize_bytes = %"PRIu64", system_total_pages = %"PRIu64", system_total_bytes = %"PRIu64", process_consumed_pages = %"PRIu64", process_consumed_bytes = %"PRIu64", system_available_pages = %"PRIu64", system_available_bytes = %"PRIu64"},\n", + mi->pagesize_bytes, mi->system_total_pages, mi->system_total_bytes, mi->process_consumed_pages, mi->process_consumed_bytes, mi->system_available_pages, mi->system_available_bytes); + + return mi; +} + + +#endif + +#ifdef OS_solaris +uint64_t +_spal_solaris_resident_pages_get() { + uint64_t procpages = 0; + struct prpsinfo psi = {0}; + char fn[40]; + int fd; + + sprintf(fn, "/proc/%d", getpid()); + fd = open(fn, O_RDONLY); + if (fd >= 0) { + if (ioctl(fd, PIOCPSINFO, (void *)&psi) == 0) { + procpages = (uint64_t)psi.pr_size; + } + close(fd); + } + return procpages; +} + +slapi_pal_meminfo * +spal_meminfo_get() { + slapi_pal_meminfo *mi = (slapi_pal_meminfo *)slapi_ch_calloc(1, sizeof(slapi_pal_meminfo)); + + uint64_t rl_mem_soft = 0; + uint64_t rl_mem_hard = 0; + + if (_spal_rlimit_get(RLIMIT_AS, &rl_mem_soft, &rl_mem_hard)) { + slapi_log_err(SLAPI_LOG_ERR, "spal_meminfo_get", "Unable to retrieve memory rlimit\n"); + } + + mi->pagesize_bytes = sysconf(_SC_PAGESIZE); + mi->system_total_pages = sysconf(_SC_PHYS_PAGES); + mi->system_total_bytes = mi->system_total_pages * mi->pagesize_bytes; + mi->system_available_bytes = rl_mem_soft; + if (rl_mem_soft != 0) { + mi->system_available_pages = rl_mem_soft / mi->pagesize_bytes; + } + mi->process_consumed_pages = _spal_solaris_resident_pages_get(); + mi->process_consumed_bytes = mi->process_consumed_pages * mi->pagesize_bytes; + + return mi; + +} +#endif + +#ifdef HPUX +#endif + +void +spal_meminfo_destroy(slapi_pal_meminfo *mi) { + slapi_ch_free((void **)&mi); +} diff --git a/ldap/servers/slapd/slapi_pal.h b/ldap/servers/slapd/slapi_pal.h new file mode 100644 index 0000000..cb61d84 --- /dev/null +++ b/ldap/servers/slapd/slapi_pal.h @@ -0,0 +1,62 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2017 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +/* + * Header for the slapi platform abstraction layer. + * + * This implements a number of functions that help to provide vendor + * neutral requests. Candidates for this are memory, thread, disk size + * and other operations. + * + * Basically anywhere you see a "ifdef PLATFORM" is a candidate + * for this. + */ + +#pragma once + +#include + +#ifdef HAVE_INTTYPES_H +#include +#else +#error Need to define portable format macros such as PRIu64 +#endif /* HAVE_INTTYPES_H */ + +/** + * Structure that contains our system memory information in bytes and pages. + * + */ +typedef struct _slapi_pal_meminfo { + uint64_t pagesize_bytes; + uint64_t system_total_pages; + uint64_t system_total_bytes; + uint64_t process_consumed_pages; + uint64_t process_consumed_bytes; + /* This value may be limited by cgroup or others. */ + uint64_t system_available_pages; + uint64_t system_available_bytes; +} slapi_pal_meminfo; + +/** + * Allocate and returne a populated memory info structure. This will be NULL + * on error, or contain a structure populated with platform information on + * success. You should free this with spal_meminfo_destroy. + * + * \return slapi_pal_meminfo * pointer to structure containing data, or NULL. + */ +slapi_pal_meminfo * spal_meminfo_get(); + +/** + * Destroy an allocated memory info structure. The caller is responsible for + * ensuring this is called. + * + * \param mi the allocated slapi_pal_meminfo structure from spal_meminfo_get(); + */ +void spal_meminfo_destroy(slapi_pal_meminfo *mi); + + diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c index 48fa3c4..a0753c0 100644 --- a/ldap/servers/slapd/util.c +++ b/ldap/servers/slapd/util.c @@ -40,20 +40,8 @@ #define FILTER_BUF 128 /* initial buffer size for attr value */ #define BUF_INCR 16 /* the amount to increase the FILTER_BUF once it fills up */ -/* Used by our util_info_sys_pages function - * - * platforms supported so far: - * Solaris, Linux, Windows - */ -#ifdef OS_solaris -#include -#endif -#ifdef LINUX -#include -#endif -#if defined ( hpux ) -#include -#endif +/* slapi-private contains the pal. */ +#include static int special_filename(unsigned char c) { @@ -1496,336 +1484,25 @@ static size_t util_getvirtualmemsize(void) return rl.rlim_cur; } -/* pages = number of pages of physical ram on the machine (corrected for 32-bit build on 64-bit machine). - * procpages = pages currently used by this process (or working set size, sometimes) - * availpages = some notion of the number of pages 'free'. Typically this number is not useful. - */ -int util_info_sys_pages(size_t *pagesize, size_t *pages, size_t *procpages, size_t *availpages) +util_cachesize_result +util_is_cachesize_sane(slapi_pal_meminfo *mi, uint64_t *cachesize) { - if ((NULL == pagesize) || (NULL == pages) || (NULL == procpages) || (NULL == availpages)) { - slapi_log_err(SLAPI_LOG_ERR, "util_info_sys_pages", - "Null return variables are passed. Skip getting the system info.\n"); - return 1; - } - *pagesize = 0; - *pages = 0; - *availpages = 0; - *procpages = 0; - -#ifdef OS_solaris - *pagesize = (int)sysconf(_SC_PAGESIZE); - *pages = (int)sysconf(_SC_PHYS_PAGES); - *availpages = util_getvirtualmemsize() / *pagesize; - /* solaris has THE most annoying way to get this info */ - { - struct prpsinfo psi = {0}; - char fn[40]; - int fd; - - sprintf(fn, "/proc/%d", getpid()); - fd = open(fn, O_RDONLY); - if (fd >= 0) { - if (ioctl(fd, PIOCPSINFO, (void *)&psi) == 0) { - *procpages = psi.pr_size; - } - close(fd); - } - } -#endif - -#ifdef LINUX - { - /* - * On linux because of the way that the virtual memory system works, we - * don't really need to think about other processes, or fighting them. - * But that's not without quirks. - * - * We are given a virtual memory space, represented by vsize (man 5 proc) - * This space is a "funny number". It's a best effort based system - * where linux instead of telling us how much memory *actually* exists - * for us to use, gives us a virtual memory allocation which is the - * value of ram + swap.... sometimes. Depends on platform. - * - * But none of these pages even exist or belong to us on the real system - * until will malloc them AND write a non-zero to them. - * - * The biggest issue with this is that vsize does NOT consider the - * effect other processes have on the system. So a process can malloc - * 2 Gig from the host, and our vsize doesn't reflect that until we - * suddenly can't malloc anything. - * - * We can see exactly what we are using inside of the vmm by - * looking at rss (man 5 proc). This shows us the current actual - * allocation of memory we are using. This is a good thing. - * - * We obviously don't want to have any pages in swap, but sometimes we - * can't help that: And there is also no guarantee that while we have - * X bytes in vsize, that we can even allocate any of them. Plus, we - * don't know if we are about to allocate to swap or not .... or get us - * killed in a blaze of oom glory. - * - * So there are now two strategies avaliable in this function. - * The first is to blindly accept what the VMM tells us about vsize - * while we hope and pray that we don't get nailed because we used - * too much. - * - * The other is a more conservative approach: We check vsize from - * proc/pid/status, and we check /proc/meminfo for freemem - * Which ever value is "lower" is the upper bound on pages we could - * potentially allocate: generally, this will be MemAvailable. - */ - - size_t freesize = 0; - size_t rlimsize = 0; - - *pagesize = getpagesize(); - - /* Get the amount of freeram, rss */ - - FILE *f; - char fn[40], s[80]; - - sprintf(fn, "/proc/%d/status", getpid()); - f = fopen(fn, "r"); - if (!f) { /* fopen failed */ - /* We should probably make noise here! */ - int errsrv = errno; - slapi_log_err(SLAPI_LOG_ERR,"util_info_sys_pages", "Unable to open file /proc/%d/status. errno=%u\n", getpid(), errsrv); - return 1; - } - while (! feof(f)) { - if (!fgets(s, 79, f)) { - break; /* error or eof */ - } - if (feof(f)) { - break; - } - /* VmRSS shows us what we are ACTUALLY using for proc pages - * Rather than "funny" pages. - */ - if (strncmp(s, "VmRSS:", 6) == 0) { - sscanf(s+6, "%lu", (long unsigned int *)procpages); - } - } - fclose(f); - - FILE *fm; - char *fmn = "/proc/meminfo"; - fm = fopen(fmn, "r"); - if (!fm) { - int errsrv = errno; - slapi_log_err(SLAPI_LOG_ERR,"util_info_sys_pages", "Unable to open file /proc/meminfo. errno=%u\n", errsrv); - return 1; - } - while (! feof(fm)) { - if (!fgets(s, 79, fm)) { - break; /* error or eof */ - } - if (feof(fm)) { - break; - } - if (strncmp(s, "MemTotal:", 9) == 0) { - sscanf(s+9, "%lu", (long unsigned int *)pages); - } - if (strncmp(s, "MemAvailable:", 13) == 0) { - sscanf(s+13, "%lu", (long unsigned int *)&freesize); - } - } - fclose(fm); - - - *pages /= (*pagesize / 1024); - freesize /= (*pagesize / 1024); - /* procpages is now in kb not pages... */ - *procpages /= (*pagesize / 1024); - - rlimsize = util_getvirtualmemsize(); - /* On a 64 bit system, this is uint64 max, but on 32 it's -1 */ - /* Either way, we should be ignoring it at this point if it's infinite */ - if (rlimsize != RLIM_INFINITY) { - /* This is in bytes, make it pages */ - rlimsize = rlimsize / *pagesize; - } - - /* Pages is the total ram on the system. */ - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "pages=%lu, \n", - (unsigned long) *pages); - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "using pages for pages \n"); - - /* Availpages is how much we *could* alloc. We should take the smallest: - * - pages - * - getrlimit (availpages) - * - freesize - */ - if (rlimsize == RLIM_INFINITY) { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "pages=%lu, getrlim=RLIM_INFINITY, freesize=%lu\n", - (unsigned long)*pages, (unsigned long)freesize); - } else { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "pages=%lu, getrlim=%lu, freesize=%lu\n", - (unsigned long)*pages, (unsigned long)*availpages, (unsigned long)freesize); - } - - if (rlimsize != RLIM_INFINITY && rlimsize < freesize && rlimsize < *pages && rlimsize > 0) { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "using getrlim for availpages \n"); - *availpages = rlimsize; - } else if (freesize < *pages && freesize > 0) { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "using freesize for availpages \n"); - *availpages = freesize; - } else { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "using pages for availpages \n"); - *availpages = *pages; - } - + /* Check we have a valid meminfo struct */ + if (mi->system_available_bytes == 0) { + slapi_log_err(SLAPI_LOG_CRIT, "util_is_cachesize_sane", ""); + return UTIL_CACHESIZE_ERROR; } -#endif /* linux */ - - -#if defined ( hpux ) - { - struct pst_static pst; - int rval = pstat_getstatic(&pst, sizeof(pst), (size_t)1, 0); - if (rval < 0) { /* pstat_getstatic failed */ - return 1; - } - *pagesize = pst.page_size; - *pages = pst.physical_memory; - *availpages = util_getvirtualmemsize() / *pagesize; - if (procpages) - { -#define BURST (size_t)32 /* get BURST proc info at one time... */ - struct pst_status psts[BURST]; - int i, count; - int idx = 0; /* index within the context */ - int mypid = getpid(); - - *procpages = 0; - /* loop until count == 0, will occur all have been returned */ - while ((count = pstat_getproc(psts, sizeof(psts[0]), BURST, idx)) > 0) { - /* got count (max of BURST) this time. process them */ - for (i = 0; i < count; i++) { - if (psts[i].pst_pid == mypid) - { - *procpages = (size_t)(psts[i].pst_dsize + psts[i].pst_tsize + psts[i].pst_ssize); - break; - } - } - if (i < count) - break; - - /* - * now go back and do it again, using the next index after - * the current 'burst' - */ - idx = psts[count-1].pst_idx + 1; - } - } - } -#endif - /* If this is a 32-bit build, it might be running on a 64-bit machine, - * in which case, if the box has tons of ram, we can end up telling - * the auto cache code to use more memory than the process can address. - * so we cap the number returned here. - */ -#if defined(__LP64__) || defined (_LP64) -#else - { -#define GIGABYTE (1024*1024*1024) - size_t one_gig_pages = GIGABYTE / *pagesize; - if (*pages > (2 * one_gig_pages) ) { - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", - "More than 2Gbytes physical memory detected. Since this is a 32-bit process, truncating memory size used for auto cache calculations to 2Gbytes\n"); - *pages = (2 * one_gig_pages); - } - } -#endif - - /* This is stupid. If you set %u to %zu to print a size_t, you get literal %zu in your logs - * So do the filthy cast instead. - */ - slapi_log_err(SLAPI_LOG_TRACE,"util_info_sys_pages", "USING pages=%lu, procpages=%lu, availpages=%lu \n", - (unsigned long)*pages, (unsigned long)*procpages, (unsigned long)*availpages); - return 0; - -} - -int util_is_cachesize_sane(size_t *cachesize) -{ - size_t pages = 0; - size_t pagesize = 0; - size_t procpages = 0; - size_t availpages = 0; - - size_t cachepages = 0; - - int issane = 1; - - if (util_info_sys_pages(&pagesize, &pages, &procpages, &availpages) != 0) { - goto out; - } -#ifdef LINUX - /* Linux we calculate availpages correctly, so USE IT */ - if (!pagesize || !availpages) { - goto out; - } -#else - if (!pagesize || !pages) { - goto out; - } -#endif - /* do nothing when we can't get the avail mem */ - - - /* If the requested cache size is larger than the remaining physical memory - * after the current working set size for this process has been subtracted, - * then we say that's insane and try to correct. - */ - - cachepages = *cachesize / pagesize; - slapi_log_err(SLAPI_LOG_TRACE,"util_is_cachesize_sane", "cachesize=%lu / pagesize=%lu \n", - (unsigned long)*cachesize,(unsigned long)pagesize); - -#ifdef LINUX - /* Linux we calculate availpages correctly, so USE IT */ - issane = (int)(cachepages <= availpages); - slapi_log_err(SLAPI_LOG_TRACE,"util_is_cachesize_sane", "cachepages=%lu <= availpages=%lu\n", - (unsigned long)cachepages,(unsigned long)availpages); - - if (!issane) { + slapi_log_err(SLAPI_LOG_TRACE, "util_is_cachesize_sane", "Available bytes %"PRIu64", requested bytes %"PRIu64"\n", mi->system_available_bytes, *cachesize); + if (*cachesize > mi->system_available_bytes) { /* Since we are ask for more than what's available, we give 3/4 of the remaining. * the remaining system mem to the cachesize instead, and log a warning */ - *cachesize = (size_t)((availpages * 0.75 ) * pagesize); - /* These are now trace warnings, because it was to confusing to log this *then* kill the request anyway. - * Instead, we will let the caller worry about the notification, and we'll just use this in debugging and tracing. - */ - slapi_log_err(SLAPI_LOG_TRACE, "util_is_cachesize_sane", - "Available pages %lu, requested pages %lu, pagesize %lu\n", (unsigned long)availpages, (unsigned long)cachepages, (unsigned long)pagesize); - slapi_log_err(SLAPI_LOG_TRACE, "util_is_cachesize_sane", - "WARNING adjusted cachesize to %lu\n", (unsigned long)*cachesize); + *cachesize = (mi->system_available_bytes * 0.75); + slapi_log_err(SLAPI_LOG_TRACE, "util_is_cachesize_sane", "Adjusted cachesize to %"PRIu64"\n", *cachesize); + return UTIL_CACHESIZE_REDUCED; } -#else - size_t freepages = 0; - freepages = pages - procpages; - slapi_log_err(SLAPI_LOG_TRACE,"util_is_cachesize_sane", "pages=%lu - procpages=%lu\n", - (unsigned long)pages,(unsigned long)procpages); - - issane = (int)(cachepages <= freepages); - slapi_log_err(SLAPI_LOG_TRACE,"util_is_cachesize_sane", "cachepages=%lu <= freepages=%lu\n", - (unsigned long)cachepages,(unsigned long)freepages); - - if (!issane) { - *cachesize = (size_t)((pages - procpages) * pagesize); - slapi_log_err(SLAPI_LOG_WARNING, "util_is_cachesize_sane", "WARNING adjusted cachesize to %lu\n", - (unsigned long )*cachesize); - } -#endif -out: - if (!issane) { - slapi_log_err(SLAPI_LOG_TRACE,"util_is_cachesize_sane", "WARNING: Cachesize not sane \n"); - } - - return issane; + return UTIL_CACHESIZE_VALID; } long diff --git a/test/libslapd/spal/meminfo.c b/test/libslapd/spal/meminfo.c new file mode 100644 index 0000000..776141a --- /dev/null +++ b/test/libslapd/spal/meminfo.c @@ -0,0 +1,54 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2017 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +#include "../../test_slapd.h" + +#include +#include + +/* + * Assert that our meminfo interface in slapi_pal works. + */ + +void +test_libslapd_pal_meminfo(void **state __attribute__((unused))) { + slapi_pal_meminfo *mi = spal_meminfo_get(); + assert_true(mi->pagesize_bytes > 0); + assert_true(mi->system_total_pages > 0); + assert_true(mi->system_total_bytes > 0); + assert_true(mi->process_consumed_pages > 0); + assert_true(mi->process_consumed_bytes > 0); + assert_true(mi->system_available_pages > 0); + assert_true(mi->system_available_bytes > 0); + spal_meminfo_destroy(mi); +} + +void +test_libslapd_util_cachesane(void **state __attribute__((unused))) { + slapi_pal_meminfo *mi = spal_meminfo_get(); + uint64_t request = 0; + mi->system_available_bytes = 0; + assert_true(util_is_cachesize_sane(mi, &request) == UTIL_CACHESIZE_ERROR); + + // Set the values to known quantities + request = 50000; + mi->system_available_bytes = 99999; + assert_true(util_is_cachesize_sane(mi, &request) == UTIL_CACHESIZE_VALID); + + request = 99999; + assert_true(util_is_cachesize_sane(mi, &request) == UTIL_CACHESIZE_VALID); + + request = 100000; + assert_true(util_is_cachesize_sane(mi, &request) == UTIL_CACHESIZE_REDUCED); + assert_true(request <= 75000); + + spal_meminfo_destroy(mi); +} + + + diff --git a/test/libslapd/test.c b/test/libslapd/test.c index 6e1171a..6fa7996 100644 --- a/test/libslapd/test.c +++ b/test/libslapd/test.c @@ -26,6 +26,8 @@ run_libslapd_tests (void) { cmocka_unit_test(test_libslapd_operation_v3c_target_spec), cmocka_unit_test(test_libslapd_counters_atomic_usage), cmocka_unit_test(test_libslapd_counters_atomic_overflow), + cmocka_unit_test(test_libslapd_pal_meminfo), + cmocka_unit_test(test_libslapd_util_cachesane), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/test_slapd.h b/test/test_slapd.h index b8f1aba..50de11b 100644 --- a/test/test_slapd.h +++ b/test/test_slapd.h @@ -42,3 +42,8 @@ void test_libslapd_operation_v3c_target_spec(void **state); void test_libslapd_counters_atomic_usage(void **state); void test_libslapd_counters_atomic_overflow(void **state); +/* libslapd-pal-meminfo */ + +void test_libslapd_pal_meminfo(void **state); +void test_libslapd_util_cachesane(void **state); +