diff --git a/Makefile.am b/Makefile.am index 508c7c9c..28de075e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -272,6 +272,7 @@ library_libproc2_la_LDFLAGS = \ library_libproc2_la_SOURCES = \ library/capname.c \ local/capnames.h \ + library/cgmeminfo.c \ library/devname.c \ library/include/devname.h \ library/diskstats.c \ @@ -365,7 +366,8 @@ check_PROGRAMS += \ library/tests/test_uptime \ library/tests/test_sysinfo \ library/tests/test_version \ - library/tests/test_namespace + library/tests/test_namespace \ + library/tests/test_cgmeminfo library_tests_test_Itemtables_SOURCES = library/tests/test_Itemtables.c library_tests_test_Itemtables_LDADD = library/libproc2.la @@ -379,6 +381,8 @@ library_tests_test_version_SOURCES = library/tests/test_version.c library_tests_test_version_LDADD = library/libproc2.la library_tests_test_namespace_SOURCES = library/tests/test_namespace.c library_tests_test_namespace_LDADD = library/libproc2.la +library_tests_test_cgmeminfo_SOURCES = library/tests/test_cgmeminfo.c +library_tests_test_cgmeminfo_LDADD = library/libproc2.la if CYGWIN src_skill_LDADD = $(CYGWINFLAGS) @@ -416,6 +420,7 @@ TESTS = \ library/tests/test_sysinfo \ library/tests/test_version \ library/tests/test_namespace \ + library/tests/test_cgmeminfo \ src/tests/test_fileutils \ src/tests/test_strtod_nol diff --git a/doc/Container-memory-Support.md b/doc/Container-memory-Support.md new file mode 100644 index 00000000..9f6404e4 --- /dev/null +++ b/doc/Container-memory-Support.md @@ -0,0 +1,267 @@ +# Container Memory Support +=========== + +## Overview + +Currently, containers do not have isolated resource views. As a result, commands +such as free, vmstat executed within a container reflect the host's global +memory state rather than the container's actual constrained resources. This +behavior is inherently misleading—especially when memory limits are imposed on +the container, since the output does not represent the resources truly +available to it. + +To resolve this discrepancy, we now extract memory usage data directly from the +container's corresponding cgroup. This enhancement enables free, vmstat to +report container-specific memory information. When run inside a container, these +commands can utilize dedicated parameters to display memory usage based on +cgroup limits, providing an accurate view of the container's resource context +instead of inheriting the host's memory statistics. + +## Features Added + +### 1. Cgroup Version Support +- Supports both cgroup v1 and cgroup v2 +- Automatically detects which version is in use + +### 2. Container Memory Information +- Reads comprehensive memory limits and usage from cgroup instead of + `/proc/meminfo` +- Provides accurate memory information within container limits +- Supports both memory and swap information when available + +### 3. New Command Line Options For `free`, `vmstat` +- `--container` option to container mode + +#### `--container` Option Design + +This implementation adds a `--container` option rather than automatically +switching display modes based on container detection. This design decision is +based on the following considerations: + +- **Resource Representation**: The limits set in cgroup represent restrictions + on container resources, not the actual physical resources available within + the container +- **User Control**: The `--container` option allows users to explicitly choose + which view they need: + - With the option: View container-specific resource limits and usage + - Without the option: Access the global memory state of the host system +- **Operational Flexibility**: This approach enables users to obtain resource + information from either perspective (container or host) as needed +- **Diagnostic Capabilities**: Maintaining access to host memory data is + crucial for comprehensive troubleshooting scenarios + +This flexible design enhances operational diagnostic capabilities compared to an +automatic detection approach that would force container-only views when in +container environments. + +## Files Added/Modified + +### New Files: +- `library/cgmeminfo.c` - Implementation of container memory detection and reading + +### Modified Files: +- `src/free.c` - Enhanced to support container memory display +- `src/vmstat.c` - Enhanced to use container memory information when available + +## Usage Examples + +### free Container Support +When executed within a container, `free --container` detects the container's +memory cgroup path and displays its memory usage and limits: + +```bash +## Start container with memory limits: 256M memory + 256M swap +$ docker run -m 256M --memory-swap 512M -it ubuntu + +## Display container memory information +$ free --container -h + total used free shared buff/cache available +Mem: 256Mi 3.0Mi 252Mi 0B 405Ki 252Mi +Swap: 256Mi 0B 256Mi + +## Display host memory information +$ free -h + total used free shared buff/cache available +Mem: 62Gi 16Gi 41Gi 1.3Gi 6.1Gi 45Gi +Swap: 15Gi 84Mi 15Gi +``` + + +### vmstat Container Support +The `vmstat` command automatically detects container environments and uses +cgroup memory information: + +```bash +## Start container with memory limits: 256M memory + 256M swap +$ docker run -m 256M --memory-swap 512M -it ubuntu + +## Display container memory information +$ vmstat -s M --container +procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- + r b swpd free buff cache si so bi bo in cs us sy id wa st gu + 2 0 0 257 0 5 0 0 376 330 12515 5 1 1 98 0 0 0 + +## Display host memory information +$ vmstat -s M +procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- + r b swpd free buff cache si so bi bo in cs us sy id wa st gu + 3 1 92 45621 28 2298 0 0 376 330 12515 5 1 1 98 0 0 0 +``` + +When running in a container, vmstat shows memory statistics based on container +limits rather than host system memory. The memory columns (free, buff, cache) +reflect the container's cgroup memory constraints. +`` + +## Technical Details + +### Container Cgroup Path Logic +The implementation uses a sophisticated multi-step approach to detect and +validate container environments: + +1. **Cgroup Path Analysis**: Examines `/proc/self/cgroup` to identify: + - cgroup v2 entries (format: `0::/path`) + - cgroup v1 memory controller entries (format: `x:memory:/path`) + +2. **Cgroup Hierarchy Traversal**: For each detected path: + - Attempts to find the current process in cgroup tasks/cgroup.procs files + - Traverses up the cgroup hierarchy if not found in the initial path + - Validates process membership in the cgroup + +3. **Mount Point Detection**: Automatically locates cgroup mount points: + - cgroup v2: Searches for `cgroup2` filesystem type + - cgroup v1: Searches for `cgroup` filesystem with `memory` option + + +### Cgroup Memory Information Retrieval + +The implementation provides comprehensive memory information by reading various +cgroup files: + +#### Memory Limits and Usage +- **Memory Limit**: Maximum memory that can be used by the container + - cgroup v1: `memory.limit_in_bytes` + - cgroup v2: `memory.max` (returns "max" for unlimited) +- **Memory Current**: Current memory usage + - cgroup v1: `memory.usage_in_bytes` + - cgroup v2: `memory.current` + +#### Swap Information +- **Swap Limit**: Maximum swap space available + - cgroup v1: `memory.memsw.limit_in_bytes` (includes memory + swap) + - cgroup v2: `memory.swap.max` +- **Swap Current**: Current swap usage + - cgroup v1: `memory.memsw.usage_in_bytes` (includes memory + swap) + - cgroup v2: `memory.swap.current` + +#### Detailed Memory Statistics (`memory.stat`) +The implementation parses comprehensive memory statistics including: + +**File and Cache Memory:** +- `total_cache` / `file`: File cache memory +- `total_mapped_file` / `file_mapped`: Memory-mapped files +- `total_dirty` / `file_dirty`: Dirty file pages +- `total_writeback` / `file_writeback`: Pages being written back + +**Anonymous Memory:** +- `total_active_anon` / `active_anon`: Active anonymous pages +- `total_inactive_anon` / `inactive_anon`: Inactive anonymous pages +- `total_rss_huge`: Huge page RSS (cgroup v1 only) + +**File Memory:** +- `total_active_file` / `active_file`: Active file pages +- `total_inactive_file` / `inactive_file`: Inactive file pages + +**Shared and Special Memory:** +- `total_shmem` / `shmem`: Shared memory +- `total_unevictable` / `unevictable`: Unevictable pages + +**Kernel Memory (cgroup v2 only):** +- `slab_reclaimable`: Reclaimable slab memory +- `slab_unreclaimable`: Unreclaimable slab memory +- `slab`: Total slab memory + +#### Memory Statistics Mapping +vmstat maps container memory information to its standard output format: + +- **swpd**: Swap used (from cgroup swap.current) +- **free**: Free memory (calculated from container memory limit - current usage) +- **buff**: Buffer memory (typically 0 in containers, mapped from cgroup cache statistics) +- **cache**: Cache memory (from cgroup file cache statistics) + +#### Data Conversion and Processing +- All memory values are converted from bytes to KB for consistency with + `/proc/meminfo` +- For cgroup v1, swap values are calculated by subtracting memory usage from + memsw values +- Memory statistics are mapped to standard `/proc/meminfo` format for + compatibility +- Host memory limits are used as fallback when container limits exceed host + capacity + +## Programming Examples + +### Basic Usage +```c +#include "meminfo.h" + +int main() { + struct meminfo_info *info = NULL; + struct meminfo_result *result; + + // Initialize container memory info + if (is_container != 0) { + rc = cgroup_meminfo_new(&mem_info); + } else { + rc = procps_meminfo_new(&mem_info); + } + + // Get memory total + result = procps_meminfo_get(info, MEMINFO_MEM_TOTAL); + if (result) { + printf("Memory Total: %lu KB\n", result->result.ul_int); + } + + // Get memory usage + result = procps_meminfo_get(info, MEMINFO_MEM_USED); + if (result) { + printf("Memory Used: %lu KB\n", result->result.ul_int); + } + + // Get swap information + result = procps_meminfo_get(info, MEMINFO_SWAP_TOTAL); + if (result) { + printf("Swap Total: %lu KB\n", result->result.ul_int); + } + + result = procps_meminfo_get(info, MEMINFO_SWAP_USED); + if (result) { + printf("Swap Used: %lu KB\n", result->result.ul_int); + } + + // Clean up + procps_meminfo_unref(&info); + return 0; +} +``` + +## Error Handling and Diagnostics + +### Common Error Scenarios +1. **Container Detection Failure**: When running outside a container or in + unsupported environments +2. **Permission Issues**: Insufficient permissions to read cgroup files +3. **Missing Cgroup Files**: Cgroup controllers not enabled or files not + available +4. **Invalid Cgroup Paths**: Malformed or inaccessible cgroup paths + + +## Benefits + +### For Both free and vmstat Commands + +1. **Accurate Container Memory Reporting**: Shows actual container limits + instead of host memory +2. **Both Cgroup Versions**: Supports both cgroup v1 and v2 +3. **Comprehensive Statistics**: Provides detailed memory breakdown including + cache, buffers, and swap diff --git a/library/cgmeminfo.c b/library/cgmeminfo.c new file mode 100644 index 00000000..82502989 --- /dev/null +++ b/library/cgmeminfo.c @@ -0,0 +1,820 @@ +/* + * cgmeminfo.c - cgroup memory information functions + * + * Copyright © 2025 Chunsheng Luo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/meminfo.h" + + +/* Conversion constants */ +#define BYTES_TO_KB 1024 +#define CGMEMINFO_LEN 8192 + +/* Cgroup version types */ +#define CGROUP_TYPE_UNKNOWN (0) +#define CGROUP_TYPE_LEGACY (1 << 0) +#define CGROUP_TYPE_UNIFIED (1 << 1) +#define CGROUP_TYPE_HYBRID (CGROUP_TYPE_LEGACY | CGROUP_TYPE_UNIFIED) + +struct memory_stat { + unsigned long total_cache; + unsigned long total_rss; /* not used now */ + unsigned long total_rss_huge; + unsigned long total_shmem; + unsigned long total_mapped_file; + unsigned long total_dirty; + unsigned long total_writeback; + unsigned long total_inactive_anon; + unsigned long total_active_anon; + unsigned long total_inactive_file; + unsigned long total_active_file; + unsigned long total_unevictable; + unsigned long slab_reclaimable; + unsigned long slab_unreclaimable; + unsigned long slab; +}; + +struct memcg_data { + unsigned long memory_limit; /* v1: memory.limit_in_bytes, v2: memory.max */ + unsigned long memory_current; /* v1: memory.usage_in_bytes, v2: memory.current */ + unsigned long swap_limit; /* v1: memory.memsw.limit_in_bytes, v2: memory.swap.max */ + unsigned long swap_current; /* v1: memory.memsw.usage_in_bytes, v2: memory.swap.current */ + struct memory_stat memory_stat; /* v1: memory.stat, v2: memory.stat */ +}; + +struct memcg_meminfo { + int version; + int refcount; + char *cgmem_mount; + char *cgmem_path; + struct memcg_data cgmem_data; +}; + +/** + * Clean up memcg_meminfo structure resources + * @info: memcg_meminfo structure to clean up + */ +static void cleanup_memcg_info(struct memcg_meminfo *info) { + if (!info) return; + + if (info->cgmem_mount) { + free(info->cgmem_mount); + info->cgmem_mount = NULL; + } + + if (info->cgmem_path) { + free(info->cgmem_path); + info->cgmem_path = NULL; + } +} + +/** + * Read all content from a file into buffer + * @path: file path to read from + * @buf: buffer to store the content + * @buf_size: size of the buffer + * + * Returns: 0 on success, other on error + */ +static int read_from_file(const char *path, char *buf, size_t buf_size) { + FILE *fp = NULL; + size_t bytes_read; + int ret; + + if (!path || !buf || buf_size == 0) { + return EINVAL; + } + + fp = fopen(path, "r"); + if (!fp) { + return errno; + } + + memset(buf, 0, buf_size); + + bytes_read = fread(buf, 1, buf_size - 1, fp); + if (ferror(fp)) { + ret = errno ? errno : EIO; + goto cleanup; + } + + buf[bytes_read] = '\0'; + ret = 0; + +cleanup: + if (fp) + fclose(fp); + + return ret; +} + +static char *cgroup_mount(int version) { + FILE *fp; + char *ret = NULL; + struct mntent *mnt; + + if (!(fp = setmntent("/proc/mounts", "r"))) { + return NULL; + } + + while ((mnt = getmntent(fp)) != NULL) { + if (version == CGROUP_TYPE_UNIFIED + && strcmp(mnt->mnt_type, "cgroup2") == 0) { + if (!(ret = strdup(mnt->mnt_dir))) + break; + break; + } else if (version == CGROUP_TYPE_LEGACY + && strcmp(mnt->mnt_type, "cgroup") == 0 + && strstr(mnt->mnt_opts, "memory") != NULL) { + if (!(ret = strdup(mnt->mnt_dir))) + break; + break; + } + } + + endmntent(fp); + return ret; +} + +enum memcg_metric_type { + MEMCG_MEMORY_LIMIT = 0, + MEMCG_MEMORY_CURRENT = 1, + MEMCG_SWAP_LIMIT = 2, + MEMCG_SWAP_CURRENT = 3, + MEMCG_MEMORY_STAT = 4 +}; + +struct memcg_file_mapping { + const char *v1_file; + const char *v2_file; +}; + +/* Define file mappings for different memory metrics */ +static const struct memcg_file_mapping file_mappings[] = { + [MEMCG_MEMORY_LIMIT] = { "memory.limit_in_bytes", "memory.max" }, + [MEMCG_MEMORY_CURRENT] = { "memory.usage_in_bytes", "memory.current" }, + [MEMCG_SWAP_LIMIT] = { "memory.memsw.limit_in_bytes", "memory.swap.max" }, + [MEMCG_SWAP_CURRENT] = { "memory.memsw.usage_in_bytes", "memory.swap.current" }, + [MEMCG_MEMORY_STAT] = { "memory.stat", "memory.stat" } +}; + +static int memcg_parse_memory_stat(char *buf, struct memory_stat *stat, int cgroup_version) { + /* Initialize all fields to 0 */ + memset(stat, 0, sizeof(struct memory_stat)); + +#define sTv(f) &stat->f + /* Define mapping table for memory statistics */ + struct { + const char *v1_key; + const char *v2_key; + unsigned long *field; + } stat_mappings[] = { + { "total_cache", "file", sTv(total_cache) }, + { "total_rss_huge", NULL, sTv(total_rss_huge) }, /* v1 only */ + { "total_shmem", "shmem", sTv(total_shmem) }, + { "total_mapped_file", "file_mapped", sTv(total_mapped_file) }, + { "total_dirty", "file_dirty", sTv(total_dirty) }, + { "total_writeback", "file_writeback", sTv(total_writeback) }, + { "total_inactive_anon", "inactive_anon", sTv(total_inactive_anon) }, + { "total_active_anon", "active_anon", sTv(total_active_anon) }, + { "total_inactive_file", "inactive_file", sTv(total_inactive_file) }, + { "total_active_file", "active_file", sTv(total_active_file) }, + { "total_unevictable", "unevictable", sTv(total_unevictable) }, + { NULL, "slab_reclaimable", sTv(slab_reclaimable) }, /* v2 only */ + { NULL, "slab_unreclaimable", sTv(slab_unreclaimable) }, /* v2 only */ + { NULL, "slab", sTv(slab) }, /* v2 only */ + { NULL, NULL, NULL } /* End marker */ + }; + + char *line = buf; + char *next_line; + + while (line && *line) { + next_line = strchr(line, '\n'); + if (next_line) { + *next_line = '\0'; + next_line++; + } + + char stat_key[64]; + unsigned long stat_value; + if (sscanf(line, "%63s %lu", stat_key, &stat_value) == 2) { + for (int i = 0; stat_mappings[i].field != NULL; i++) { + const char *key = NULL; + + if (cgroup_version & CGROUP_TYPE_LEGACY) + key = stat_mappings[i].v1_key; + else + key = stat_mappings[i].v2_key; + + if (key && strcmp(stat_key, key) == 0) { + *(stat_mappings[i].field) = stat_value; + break; + } + } + } + + if (next_line && next_line > line) { + *(next_line - 1) = '\n'; + } + + line = next_line; + } + +#undef sTv + return 0; +} + +/** + * Common helper function to build cgroup metric file path + * @info: memcg_meminfo structure + * @metric_type: type of metric to read + * @path_buffer: buffer to store the constructed path + * @buffer_size: size of the path buffer + * + * Returns: 0 on success, other on error + */ +static inline int memcg_build_file_path(struct memcg_meminfo *info, + enum memcg_metric_type metric_type, + char *path_buffer, + size_t buffer_size) { + if (metric_type >= sizeof(file_mappings)/sizeof(file_mappings[0])) + return 1; + + const char *filename = (info->version & CGROUP_TYPE_LEGACY) + ? file_mappings[metric_type].v1_file + : file_mappings[metric_type].v2_file; + + if (!filename) + return 1; + + int ret = snprintf(path_buffer, buffer_size, "%s%s/%s", + info->cgmem_mount, info->cgmem_path, filename); + if (ret < 0 || ret >= (int)buffer_size) + return 1; + + return 0; +} + +/** + * Generic function to read memory metric from cgroup file + * @info: memcg_meminfo structure + * @metric_type: type of metric to read + * + * Returns: 0 on success, other on error + */ +static int memcg_read_metric(struct memcg_meminfo *info, + enum memcg_metric_type metric_type) { + char path[PATH_MAX]; + char *buf = NULL; + int ret; + unsigned long result = 0; + + if (memcg_build_file_path(info, metric_type, path, sizeof(path)) != 0) + return 1; + + buf = (char *)malloc(CGMEMINFO_LEN); + if (!buf) + return ENOMEM; + + ret = read_from_file(path, buf, CGMEMINFO_LEN); + if (ret != 0) { + goto out; + } + + /* For memory.stat, contents are key-value, so parsing is handled separately */ + if (metric_type != MEMCG_MEMORY_STAT) { + /* cgroup v2 memory.max can return "max", so we need to handle that. */ + if (sscanf(buf, "%lu", &result) != 1) { + result = ULLONG_MAX; + } + } + + ret = EINVAL; + switch (metric_type) { + case MEMCG_MEMORY_LIMIT: + info->cgmem_data.memory_limit = result; + break; + case MEMCG_MEMORY_CURRENT: + info->cgmem_data.memory_current = result; + break; + case MEMCG_SWAP_LIMIT: + if (info->version & CGROUP_TYPE_LEGACY) { + if (info->cgmem_data.memory_limit > result) { + result = 0; + } else { + result -= info->cgmem_data.memory_limit; + } + } + info->cgmem_data.swap_limit = result; + break; + case MEMCG_SWAP_CURRENT: + if (info->version & CGROUP_TYPE_LEGACY) { + if (info->cgmem_data.memory_current > result + || info->cgmem_data.swap_limit == 0) { + result = 0; + } else { + result -= info->cgmem_data.memory_current; + } + } + info->cgmem_data.swap_current = result; + break; + case MEMCG_MEMORY_STAT: + memcg_parse_memory_stat(buf, &info->cgmem_data.memory_stat, info->version); + break; + default: + goto out; + } + ret = 0; + +out: + free(buf); + return ret; +} + +static int memcg_get_memory_limit(struct memcg_meminfo *info) { + return memcg_read_metric(info, MEMCG_MEMORY_LIMIT); +} + +static int memcg_get_memory_current(struct memcg_meminfo *info) { + return memcg_read_metric(info, MEMCG_MEMORY_CURRENT); +} + +static int memcg_get_swap_limit(struct memcg_meminfo *info) { + return memcg_read_metric(info, MEMCG_SWAP_LIMIT); +} + +static int memcg_get_swap_current(struct memcg_meminfo *info) { + return memcg_read_metric(info, MEMCG_SWAP_CURRENT); +} + +static int memcg_get_memory_stat(struct memcg_meminfo *info) { + return memcg_read_metric(info, MEMCG_MEMORY_STAT); +} + +/* + * Check if current process exists in cgroup tasks + * @cgroup_version: cgroup version flags (CGROUP_TYPE_LEGACY or CGROUP_TYPE_UNIFIED) + * @cgroup_mount: mount point of the cgroup + * @cgroup_path: path to the cgroup + * + * Returns: true if current process exists in cgroup tasks, false otherwise + */ +static bool memcg_process_in_cgroup_tasks(int cgroup_type, + const char* cgmount, const char *path) { + pid_t current_pid = getpid(); + char tasks_file[PATH_MAX]; + FILE *fp; + pid_t task_pid; + bool found = false; + char *line = NULL; + size_t line_len = 0; + ssize_t rlen; + + if (cgroup_type & CGROUP_TYPE_UNIFIED) { + snprintf(tasks_file, sizeof(tasks_file), "%s%s/cgroup.procs", cgmount, path); + } else if (cgroup_type & CGROUP_TYPE_LEGACY) { + snprintf(tasks_file, sizeof(tasks_file), "%s%s/tasks", cgmount, path); + } + + fp = fopen(tasks_file, "r"); + if (!fp) + return false; + + /* Search for current process ID in the file */ + while ((rlen = getline(&line, &line_len, fp)) != -1) { + if (sscanf(line, "%d", &task_pid) == 1) { + if (task_pid == current_pid) { + found = true; + break; + } + } + } + free(line); + fclose(fp); + + return found; +} + +/** + * Remove the first layer of a path (e.g., "/a/b/c" -> "/b/c") + * @path: Input path to modify (non-NULL, and not a string constant) + + * Note: The input path string will be modified in-place. + * For root path ("/"), returns NULL as no layer can be removed. + * + * Returns: Pointer to the modified path, or NULL if path is "/" + */ +static char *remove_path_layer(char *path) { + if (strcmp(path, "/") == 0) { + return NULL; + } + + char *first_slash = strchr(path + 1, '/'); + if (first_slash) { + memmove(path, first_slash, strlen(first_slash) + 1); + } else { + strcpy(path, "/"); + } + return path; +} + +/* + * Traverse cgroup path and find real memory cgroup path + * @path: path to the cgroup directory + * @cgroup_type: cgroup version flags + * @info: pointer to memcg_meminfo structure to populate + * @found: pointer to bool to indicate if cgroup was found + * + * Returns: 0 on success, >0 on error + */ +static int traverse_cgroup_path(const char *path, int cgroup_type, + struct memcg_meminfo *info, bool *found) { + char *current_path = NULL; + char *cgmount = NULL; + int ret = 0; + + *found = false; + + if (!(current_path = strdup(path))) + return ENOMEM; + + cgmount = cgroup_mount(cgroup_type); + if (!cgmount) { + free(current_path); + return ENOENT; + } + + while (current_path && strlen(current_path) > 0) { + if (memcg_process_in_cgroup_tasks(cgroup_type, cgmount, current_path)) { + info->version = cgroup_type; + if (!(info->cgmem_path = strdup(current_path))) { + ret = ENOMEM; + goto out; + } + info->cgmem_mount = cgmount; + cgmount = NULL; + *found = true; + break; + } + current_path = remove_path_layer(current_path); + } + +out: + free(current_path); + free(cgmount); + + return ret; +} + +static int parse_thread_cgroup_paths(char **v1_path, char **v2_path) { + FILE *fp; + char *line = NULL; + size_t line_len = 0; + ssize_t rlen; + int ret = 0; + + /* Check cgroup version by reading /proc/self/cgroup */ + fp = fopen("/proc/self/cgroup", "r"); + if (!fp) { + return errno; + } + + while ((rlen = getline(&line, &line_len, fp)) != -1) { + char *newline = strchr(line, '\n'); + if (newline) *newline = '\0'; + + if (strncmp(line, "0::", 3) == 0) { + /* cgroup v2 format: "0::/user.slice/user-0.slice" */ + if (!(*v2_path = strdup(line + 3))) { + ret = ENOMEM; + goto cleanup; + } + } else { + /* cgroup v1 format: "13:memory:/user.slice/user-0.slice/" */ + char *first_colon = strchr(line, ':'); + if (first_colon) { + char *second_colon = strchr(first_colon + 1, ':'); + if (second_colon) { + *second_colon = '\0'; + /* Check if subsystems contain "memory" */ + if (strstr(first_colon + 1, "memory")) { + char *path_start = second_colon + 1; + char *newline = strchr(path_start, '\n'); + if (newline) { + *newline = '\0'; + } + if (!(*v1_path = strdup(path_start))) { + ret = ENOMEM; + goto cleanup; + } + } + + *second_colon = ':'; + } + } + } + } + +cleanup: + if (line) + free(line); + + if (fp) + fclose(fp); + + return ret; +} + +static int memcg_get_memory_info(struct memcg_meminfo *info) { + char *v1_path = NULL; + char *v2_path = NULL; + bool found = false; + int ret = ENOENT; + + ret = parse_thread_cgroup_paths(&v1_path, &v2_path); + if (ret != 0) { + goto cleanup; + } + + if (v1_path) + traverse_cgroup_path(v1_path, CGROUP_TYPE_LEGACY, info, &found); + + if (!found && v2_path) + traverse_cgroup_path(v2_path, CGROUP_TYPE_UNIFIED, info, &found); + + if (!found) { + ret = ENOENT; + goto cleanup; + } + + /* Get all memory information - fail fast on any error */ + ret = memcg_get_memory_limit(info); + if (ret != 0) + goto cleanup; + + ret = memcg_get_memory_current(info); + if (ret != 0) + goto cleanup; + + ret = memcg_get_swap_limit(info); + if (ret != 0) + goto cleanup; + + ret = memcg_get_swap_current(info); + if (ret != 0) + goto cleanup; + + ret = memcg_get_memory_stat(info); + if (ret != 0) + goto cleanup; + + ret = 0; + +cleanup: + free(v1_path); + free(v2_path); + + return ret; +} + +static int memcg_meminfo_unref(struct memcg_meminfo **info) { + if (info == NULL || *info == NULL) + return EINVAL; + + (*info)->refcount--; + + if ((*info)->refcount < 1) { + cleanup_memcg_info(*info); + free(*info); + *info = NULL; + return 0; + } + + return (*info)->refcount; +} + +#define STRLITERALLEN(x) (sizeof(""x"") - 1) +static inline bool startswith(const char *line, const char *pref) +{ + return strncmp(line, pref, strlen(pref)) == 0; +} + +int cgroup_meminfo_read_buf(struct meminfo_info *info, char *buf, int len) { + struct memcg_meminfo *cginfo = NULL; + struct memcg_data data; + struct memory_stat mstat; + int total_len = 0; + unsigned long memlimit = 0, memusage = 0, swfree = 0, swusage = 0, swtotal = 0; + FILE *meminfo_file = NULL; + int ret; + char *line = NULL; + size_t line_len = 0; + ssize_t rlen; + + if (!buf || len <= 0) { + errno = EINVAL; + return EINVAL; + } + + cginfo = calloc(1, sizeof(struct memcg_meminfo)); + if (!cginfo) { + return ENOMEM; + } + + ret = memcg_get_memory_info(cginfo); + if (ret != 0) { + errno = ret; /* Set errno to the actual error code */ + goto cleanup; + } + + data = cginfo->cgmem_data; + mstat = data.memory_stat; + + meminfo_file = fopen("/proc/meminfo", "r"); + if (!meminfo_file) { + ret = errno; + goto cleanup; + } + + memusage = data.memory_current / BYTES_TO_KB; + memlimit = data.memory_limit / BYTES_TO_KB; + swtotal = data.swap_limit / BYTES_TO_KB; + swusage = data.swap_current / BYTES_TO_KB; + swfree = swtotal - swusage; + + while ((rlen = getline(&line, &line_len, meminfo_file)) != -1) { + ssize_t l; + char *printme, lbuf[100]; + memset(lbuf, 0, 100); + + if (startswith(line, "MemTotal:")) { + unsigned long hosttotal = 0; + sscanf(line+sizeof("MemTotal:")-1, "%" PRIu64, &hosttotal); + if (memlimit == 0) + memlimit = hosttotal; + + if (hosttotal < memlimit) + memlimit = hosttotal; + snprintf(lbuf, 100, "MemTotal: %8" PRIu64 " kB\n", memlimit); + printme = lbuf; + } else if (startswith(line, "MemFree:")) { + snprintf(lbuf, 100, "MemFree: %8" PRIu64 " kB\n", memlimit - memusage); + printme = lbuf; + } else if (startswith(line, "MemAvailable:")) { + snprintf(lbuf, 100, "MemAvailable: %8" PRIu64 " kB\n", memlimit - memusage + + (mstat.total_active_file + mstat.total_inactive_file + mstat.slab_reclaimable) / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "SwapTotal:")) { + unsigned long hostswtotal = 0; + sscanf(line + STRLITERALLEN("SwapTotal:"), "%" PRIu64, &hostswtotal); + if (hostswtotal < swtotal) { + swtotal = hostswtotal; + } + + snprintf(lbuf, 100, "SwapTotal: %8" PRIu64 " kB\n", swtotal); + printme = lbuf; + } else if (startswith(line, "SwapFree:")) { + swfree = swtotal - swusage; + snprintf(lbuf, 100, "SwapFree: %8" PRIu64 " kB\n", swfree); + printme = lbuf; + } else if (startswith(line, "Slab:")) { + snprintf(lbuf, 100, "Slab: %8" PRIu64 " kB\n", mstat.slab / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Buffers:")) { + snprintf(lbuf, 100, "Buffers: %8" PRIu64 " kB\n", (uint64_t)0); + printme = lbuf; + } else if (startswith(line, "Cached:")) { + snprintf(lbuf, 100, "Cached: %8" PRIu64 " kB\n", + mstat.total_cache / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "SwapCached:")) { + snprintf(lbuf, 100, "SwapCached: %8" PRIu64 " kB\n", (uint64_t)0); + printme = lbuf; + } else if (startswith(line, "Active:")) { + snprintf(lbuf, 100, "Active: %8" PRIu64 " kB\n", + (mstat.total_active_anon + + mstat.total_active_file) / + BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Inactive:")) { + snprintf(lbuf, 100, "Inactive: %8" PRIu64 " kB\n", + (mstat.total_inactive_anon + + mstat.total_inactive_file) / + BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Active(anon):")) { + snprintf(lbuf, 100, "Active(anon): %8" PRIu64 " kB\n", + mstat.total_active_anon / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Inactive(anon):")) { + snprintf(lbuf, 100, "Inactive(anon): %8" PRIu64 " kB\n", + mstat.total_inactive_anon / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Active(file):")) { + snprintf(lbuf, 100, "Active(file): %8" PRIu64 " kB\n", + mstat.total_active_file / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Inactive(file):")) { + snprintf(lbuf, 100, "Inactive(file): %8" PRIu64 " kB\n", + mstat.total_inactive_file / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Unevictable:")) { + snprintf(lbuf, 100, "Unevictable: %8" PRIu64 " kB\n", + mstat.total_unevictable / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Dirty:")) { + snprintf(lbuf, 100, "Dirty: %8" PRIu64 " kB\n", + mstat.total_dirty / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Writeback:")) { + snprintf(lbuf, 100, "Writeback: %8" PRIu64 " kB\n", + mstat.total_writeback / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "AnonPages:")) { + snprintf(lbuf, 100, "AnonPages: %8" PRIu64 " kB\n", + (mstat.total_active_anon + + mstat.total_inactive_anon - mstat.total_shmem) / + BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Mapped:")) { + snprintf(lbuf, 100, "Mapped: %8" PRIu64 " kB\n", + mstat.total_mapped_file / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "SReclaimable:")) { + snprintf(lbuf, 100, "SReclaimable: %8" PRIu64 " kB\n", mstat.slab_reclaimable / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "SUnreclaim:")) { + snprintf(lbuf, 100, "SUnreclaim: %8" PRIu64 " kB\n", mstat.slab_unreclaimable / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "Shmem:")) { + snprintf(lbuf, 100, "Shmem: %8" PRIu64 " kB\n", + mstat.total_shmem / BYTES_TO_KB); + printme = lbuf; + } else if (startswith(line, "ShmemHugePages:")) { + snprintf(lbuf, 100, "ShmemHugePages: %8" PRIu64 " kB\n", (uint64_t)0); + printme = lbuf; + } else if (startswith(line, "ShmemPmdMapped:")) { + snprintf(lbuf, 100, "ShmemPmdMapped: %8" PRIu64 " kB\n", (uint64_t)0); + printme = lbuf; + } else if (startswith(line, "AnonHugePages:")) { + snprintf(lbuf, 100, "AnonHugePages: %8" PRIu64 " kB\n", + mstat.total_rss_huge / BYTES_TO_KB); + printme = lbuf; + } else { + printme = line; + } + + l = snprintf(buf + total_len, len - total_len, "%s", printme); + if (l < 0 || l >= (len - total_len)) { + ret = EOVERFLOW; + errno = ret; /* Set errno to the actual error code */ + goto cleanup; + } + total_len += l; + } + + buf[total_len] = '\0'; + ret = 0; + +cleanup: + free(line); + + if (meminfo_file) + fclose(meminfo_file); + + if (cginfo) + memcg_meminfo_unref(&cginfo); + + return ret; +} \ No newline at end of file diff --git a/library/include/meminfo.h b/library/include/meminfo.h index 8c115f5e..dbef6312 100644 --- a/library/include/meminfo.h +++ b/library/include/meminfo.h @@ -204,6 +204,7 @@ struct meminfo_info; int procps_meminfo_new (struct meminfo_info **info); int procps_meminfo_ref (struct meminfo_info *info); int procps_meminfo_unref (struct meminfo_info **info); +int cgroup_meminfo_new (struct meminfo_info **info); struct meminfo_result *procps_meminfo_get ( struct meminfo_info *info, diff --git a/library/libproc2.sym b/library/libproc2.sym index 1998b1ae..62473e1a 100644 --- a/library/libproc2.sym +++ b/library/libproc2.sym @@ -76,3 +76,7 @@ LIBPROC_2.2 { procps_sigmask_names; procps_capmask_names; } LIBPROC_2.1; + +LIBPROC_2.3 { + cgroup_meminfo_new; +} LIBPROC_2.2; diff --git a/library/meminfo.c b/library/meminfo.c index 59d0e0b7..6721974c 100644 --- a/library/meminfo.c +++ b/library/meminfo.c @@ -130,6 +130,11 @@ struct stacks_extent { struct meminfo_stack **stacks; }; +enum meminfo_source_type { + PROC_MEMINFO_TYPE = 0, + CGROUP_MEMINFO_TYPE = 1, +}; + struct meminfo_info { int refcount; int meminfo_fd; @@ -140,6 +145,7 @@ struct meminfo_info { struct hsearch_data hashtab; struct meminfo_result get_this; time_t sav_secs; + enum meminfo_source_type stype; }; @@ -648,29 +654,10 @@ static int meminfo_make_hash_failed ( #undef htXTRA } // end: meminfo_make_hash_failed +extern int cgroup_meminfo_read_buf(struct meminfo_info *info, char *buf, int len); -/* - * meminfo_read_failed(): - * - * Read the data out of /proc/meminfo putting the information - * into the supplied info structure - */ -static int meminfo_read_failed ( - struct meminfo_info *info) -{ - /* a 'memory history reference' macro for readability, - so we can focus the field names ... */ - #define mHr(f) info->hist.new. f - char buf[MEMINFO_BUFF]; - char *head, *tail; +static int procps_meminfo_read_buf(struct meminfo_info *info,char *buf, int len) { int size; - unsigned long *valptr; - signed long mem_used; - - // remember history from last time around - memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data)); - // clear out the soon to be 'current' values - memset(&info->hist.new, 0, sizeof(struct meminfo_data)); if (-1 == info->meminfo_fd && (-1 == (info->meminfo_fd = open(MEMINFO_FILE, O_RDONLY)))) @@ -688,7 +675,7 @@ static int meminfo_read_failed ( } for (;;) { - if ((size = read(info->meminfo_fd, buf, sizeof(buf)-1)) < 0) { + if ((size = read(info->meminfo_fd, buf, len-1)) < 0) { if (errno == EINTR || errno == EAGAIN) continue; return 1; @@ -700,6 +687,44 @@ static int meminfo_read_failed ( return 1; } buf[size] = '\0'; + return 0; +} + +/* + * meminfo_read_failed(): + * + * Read the data out of /proc/meminfo putting the information + * into the supplied info structure + */ +static int meminfo_read_failed ( + struct meminfo_info *info) +{ + /* a 'memory history reference' macro for readability, + so we can focus the field names ... */ + #define mHr(f) info->hist.new. f + char buf[MEMINFO_BUFF]; + char *head, *tail; + int ret = -1; + enum meminfo_source_type stype = info->stype; + unsigned long *valptr; + signed long mem_used; + + if (stype != PROC_MEMINFO_TYPE && stype != CGROUP_MEMINFO_TYPE) + return ret; + + // remember history from last time around + memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data)); + // clear out the soon to be 'current' values + memset(&info->hist.new, 0, sizeof(struct meminfo_data)); + + if (stype == PROC_MEMINFO_TYPE) { + ret = procps_meminfo_read_buf(info, buf, sizeof(buf)); + } else if (stype == CGROUP_MEMINFO_TYPE) { + ret = cgroup_meminfo_read_buf(info, buf, sizeof(buf)); + } + + if (ret != 0) + return ret; head = buf; @@ -811,24 +836,8 @@ static struct stacks_extent *meminfo_stacks_alloc ( return p_blob; } // end: meminfo_stacks_alloc - -// ___ Public Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||| - -// --- standard required functions -------------------------------------------- - -/* - * procps_meminfo_new: - * - * Create a new container to hold the stat information - * - * The initial refcount is 1, and needs to be decremented - * to release the resources of the structure. - * - * Returns: < 0 on failure, 0 on success along with - * a pointer to a new context struct - */ -PROCPS_EXPORT int procps_meminfo_new ( - struct meminfo_info **info) +static int meminfo_new_internal (struct meminfo_info **info, + enum meminfo_source_type type) { struct meminfo_info *p; @@ -857,6 +866,7 @@ PROCPS_EXPORT int procps_meminfo_new ( return -errno; } + p->stype = type; /* do a priming read here for the following potential benefits: | 1) ensure there will be no problems with subsequent access | 2) make delta results potentially useful, even if 1st time | @@ -870,6 +880,30 @@ PROCPS_EXPORT int procps_meminfo_new ( return 0; } // end: procps_meminfo_new +// ___ Public Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||| + +// --- standard required functions -------------------------------------------- + +PROCPS_EXPORT int cgroup_meminfo_new ( + struct meminfo_info **info){ + return meminfo_new_internal(info, CGROUP_MEMINFO_TYPE); +} + +/* + * procps_meminfo_new: + * + * Create a new container to hold the stat information + * + * The initial refcount is 1, and needs to be decremented + * to release the resources of the structure. + * + * Returns: < 0 on failure, 0 on success along with + * a pointer to a new context struct + */ +PROCPS_EXPORT int procps_meminfo_new ( + struct meminfo_info **info){ + return meminfo_new_internal(info, PROC_MEMINFO_TYPE); +} PROCPS_EXPORT int procps_meminfo_ref ( struct meminfo_info *info) diff --git a/library/tests/test_cgmeminfo.c b/library/tests/test_cgmeminfo.c new file mode 100644 index 00000000..ce1a4ca3 --- /dev/null +++ b/library/tests/test_cgmeminfo.c @@ -0,0 +1,265 @@ +/* + * libproc2 - Library to read proc filesystem + * Tests for cgmeminfo library calls + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include + +#include "tests.h" +#include "meminfo.h" + +/* Include the cgmeminfo.c source to test internal functions */ +#include "library/cgmeminfo.c" + +/* + * Test cgroup_meminfo_new() function + */ +int test_cgroup_meminfo_new(void *data) +{ + struct meminfo_info *info = NULL; + int rc; + + testname = "cgroup_meminfo_new: basic functionality"; + + rc = cgroup_meminfo_new(&info); + /* In non-container environment, this might fail, which is expected */ + if (rc < 0) { + /* Check if we're in a container environment */ + struct stat st; + if (stat("/proc/self/cgroup", &st) != 0) { + /* No cgroup file, skip test */ + return 1; + } + /* cgroup file exists but function failed - this could be normal + * if not in a container or cgroup memory controller not available */ + return 1; + } + + if (info == NULL) { + return 0; + } + + procps_meminfo_unref(&info); + return 1; +} + +/* + * Test cgroup_meminfo_new() with NULL parameter + */ +int test_cgroup_meminfo_new_null(void *data) +{ + int rc; + + testname = "cgroup_meminfo_new: NULL parameter handling"; + + rc = cgroup_meminfo_new(NULL); + /* Should return error for NULL parameter */ + if (rc >= 0) { + return 0; + } + + return 1; +} + +/* + * Test memory info retrieval in container environment + */ +int test_cgroup_meminfo_get(void *data) +{ + struct meminfo_info *info = NULL; + struct meminfo_result *result; + int rc; + + testname = "cgroup_meminfo: memory info retrieval"; + + rc = cgroup_meminfo_new(&info); + if (rc < 0 || info == NULL) { + /* Skip test if not in container environment */ + return 1; + } + + /* Try to get memory total */ + result = procps_meminfo_get(info, MEMINFO_MEM_TOTAL); + if (result == NULL) { + procps_meminfo_unref(&info); + return 0; + } + + /* Memory total should be positive */ + if (result->result.ul_int <= 0) { + procps_meminfo_unref(&info); + return 0; + } + + /* Try to get memory available */ + result = procps_meminfo_get(info, MEMINFO_MEM_AVAILABLE); + if (result == NULL) { + procps_meminfo_unref(&info); + return 0; + } + + procps_meminfo_unref(&info); + return 1; +} + +/* + * Test reference counting + */ +int test_cgroup_meminfo_ref(void *data) +{ + struct meminfo_info *info = NULL; + int rc, refcount; + + testname = "cgroup_meminfo: reference counting"; + + rc = cgroup_meminfo_new(&info); + if (rc < 0 || info == NULL) { + /* Skip test if not in container environment */ + return 1; + } + + /* Initial refcount should be 1 */ + refcount = procps_meminfo_ref(info); + if (refcount != 2) { /* Should be 2 after ref() call */ + procps_meminfo_unref(&info); + return 0; + } + + /* Decrease refcount */ + refcount = procps_meminfo_unref(&info); + if (refcount != 1) { + procps_meminfo_unref(&info); + return 0; + } + + /* Final cleanup */ + procps_meminfo_unref(&info); + return 1; +} + +/* + * Test memory statistics parsing with cgroup v1 format + */ +int test_memcg_parse_memory_stat_v1(void *data) +{ + char *test_stat_v1 = strdup( + "total_cache 1048576\n" + "total_rss 2097152\n" + "total_rss_huge 0\n" + "total_shmem 524288\n" + "total_mapped_file 262144\n" + "total_dirty 4096\n" + "total_writeback 0\n" + "total_inactive_anon 1048576\n" + "total_active_anon 1048576\n" + "total_inactive_file 524288\n" + "total_active_file 524288\n" + "total_unevictable 0\n"); + + struct memory_stat mstat; + int ret; + + testname = "memcg_parse_memory_stat: cgroup v1 format"; + + memset(&mstat, 0, sizeof(mstat)); + ret = memcg_parse_memory_stat(test_stat_v1, &mstat, CGROUP_TYPE_LEGACY); + if (ret != 0) { + free(test_stat_v1); + return 0; + } + + /* Verify parsed values */ + if (mstat.total_cache != 1048576 || + mstat.total_shmem != 524288 || + mstat.total_mapped_file != 262144 || + mstat.total_active_anon != 1048576 || + mstat.total_inactive_anon != 1048576) { + free(test_stat_v1); + return 0; + } + + free(test_stat_v1); + return 1; +} + +/* + * Test memory statistics parsing with cgroup v2 format + */ +int test_memcg_parse_memory_stat_v2(void *data) +{ + char *test_stat_v2 = strdup( + "file 1048576\n" + "anon 2097152\n" + "file_mapped 262144\n" + "file_dirty 4096\n" + "file_writeback 0\n" + "shmem 524288\n" + "inactive_anon 1048576\n" + "active_anon 1048576\n" + "inactive_file 524288\n" + "active_file 524288\n" + "unevictable 0\n" + "slab_reclaimable 131072\n" + "slab_unreclaimable 65536\n" + "slab 196608\n"); + + struct memory_stat mstat; + int ret; + + testname = "memcg_parse_memory_stat: cgroup v2 format"; + + memset(&mstat, 0, sizeof(mstat)); + ret = memcg_parse_memory_stat(test_stat_v2, &mstat, CGROUP_TYPE_UNIFIED); + if (ret != 0) { + free(test_stat_v2); + return 0; + } + + /* Verify parsed values for v2 format */ + if (mstat.total_cache != 1048576 || /* file in v2 */ + mstat.total_shmem != 524288 || + mstat.total_mapped_file != 262144 || + mstat.total_active_anon != 1048576 || + mstat.total_inactive_anon != 1048576 || + mstat.slab_reclaimable != 131072 || + mstat.slab_unreclaimable != 65536 || + mstat.slab != 196608) { + free(test_stat_v2); + return 0; + } + + free(test_stat_v2); + return 1; +} + +TestFunction test_funcs[] = { + test_cgroup_meminfo_new, + test_cgroup_meminfo_new_null, + test_cgroup_meminfo_get, + test_cgroup_meminfo_ref, + test_memcg_parse_memory_stat_v1, + test_memcg_parse_memory_stat_v2, + NULL +}; + +int main(int argc, char *argv[]) +{ + return run_tests(test_funcs, NULL); +} \ No newline at end of file diff --git a/src/free.c b/src/free.c index d2b79de1..d334e43d 100644 --- a/src/free.c +++ b/src/free.c @@ -55,6 +55,7 @@ #define FREE_REPEATCOUNT (1 << 7) #define FREE_COMMITTED (1 << 8) #define FREE_LINE (1 << 9) +#define FREE_CONTAINER (1 << 10) struct commandline_arguments { int exponent; /* demanded in kilos, magas... */ @@ -92,6 +93,7 @@ static void __attribute__ ((__noreturn__)) fputs(_(" -s N, --seconds N repeat printing every N seconds\n"), out); fputs(_(" -c N, --count N repeat printing N times, then exit\n"), out); fputs(_(" -w, --wide wide output\n"), out); + fputs(_(" --container show container memory info\n"), out); fputs(USAGE_SEPARATOR, out); fputs(_(" --help display this help and exit\n"), out); fputs(USAGE_VERSION, out); @@ -156,6 +158,7 @@ int main(int argc, char **argv) PETA_OPTION, TEBI_OPTION, PEBI_OPTION, + CONTAINER_OPTION, HELP_OPTION }; @@ -180,6 +183,7 @@ int main(int argc, char **argv) { "seconds", required_argument, NULL, 's' }, { "count", required_argument, NULL, 'c' }, { "wide", no_argument, NULL, 'w' }, + { "container", no_argument, NULL, CONTAINER_OPTION }, { "help", no_argument, NULL, HELP_OPTION }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } @@ -287,6 +291,9 @@ int main(int argc, char **argv) case 'w': flags |= FREE_WIDE; break; + case CONTAINER_OPTION: + flags |= FREE_CONTAINER; + break; case HELP_OPTION: usage(stdout); case 'V': @@ -298,7 +305,13 @@ int main(int argc, char **argv) if (optind != argc) usage(stderr); - if ( (rc = procps_meminfo_new(&mem_info)) < 0) + if (flags & FREE_CONTAINER) { + rc = cgroup_meminfo_new(&mem_info); + } else { + rc = procps_meminfo_new(&mem_info); + } + + if (rc < 0) { if (rc == -ENOENT) errx(EXIT_FAILURE, diff --git a/src/vmstat.c b/src/vmstat.c index 65fa5a2f..4fce2fb7 100644 --- a/src/vmstat.c +++ b/src/vmstat.c @@ -82,6 +82,8 @@ static int y_option; /* "-t" means "show timestamp" */ static int t_option; +/* show memory info from cgroup inside container */ +static int container_mode = 0; static unsigned sleep_time = 1; static int infinite_updates = 0; static unsigned long num_updates =1; @@ -232,6 +234,7 @@ static void __attribute__ ((__noreturn__)) fputs(_(" -w, --wide wide output\n"), out); fputs(_(" -t, --timestamp show timestamp\n"), out); fputs(_(" -y, --no-first skips first line of output\n"), out); + fputs(_(" -C, --container show container memory info\n"), out); fputs(USAGE_SEPARATOR, out); fputs(USAGE_HELP, out); fputs(USAGE_VERSION, out); @@ -404,6 +407,7 @@ static void new_format(void) unsigned long pgpgin[2], pgpgout[2], pswpin[2] = {0,0}, pswpout[2]; unsigned int sleep_half; unsigned long kb_per_page = sysconf(_SC_PAGESIZE) / 1024ul; + int ret = 0; int debt = 0; /* handle idle ticks running backwards */ struct tm *tm_ptr; time_t the_time; @@ -422,7 +426,12 @@ static void new_format(void) errx(EXIT_FAILURE, _("Unable to create vmstat structure")); if (procps_stat_new(&stat_info) < 0) errx(EXIT_FAILURE, _("Unable to create system stat structure")); - if (procps_meminfo_new(&mem_info) < 0) + if (container_mode == 1) { + ret = cgroup_meminfo_new(&mem_info); + } else { + ret = procps_meminfo_new(&mem_info); + } + if (ret < 0) errx(EXIT_FAILURE, _("Unable to create meminfo structure")); if (procps_uptime(&uptime, NULL) < 0) err(EXIT_FAILURE, _("Unable to get uptime")); @@ -886,6 +895,7 @@ static void sum_format(void) #define TICv(E) STAT_VAL(E, ull_int, stat_stack) #define SYSv(E) STAT_VAL(E, ul_int, stat_stack) #define MEMv(E) unitConvert(MEMINFO_VAL(E, ul_int, mem_stack)) + int ret = 0; struct stat_info *stat_info = NULL; struct vmstat_info *vm_info = NULL; struct meminfo_info *mem_info = NULL; @@ -898,7 +908,12 @@ static void sum_format(void) errx(EXIT_FAILURE, _("Unable to select stat information")); if (procps_vmstat_new(&vm_info) < 0) errx(EXIT_FAILURE, _("Unable to create vmstat structure")); - if (procps_meminfo_new(&mem_info) < 0) + if (container_mode == 1) { + ret = cgroup_meminfo_new(&mem_info); + } else { + ret = procps_meminfo_new(&mem_info); + } + if (ret < 0) errx(EXIT_FAILURE, _("Unable to create meminfo structure")); if (!(mem_stack = procps_meminfo_select(mem_info, Sum_mem_items, 10))) errx(EXIT_FAILURE, _("Unable to select memory information")); @@ -991,6 +1006,7 @@ int main(int argc, char *argv[]) {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {"no-first", no_argument, NULL, 'y'}, + {"container", no_argument, NULL, 'C'}, {NULL, 0, NULL, 0} }; @@ -1003,7 +1019,7 @@ int main(int argc, char *argv[]) atexit(close_stdout); while ((c = - getopt_long(argc, argv, "afmnsdDp:S:wthVy", longopts, NULL)) != -1) + getopt_long(argc, argv, "afmnsdDp:S:wthVyC", longopts, NULL)) != -1) switch (c) { case 'V': printf(PROCPS_NG_VERSION); @@ -1037,6 +1053,9 @@ int main(int argc, char *argv[]) if (strncmp(partition, "/dev/", 5) == 0) partition += 5; break; + case 'C': + container_mode = 1; + break; case 'S': switch (optarg[0]) { case 'b':