/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1996-2013. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Purpose: Portprogram for supervision of memory usage.
*
* Synopsis: memsup
*
* PURPOSE OF THIS PROGRAM
*
* This program supervises the memory status of the entire system, and
* sends status reports upon request from the Erlang system
*
* SPAWNING FROM ERLANG
*
* This program is started from Erlang as follows,
*
* Port = open_port({spawn, 'memsup'}, [{packet,1}]) for UNIX
*
* Erlang sends one of the request condes defined in memsup.h and this program
* answers in one of two ways:
* * If the request is for simple memory data (which is used periodically
* for monitoring) the answer is simply sent in two packets.
* * If the request is for the system specific data, the answer is delivered
* in two packets per value, first a tag value, then the actual
* value. The values are delivered "as is", this interface is
* mainly for VxWorks.
* All numbers are sent as strings of hexadecimal digits.
*
* SUNOS FAKING
*
* When using SunOS 4, the memory report is faked. The total physical memory
* is always reported to be 256MB, and the used fraction to be 128MB.
*
* If capabilities, such as sysconf or procfs, is not defined on the system
* memsup will fake memory usage as well.
*
* Following ordering is defined for extended memory,
* Linux: procfs -> sysinfo -> sysconf -> fake
* Sunos: sysconf -> fake
* other: arch specific
*
* Todo:
* Memory retrieval should be defined by capabilities and not by archs.
* Ordering should be defined arch.
*
* STANDARD INPUT, OUTPUT AND ERROR
*
* This program communicates with Erlang through the standard
* input and output file descriptors (0 and 1). These descriptors
* (and the standard error descriptor 2) must NOT be closed
* explicitely by this program at termination (in UNIX it is
* taken care of by the operating system itself; in VxWorks
* it is taken care of by the spawn driver part of the Emulator).
*
* END OF FILE
*
* If a read from a file descriptor returns zero (0), it means
* that there is no process at the other end of the connection
* having the connection open for writing (end-of-file).
*
*/
#if defined(sgi) || defined(__sgi) || defined(__sgi__)
#include <sys/types.h>
#include <sys/sysmp.h>
#endif
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#if (defined(__unix__) || defined(unix)) && !defined(USG)
#include <sys/param.h>
#endif
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#ifdef BSD4_4
#include <sys/types.h>
#include <sys/sysctl.h>
#if !defined (__OpenBSD__) && !defined (__NetBSD__)
#include <vm/vm_param.h>
#endif
#if defined (__FreeBSD__) || defined(__DragonFly__) || defined (__NetBSD__)
#include <sys/vmmeter.h>
#endif
#endif
#if defined (__linux__)
#include <sys/sysinfo.h>
#endif
/* commands */
#include "memsup.h"
#define CMD_SIZE 1
#define MAX_CMD_BUF 10
#define ERLIN_FD 0
#define ERLOUT_FD 1
/* procfs */
#if defined(__linux__)
#include <fcntl.h>
#define MEMINFO "/proc/meminfo"
#endif
/* prototypes */
static void print_error(const char *,...);
#define MAIN main
/*
* example, we want procfs information, now give them something equivalent:
*
* MemTotal: 4029352 kB old HighTotal + LowTotal
* MemFree: 1674168 kB old HighFree + LowFree
* MemShared: 0 kB old now always zero; not calculated
* Buffers: 417164 kB old temporary storage for raw disk blocks
* Cached: 371312 kB old in-memory cache for files read from the disk (the page cache)
* Active: 1408492 kB new
* Inact_dirty: 7772 kB new
* Inact_clean: 2008 kB new
* Inact_target: 0 kB new
* Inact_laundry: 0 kB new, and might be missing too
* HighTotal: 0 kB
* HighFree: 0 kB memory area for userspace programs or for the pagecache
* LowTotal: 4029352 kB
* LowFree: 1674168 kB Highmem + kernel stuff, slab allocates here
* SwapTotal: 4194296 kB old total amount of swap space available
* SwapFree: 4194092 kB old Memory which has been evicted from RAM
* Inactive: 549224 kB 2.5.41+
* Dirty: 872 kB 2.5.41+ Memory which is waiting to get written back to the disk
* Writeback: 0 kB 2.5.41+ Memory which is actively being written back to the disk
* AnonPages: 787616 kB ??
* Mapped: 113612 kB 2.5.41+ files which have been mmaped, such as libraries
* Slab: 342864 kB 2.5.41+ in-kernel data structures cache
* CommitLimit: 6208972 kB ??
* Committed_AS: 1141444 kB 2.5.41+
* PageTables: 9368 kB 2.5.41+
* VmallocTotal: 34359738367 kB ?? total size of vmalloc memory area
* VmallocUsed: 57376 kB ?? amount of vmalloc area which is used
* VmallocChunk: 34359677947 kB ?? largest contigious block of vmalloc area which is free
* ReverseMaps: 5738 2.5.41+ number of rmap pte chains
* SwapCached: 0 kB 2.5.??+
* HugePages_Total: 0 2.5.??+
* HugePages_Free: 0 2.5.??+
* HugePages_Rsvd: 0 2.5.??+
* Hugepagesize: 2048 kB 2.5.??
*
* This information should be generalized for generic platform i.e. erlang.
*/
#define F_MEM_TOTAL (1 << 0)
#define F_MEM_FREE (1 << 1)
#define F_MEM_BUFFERS (1 << 2)
#define F_MEM_CACHED (1 << 3)
#define F_MEM_SHARED (1 << 4)
#define F_SWAP_TOTAL (1 << 5)
#define F_SWAP_FREE (1 << 6)
typedef struct {
unsigned int flag;
unsigned long pagesize;
unsigned long total;
unsigned long free;
unsigned long buffered;
unsigned long cached;
unsigned long shared;
unsigned long total_swap;
unsigned long free_swap;
} memory_ext;
typedef struct mem_table_struct {
const char *name; /* memory type name */
unsigned long *slot; /* slot in return struct */
} mem_table_struct;
/* static variables */
static char *program_name;
static void
send(unsigned long value, unsigned long pagesize) {
char buf[32];
int left, bytes, res;
int hex_zeroes;
for (hex_zeroes = 0; (pagesize % 16) == 0; pagesize /= 16) {
hex_zeroes++;
}
sprintf(buf+1, "%lx", value*pagesize);
bytes = strlen(buf+1);
while (hex_zeroes-- > 0) {
bytes++;
buf[bytes] = '0';
}
buf[0] = (char) bytes;
left = ++bytes;
while (left > 0) {
res = write(ERLOUT_FD, buf+bytes-left, left);
if (res <= 0){
perror("Error writing to pipe");
exit(1);
}
left -= res;
}
}
static void
send_tag(int value){
unsigned char buf[2];
int res,left;
buf[0] = 1U;
buf[1] = (unsigned char) value;
left = 2;
while(left > 0) {
if((res = write(ERLOUT_FD, buf+left-2,left)) <= 0){
perror("Error writing to pipe");
exit(1);
} else {
left -= res;
}
}
}
#ifdef BSD4_4
static int
get_vmtotal(struct vmtotal *vt) {
static int vmtotal_mib[] = {CTL_VM, VM_METER};
size_t size = sizeof *vt;
return sysctl(vmtotal_mib, 2, vt, &size, NULL, 0) != -1;
}
#endif
#if defined(__linux__)
static int
get_mem_procfs(memory_ext *me){
int fd, nread;
char buffer[4097];
char *bp;
unsigned long value;
me->flag = 0;
if ( (fd = open(MEMINFO, O_RDONLY)) < 0) return -1;
if ( (nread = read(fd, buffer, 4096)) < 0) {
close(fd);
return -1;
}
close(fd);
buffer[nread] = '\0';
/* Total and free is NEEDED! */
bp = strstr(buffer, "MemTotal:");
if (bp != NULL && sscanf(bp, "MemTotal: %lu kB\n", &(me->total))) me->flag |= F_MEM_TOTAL;
bp = strstr(buffer, "MemFree:");
if (bp != NULL && sscanf(bp, "MemFree: %lu kB\n", &(me->free))) me->flag |= F_MEM_FREE;
/* Extensions */
bp = strstr(buffer, "Buffers:");
if (bp != NULL && sscanf(bp, "Buffers: %lu kB\n", &(me->buffered))) me->flag |= F_MEM_BUFFERS;
bp = strstr(buffer, "Cached:");
if (bp != NULL && sscanf(bp, "Cached: %lu kB\n", &(me->cached))) me->flag |= F_MEM_CACHED;
/* Swap */
bp = strstr(buffer, "SwapTotal:");
if (bp != NULL && sscanf(bp, "SwapTotal: %lu kB\n", &(me->total_swap))) me->flag |= F_SWAP_TOTAL;
bp = strstr(buffer, "SwapFree:");
if (bp != NULL && sscanf(bp, "SwapFree: %lu kB\n", &(me->free_swap))) me->flag |= F_SWAP_FREE;
me->pagesize = 1024; /* procfs defines its size in kB */
return 1;
}
#endif
/* arch specific functions */
#if defined(__linux__) /* ifdef SYSINFO */
/* sysinfo does not include cached memory which is a problem. */
static int
get_extended_mem_sysinfo(memory_ext *me) {
struct sysinfo info;
me->flag = 0;
if (sysinfo(&info) < 0) return -1;
me->pagesize = 1;
me->total = info.totalram;
me->free = info.freeram;
me->buffered = info.bufferram;
me->shared = info.sharedram;
me->total_swap = info.totalswap;
me->free_swap = info.freeswap;
me->flag = F_MEM_TOTAL | F_MEM_FREE | F_MEM_SHARED | F_MEM_BUFFERS | F_SWAP_TOTAL | F_SWAP_FREE;
return 1;
}
#endif
#if defined(_SC_AVPHYS_PAGES)
static int
get_extended_mem_sysconf(memory_ext *me) {
me->total = sysconf(_SC_PHYS_PAGES);
me->free = sysconf(_SC_AVPHYS_PAGES);
me->pagesize = sysconf(_SC_PAGESIZE);
me->flag = F_MEM_TOTAL | F_MEM_FREE;
return 1;
}
#endif
#if defined(BSD4_4)
static int
get_extended_mem_bsd4(memory_ext *me) {
struct vmtotal vt;
long pgsz;
if (!get_vmtotal(&vt)) return 0;
if ((pgsz = sysconf(_SC_PAGESIZE)) == -1) return 0;
me->total = (vt.t_free + vt.t_rm);
me->free = vt.t_free;
me->pagesize = pgsz;
me->flag = F_MEM_TOTAL | F_MEM_FREE;
return 1;
}
#endif
#if defined(sgi) || defined(__sgi) || defined(__sgi__)
static int
get_extended_mem_sgi(memory_ext *me) {
struct rminfo rmi;
if (sysmp(MP_SAGET, MPSA_RMINFO, &rmi, sizeof(rmi)) < 0) return -1;
me->total = (unsigned long)(rmi.physmem);
me->free = (unsigned long)(rmi.freemem);
me->pagesize = (unsigned long)getpagesize();
me->flag = F_MEM_TOTAL | F_MEM_FREE;
return 1;
}
#endif
static void
get_extended_mem(memory_ext *me) {
/* linux */
#if defined(__linux__)
if (get_mem_procfs(me)) return;
if (get_extended_mem_sysinfo(me)) return;
/* bsd */
#elif defined(BSD4_4)
if (get_extended_mem_bsd4(me)) return;
/* sgi */
#elif defined(sgi) || defined(__sgi) || defined(__sgi__)
if (get_extended_mem_sgi(me)) return;
#endif
/* Does this exist on others than Solaris2? */
#if defined(_SC_AVPHYS_PAGES)
if (get_extended_mem_sysconf(me)) return;
/* We fake the rest */
/* SunOS4 (for example) */
#else
me->free = (1<<27); /* Fake! 128 MB used */
me->total = (1<<28); /* Fake! 256 MB total */
me->pagesize = 1;
me->flag = F_MEM_TOTAL | F_MEM_FREE;
#endif
}
static void
get_basic_mem(unsigned long *tot, unsigned long *used, unsigned long *pagesize){
#if defined(_SC_AVPHYS_PAGES) /* Does this exist on others than Solaris2? */
unsigned long avPhys, phys, pgSz;
phys = sysconf(_SC_PHYS_PAGES);
avPhys = sysconf(_SC_AVPHYS_PAGES);
*used = (phys - avPhys);
*tot = phys;
*pagesize = sysconf(_SC_PAGESIZE);
#elif defined(__linux__) && !defined(_SC_AVPHYS_PAGES)
memory_ext me;
if (get_mem_procfs(&me) < 0) {
print_error("ProcFS read error");
exit(1);
}
*tot = me.total;
*pagesize = me.pagesize;
*used = me.total - me.free;
#elif defined(BSD4_4)
struct vmtotal vt;
long pgsz;
if (!get_vmtotal(&vt)) goto fail;
if ((pgsz = sysconf(_SC_PAGESIZE)) == -1) goto fail;
*tot = (vt.t_free + vt.t_rm);
*used = vt.t_rm;
*pagesize = pgsz;
return;
fail:
print_error("%s", strerror(errno));
exit(1);
#elif defined(sgi) || defined(__sgi) || defined(__sgi__)
struct rminfo rmi;
if (sysmp(MP_SAGET, MPSA_RMINFO, &rmi, sizeof(rmi)) != -1) {
*tot = (unsigned long)(rmi.physmem);
*used = (unsigned long)(rmi.physmem - rmi.freemem);
*pagesize = (unsigned long)getpagesize();
} else {
print_error("%s", strerror(errno));
exit(1);
}
#else /* SunOS4 */
*used = (1<<27); /* Fake! 128 MB used */
*tot = (1<<28); /* Fake! 256 MB total */
*pagesize = 1;
#endif
}
static void
simple_show_mem(void){
unsigned long tot, used, pagesize;
get_basic_mem(&tot, &used, &pagesize);
send(used, pagesize);
send(tot, pagesize);
}
static void
extended_show_mem(void){
memory_ext me;
unsigned long ps;
get_extended_mem(&me);
ps = me.pagesize;
if (me.flag & F_MEM_TOTAL) { send_tag(MEM_TOTAL); send(me.total, ps); }
if (me.flag & F_MEM_FREE) { send_tag(MEM_FREE); send(me.free, ps); }
/* extensions */
if (me.flag & F_MEM_BUFFERS){ send_tag(MEM_BUFFERS); send(me.buffered, ps); }
if (me.flag & F_MEM_CACHED) { send_tag(MEM_CACHED); send(me.cached, ps); }
if (me.flag & F_MEM_SHARED) { send_tag(MEM_SHARED); send(me.shared, ps); }
/* swap */
if (me.flag & F_SWAP_TOTAL) { send_tag(SWAP_TOTAL); send(me.total_swap, ps); }
if (me.flag & F_SWAP_FREE) { send_tag(SWAP_FREE); send(me.free_swap, ps); }
/* total is system total*/
if (me.flag & F_MEM_TOTAL) { send_tag(MEM_SYSTEM_TOTAL); send(me.total, ps); }
send_tag(SHOW_SYSTEM_MEM_END);
}
static void
message_loop(int erlin_fd)
{
char cmdLen, cmd;
int res;
while (1){
/*
* Wait for command from Erlang
*/
if ((res = read(erlin_fd, &cmdLen, 1)) < 0) {
print_error("Error reading from Erlang");
return;
}
if (res == 1) { /* Exactly one byte read ? */
if (cmdLen == 1){ /* Should be! */
switch (read(erlin_fd, &cmd, 1)){
case 1:
switch (cmd){
case SHOW_MEM:
simple_show_mem();
break;
case SHOW_SYSTEM_MEM:
extended_show_mem();
break;
default: /* ignore all other messages */
break;
}
break;
case 0:
print_error("Erlang has closed");
return;
default:
print_error("Error reading from Erlang");
return;
} /* switch() */
} else { /* cmdLen != 1 */
print_error("Invalid command length (%d) received", cmdLen);
return;
}
} else { /* Erlang end closed */
print_error("Erlang has closed");
return;
}
}
}
/*
* main
*/
int
MAIN(int argc, char **argv)
{
program_name = argv[0];
message_loop(ERLIN_FD);
return 0;
}
/*
* print_error
*
*/
static void
print_error(const char *format,...)
{
va_list args;
char buffer[256];
va_start(args, format);
vsnprintf(buffer, 256, format, args);
va_end(args);
/* try to use one write only */
fprintf(stderr, "[os_mon] memory supervisor port (memsup): %s\r\n", buffer);
fflush(stderr);
}