From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/os_mon/AUTHORS | 12 + lib/os_mon/Makefile | 40 + lib/os_mon/c_src/Makefile | 5 + lib/os_mon/c_src/Makefile.in | 144 ++++ lib/os_mon/c_src/cpu_sup.c | 455 +++++++++++ lib/os_mon/c_src/ferrule.c | 160 ++++ lib/os_mon/c_src/memsup.c | 655 ++++++++++++++++ lib/os_mon/c_src/memsup.h | 46 ++ lib/os_mon/c_src/mod_syslog.c | 137 ++++ lib/os_mon/c_src/nteventlog/elog_format.c | 173 +++++ lib/os_mon/c_src/nteventlog/elog_format.h | 40 + lib/os_mon/c_src/nteventlog/elog_global.h | 57 ++ lib/os_mon/c_src/nteventlog/elog_main.c | 504 ++++++++++++ lib/os_mon/c_src/nteventlog/elog_pipe_stdin.c | 151 ++++ lib/os_mon/c_src/nteventlog/elog_pipe_stdin.h | 90 +++ lib/os_mon/c_src/nteventlog/elog_registry.c | 295 +++++++ lib/os_mon/c_src/nteventlog/elog_registry.h | 67 ++ lib/os_mon/c_src/nteventlog/elog_util.c | 73 ++ lib/os_mon/c_src/nteventlog/elog_util.h | 79 ++ lib/os_mon/c_src/win32sysinfo.c | 318 ++++++++ lib/os_mon/doc/html/.gitignore | 0 lib/os_mon/doc/man3/.gitignore | 0 lib/os_mon/doc/man6/.gitignore | 0 lib/os_mon/doc/pdf/.gitignore | 0 lib/os_mon/doc/src/Makefile | 124 +++ lib/os_mon/doc/src/book.xml | 45 ++ lib/os_mon/doc/src/cpu_sup.xml | 281 +++++++ lib/os_mon/doc/src/disksup.xml | 161 ++++ lib/os_mon/doc/src/fascicules.xml | 15 + lib/os_mon/doc/src/make.dep | 21 + lib/os_mon/doc/src/memsup.xml | 328 ++++++++ lib/os_mon/doc/src/note.gif | Bin 0 -> 1539 bytes lib/os_mon/doc/src/notes.xml | 537 +++++++++++++ lib/os_mon/doc/src/nteventlog.xml | 104 +++ lib/os_mon/doc/src/os_mon_app.xml | 126 +++ lib/os_mon/doc/src/os_mon_mib.xml | 70 ++ lib/os_mon/doc/src/os_sup.xml | 241 ++++++ lib/os_mon/doc/src/part_notes.xml | 36 + lib/os_mon/doc/src/ref_man.xml | 42 + lib/os_mon/doc/src/user_guide.gif | Bin 0 -> 1581 bytes lib/os_mon/doc/src/warning.gif | Bin 0 -> 1498 bytes lib/os_mon/ebin/.gitignore | 0 lib/os_mon/include/memsup.hrl | 43 ++ lib/os_mon/info | 3 + lib/os_mon/mibs/Makefile | 97 +++ lib/os_mon/mibs/OTP-OS-MON-MIB.funcs | 5 + lib/os_mon/mibs/OTP-OS-MON-MIB.mib | 422 ++++++++++ lib/os_mon/mibs/v1/.gitignore | 0 lib/os_mon/priv/bin/.gitignore | 0 lib/os_mon/priv/mibs/.gitignore | 0 lib/os_mon/priv/obj/.gitignore | 0 lib/os_mon/src/Makefile | 110 +++ lib/os_mon/src/cpu_sup.erl | 776 +++++++++++++++++++ lib/os_mon/src/disksup.erl | 369 +++++++++ lib/os_mon/src/memsup.erl | 1022 +++++++++++++++++++++++++ lib/os_mon/src/nteventlog.erl | 162 ++++ lib/os_mon/src/os_mon.app.src | 32 + lib/os_mon/src/os_mon.appup.src | 41 + lib/os_mon/src/os_mon.erl | 179 +++++ lib/os_mon/src/os_mon_mib.erl | 250 ++++++ lib/os_mon/src/os_mon_sysinfo.erl | 147 ++++ lib/os_mon/src/os_sup.erl | 258 +++++++ lib/os_mon/vsn.mk | 1 + 63 files changed, 9549 insertions(+) create mode 100644 lib/os_mon/AUTHORS create mode 100644 lib/os_mon/Makefile create mode 100644 lib/os_mon/c_src/Makefile create mode 100644 lib/os_mon/c_src/Makefile.in create mode 100644 lib/os_mon/c_src/cpu_sup.c create mode 100644 lib/os_mon/c_src/ferrule.c create mode 100644 lib/os_mon/c_src/memsup.c create mode 100644 lib/os_mon/c_src/memsup.h create mode 100644 lib/os_mon/c_src/mod_syslog.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_format.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_format.h create mode 100644 lib/os_mon/c_src/nteventlog/elog_global.h create mode 100644 lib/os_mon/c_src/nteventlog/elog_main.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_pipe_stdin.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_pipe_stdin.h create mode 100644 lib/os_mon/c_src/nteventlog/elog_registry.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_registry.h create mode 100644 lib/os_mon/c_src/nteventlog/elog_util.c create mode 100644 lib/os_mon/c_src/nteventlog/elog_util.h create mode 100644 lib/os_mon/c_src/win32sysinfo.c create mode 100644 lib/os_mon/doc/html/.gitignore create mode 100644 lib/os_mon/doc/man3/.gitignore create mode 100644 lib/os_mon/doc/man6/.gitignore create mode 100644 lib/os_mon/doc/pdf/.gitignore create mode 100644 lib/os_mon/doc/src/Makefile create mode 100644 lib/os_mon/doc/src/book.xml create mode 100644 lib/os_mon/doc/src/cpu_sup.xml create mode 100644 lib/os_mon/doc/src/disksup.xml create mode 100644 lib/os_mon/doc/src/fascicules.xml create mode 100644 lib/os_mon/doc/src/make.dep create mode 100644 lib/os_mon/doc/src/memsup.xml create mode 100644 lib/os_mon/doc/src/note.gif create mode 100644 lib/os_mon/doc/src/notes.xml create mode 100644 lib/os_mon/doc/src/nteventlog.xml create mode 100644 lib/os_mon/doc/src/os_mon_app.xml create mode 100644 lib/os_mon/doc/src/os_mon_mib.xml create mode 100644 lib/os_mon/doc/src/os_sup.xml create mode 100644 lib/os_mon/doc/src/part_notes.xml create mode 100644 lib/os_mon/doc/src/ref_man.xml create mode 100644 lib/os_mon/doc/src/user_guide.gif create mode 100644 lib/os_mon/doc/src/warning.gif create mode 100644 lib/os_mon/ebin/.gitignore create mode 100644 lib/os_mon/include/memsup.hrl create mode 100644 lib/os_mon/info create mode 100644 lib/os_mon/mibs/Makefile create mode 100644 lib/os_mon/mibs/OTP-OS-MON-MIB.funcs create mode 100644 lib/os_mon/mibs/OTP-OS-MON-MIB.mib create mode 100644 lib/os_mon/mibs/v1/.gitignore create mode 100644 lib/os_mon/priv/bin/.gitignore create mode 100644 lib/os_mon/priv/mibs/.gitignore create mode 100644 lib/os_mon/priv/obj/.gitignore create mode 100644 lib/os_mon/src/Makefile create mode 100644 lib/os_mon/src/cpu_sup.erl create mode 100644 lib/os_mon/src/disksup.erl create mode 100644 lib/os_mon/src/memsup.erl create mode 100644 lib/os_mon/src/nteventlog.erl create mode 100644 lib/os_mon/src/os_mon.app.src create mode 100644 lib/os_mon/src/os_mon.appup.src create mode 100644 lib/os_mon/src/os_mon.erl create mode 100644 lib/os_mon/src/os_mon_mib.erl create mode 100644 lib/os_mon/src/os_mon_sysinfo.erl create mode 100644 lib/os_mon/src/os_sup.erl create mode 100644 lib/os_mon/vsn.mk (limited to 'lib/os_mon') diff --git a/lib/os_mon/AUTHORS b/lib/os_mon/AUTHORS new file mode 100644 index 0000000000..22e34f8278 --- /dev/null +++ b/lib/os_mon/AUTHORS @@ -0,0 +1,12 @@ +Original Authors and Contributors: + +Martin Björklund +Mats Nilsson +Magnus Fröberg +Janne Lindblad +Kenneth Lundin +Patrik Nyblom +Peter Högfeldt +Rickard Green +Ingela Anderton +Björn-Egil Dahlberg diff --git a/lib/os_mon/Makefile b/lib/os_mon/Makefile new file mode 100644 index 0000000000..7eeec1577c --- /dev/null +++ b/lib/os_mon/Makefile @@ -0,0 +1,40 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# +# Macros +# +ifeq ($(findstring win32,$(TARGET)),win32) +SUB_DIRECTORIES = src c_src mibs doc/src +else +SUB_DIRECTORIES = src c_src mibs doc/src +endif + +include vsn.mk +VSN = $(OS_MON_VSN) + +SPECIAL_TARGETS = + +# +# Default Subdir Targets +# +include $(ERL_TOP)/make/otp_subdir.mk + diff --git a/lib/os_mon/c_src/Makefile b/lib/os_mon/c_src/Makefile new file mode 100644 index 0000000000..a34434a7e6 --- /dev/null +++ b/lib/os_mon/c_src/Makefile @@ -0,0 +1,5 @@ +# +# Invoke with GNU make or clearmake -C gnu. +# + +include $(ERL_TOP)/make/run_make.mk diff --git a/lib/os_mon/c_src/Makefile.in b/lib/os_mon/c_src/Makefile.in new file mode 100644 index 0000000000..1a371eb380 --- /dev/null +++ b/lib/os_mon/c_src/Makefile.in @@ -0,0 +1,144 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +CC = @CC@ +LD = @LD@ +LIBS = @LIBS@ +CPU_SUP_LIBS = @CPU_SUP_LIBS@ + +BINDIR = ../priv/bin/$(TARGET) +OBJDIR = ../priv/obj/$(TARGET) + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(OS_MON_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +ifeq ($(findstring win32,$(TARGET)),win32) +PROGRAMS = \ + win32sysinfo.exe \ + nteventlog.exe +C_FILES=win32sysinfo.c \ + nteventlog/elog_main.c \ + nteventlog/elog_util.c \ + nteventlog/elog_registry.c \ + nteventlog/elog_pipe_stdin.c \ + nteventlog/elog_format.c + +EVLOG_OBJECTS = \ + $(OBJDIR)/elog_main.o \ + $(OBJDIR)/elog_util.o \ + $(OBJDIR)/elog_registry.o \ + $(OBJDIR)/elog_pipe_stdin.o \ + $(OBJDIR)/elog_format.o + +ENTRY_OBJ=$(ERL_TOP)/erts/obj/$(TARGET)/port_entry.o +PORT_ENTRY_POINT=erl_port_entry +ENTRY_LDFLAGS=-entry:$(PORT_ENTRY_POINT) +else +ifeq ($(findstring vxworks_simso,$(TARGET)),vxworks_simso) +PROGRAMS = +else +PROGRAMS = \ + memsup @os_mon_programs@ +C_FILES= $(PROGRAMS:%=%.c) +endif +endif + +TARGET_FILES= $(PROGRAMS:%=$(BINDIR)/%) + +LDFLAGS = @LDFLAGS@ + +ALL_CFLAGS = @CFLAGS@ @DEFS@ $(CFLAGS) + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(OBJDIR) $(BINDIR) $(TARGET_FILES) + +$(OBJDIR): + -@mkdir -p $(OBJDIR) + +$(BINDIR): + -@mkdir -p $(BINDIR) + +clean: + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +$(BINDIR)/win32sysinfo.exe: $(OBJDIR)/win32sysinfo.o $(ENTRY_OBJ) + $(LD) $(LDFLAGS) $(ENTRY_LDFLAGS) -o $@ $(OBJDIR)/win32sysinfo.o $(ENTRY_OBJ) + +$(BINDIR)/nteventlog.exe: $(EVLOG_OBJECTS) + $(LD) $(LDFLAGS) $(ENTRY_LDFLAGS) -o $@ $(EVLOG_OBJECTS) $(ENTRY_OBJ) + +$(BINDIR)/ferrule: $(OBJDIR)/ferrule.o + $(LD) $(LDFLAGS) -o $@ $< + +$(BINDIR)/mod_syslog: $(OBJDIR)/mod_syslog.o + $(LD) $(LDFLAGS) -o $@ $< + +$(BINDIR)/memsup: $(OBJDIR)/memsup.o + $(LD) $(LDFLAGS) -o $@ $< + +$(BINDIR)/cpu_sup: $(OBJDIR)/cpu_sup.o + $(LD) $(LDFLAGS) -o $@ $< $(CPU_SUP_LIBS) + +$(OBJDIR)/%.o: %.c + $(CC) -c -o $@ $(ALL_CFLAGS) $< + +$(OBJDIR)/%.o: nteventlog/%.c + $(CC) -c -o $@ $(ALL_CFLAGS) $< + +$(OBJDIR)/memsup.o: memsup.h + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +ifeq ($(findstring vxworks_simso,$(TARGET)),vxworks_simso) +release_spec: +else +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(C_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/priv/bin + $(INSTALL_PROGRAM) $(TARGET_FILES) $(RELSYSDIR)/priv/bin +endif + +release_docs_spec: diff --git a/lib/os_mon/c_src/cpu_sup.c b/lib/os_mon/c_src/cpu_sup.c new file mode 100644 index 0000000000..fbf318c614 --- /dev/null +++ b/lib/os_mon/c_src/cpu_sup.c @@ -0,0 +1,455 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. 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% + */ +/* + * CPU supervision + * + * Uses kstat library only available on Solaris 2 + * Compile with: gcc -o cpu_sup cpu_sup.c -lkstat + * + * Use open_port({spawn,Prog},[stream]) to communicate. + * + */ + +#include +#include +#include + +#if defined(__sun__) +#include +#endif + +#include +#include + +#if defined(__linux__) +#include /* strlen */ + +#define PROCSTAT "/proc/stat" +#define BUFFERSIZE (256) +typedef struct { + unsigned int id; + unsigned long long + /* total, */ + user, + nice_user, + kernel, + idle, + io_wait, + hard_irq, + soft_irq, + steal; +} cpu_t; + +#endif + +#define FD_IN (0) +#define FD_OUT (1) +#define FD_ERR (2) + +#define PING 'p' +#define NPROCS 'n' +#define AVG1 '1' +#define AVG5 '5' +#define AVG15 'f' +#define UTIL 'u' +#define QUIT 'q' + + +#define CU_CPU_ID (0) +#define CU_USER (1) +#define CU_NICE_USER (2) +#define CU_KERNEL (3) +#define CU_IO_WAIT (4) +#define CU_IDLE (5) +#define CU_HARD_IRQ (6) +#define CU_SOFT_IRQ (7) +#define CU_STEAL (8) + +#define CU_VALUES (9) +#define CU_KSTAT_VALUES (5) + +/* +#define CU_FLG_CPU_ID (0 << 1) +#define CU_FLG_USER (0 << 2) +#define CU_FLG_NICE_USER (0 << 3) +#define CU_FLG_KERNEL (0 << 4) +#define CU_FLG_IO_WAIT (0 << 5) +#define CU_FLG_IDLE (0 << 6) +#define CU_FLG_HARD_IRQ (0 << 7) +#define CU_FLG_SOFT_IRQ (0 << 8) +#define CU_FLG_STEAL (0 << 9) +*/ + +/* util_measure + * In: + * unsigned int **result_vec + * int *result_sz + * Purpose: + * Retrieve CPU utilization + * result_vec has 2 + np*ne*2 entries where np is number_of_cpus + * |------|------| + * | np | ne | + * |------|------| + * |val_id| value| (One entry) + * |------|------| + * |val_id| value| (One entry) + * |------|------| + * ...... + * |------|------| + * |val_id| value| + * |------|------| + * np = number of processors + * ne = number of entries per processor + */ + +static void util_measure(unsigned int **result_vec, int *result_sz); + +static unsigned int misc_measure(char* name); +static void send(unsigned int data); +static void sendv(unsigned int data[], int ints); +static void error(char* err_msg); + +#if defined(__sun__) +static kstat_ctl_t *kstat_ctl; +#endif + +#if defined(__linux__) +static int processors_online() { + return (int)sysconf(_SC_NPROCESSORS_ONLN); +} +#endif + +int main(int argc, char** argv) { + char cmd; + int rc; + int sz; + unsigned int *rv; + unsigned int no_of_cpus = 0; + +#if defined(__sun__) + kstat_ctl = kstat_open(); + if(!kstat_ctl) + error("Can't open header kstat"); +#endif + +#if defined(__linux__) + no_of_cpus = processors_online(); + if ( (rv = (unsigned int*)malloc(sizeof(unsigned int)*(2 + 2*no_of_cpus*CU_VALUES))) == NULL) { + error("cpu_cup: malloc error"); + } +#endif + + while(1) { + + rc = read(FD_IN, &cmd, 1); + if (rc < 0) { + if (errno == EINTR) + continue; + error("Error reading from Erlang"); + } + + if(rc == 0) + error("Erlang has closed"); + + switch(cmd) { + case PING: send(4711); break; +#if defined(__sun__) + case NPROCS: send(misc_measure("nproc")); break; + case AVG1: send(misc_measure("avenrun_1min")); break; + case AVG5: send(misc_measure("avenrun_5min")); break; + case AVG15: send(misc_measure("avenrun_15min")); break; +#endif + case UTIL: util_measure(&rv,&sz); sendv(rv, sz); break; + case QUIT: free((void*)rv); return 0; + default: error("Bad command"); break; + } + } + return 0; /* supress warnings */ +} +/* ---------------------------- * + * Linux stat functions * + * ---------------------------- */ + +#if defined(__linux__) + +static cpu_t *read_procstat(FILE *fp, cpu_t *cpu) { + char buffer[BUFFERSIZE]; + + fgets(buffer, BUFFERSIZE, fp); + sscanf(buffer, "cpu%u %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", + &(cpu->id), + &(cpu->user), + &(cpu->nice_user), + &(cpu->kernel), + &(cpu->idle), + &(cpu->io_wait), + &(cpu->hard_irq), + &(cpu->soft_irq), + &(cpu->steal)); + + return cpu; +} + +static void util_measure(unsigned int **result_vec, int *result_sz) { + int no_of_cpus = processors_online(); + int i; + char buffer[BUFFERSIZE]; + FILE *fp; + unsigned int *rv = NULL; + cpu_t cpu; + + if ( (fp = fopen(PROCSTAT,"r")) == NULL) { + /* Check if procfs is mounted, + * otherwise: + * try and try again, bad procsfs. + */ + *result_sz = 0; + return; + } + + fgets(buffer, BUFFERSIZE, fp); /*ignore read*/ + rv = *result_vec; + rv[0] = no_of_cpus; + rv[1] = CU_VALUES; + ++rv; /* first value is number of cpus */ + ++rv; /* second value is number of entries */ + + for (i = 0; i < no_of_cpus; ++i) { + read_procstat(fp, &cpu); + + rv[ 0] = CU_CPU_ID; rv[ 1] = cpu.id; + rv[ 2] = CU_USER; rv[ 3] = cpu.user; + rv[ 4] = CU_NICE_USER; rv[ 5] = cpu.nice_user; + rv[ 6] = CU_KERNEL; rv[ 7] = cpu.kernel; + rv[ 8] = CU_IO_WAIT; rv[ 9] = cpu.io_wait; + rv[10] = CU_IDLE; rv[11] = cpu.idle; + rv[12] = CU_HARD_IRQ; rv[13] = cpu.hard_irq; + rv[14] = CU_SOFT_IRQ; rv[15] = cpu.soft_irq; + rv[16] = CU_STEAL; rv[17] = cpu.steal; + rv += CU_VALUES*2; + } + + fclose(fp); + *result_sz = 2 + 2*CU_VALUES * no_of_cpus; +} + +#endif + +/* ---------------------------- * + * Unix kstat functions * + * ---------------------------- */ + +#if defined(__sun__) +static unsigned int misc_measure(char* name) { + static kstat_t *ksp = NULL; + kstat_named_t* entry; + kid_t kcid; + + kcid = kstat_chain_update(kstat_ctl); + + if(kcid == -1) + error("Error updating kstat chain"); + + if (!ksp || kcid != 0) { + + /* The kstat chain changed (or we are initializing); + find system misc entry in the new chain... */ + + ksp = kstat_lookup(kstat_ctl,"unix",0,"system_misc"); + if(!ksp) + error("Can't open system_misc kstat"); + } + + kstat_read(kstat_ctl,ksp,NULL); + entry = kstat_data_lookup(ksp,name); + if(!entry) + return -1; + + if(entry->data_type != KSTAT_DATA_ULONG) + return -1; + + return entry->value.ul; +} + + +static int cpu_cmp(const void *p1, const void *p2) { + kstat_t *ksp1 = *((kstat_t **) p1); + kstat_t *ksp2 = *((kstat_t **) p2); + + if (ksp1->ks_instance > ksp2->ks_instance) + return 1; + if (ksp1->ks_instance < ksp2->ks_instance) + return -1; + return 0; +} + + +static void util_measure(unsigned int **result_vec, int *result_sz) { + static int no_of_cpus = 0; + static kstat_t **cpu_ksps = NULL; + static unsigned int * resv = NULL; + unsigned int *rv = NULL; + kstat_t *ksp; + kid_t kcid; + int cpu_stats_read; + int i; + + kcid = kstat_chain_update(kstat_ctl); + + if(kcid == -1) + error("Error updating kstat chain"); + + if (no_of_cpus == 0 || kcid != 0) { + + /* The kstat chain changed (or we are initializing); + find cpu_stat entries in the new chain... */ + + no_of_cpus = 0; + + for(ksp = kstat_ctl->kc_chain; ksp; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_stat") == 0 + && ksp->ks_type == KSTAT_TYPE_RAW) { + no_of_cpus++; + /* Assumes that modifications of the cpu_stat_t struct + in future releases of Solaris only are additions + of fields at the end of the struct. */ + if(ksp->ks_data_size < sizeof(cpu_stat_t)) + error("Error: unexpected kstat data size"); + } + } + + free((void *) cpu_ksps); + if (no_of_cpus > 0) { + cpu_ksps = (kstat_t **) malloc(no_of_cpus*sizeof(kstat_t *)); + if(!cpu_ksps) + error("Error allocating memory"); + + i = 0; + for(ksp = kstat_ctl->kc_chain; + ksp && i < no_of_cpus; + ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_stat") == 0 + && ksp->ks_type == KSTAT_TYPE_RAW) { + cpu_ksps[i++] = ksp; + } + } + + if (i != no_of_cpus) + error("Error: private kstat chain copy unexpectedly changed"); + + /* Erlang assumes that cpu information are sent in ascending order; + sort them ... */ + qsort((void *)cpu_ksps,(size_t)no_of_cpus,sizeof(kstat_t *),cpu_cmp); + + } + + free((void *) resv); +/* kstat defined values are: + * CU_CPU_ID + * CU_USER + * CU_KERNEL + * CU_IO_WAIT + * CU_IDLE + */ + resv = (unsigned int *) malloc(sizeof(unsigned int)*(2 + 2*no_of_cpus*CU_KSTAT_VALUES)); + if(!resv) + error("Error allocating memory"); + + } + + /* Read cpu utilization statistics ... */ + + rv = resv; + rv++; /*first entry is np*/ + rv++; /*second entry is ne*/ + cpu_stats_read = 0; + + for(i = 0; i < no_of_cpus; i++) { + if (kstat_read(kstat_ctl, cpu_ksps[i], NULL) != -1) { + cpu_stat_t *cpu_stat = (cpu_stat_t *)cpu_ksps[i]->ks_data; + + rv[ 0] = CU_CPU_ID; rv[ 1] = cpu_ksps[i]->ks_instance; + rv[ 2] = CU_USER; rv[ 3] = cpu_stat->cpu_sysinfo.cpu[CPU_USER]; + rv[ 4] = CU_KERNEL; rv[ 5] = cpu_stat->cpu_sysinfo.cpu[CPU_KERNEL]; + rv[ 6] = CU_IO_WAIT; rv[ 7] = cpu_stat->cpu_sysinfo.cpu[CPU_WAIT]; + rv[ 8] = CU_IDLE; rv[ 9] = cpu_stat->cpu_sysinfo.cpu[CPU_IDLE]; + + rv += CU_KSTAT_VALUES*2; + cpu_stats_read++; + } + } + + resv[0] = cpu_stats_read; + resv[1] = CU_KSTAT_VALUES; + + *result_vec = resv; + *result_sz = 2 + 2* CU_KSTAT_VALUES * cpu_stats_read; + +} +#endif + +/* ---------------------------- * + * Generic functions * + * ---------------------------- */ + +static void send(unsigned int data) { sendv(&data, 1); } + +static void sendv(unsigned int data[], int ints) { + static unsigned char *buf = NULL; + static int bufsz = 0; + int rc, di, bi, msgsz; + + /* Assumes 32-bit integers... */ + + msgsz = 4*ints; + + if(bufsz < msgsz) { + if (buf != NULL) free((void *) buf); + buf = malloc(msgsz); + if (!buf) error("Error allocating memory"); + bufsz = msgsz; + } + + for(bi = 0, di = 0; di < ints; di++) { + buf[bi++] = (data[di] >> 24) & 0xff; + buf[bi++] = (data[di] >> 16) & 0xff; + buf[bi++] = (data[di] >> 8) & 0xff; + buf[bi++] = (data[di] ) & 0xff; + } + + bi = 0; + do { + rc = write(FD_OUT, &buf[bi], msgsz - bi); + if (rc < 0) { + if (errno == EINTR) + continue; + error("Error writing to Erlang"); + } + bi += rc; + } while(msgsz - bi > 0); + +} + +static void error(char* err_msg) { + write(FD_ERR, err_msg, strlen(err_msg)); + write(FD_ERR, "\n", 1); + exit(-1); +} + + diff --git a/lib/os_mon/c_src/ferrule.c b/lib/os_mon/c_src/ferrule.c new file mode 100644 index 0000000000..744f302b2d --- /dev/null +++ b/lib/os_mon/c_src/ferrule.c @@ -0,0 +1,160 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. 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% + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* arguments */ +#define OWNPATH 1 +#define NARG 1 + + /* status codes */ +#define OK 0 +#define NOT_OK 1 + + /* hardwired constants*/ +#define PIPENAME "syslog.otp" +#define DIE_CMD "die" +#define ONLY_STDIN_CMD "only_stdin" +#define BUFFER_SIZE 1001 +#define MAXPATH_SIZE 1001 +#define STDIN 0 +#define STDOUT 1 +#define HEADER_SIZE 2 +#define WAIT 10 +#define INTERVAL 1 +#define FALSE 0 +#define TRUE 1 +#define FDS_STDIN 0 +#define FDS_PIPE 1 + +main(int argc, char *argv[]) +/* usage: ferrule ownpath */ +{ + int i, pipe_fd; + long int nfds=2L; + char buf[BUFFER_SIZE], pipename[MAXPATH_SIZE], packet_size[2]; + struct pollfd fds[2]; + struct stat stat_buf; + + /* enough arguments? */ + if(argc < NARG+1) + exit(NOT_OK); + + /* make pipe name */ + strcpy(pipename, argv[OWNPATH]); + strcat(pipename, "/"); + strcat(pipename, PIPENAME); + + /* wait for creation of pipe */ + for(i=WAIT; i>0; i--) + if(stat(pipename, &stat_buf) == 0) + break; + else{ + if(i == 0) + exit(NOT_OK); + else + sleep(INTERVAL); + } + + /* open pipe, exit if error */ + if((pipe_fd = open(pipename, O_RDONLY | O_NONBLOCK)) == -1) + exit(NOT_OK); + + /* setup for pipe */ + fds[FDS_PIPE].fd = pipe_fd; + fds[FDS_PIPE].events = POLLRDNORM; + fds[FDS_PIPE].revents = 0; + + /* setup for stdin */ + fds[FDS_STDIN].fd = STDIN; + fds[FDS_STDIN].events = POLLRDNORM; + fds[FDS_STDIN].revents = 0; + + /* loop */ + while(1){ + /* wait for input */ + if(poll(&fds[0], nfds, INFTIM) == -1) + exit(NOT_OK); + + /* analyse input from pipe */ + if(fds[FDS_PIPE].revents != 0){ + + /* pipe error, error exit */ + if((fds[FDS_PIPE].revents & (POLLHUP | POLLERR | POLLNVAL)) != 0) + exit(NOT_OK); + + /* read pipe and write to stdout, exit if error */ + if((fds[FDS_PIPE].revents & POLLRDNORM) != 0){ + i=0; + do{ + read(pipe_fd, &buf[i++], (size_t)1); + }while(buf[i-1] != '\n'); + i--; + + /* send if string is not empty */ + if(i != 0){ + /* make packet size, [0]=MSB, [1]=LSB */ + packet_size[0] = (i >> 8) & 0xff; + packet_size[1] = i & 0xff; + + /* send to OTP process */ + if(write(STDOUT, packet_size, HEADER_SIZE) != HEADER_SIZE) + exit(NOT_OK); + if(write(STDOUT, buf, i) != i) + exit(NOT_OK); + } + } + } + + /* analyse input from stdin */ + if(fds[FDS_STDIN].revents != 0){ + + /* error exit */ + if((fds[FDS_STDIN].revents & (POLLHUP | POLLERR | POLLNVAL)) != 0) + exit(NOT_OK); + + /* read bytes */ + if((fds[FDS_STDIN].revents & POLLRDNORM) != 0){ + /* get packet size, [0]=MSB, [1]=LSB */ + if(read(STDIN, packet_size, HEADER_SIZE) != HEADER_SIZE) + exit(NOT_OK); + i = (packet_size[0] << 8) | packet_size[1]; + + /* get packet */ + if(read(STDIN, buf, i) != i) + exit(NOT_OK); + + /* check if die command */ + if(strncmp(DIE_CMD, buf, i) == FALSE) + exit(OK); + + /* check if only_stdin command */ + if(strncmp(ONLY_STDIN_CMD, buf, i) == FALSE) + nfds=1L; + } + } + } +} diff --git a/lib/os_mon/c_src/memsup.c b/lib/os_mon/c_src/memsup.c new file mode 100644 index 0000000000..241e7718db --- /dev/null +++ b/lib/os_mon/c_src/memsup.c @@ -0,0 +1,655 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. 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 and VxWorks + * + * 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). + * + * COMPILING + * + * When the target is VxWorks the identifier VXWORKS must be defined for + * the preprocessor (usually by a -D option). + */ + +#if defined(sgi) || defined(__sgi) || defined(__sgi__) +#include +#include +#endif + +#include +#include +#include + +#ifndef VXWORKS +#include +#endif + +#if (defined(__unix__) || defined(unix)) && !defined(USG) +#include +#endif + +#include + +#include +#include +#include + +#ifdef VXWORKS +#include +#include +#include +#endif + +#ifdef BSD4_4 +#include +#include +#if !defined (__OpenBSD__) && !defined (__NetBSD__) +#include +#endif +#if defined (__FreeBSD__) || defined(__DragonFly__) +#include +#endif +#endif + +#if defined (__linux__) +#include +#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 +#define MEMINFO "/proc/meminfo" +#endif + +/* prototypes */ + +static void print_error(const char *,...); +#ifdef VXWORKS +extern int erl_mem_info_get(MEM_PART_STATS *); +#endif + +#ifdef VXWORKS +#define MAIN memsup + +static MEM_PART_STATS latest; +static unsigned long latest_system_total; /* does not fit in the struct */ + +#else +#define MAIN main +#endif + + +/* + * 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 VXWORKS +static void load_statistics(void){ + if(memPartInfoGet(memSysPartId,&latest) != OK) + memset(&latest,0,sizeof(latest)); + latest_system_total = latest.numBytesFree + latest.numBytesAlloc; + erl_mem_info_get(&latest); /* if it fails, latest is untouched */ +} +#endif + +#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 (sscanf(bp, "MemTotal: %lu kB\n", &(me->total))) me->flag |= F_MEM_TOTAL; + + bp = strstr(buffer, "MemFree:"); + if (sscanf(bp, "MemFree: %lu kB\n", &(me->free))) me->flag |= F_MEM_FREE; + + /* Extensions */ + + bp = strstr(buffer, "Buffers:"); + if (sscanf(bp, "Buffers: %lu kB\n", &(me->buffered))) me->flag |= F_MEM_BUFFERS; + + bp = strstr(buffer, "Cached:"); + if (sscanf(bp, "Cached: %lu kB\n", &(me->cached))) me->flag |= F_MEM_CACHED; + + + /* Swap */ + + bp = strstr(buffer, "SwapTotal:"); + if (sscanf(bp, "SwapTotal: %lu kB\n", &(me->total_swap))) me->flag |= F_SWAP_TOTAL; + + bp = strstr(buffer, "SwapFree:"); + if (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(VXWORKS) +static int +get_extended_mem_vxwork(memory_ext *me) { + load_statistics(); + me->total = (latest.numBytesFree + latest.numBytesAlloc); + me->free = latest.numBytesFree; + me->pagesize = 1; + me->flag = F_MEM_TOTAL | F_MEM_FREE; + return 1; +} +#endif + + +#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) { +/* vxworks */ +#if defined(VXWORKS) + if (get_extended_mem_vxworks(me)) return; + +/* linux */ +#elif 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(VXWORKS) + load_statistics(); + *tot = (latest.numBytesFree + latest.numBytesAlloc); + *used = latest.numBytesAlloc; + *pagesize = 1; +#elif 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); } + +#ifdef VXWORKS + send_tag(SM_SYSTEM_TOTAL); + send(latest_system_total, 1); + send_tag(SM_LARGEST_FREE); + send(latest.maxBlockSizeFree, 1); + send_tag(SM_NUMBER_OF_FREE); + send(latest.numBlocksFree, 1); +#else + /* total is system total*/ + if (me.flag & F_MEM_TOTAL) { send_tag(MEM_SYSTEM_TOTAL); send(me.total, ps); } +#endif + 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; + + va_start(args, format); + fprintf(stderr, "%s: ", program_name); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, " \n"); +} + + + + + diff --git a/lib/os_mon/c_src/memsup.h b/lib/os_mon/c_src/memsup.h new file mode 100644 index 0000000000..926df0a847 --- /dev/null +++ b/lib/os_mon/c_src/memsup.h @@ -0,0 +1,46 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +/* + * This header defines the protocol between the erlang + * memsup module and the C module. + */ +#ifndef _SYSMEM_H +#define _SYSMEM_H + +/* Simple memory statistics */ +/*IG*/ #define SHOW_MEM 1 + +/* Extended memory statistics */ +/*IG*/ #define SHOW_SYSTEM_MEM 2 + +/* Tags for the extended statistics */ +/*IG*/ #define SHOW_SYSTEM_MEM_END 0 +/*IG*/ #define MEM_SYSTEM_TOTAL 1 +/*IG*/ #define MEM_TOTAL 2 +/*IG*/ #define MEM_FREE 3 +/*IG*/ #define MEM_LARGEST_FREE 4 +/*IG*/ #define MEM_NUMBER_OF_FREE 5 +/*Extension*/ +/*IG*/ #define MEM_BUFFERS 6 +/*IG*/ #define MEM_CACHED 7 +/*IG*/ #define MEM_SHARED 8 +/*IG*/ #define SWAP_TOTAL 9 +/*IG*/ #define SWAP_FREE 10 + +#endif diff --git a/lib/os_mon/c_src/mod_syslog.c b/lib/os_mon/c_src/mod_syslog.c new file mode 100644 index 0000000000..87fbfbac22 --- /dev/null +++ b/lib/os_mon/c_src/mod_syslog.c @@ -0,0 +1,137 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. 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% + */ +#include +#include +#include +#include +#include +#include + + /* arguments */ +#define MODE 1 +#define OWNPATH 2 +#define SYSLOGCONF 3 +#define NARG 3 + + /* status codes */ +#define OK 0 +#define ARG_ERROR -1 +#define FILE_ERROR -2 +#define KILL_ERROR -3 +#define PIPE_NOT_FOUND 1 + + /* hardwired */ +#define SYSLOG_PID "/etc/syslog.pid" +#define PIPENAME "syslog.otp" +#define SYSLOGCONF_ORIG "syslog.conf.ORIG" +#define SYSLOGCONF_OTP "syslog.conf.OTP" +#define BUFFER_SIZE 1001 +#define MAXNAME_SIZE 1001 +#define FALSE 0 +#define TRUE 1 +#define WAIT 1 + +main(int argc, char *argv[]) +/* usage: mod_syslog mode ownpath syslogconf */ +{ + int syslogd_pid, n_lines_copied=0; + int otp_sw, find_sw=FALSE; + char buf[BUFFER_SIZE], pipename[MAXNAME_SIZE], srcname[MAXNAME_SIZE]; + FILE *srcfile, *destfile, *pidfile; + + void make_exit(int); + + /* enough arguments? */ + if(argc < NARG+1) + make_exit(ARG_ERROR); + + /* enable OTP or not */ + if(strcmp(argv[MODE], "otp") == 0) + otp_sw = TRUE; + else if(strcmp(argv[MODE], "nootp") == 0) + otp_sw = FALSE; + else + make_exit(ARG_ERROR); + + /* make pipename */ + strcpy(pipename, argv[OWNPATH]); + strcat(pipename, "/"); + strcat(pipename, PIPENAME); + + /* remove old pipe and make a new one */ + if(otp_sw){ + /* remove */ + unlink(pipename); + + /* make a new */ + if(mknod(pipename, S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH, (dev_t)0) != 0) + make_exit(FILE_ERROR); + } + + /* make source filename */ + strcpy(srcname, argv[OWNPATH]); + strcat(srcname, "/"); + strcat(srcname, otp_sw?SYSLOGCONF_OTP:SYSLOGCONF_ORIG); + + /* open source and destination, exit if error */ + if((srcfile = fopen(srcname, "r")) == NULL || + (destfile = fopen(argv[SYSLOGCONF], "w")) == NULL) + make_exit(FILE_ERROR); + + /* copy source and destination, exit if error */ + while(fgets(buf, BUFFER_SIZE-1, srcfile) != NULL){ + /* find_sw |= strstr(buf, PIPENAME) != NULL; */ + n_lines_copied++; + if(fputs(buf, destfile) == EOF) + make_exit(FILE_ERROR); + } + if(ferror(srcfile) || n_lines_copied == 0) + make_exit(FILE_ERROR); + + /* open pidfile, exit if error */ + if((pidfile = fopen(SYSLOG_PID, "r")) == NULL) + make_exit(FILE_ERROR); + + /* read pid for syslogd, exit if error */ + if(fscanf(pidfile, "%d", &syslogd_pid) == 0) + make_exit(FILE_ERROR); + + /* send HUP to syslogd, exit if error */ + if(syslogd_pid < 0 || kill((pid_t)syslogd_pid, SIGHUP) != 0) + make_exit(KILL_ERROR); + + /* remove pipe */ + if(!otp_sw){ + sleep((unsigned)WAIT); + unlink(pipename); + } + + /* ending successful or with warning */ + /* if(!find_sw && otp_sw) + make_exit(PIPE_NOT_FOUND); + else */ + make_exit(OK); +} + +void make_exit(int exit_code) +{ + printf("%d", exit_code); + exit(0); +} diff --git a/lib/os_mon/c_src/nteventlog/elog_format.c b/lib/os_mon/c_src/nteventlog/elog_format.c new file mode 100644 index 0000000000..c9fb6b7e1a --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_format.c @@ -0,0 +1,173 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#include "elog_global.h" +#include "elog_format.h" + +/* + * The Newline treatment bits of FormatMessage + * This value should suppress all other than hardcoded newlines + */ +#define NL_TREATMENT FORMAT_MESSAGE_MAX_WIDTH_MASK + +/* + * Expands %%NNN formats in strings with strings from a + * ParameterMessageFile (open). + * A return of NULL means there's nothing to expand + * or that the buffer is to small, which probably means the + * same thing to the caller, that is use the + * original string as it is. + */ +static char *expand_message(char *toexpand, + HINSTANCE paramlib, + char *buff, int bufflen){ + char *oldpos; + int buffpos = 0; + char *pos = toexpand; + char *end; + unsigned long num; + char *replbuff = malloc(bufflen); + char *repl; + int replaced = 0; + + while((oldpos = pos, pos = strstr(pos,"%%"))){ + num = strtoul(pos + 2, &end, 0); + replaced = 1; + if(end == pos + 2 || num == 0){ + repl = "%%"; + } else { + if(!FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_IGNORE_INSERTS | + NL_TREATMENT, + (LPCVOID) paramlib, + (DWORD) num, + DEFAULT_LANGID, + replbuff, + (DWORD) bufflen, + NULL)){ + repl = ""; /* this is how the event logger treats it... */ + } else { + repl = replbuff; + } + } + if((int)(buffpos + strlen(repl) + (pos - oldpos) + 1) > bufflen){ + free(replbuff); + return NULL; + } + strncpy(&(buff[buffpos]),oldpos, pos - oldpos); + buffpos += pos - oldpos; + strcpy(&(buff[buffpos]), repl); + buffpos += strlen(repl); + pos = end; + } + free(replbuff); + if(!replaced) + return NULL; + if((int) (buffpos + strlen(oldpos) + 1) > bufflen) + return NULL; + strcpy(&(buff[buffpos]),oldpos); + return buff; +} + +/* + * A lot to free when returning from format_message, lets make it easier + */ +static char *fm_free_up(char **argv, char *tmpbuff, + char * tmpbuff2, + HINSTANCE elibrary, + HINSTANCE plibrary){ + if(plibrary != NULL){ + FreeLibrary(plibrary); + while(*argv) + free(*argv++); + } + free(tmpbuff); + free(tmpbuff2); + if(elibrary != NULL) + FreeLibrary(elibrary); + return NULL; +} + +#define FM_RETURN(X) \ +return (fm_free_up(argv, tmpbuff, tmpbuff2, elibrary, plibrary), (X)) + +/* + * Formats an eventlog message into a string buffer. + * Returns NULL if message could not be formatted (buffer to small or + * library error). + */ +char *format_message(MessageFiles mf, DWORD id, + char *strings, int numstrings, + char *buff, int bufflen){ + char *argv[MAX_PARAM_STRINGS]; + int argc,i; + HINSTANCE elibrary = NULL; + HINSTANCE plibrary = NULL; + char *tmpbuff = malloc(bufflen); + char *tmpbuff2 = malloc(bufflen); + + for(argc=0;argc < numstrings && argc < MAX_PARAM_STRINGS - 1; ++argc) + argv[argc] = + (argc) ? argv[argc - 1] + strlen(argv[argc - 1]) + 1 : strings; + + argv[argc] = NULL; + + if((elibrary = LoadLibraryEx(mf.event, NULL, DONT_RESOLVE_DLL_REFERENCES)) + == NULL) + FM_RETURN(NULL); + + if(!FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_IGNORE_INSERTS | NL_TREATMENT, + (LPCVOID) elibrary, + id, + DEFAULT_LANGID, + tmpbuff2, + (DWORD) bufflen, + NULL)){ + FM_RETURN(NULL); + } + + if(mf.param != NULL) + plibrary = LoadLibraryEx(mf.param, NULL, DONT_RESOLVE_DLL_REFERENCES); + + if(plibrary){ + for(i=0;argv[i];++i) + if(expand_message(argv[i], plibrary, tmpbuff, bufflen) != NULL) + argv[i] = strdup(tmpbuff); + else + argv[i] = strdup(argv[i]); /* All gets malloced, so I don't have to + bother what to free... */ + if(expand_message(tmpbuff2, plibrary, tmpbuff, bufflen) != NULL) + strcpy(tmpbuff2,tmpbuff); + } + + if(!FormatMessage(FORMAT_MESSAGE_FROM_STRING | + FORMAT_MESSAGE_ARGUMENT_ARRAY | NL_TREATMENT, + (LPCVOID) tmpbuff2, + id, + DEFAULT_LANGID, + buff, + (DWORD) bufflen, + argv)){ + FM_RETURN(NULL); + } + + FM_RETURN(buff); + +} +#undef FM_RETURN diff --git a/lib/os_mon/c_src/nteventlog/elog_format.h b/lib/os_mon/c_src/nteventlog/elog_format.h new file mode 100644 index 0000000000..3fb19367ab --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_format.h @@ -0,0 +1,40 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#ifndef _ELOG_FORMAT_H +#define _ELOG_FORMAT_H +/* + * Module: elog_format + * Purpouse: Format messages in the eventlog. + * ToDo: Maximum buffersize is used... + */ + +#include "elog_global.h" + +char *format_message(MessageFiles mf, DWORD id, + char *strings, int numstrings, + char *buff, int bufflen); +/* + * Formats an eventlog message with the messagefiles + * in mf, the ID id, the stringarray strings, + * containing numstrings strings into buff. + * if bufflen is to small or anything else failes, + * the return value is NULL. + */ + +#endif /* _ELOG_FORMAT_H */ diff --git a/lib/os_mon/c_src/nteventlog/elog_global.h b/lib/os_mon/c_src/nteventlog/elog_global.h new file mode 100644 index 0000000000..f992b7184f --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_global.h @@ -0,0 +1,57 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#ifndef _ELOG_GLOBAL_H +#define _ELOG_GLOBAL_H +#include +#include +#include +#include +#include + +#define STRICT +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +#ifdef _DEBUG +/*#define HARDDEBUG*/ +#define DEBUG +#endif + +/* + * Compile time limits + */ +#define SMALLBUFSIZ 512 +#define BIGBUFSIZ 2048 +#define MAX_PARAM_STRINGS 200 +#define MAX_FACILITY_NAME 100 + +/* + * Structure containing message file names + */ +typedef struct _message_files { + char *event; + char *param; +} MessageFiles; + +/* + * How to get the default language + */ +#define DEFAULT_LANGID MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + +#endif /* _ELOG_GLOBAL_H */ diff --git a/lib/os_mon/c_src/nteventlog/elog_main.c b/lib/os_mon/c_src/nteventlog/elog_main.c new file mode 100644 index 0000000000..f79f32c8ef --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_main.c @@ -0,0 +1,504 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +/* + * Module: elog_main + * Purpouse: Main program and logic in the nteventlog program + * which waits for logging events and sends them to erlang. + * At startup, the registry keys for the identifier given on the + * command line are read to see what log record was the last + * displayed for this identifier. All "new" records are then + * displayed. After that the program waits for new events to arrive + * and displays them (sends them to erlang). If stdin is a pipe + * (not a console) an ACK is requested for every entry displayed. + * when the ACK is received, the registry entries for the identifier + * is updated, so that next time the program starts, only the new + * entries are displayed. + */ +#include "elog_global.h" +#include "elog_util.h" +#include "elog_pipe_stdin.h" +#include "elog_format.h" +#include "elog_registry.h" + +/* + * A race condition in the event log notification and the + * event log reading results in us having to retry reading the + * eventlog after some time. One second seems to do it... + */ +#define RETRY_TIMEOUT 1000 + +/* + * Constants for the logging formats. + */ +#define LOG_FORMAT "%s [%s] %s, %s: %s\n" +#define PIPE_LOG_FORMAT "%dH%s%dH%s%dH%s%dH%s%dH%s" +#define PIPE_LOG_ACK "A" +#define PIPE_LOG_EXTRA (5*10) /* 5 int */ +#define ACK_MAX 100 +#define TIME_FORMAT "%d-%b-%Y %H:%M:%S GMT" +/* 2 3 4 2 2 2 */ +#define TIME_BUFSIZ 25 +#define CANT_FORMAT_MESSAGE "[EventLogReader unable to format message text]" + +/* the default registry identification */ +#define DEFAULT_IDENT "DefaultIdent" + +/*#define HARDDEBUG*/ + +/* Flag set if eventlog is purged and need reopening.*/ +static int reopen_event_log = 0; + +/* + * Calculates the needed buffersize for a record in the eventlog. + * if recordnum == 0, looks at next record, otherwise looks + * at the record with the given number. + */ +static DWORD needed(HANDLE elog, DWORD recordnum){ + EVENTLOGRECORD dummy; + DWORD red, need; + DWORD last_error; + DWORD flags = + EVENTLOG_FORWARDS_READ | + ((recordnum) ? EVENTLOG_SEEK_READ : EVENTLOG_SEQUENTIAL_READ); +#ifdef HARDDEBUG + fprintf(stderr,"Calculating need when recordnum = %lu\n",recordnum); +#endif + if(!ReadEventLog(elog, + flags, + recordnum, + &dummy, + (DWORD) sizeof(EVENTLOGRECORD), + &red, + &need) && + (last_error = GetLastError()) == ERROR_INSUFFICIENT_BUFFER){ + return need; + } else if(last_error == ERROR_EVENTLOG_FILE_CHANGED){ + reopen_event_log = 1; + return (DWORD) 0; + } else { +#ifdef HARDDEBUG + output_error(last_error,"needed() failed to read eventlog"); +#endif + return (DWORD) 0; + } +} + + +/* + * Checks (any type of) stdin for end of file. + * Expects data present on stdin. + */ +BOOL eof_on_stdin(void){ + HANDLE in = GetStdHandle(STD_INPUT_HANDLE); + char x[1]; + DWORD y; + INPUT_RECORD input; + DWORD red; + static int state = 1; /* Return pressed = 1, ^Z after that = 2 */ + + if(!console_stdin()){ + return peek_pipe_stdin_eof(); + } + /* Console input, may be just about every type of event, look for + ^Z pressed... */ + if(!ReadConsoleInput(in,&input,1,&red)) + return FALSE; + if(input.EventType != KEY_EVENT) + return FALSE; + if(input.Event.KeyEvent.bKeyDown){ + switch(input.Event.KeyEvent.uChar.AsciiChar){ + case 0: + break; + case 13: + x[0] = '\r'; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"\r\n",2,&y,NULL); + if(state == 2) + return TRUE; + else + state = 1; + break; + case 26: + if(state == 1) + state = 2; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"^Z",2,&y,NULL); + break; + default: + if(((unsigned char) input.Event.KeyEvent.uChar.AsciiChar) < ' '){ + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"^",1,&y,NULL); + x[0] = input.Event.KeyEvent.uChar.AsciiChar + '@'; + } else + x[0] = input.Event.KeyEvent.uChar.AsciiChar; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),x,1,&y,NULL); + state = 0; + break; + } + return FALSE; + } + return FALSE; +} + +/* + * Writes eventlog entries to erlang and requires ACK for + * each record. + */ +BOOL data_to_pipe(char *string, char *answer, int answer_siz){ + unsigned char len[2]; + unsigned char *ptr; + int siz = strlen(string); + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE in = GetStdHandle(STD_INPUT_HANDLE); + DWORD written, red; + DWORD left; + + len[0] = (siz >> 8) & 0xFF; + len[1] = siz & 0xFF; + if(!WriteFile(out, len, 2, + &written, NULL) || written != 2) + return FALSE; + if(!WriteFile(out, string, siz, &written, NULL) || + written != (DWORD) siz) + return FALSE; + /* Read ACK from erlang */ + left = 2; + ptr = len; + for(;;){ + if(!(red = read_pipe_stdin(ptr, left))) + return FALSE; + else if(red < left){ + ptr += red; + left -= red; + } else { + break; + } + } + siz = len[0] << 8 | len[1]; + + if(siz >= answer_siz - 1) + return FALSE; + + left = siz; + ptr = (unsigned char *) answer; + for(;;){ + if(!(red = read_pipe_stdin(ptr, left))){ + return FALSE; + } else if(red < left){ + ptr += red; + left -= red; + } else { + break; + } + } + answer[siz] = '\0'; + return TRUE; +} + +/* + * The actual writing of records. + * Behaves differently if stdout is a pipe (erlang) + * or a console (test run). + */ + +BOOL output_record(char *category, EVENTLOGRECORD *event){ + char *strbeg, *fac, *sev; + char eventfilename[MAX_PATH]; + char paramfilename[MAX_PATH]; + char bigbuff[BIGBUFSIZ]; + MessageFiles mf; + char tbuff[TIME_BUFSIZ]; + BOOL ret; + DWORD written; + char *buff; + char ackbuff[ACK_MAX]; + + mf = get_messagefiles(category,((char *)event)+sizeof(EVENTLOGRECORD), + eventfilename, MAX_PATH, + paramfilename, MAX_PATH); + if(!mf.event){ + strcpy(bigbuff, CANT_FORMAT_MESSAGE); + } else { + strbeg = (char *) event; + strbeg += event->StringOffset; + if(!format_message(mf, event->EventID, + strbeg, event->NumStrings, bigbuff, BIGBUFSIZ)){ + strcpy(bigbuff, CANT_FORMAT_MESSAGE); + } + } + fac = ((char *)event)+sizeof(EVENTLOGRECORD); + sev = lookup_severity(event->EventType); + if(console_stdin()){ + *tbuff = '\0'; + strftime(tbuff, (size_t) TIME_BUFSIZ, TIME_FORMAT, + gmtime((time_t *)&(event->TimeGenerated))); + buff = + malloc(strlen(bigbuff) + TIME_BUFSIZ /* date-time */ + + strlen(category) + + strlen(fac) /* facility */ + + strlen(sev) /*severity */+ + strlen(LOG_FORMAT) /* More than actually needed */ + 1); + sprintf(buff, LOG_FORMAT, tbuff, fac, category, sev, bigbuff); + ret = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buff, strlen(buff), + &written, NULL); /* Overlapped structure for stdout? */ + free(buff); + } else { /* pipe */ + sprintf(tbuff,"%lu", event->TimeGenerated); + buff = malloc(strlen(bigbuff) + + strlen(tbuff) + + strlen(category) + + strlen(fac) + + strlen(sev) + + strlen(PIPE_LOG_FORMAT) + + PIPE_LOG_EXTRA); + sprintf(buff,PIPE_LOG_FORMAT, + strlen(tbuff),tbuff, + strlen(category), category, + strlen(fac), fac, + strlen(sev), sev, + strlen(bigbuff), bigbuff); + ret = data_to_pipe(buff,ackbuff, ACK_MAX); + if(ret && strcmp(ackbuff,PIPE_LOG_ACK)) + ret = FALSE; + free(buff); + } + return ret; +} + + +/* + * Read eventlog entries FOLLOWING the given record + * number and timestamp, and sends them to + * stdout. If timestamp does + * not correspond with record number, the + * log is concidered wrapped around + * and is reread from the beginning. + * time is ignored if 0. + * If record_number is 0, the whole log is read (if there is one). + * If the function is unsuccessful, the global variable + * reopen_event_log may be set to TRUE, which means + * that the eventlog has changed and has to be reopened. + * It's the callers responsibility to set reopen_event_log to + * 0 before calling. + */ +static int read_from(DWORD *record_number, DWORD *time, + HANDLE elog, char *category){ + DWORD dummy; + static EVENTLOGRECORD *event = NULL; + static DWORD eventsiz = 0; + DWORD red, need; + /* Always begin reading from record 1, record 0 never exists */ + DWORD tmp = (*record_number) ? *record_number : 1; + DWORD ttmp = *time; + int skip = !!(*record_number); /* Dont skip if record_number == 0 */ + int maybe_done = 0; + int i; + + /* First try seeking to the correct place in the eventlog */ + /* the variable skip tells us that we are rereading the */ + /* last read eventlog record and the variable */ + /* maybe_done tells us that we have read the last eventlog */ + /* and an EOF is OK. */ + for(;;){ + if(!(need = needed(elog, tmp))){ + if(maybe_done) /* Has read one correct record + and are satisfied with that. */ + return 0; + /* Hmm, could not find the record? Try the oldest... */ + if(!GetOldestEventLogRecord(elog, &tmp) || + !(need = needed(elog, tmp))){ + /* Something's terribly wrong. */ +#ifdef HARDDEBUG + fprintf(stderr,"Could not get oldest eventlog record!\n", + need); +#endif + return -1; + } + skip = 0; + } + /* need == number of bytes for this record, + tmp == this recordnumber */ + if(!event) + event = malloc(eventsiz = need); + else if(eventsiz < need) + event = realloc(event, eventsiz = need); + if(!ReadEventLog(elog, + (DWORD) (EVENTLOG_FORWARDS_READ | + EVENTLOG_SEEK_READ), + tmp, + event, + need, + &red, + &dummy)){ + if(GetLastError() == ERROR_EVENTLOG_FILE_CHANGED){ + reopen_event_log = 1; + } +#ifdef HARDDEBUG + output_error(GetLastError(), + "Failed first eventlog read in read_from\n"); +#endif + return -1; + } + if(skip){ + if(ttmp && event->TimeWritten != ttmp){ + /* Wrapped around eventlog */ + tmp = 1; + } else { + maybe_done = 1; + ++tmp; + } + skip = 0; + } else + break; + } + /* Got the first record in buffer, display and continue reading */ + /* sequentially from here. */ + for(i=1;;++i){ + if(!output_record(category, event)) + return -1; + *record_number = event->RecordNumber; + *time = event->TimeWritten; + if(!(need = needed(elog,(DWORD) 0))) + break; /* End of log */ + if(eventsiz < need) + event = realloc(event, eventsiz = need); + if(!ReadEventLog(elog, + (DWORD) EVENTLOG_FORWARDS_READ | + EVENTLOG_SEQUENTIAL_READ, + (DWORD) 0, + event, + need, + &red, + &dummy)){ + if(GetLastError() == ERROR_EVENTLOG_FILE_CHANGED){ + reopen_event_log = 1; + } + return -1; + } + } + return i; +} + +/* + * Read unread events and wait for new to arrive. + */ +int main(int argc, char **argv){ + HANDLE elog[NUM_CATEGORIES]; + HANDLE events[NUM_CATEGORIES + 1]; /* The stdin handle goes + in here to */ + int eventlen; + char *ident; + DWORD record = 0, time = 0; + RegKeys rk[NUM_CATEGORIES]; + int rklen = NUM_CATEGORIES; + int i; + int ret; + int x = 0; + int retry = 0; + + if(argc < 2){ + ident = DEFAULT_IDENT; + } else { + ident = argv[1]; + } + + if(!setup_pipe_stdin()){ + fprintf(stderr,"%s: Stdin could not be initialized.\n",argv[0]); + return 1; + } + if(get_regkeys(ident, rk, &rklen) != 0){ + fprintf(stderr, + "%s: Could not get/create registry parameters.\n", argv[0]); + return 1; + } + for(;;){ + for(i=0; i 0) + set_regkeys(ident, rk + i, 1); + } + eventlen = rklen; + events[eventlen] = get_stdin_event(); + ++eventlen; + + for(;;){ +#ifdef HARDDEBUG + fprintf(stderr,"Entering Wait...\n"); +#endif + reopen_event_log = 0; + ret = WaitForMultipleObjects(eventlen, + events, + FALSE, + (retry) ? + RETRY_TIMEOUT : + INFINITE); +#ifdef HARDDEBUG + fprintf(stderr,"Wait returned!\n"); +#endif + if(ret == WAIT_TIMEOUT){ + if(!retry){ + fprintf(stderr,"%s: Timeout when no such possible!\n", + argv[0]); + return 1; + } + retry = 0; + } else { + if(((int) (ret - WAIT_OBJECT_0)) >= rklen && eof_on_stdin()) + goto done; + retry = 1; + } + for(i=0;i 0) + set_regkeys(ident, rk + i, 1); + } + if(reopen_event_log) + break; + } + for(i=0; i < rklen; ++i){ + CloseEventLog(elog[i]); + CloseHandle(events[i]); + } + } +done: +#ifdef DEBUG + fprintf(stderr,"%s: EOF\n", argv[0]); +#endif + for(i=0; i < rklen; ++i) + CloseEventLog(elog[i]); + return 0; +} diff --git a/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.c b/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.c new file mode 100644 index 0000000000..c333c455a3 --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.c @@ -0,0 +1,151 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#include "elog_global.h" +#include "elog_pipe_stdin.h" + +/* + * Data for the handling of a pipe stdin, + * data is read in a separate thread, so locking and + * event signaling needs to be done. + */ + +static CRITICAL_SECTION io_crit; +static char *stdin_buff = NULL; +static int stdin_siz = 0; +static int stdin_len = 0; +static int stdin_eof = 0; +/* end syncronized objects */ +static int stdin_is_console = 0; +static HANDLE stdin_event; + +DWORD WINAPI stdin_thread(LPVOID ptr){ + HANDLE in = GetStdHandle(STD_INPUT_HANDLE); + char buff[1]; + DWORD red; + for(;;){ + if(!ReadFile(in, buff, (DWORD) 1, &red, NULL)){ + if(GetLastError() == ERROR_BROKEN_PIPE){ + EnterCriticalSection(&io_crit); + stdin_eof = 1; + SetEvent(stdin_event); + LeaveCriticalSection(&io_crit); + return 0; + } + return 1; + }else if(red == 0){ + EnterCriticalSection(&io_crit); + stdin_eof = 1; + SetEvent(stdin_event); + LeaveCriticalSection(&io_crit); + return 0; + } +#ifdef HARDDEBUG + fprintf(stderr,"stdin_thread go data (%d)\n",(int)*buff); +#endif + EnterCriticalSection(&io_crit); + if(stdin_len + 1 >= stdin_siz){ + if(!stdin_siz) + stdin_buff = malloc(stdin_siz = 100); + else + stdin_buff = realloc(stdin_buff, stdin_siz +=100); + } + stdin_buff[stdin_len++] = *buff; + SetEvent(stdin_event); + LeaveCriticalSection(&io_crit); + } + return 0; +} + +BOOL peek_pipe_stdin_eof(void){ + BOOL ret; + EnterCriticalSection(&io_crit); + if((ret = !!stdin_eof)) + ResetEvent(stdin_event); /* Now we "unsignal" */ + LeaveCriticalSection(&io_crit); + return ret; +} + +int read_pipe_stdin(char *buff, int max){ + int ret; + EnterCriticalSection(&io_crit); + if(stdin_len == 0){ + if(!stdin_eof){ + LeaveCriticalSection(&io_crit); + WaitForSingleObject(stdin_event,INFINITE); + EnterCriticalSection(&io_crit); + if(!stdin_len){ + if(stdin_eof){ + /* Stay signaled */ + LeaveCriticalSection(&io_crit); + return 0; + } else { + ResetEvent(stdin_event); + LeaveCriticalSection(&io_crit); + return -1; + } + } + } else { + /* Stay signaled */ + LeaveCriticalSection(&io_crit); + return 0; + } + } +#ifdef HARDDEBUG + fprintf(stderr,"read_pipe_stdin got data.\n" + "max = %d, stdin_len = %d, *stdin_buff = %d\n", + max,stdin_len,*stdin_buff); +#endif + /* stdin_len should be something now */ + if(stdin_len > max){ + memcpy(buff,stdin_buff,max); + memmove(stdin_buff,stdin_buff + max,stdin_len - max); + stdin_len -= max; + ret = max; + } else { + memcpy(buff,stdin_buff,stdin_len); + ret = stdin_len; + stdin_len = 0; + } + if(!stdin_eof) /* Stay signaled if EOF */ + ResetEvent(stdin_event); + LeaveCriticalSection(&io_crit); + return ret; +} + +BOOL setup_pipe_stdin(void){ + HANDLE in = GetStdHandle(STD_INPUT_HANDLE); + DWORD dummy; + if(GetConsoleMode(in, &dummy)){ + stdin_is_console = 1; + stdin_event = in; + return TRUE; + } + stdin_event = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&io_crit); + return (_beginthreadex(NULL,0,&stdin_thread,NULL,0,&dummy)); +} + +BOOL console_stdin(void){ + return stdin_is_console; +} + +HANDLE get_stdin_event(void){ + return stdin_event; +} + diff --git a/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.h b/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.h new file mode 100644 index 0000000000..a9a91b685f --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_pipe_stdin.h @@ -0,0 +1,90 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#ifndef _ELOG_PIPE_STDIN_H +#define _ELOG_PIPE_STDIN_H +/* + * Module: elog_pipe_stdin + * Purpouse: Read data from stdin when stdin is a pipe + * and deliver events only when data is really availabel or + * end of file is reached. + * If we would wait on an ordinary pipe handle, we + * would return immediately as it's always "signaled". + * some kind of asyncronous I/O in the win32 way is + * not possible as it's not supported on anonymous pipes + * (besides we have not opened the file ourselves so we + * cannot specify that we want async I/O...). + * ToDo: The reading is inneficcient, the buffering + * goes on forever, which would be dangerous if to much + * data was passed into stdin. The handling of + * Console stdin should be transparent instead of + * forcing the user of the module to check if this is a + * console for selecting between ReadFile and + * read_pipe_stdin. + * The handling of the event object is somewhat strange + * because I want to know about EOF before I've read + * to it. + */ + +BOOL peek_pipe_stdin_eof(void); +/* + * Returns TRUE if eof is reached, regardless of + * if there still is unread data in the buffer. + * Should not be called if console_stdin() returns TRUE. + * Resets the event object if it returns TRUE. + */ + +int read_pipe_stdin(char *buff, int max); +/* + * Reads from stdin, minimum 1 byte and + * maximum max bytes into buff. If EOF + * is reached and no bytes were read, + * the return value is 0. + * Should not be called if console_stdin() returns TRUE. + * The event object for stdin will get unsignaled if + * end of file is not reached (if peek_pipe_stdin_eof() + * would return false). + */ + +BOOL setup_pipe_stdin(void); +/* + * Initializes the module, returns TRUE if OK. + * If stdin is a console, no thread is created + * and the event objet returned by get_Stdin_event + * will be the console handle. + * Check if stdin was a console with the console_stdin() + * function. + */ + +BOOL console_stdin(void); +/* + * Returns true if stdin was a console, in which case + * normal Win32 console I/O functions have to + * be used. + * get_stdin_event() will return the console handle, + * which is signalled whenever an event reaches + * the console window (like mouse events etc). + */ + +HANDLE get_stdin_event(void); +/* + * Returns a event handle that can be waited upon with + * WaitForSingleObject and friends. It is possibly a console + * handle, see console_stdin(). + */ +#endif /* _ELOG_PIPE_STDIN_H */ diff --git a/lib/os_mon/c_src/nteventlog/elog_registry.c b/lib/os_mon/c_src/nteventlog/elog_registry.c new file mode 100644 index 0000000000..478db1e56b --- /dev/null +++ b/lib/os_mon/c_src/nteventlog/elog_registry.c @@ -0,0 +1,295 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. 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% + */ +#include "elog_global.h" +#include "elog_util.h" +#include "elog_registry.h" + +/* + * Constants for get/set_regkeys + */ +#define APP_ROOT_KEY "SOFTWARE\\Ericsson\\Erlang" +#define APP_SUB_KEY "EventLogReader" +#define APP_VERSION "1.0" +#define LATEST_RECORD_NAME "LatestRecord" +#define LATEST_TIME_NAME "LatestTime" +/* + * Constants for get_messagefiles + */ +#define LOG_KEY_TMPL "SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s" +#define EVENT_MSG_FILE_NAME "EventMessageFile" +#define PARAM_MSG_FILE_NAME "ParameterMessageFile" +MessageFiles get_messagefiles(const char *category, const char *facility, + char *eventbuff, int eventbufflen, + char *parambuff, int parambufflen){ + char *b1 = malloc(strlen(LOG_KEY_TMPL)+strlen(category)+strlen(facility)+1); + HKEY key; + char name[1024]; + char val[MAX_PATH]; + MessageFiles mf = { NULL, NULL }; + DWORD namelen, vallen, type, i, ret; + + if(!b1) + return mf; + sprintf(b1,LOG_KEY_TMPL,category,facility); + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, b1, 0, KEY_QUERY_VALUE, &key) != + ERROR_SUCCESS){ + free(b1); + return mf; + } + free(b1); + for(namelen=1024,vallen=MAX_PATH,i=0; + ERROR_SUCCESS == RegEnumValue(key, + i, + name, + &namelen, + NULL, + &type, + (LPBYTE) val, + &vallen); + namelen=1024,vallen=MAX_PATH,++i){ + if(!strcmp(name,EVENT_MSG_FILE_NAME)){ + if(type == REG_EXPAND_SZ){ + ret = ExpandEnvironmentStrings(val,eventbuff,eventbufflen); + if(((int) ret) > eventbufflen || !ret) + break; + } else { + if(((int) strlen(val)) >= eventbufflen) + break; + else + strcpy(eventbuff,val); + } + mf.event = eventbuff; + } else if(!strcmp(name,PARAM_MSG_FILE_NAME)){ + if(type == REG_EXPAND_SZ){ + ret = ExpandEnvironmentStrings(val,parambuff,parambufflen); + if(((int) ret) > parambufflen || !ret) + break; + } else { + if(((int) strlen(val)) >= parambufflen) + break; + else + strcpy(parambuff,val); + } + mf.param = parambuff; + } + } + RegCloseKey(key); + return mf; +} + + +int create_regkeys(char *identifier){ + HKEY key,key2; + DWORD dispositions; + int i,j; + char *values[] = { + LATEST_RECORD_NAME, + LATEST_TIME_NAME, + NULL + }; + + DWORD zero = 0; + + if(RegCreateKeyEx(HKEY_LOCAL_MACHINE, + APP_ROOT_KEY "\\" APP_SUB_KEY "\\" + APP_VERSION, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_CREATE_SUB_KEY, + NULL, + &key, + &dispositions) != ERROR_SUCCESS){ + return -1; + } + if(RegCreateKeyEx(key, + identifier, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_CREATE_SUB_KEY, + NULL, + &key2, + &dispositions)){ + RegCloseKey(key); + return -1; + } + RegCloseKey(key); + for(i=0; category_tab[i] != NULL; ++i){ + if(RegCreateKeyEx(key2, + category_tab[i], + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_SET_VALUE, + NULL, + &key, + &dispositions) != ERROR_SUCCESS){ + RegCloseKey(key2); + return -1; + } + for(j=0; values[j] != NULL; ++j){ + if(RegSetValueEx(key, + values[j], + 0, + REG_DWORD, + (BYTE *) &zero, + sizeof(DWORD)) != ERROR_SUCCESS){ + RegCloseKey(key); + RegCloseKey(key2); + return -1; + } + } + RegCloseKey(key); + } + RegCloseKey(key2); + return 0; +} + + +int set_regkeys(char *identifier, RegKeys *keys, int num_keys){ + HKEY key; + char knbuff[SMALLBUFSIZ]; + int i; + for(i=0; i +#include +#include +#include +#include +#include +#include +#include +#include "winbase.h" + +#define MEM_INFO 'm' +#define DISK_INFO 'd' +#define OK "o" + +#define ERLIN_FD 0 +#define ERLOUT_FD 1 + + +typedef BOOL (WINAPI *tfpGetDiskFreeSpaceEx)(LPCTSTR, PULARGE_INTEGER,PULARGE_INTEGER,PULARGE_INTEGER); + +static tfpGetDiskFreeSpaceEx fpGetDiskFreeSpaceEx; + +static void +return_answer(char* value) +{ + int left, bytes, res; + + bytes = strlen(value); /* Skip trailing zero */ + + res = write(1,(char*) &bytes,1); + if (res != 1) { + fprintf(stderr,"win32sysinfo:Error writing to pipe"); + exit(1); + } + + left = bytes; + + while (left > 0) + { + res = write(1, value+bytes-left, left); + if (res <= 0) + { + fprintf(stderr,"win32sysinfo:Error writing to pipe"); + exit(1); + } + left -= res; + } +} + +void output_drive_info(char* drive){ + ULARGE_INTEGER availbytes,totbytesfree,totbytes; + OSVERSIONINFO osinfo; + char answer[512]; + osinfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO); + GetVersionEx(&osinfo); + switch (GetDriveType(drive)) { + case DRIVE_UNKNOWN: + sprintf(answer,"%s DRIVE_UNKNOWN 0 0 0\n",drive); + return_answer(answer); + break; + case DRIVE_NO_ROOT_DIR: + sprintf(answer,"%s DRIVE_NO_ROOT_DIR 0 0 0\n",drive); + return_answer(answer); + break; + case DRIVE_REMOVABLE: + sprintf(answer,"%s DRIVE_REMOVABLE 0 0 0\n",drive); + return_answer(answer); + break; + case DRIVE_FIXED: + /* if ((osinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && + (LOWORD(osinfo.dwBuildNumber) <= 1000)) { + sprintf(answer,"%s API_NOT_SUPPORTED 0 0 0\n",drive); + return_answer(answer); + } + else + */ + if (fpGetDiskFreeSpaceEx == NULL){ + sprintf(answer,"%s API_NOT_SUPPORTED 0 0 0\n",drive); + return_answer(answer); + } + else + if (fpGetDiskFreeSpaceEx(drive,&availbytes,&totbytes,&totbytesfree)){ + sprintf(answer,"%s DRIVE_FIXED %I64u %I64u %I64u\n",drive,availbytes,totbytes,totbytesfree); + return_answer(answer); + } + else { + sprintf(answer,"%s API_ERROR 0 0 0\n",drive); + return_answer(answer); + } + break; + case DRIVE_REMOTE: + sprintf(answer,"%s DRIVE_REMOTE 0 0 0\n",drive); + return_answer(answer); + break; + case DRIVE_CDROM: + sprintf(answer,"%s DRIVE_CDROM 0 0 0\n",drive); + return_answer(answer); + break; + case DRIVE_RAMDISK: + sprintf(answer,"%s DRIVE_RAMDISK 0 0 0\n",drive); + return_answer(answer); + break; + default: + sprintf(answer,"%s DRIVE_NOT_EXIST 0 0 0\n",drive); + return_answer(answer); + } /* switch */ +} + +int load_if_possible() { + HINSTANCE lh; + if((lh = LoadLibrary("KERNEL32")) ==NULL) + return 0; /* error */ + if ((fpGetDiskFreeSpaceEx = + (tfpGetDiskFreeSpaceEx) GetProcAddress(lh,"GetDiskFreeSpaceExA")) ==NULL) + return GetLastError(); /* error */ + return 1; +} + +void get_disk_info_all(){ + DWORD dwNumBytesForDriveStrings; + char DriveStrings[255]; + char* dp = DriveStrings; + + dwNumBytesForDriveStrings = GetLogicalDriveStrings(254,dp); + if (dwNumBytesForDriveStrings != 0) { + /* GetLogicalDriveStringsIs supported on this platform */ + while (*dp != 0) { + output_drive_info(dp); + dp = strchr(dp,0) +1; + } + } + else { + /* GetLogicalDriveStrings is not supported (some old W95) */ + DWORD dwDriveMask = GetLogicalDrives(); + int nDriveNum; + char drivename[]="A:\\"; + /*printf("DriveName95 DriveType BytesAvail BytesTotal BytesTotalFree\n");*/ + for (nDriveNum = 0; dwDriveMask != 0;nDriveNum++) { + if (dwDriveMask & 1) { + drivename[0]='A'+ nDriveNum; + output_drive_info(drivename); + } + dwDriveMask = dwDriveMask >> 1; + } + } +} + +void get_avail_mem_ext() { + char answer[512]; + MEMORYSTATUSEX ms; + ms.dwLength=sizeof(MEMORYSTATUSEX); + GlobalMemoryStatusEx(&ms); + sprintf(answer,"%d %I64d %I64d %I64d %I64d %I64d %I64d\n", + ms.dwMemoryLoad, + ms.ullTotalPhys, + ms.ullAvailPhys, + ms.ullTotalPageFile, + ms.ullAvailPageFile, + ms.ullTotalVirtual, + ms.ullAvailVirtual + ); + return_answer(answer); + /* + DWORD dwLength; + DWORD dwMemoryLoad; + DWORDLONG ullTotalPhys; + DWORDLONG ullAvailPhys; + DWORDLONG ullTotalPageFile; + DWORDLONG ullAvailPageFile; + DWORDLONG ullTotalVirtual; + DWORDLONG ullAvailVirtual; + */ +} + +static void +message_loop() +{ + char cmdLen; + char cmd[512]; + int res; + + fprintf(stderr,"in message_loop\n"); + /* Startup ACK. */ + return_answer(OK); + while (1) + { + /* + * Wait for command from Erlang + */ + if ((res = read(0, &cmdLen, 1)) < 0) { + fprintf(stderr,"win32sysinfo:Error reading from Erlang."); + return; + } + + if (res != 1){ /* Exactly one byte read ? */ + fprintf(stderr,"win32sysinfo:Erlang has closed."); + return; + } + if ((res = read(0, &cmd, cmdLen)) == cmdLen){ + if (cmdLen == 1) { + switch (cmd[0]) { + case MEM_INFO: + get_avail_mem_ext(); + return_answer(OK); + break; + case DISK_INFO: + get_disk_info_all(); + return_answer(OK); + break; + default: /* ignore all other messages */ + break; + } /* switch */ + } + else + if ((res > 0) && (cmd[0]==DISK_INFO)) { + cmd[cmdLen] = 0; + output_drive_info(&cmd[1]); + return_answer("OK"); + return; + } + else + return_answer("xEND"); + } + else if (res == 0) { + fprintf(stderr,"win32sysinfo:Erlang has closed."); + return; + } + else { + fprintf(stderr,"win32sysinfo:Error reading from Erlang."); + return; + } + } +} + +int main(int argc, char ** argv){ + + _setmode(0, _O_BINARY); + _setmode(1, _O_BINARY); + load_if_possible(); + message_loop(); + return 0; +} + + + + + + + diff --git a/lib/os_mon/doc/html/.gitignore b/lib/os_mon/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/doc/man3/.gitignore b/lib/os_mon/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/doc/man6/.gitignore b/lib/os_mon/doc/man6/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/doc/pdf/.gitignore b/lib/os_mon/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/doc/src/Makefile b/lib/os_mon/doc/src/Makefile new file mode 100644 index 0000000000..c9765749c9 --- /dev/null +++ b/lib/os_mon/doc/src/Makefile @@ -0,0 +1,124 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(OS_MON_VSN) +APPLICATION=os_mon + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml +XML_REF3_FILES = cpu_sup.xml \ + disksup.xml \ + memsup.xml \ + os_mon_mib.xml \ + os_sup.xml \ + nteventlog.xml + +XML_REF6_FILES = os_mon_app.xml + +XML_PART_FILES = part_notes.xml +XML_CHAPTER_FILES = notes.xml + +BOOK_FILES = book.xml + +GIF_FILES = \ + note.gif \ + warning.gif + +XML_FILES = \ + $(BOOK_FILES) $(XML_CHAPTER_FILES) \ + $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) $(XML_APPLICATION_FILES) + +# ---------------------------------------------------- + +HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) + +INFO_FILE = ../../info + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) + +MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6) + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +$(HTMLDIR)/%.gif: %.gif + $(INSTALL_DATA) $< $@ + +docs: pdf html man + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + +man: $(MAN3_FILES) $(MAN6_FILES) + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) + +debug opt: + +clean clean_docs: + rm -rf $(HTMLDIR)/* + rm -f $(MAN3DIR)/* + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTMLDIR)/* \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 + $(INSTALL_DIR) $(RELEASE_PATH)/man/man6 + $(INSTALL_DATA) $(MAN6DIR)/* $(RELEASE_PATH)/man/man6 + +release_spec: + diff --git a/lib/os_mon/doc/src/book.xml b/lib/os_mon/doc/src/book.xml new file mode 100644 index 0000000000..17645b81fd --- /dev/null +++ b/lib/os_mon/doc/src/book.xml @@ -0,0 +1,45 @@ + + + + +
+ + 19972009 + Ericsson AB. 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. + + + + OS_Mon + + + + +
+ + + OS_Mon + + + + + + + + + + + +
+ diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml new file mode 100644 index 0000000000..7b28083fbc --- /dev/null +++ b/lib/os_mon/doc/src/cpu_sup.xml @@ -0,0 +1,281 @@ + + + + +
+ + 19972009 + Ericsson AB. 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. + + + + cpu_sup + + + + +
+ cpu_sup + A CPU Load and CPU Utilization Supervisor Process + +

cpu_sup is a process which supervises the CPU load + and CPU utilization. It is part of the OS_Mon application, see + os_mon(6). Available for Unix, + although CPU utilization values (util/0,1) are only + available for Solaris and Linux.

+

The load values are proportional to how long time a runnable + Unix process has to spend in the run queue before it is scheduled. + Accordingly, higher values mean more system load. The returned + value divided by 256 produces the figure displayed by rup + and top. What is displayed as 2.00 in rup, is + displayed as load up to the second mark in xload.

+

For example, rup displays a load of 128 as 0.50, and + 512 as 2.00.

+

If the user wants to view load values as percentage of machine + capacity, then this way of measuring presents a problem, because + the load values are not restricted to a fixed interval. In this + case, the following simple mathematical transformation can + produce the load value as a percentage:

+ + PercentLoad = 100 * (1 - D/(D + Load)) + +

D determines which load value should be associated with + which percentage. Choosing D = 50 means that 128 is 60% + load, 256 is 80%, 512 is 90%, and so on.

+

Another way of measuring system load is to divide the number of + busy CPU cycles by the total number of CPU cycles. This produces + values in the 0-100 range immediately. However, this method hides + the fact that a machine can be more or less saturated. CPU + utilization is therefore a better name than system load for this + measure.

+

A server which receives just enough requests to never become + idle will score a CPU utilization of 100%. If the server receives + 50% more requests, it will still scores 100%. When the system load + is calculated with the percentage formula shown previously, + the load will increase from 80% to 87%.

+

The avg1/0, avg5/0, and avg15/0 functions + can be used for retrieving system load values, and + the util/0 and util/1 functions can be used for + retrieving CPU utilization values.

+

When run on Linux, cpu_sup assumes that the /proc + file system is present and accessible by cpu_sup. If it is + not, cpu_sup will terminate.

+
+ + + nprocs() -> UnixProcesses | {error, Reason} + Get the number of UNIX processes running on this host + + UnixProcesses = int() + Reason = term() + + +

Returns the number of UNIX processes running on this machine. + This is a crude way of measuring the system load, but it may + be of interest in some cases.

+

Returns 0 if cpu_sup is not available.

+
+
+ + avg1() -> SystemLoad | {error, Reason} + Get the system load average for the last minute + + SystemLoad = int() + Reason = term() + + +

Returns the average system load in the last minute, as + described above. 0 represents no load, 256 represents the load + reported as 1.00 by rup.

+

Returns 0 if cpu_sup is not available.

+
+
+ + avg5() -> SystemLoad | {error, Reason} + Get the system load average for the last five minutes + + SystemLoad = int() + Reason = term() + + +

Returns the average system load in the last five minutes, as + described above. 0 represents no load, 256 represents the load + reported as 1.00 by rup.

+

Returns 0 if cpu_sup is not available.

+
+
+ + avg15() -> SystemLoad | {error, Reason} + Get the system load average for the last fifteen minutes + + SystemLoad = int() + Reason = term() + + +

Returns the average system load in the last 15 minutes, as + described above. 0 represents no load, 256 represents the load + reported as 1.00 by rup.

+

Returns 0 if cpu_sup is not available.

+
+
+ + util() -> CpuUtil | {error, Reason} + Get the CPU utilization + + CpuUtil = float() + Reason = term() + + +

Returns CPU utilization since the last call to + util/0 or util/1 by the calling process.

+ +

The returned value of the first call to util/0 or + util/1 by a process will on most systems be the CPU + utilization since system boot, but this is not guaranteed + and the value should therefore be regarded as garbage. This + also applies to the first call after a restart of + cpu_sup.

+
+

The CPU utilization is defined as the sum of the percentage + shares of the CPU cycles spent in all busy processor states + (see util/1 below) in average on all CPUs.

+

Returns 0 if cpu_sup is not available.

+
+
+ + util(Opts) -> UtilSpec | {error, Reason} + Get the CPU utilization + + Opts = [detailed | per_cpu] + UtilSpec = UtilDesc | [UtilDesc] +  UtilDesc = {Cpus, Busy, NonBusy, Misc} +   Cpus = all | int() | [int()]() +   Busy = NonBusy = {State, Share} | Share +    State = user | nice_user | kernel +     | wait | idle | atom() +    Share = float() +   Misc = [] + Reason = term() + + +

Returns CPU utilization since the last call to + util/0 or util/1 by the calling process, in + more detail than util/0.

+ +

The returned value of the first call to util/0 or + util/1 by a process will on most systems be the CPU + utilization since system boot, but this is not guaranteed + and the value should therefore be regarded as garbage. This + also applies to the first call after a restart of + cpu_sup.

+
+

Currently recognized options:

+ + detailed + +

The returned UtilDesc(s) will be even more + detailed.

+
+ per_cpu + +

Each CPU will be specified separately (assuming this + information can be retrieved from the operating system), + that is, a list with one UtilDesc per CPU will be + returned.

+
+
+

Description of UtilDesc = {Cpus, Busy, NonBusy, Misc}:

+ + Cpus + +

If the detailed and/or per_cpu option is + given, this is the CPU number, or a list of the CPU + numbers.

+

If not, this is the atom all which implies that + the UtilDesc contains information about all CPUs.

+
+ Busy + +

If the detailed option is given, this is a list + of {State, Share} tuples, where each tuple + contains information about a processor state that has + been identified as a busy processor state (see below). + The atom State is the name of the state, and + the float Share represents the percentage share of + the CPU cycles spent in this state since the last call to + util/0 or util/1.

+

If not, this is the sum of the percentage shares of + the CPU cycles spent in all states identified as busy.

+

If the per_cpu is not given, the value(s) + presented are the average of all CPUs.

+
+ NonBusy + +

Similar to Busy, but for processor states that + have been identified as non-busy (see below).

+
+ Misc + +

Currently unused; reserved for future use.

+
+
+

Currently these processor states are identified as busy:

+ + user + +

Executing code in user mode.

+
+ nice_user + +

Executing code in low priority (nice) user mode. + This state is currently only identified on Linux.

+
+ kernel + +

Executing code in kernel mode.

+
+
+

Currently these processor states are identified as non-busy:

+ + wait + +

Waiting. This state is currently only identified on + Solaris.

+
+ idle + +

Idle.

+
+
+ +

Identified processor states may be different on different + operating systems and may change between different versions + of cpu_sup on the same operating system. The sum of + the percentage shares of the CPU cycles spent in all busy + and all non-busy processor states will always add up to + 100%, though.

+
+

Returns {all,0,0,[]} if cpu_sup is not + available.

+
+
+
+ +
+ See Also +

os_mon(3)

+
+
+ diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml new file mode 100644 index 0000000000..bfa3d3578f --- /dev/null +++ b/lib/os_mon/doc/src/disksup.xml @@ -0,0 +1,161 @@ + + + + +
+ + 19962009 + Ericsson AB. 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. + + + + disksup + + + + +
+ disksup + A Disk Supervisor Process + +

disksup is a process which supervises the available disk + space in the system. It is part of the OS_Mon application, see + os_mon(6). Available for Unix + and Windows.

+

Periodically checks the disks. For each disk or partition which + uses more than a certain amount of the available space, the alarm + {{disk_almost_full, MountedOn}, []} is set.

+ + On Unix + +

All (locally) mounted disks are checked, including the swap + disk if it is present.

+
+ On WIN32 + +

All logical drives of type "FIXED_DISK" are checked.

+
+
+

Alarms are reported to the SASL alarm handler, see + alarm_handler(3). + To set an alarm, alarm_handler:set_alarm(Alarm) is called + where Alarm is the alarm specified above.

+

The alarms are cleared automatically when the alarm cause is no + longer valid.

+
+ +
+ + Configuration +

The following configuration parameters can be used to change + the default values for time interval and threshold:

+ + disk_space_check_interval = int()>0 + +

The time interval, in minutes, for the periodic disk space + check. The default is 30 minutes.

+
+ disk_almost_full_threshold = float() + +

The threshold, as percentage of total disk space, for how + much disk can be utilized before the disk_almost_full + alarm is set. The default is 0.80 (80%).

+
+
+

See config(4) for + information about how to change the value of configuration + parameters.

+
+ + + get_disk_data() -> [DiskData] + Get data for the disks in the system + + DiskData = {Id, KByte, Capacity} +  Id = string() +  KByte = int() +  Capacity = int() + + +

Returns the result of the latest disk check. Id is a + string that identifies the disk or partition. KByte is + the total size of the disk or partition in kbytes. + Capacity is the percentage of disk space used.

+

The function is asynchronous in the sense that it does not + invoke a disk check, but returns the latest available value.

+

Returns [{"none",0,0}] if disksup is not + available.

+
+
+ + get_check_interval() -> MS + Get time interval, in milliseconds, for the periodic disk space check + + MS = int() + + +

Returns the time interval, in milliseconds, for the periodic + disk space check.

+
+
+ + set_check_interval(Minutes) -> ok + Set time interval, in minutes, for the periodic disk space check + + Minutes = int()>=1 + + +

Changes the time interval, given in minutes, for the periodic + disk space check.

+

The change will take effect after the next disk space check + and is non-persist. That is, in case of a process restart, + this value is forgotten and the default value will be used. + See Configuration above.

+
+
+ + get_almost_full_threshold() -> Percent + Get threshold, in percent, for disk space utilization + + Percent = int() + + +

Returns the threshold, in percent, for disk space utilization.

+
+
+ + set_almost_full_threshold(Float) -> ok + Set threshold, as percentage represented by a float, for disk space utilization + + Float = float(), 0=<Float=<1 + + +

Changes the threshold, given as a float, for disk space + utilization.

+

The change will take effect during the next disk space check + and is non-persist. That is, in case of a process restart, + this value is forgotten and the default value will be used. + See Configuration above.

+
+
+
+ +
+ See Also +

alarm_handler(3), + os_mon(3)

+
+
+ diff --git a/lib/os_mon/doc/src/fascicules.xml b/lib/os_mon/doc/src/fascicules.xml new file mode 100644 index 0000000000..43090b4aed --- /dev/null +++ b/lib/os_mon/doc/src/fascicules.xml @@ -0,0 +1,15 @@ + + + + + + Reference Manual + + + Release Notes + + + Off-Print + + + diff --git a/lib/os_mon/doc/src/make.dep b/lib/os_mon/doc/src/make.dep new file mode 100644 index 0000000000..b657f2e036 --- /dev/null +++ b/lib/os_mon/doc/src/make.dep @@ -0,0 +1,21 @@ +# ---------------------------------------------------- +# >>>> Do not edit this file <<<< +# This file was automaticly generated by +# /home/otp/bin/docdepend +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# TeX files that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: book.tex cpu_sup.tex disksup.tex memsup.tex \ + nteventlog.tex os_mon.tex os_mon_mib.tex os_sup.tex \ + ref_man.tex + +# ---------------------------------------------------- +# Source inlined when transforming from source to LaTeX +# ---------------------------------------------------- + +book.tex: ref_man.xml + diff --git a/lib/os_mon/doc/src/memsup.xml b/lib/os_mon/doc/src/memsup.xml new file mode 100644 index 0000000000..67d617375e --- /dev/null +++ b/lib/os_mon/doc/src/memsup.xml @@ -0,0 +1,328 @@ + + + + +
+ + 19962009 + Ericsson AB. 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. + + + + memsup + + + + +
+ memsup + A Memory Supervisor Process + +

memsup is a process which supervises the memory usage for + the system and for individual processes. It is part of the OS_Mon + application, see os_mon(6). + Available for Unix, Windows and VxWorks.

+

Periodically performs a memory check:

+ + +

If more than a certain amount of available system memory is + allocated, as reported by the underlying operating system, + the alarm {system_memory_high_watermark, []} is set.

+
+ +

If any Erlang process Pid in the system has allocated + more than a certain amount of total system memory, the alarm + {process_memory_high_watermark, Pid} is set.

+
+
+

Alarms are reported to the SASL alarm handler, see + alarm_handler(3). + To set an alarm, alarm_handler:set_alarm(Alarm) is called + where Alarm is either of the alarms specified above.

+

The alarms are cleared automatically when the alarm cause is no + longer valid.

+

The function + get_memory_data() + can be used to retrieve the result of the latest periodic memory + check.

+

There is also a interface to system dependent memory data, + get_system_memory_data(). + The result is highly dependent on the underlying operating + system and the interface is targeted primarily for systems + without virtual memory (e.g. VxWorks). The output on other + systems is however still valid, although sparse.

+

A call to get_system_memory_data/0 is more costly + than a call to get_memory_data/0 as data is collected + synchronously when this function is called.

+

The total system memory reported under UNIX is the number of + physical pages of memory times the page size, and the available + memory is the number of available physical pages times the page + size. This is a reasonable measure as swapping should be avoided + anyway, but the task of defining total memory and available + memory is difficult because of virtual memory and swapping.

+
+ +
+ + Configuration +

The following configuration parameters can be used to change + the default values for time intervals and thresholds:

+ + memory_check_interval = int()>0 + +

The time interval, in minutes, for the periodic memory check. + The default is one minute.

+
+ system_memory_high_watermark = float() + +

The threshold, as percentage of system memory, for how much + system memory can be allocated before the corresponding alarm + is set. The default is 0.80 (80%).

+
+ process_memory_high_watermark = float() + +

The threshold, as percentage of system memory, for how much + system memory can be allocated by one Erlang process before + the corresponding alarm is set. The default is 0.05 (5%).

+
+ memsup_helper_timeout = int()>0 + +

A timeout, in seconds, for how long the memsup + process should wait for a result from a memory check. If + the timeout expires, a warning message "OS_MON (memsup) timeout" is issued via error_logger and any + pending, synchronous client calls will return a dummy value. + Normally, this situation should not occur. There have been + cases on Linux, however, where the pseudo file from which + system data is read is temporarily unavailable when the system + is heavily loaded.

+

The default is 30 seconds.

+
+ memsup_system_only = bool() + +

Specifies whether the memsup process should only + check system memory usage (true) or not. The default is + false, meaning that information regarding both system + memory usage and Erlang process memory usage is collected.

+

It is recommended to set this parameter to false on + systems with many concurrent processes, as each process memory + check makes a traversal of the entire list of processes.

+
+
+

See config(4) for + information about how to change the value of configuration + parameters.

+
+ + + get_memory_data() -> {Total,Allocated,Worst} + Get data for the memory in the system + + Total = Allocated = int() + Worst = {Pid, PidAllocated} | undefined +  Pid = pid() +  PidAllocated = int() + + +

Returns the result of the latest memory check, where + Total is the total memory size and Allocated + the allocated memory size, in bytes.

+

Worst is the pid and number of allocated bytes of + the largest Erlang process on the node. If memsup + should not collect process data, that is if the configuration + parameter memsup_system_only was set to true, + Worst is undefined.

+

The function is normally asynchronous in the sense that it + does not invoke a memory check, but returns the latest + available value. The one exception if is the function is + called before a first memory check is finished, in which case + it does not return a value until the memory check is finished.

+

Returns {0,0,{pid(),0}} or {0,0,undefined} if + memsup is not available, or if all memory checks so far + have timed out.

+
+
+ + get_system_memory_data() -> MemDataList + Get system dependent memory data + + MemDataList = [{Tag, Size}] +  Tag = atom() +  Size = int() + + +

Invokes a memory check and returns the resulting, system + dependent, data as a list of tagged tuples, where Tag + can be one of the following:

+ + total_memory + The total amount of memory available to the Erlang emulator, + allocated and free. May or may not be equal to the amount + of memory configured in the system. + free_memory + The amount of free memory available to the Erlang emulator + for allocation. + system_total_memory + The amount of memory available to the whole operating + system. This may well be equal to total_memory but + not necessarily. + largest_free + The size of the largest contiguous free memory block + available to the Erlang emulator. + number_of_free + The number of free blocks available to the Erlang runtime + system. This gives a fair indication of how fragmented + the memory is. + buffered_memory + + The amount of memory the system uses for temporary storing raw disk blocks. + + cached_memory + + The amount of memory the system uses for cached files read from disk. + + total_swap + + The amount of total amount of memory the system has available + for disk swap. + + free_swap + + The amount of memory the system has available for disk swap. + + + +

All memory sizes are presented as number of bytes.

+

The largest_free and number_of_free tags are + currently only returned on a VxWorks system.

+

Returns the empty list [] if memsup is not available, + or if the memory check times out.

+

+ On linux the memory available to the emulator is cached_memory and buffered_memory in addition to + free_memory.

+
+
+
+ + get_os_wordsize() -> Wordsize + Get the wordsize of running os. + + Wordsize = 32 | 64 | unsupported_os + + +

Returns the wordsize of the current running operating system.

+
+
+ + get_check_interval() -> MS + Get time interval, in milliseconds, for the periodic memory check + + MS = int() + + +

Returns the time interval, in milliseconds, for the periodic + memory check.

+
+
+ + set_check_interval(Minutes) -> ok + Set time interval, in minutes, for the periodic memory check + + Minutes = int()>0 + + +

Changes the time interval, given in minutes, for the periodic + memory check.

+

The change will take effect after the next memory check and is + non-persistent. That is, in case of a process restart, this + value is forgotten and the default value will be used. See + Configuration above.

+
+
+ + get_procmem_high_watermark() -> int() + Get threshold, in percent, for process memory allocation + +

Returns the threshold, in percent, for process memory + allocation.

+
+
+ + set_procmem_high_watermark(Float) -> ok + Set threshold, as percentage represented by a float, for process memory allocation + +

Changes the threshold, given as a float, for process memory + allocation.

+

The change will take effect during the next periodic memory + check and is non-persistent. That is, in case of a process + restart, this value is forgotten and the default value will be + used. See Configuration + above.

+
+
+ + get_sysmem_high_watermark() -> int() + Get threshold, in percent, for system memory allocation + +

Returns the threshold, in percent, for system memory + allocation.

+
+
+ + set_sysmem_high_watermark(Float) -> ok + Set threshold, given as a float, for system memory allocation + +

Changes the threshold, given as a float, for system memory + allocation.

+

The change will take effect during the next periodic memory + check and is non-persistent. That is, in case of a process + restart, this value is forgotten and the default value will be + used. See Configuration + above.

+
+
+ + get_helper_timeout() -> Seconds + Get the timeout value, in seconds, for memory checks + + Seconds = int() + + +

Returns the timeout value, in seconds, for memory checks.

+
+
+ + set_helper_timeout(Seconds) -> ok + Set the timeout value, in seconds, for memory checks + + Seconds = int() (>= 1) + + +

Changes the timeout value, given in seconds, for memory + checks.

+

The change will take effect for the next memory check and is + non-persistent. That is, in the case of a process restart, this + value is forgotten and the default value will be used. See + Configuration above.

+
+
+
+ +
+ See Also +

alarm_handler(3), + os_mon(3)

+
+
+ diff --git a/lib/os_mon/doc/src/note.gif b/lib/os_mon/doc/src/note.gif new file mode 100644 index 0000000000..6fffe30419 Binary files /dev/null and b/lib/os_mon/doc/src/note.gif differ diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml new file mode 100644 index 0000000000..1a8551f57d --- /dev/null +++ b/lib/os_mon/doc/src/notes.xml @@ -0,0 +1,537 @@ + + + + +
+ + 20042009 + Ericsson AB. 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. + + + + OS_Mon Release Notes + otp_appnotes + nil + nil + nil + notes.xml +
+

This document describes the changes made to the OS_Mon application.

+ +
Os_Mon 2.2.4 + +
Fixed Bugs and Malfunctions + + +

+ Memsup did not read memory correctly on MaxOSX + Snowleopard. This has now been corrected. (Thanks to Joel + Reymont)

+

+ Own Id: OTP-8211

+
+ +

+ Removed unused code in cpu_sup.erl.

+

+ Own Id: OTP-8226

+
+
+
+ + +
Improvements and New Features + + +

+ The documentation is now built with open source tools + (xsltproc and fop) that exists on most platforms. One + visible change is that the frames are removed.

+

+ Own Id: OTP-8201

+
+
+
+ +
+ +
Os_Mon 2.2.3 + +
Fixed Bugs and Malfunctions + + +

+ A missing define in memsup.c caused a build error + on IRIX machines. This has now been fixed.

+

+ Own Id: OTP-8094

+
+
+
+ +
+ +
Os_Mon 2.2.2 + +
Fixed Bugs and Malfunctions + + +

+ disksup:get_disk_data/0 returned disk volume in bytes + instead of kbytes as stated in the documentation. The + problem occurred on Windows only and is now corrected.

+

+ *** POTENTIAL INCOMPATIBILITY ***

+

+ Own Id: OTP-7741

+
+
+
+ +
+ +
Os_Mon 2.2.1 + +
Fixed Bugs and Malfunctions + + +

An error in memsup could cause os_mon to + report erroneous memory values on windows for ranges of + memory between 2GB and 4GB. This have now been fixed.

+

+ Own Id: OTP-7944

+
+
+
+ + +
Improvements and New Features + + +

Added support for dragonfly OS.

+

+ Own Id: OTP-7938

+
+
+
+ +
+ +
Os_Mon 2.2 + +
Improvements and New Features + + +

The copyright notices have been updated.

+

+ Own Id: OTP-7851

+
+
+
+ +
+ +
Os_Mon 2.1.8 + +
Fixed Bugs and Malfunctions + + +

A problem with OTP-OS-MON-MIB.mib for 64-bit + environments has now been fixed. The mib has been + extended with 64-bit memory retrieval + counterparts.

In addition, a new function + get_os_wordsize/0 has been added in the + memsup module

+

+ Own Id: OTP-7441

+
+ +

An error in memsup.c caused the compilation to + crash on bsd environments. This has now been fixed.

+

+ Own Id: OTP-7558

+
+
+
+ +
+ +
Os_Mon 2.1.7 + +
Fixed Bugs and Malfunctions + + +

Fixed a build error that occurred on NetBSD (Thanks to + Per Hedeland and Raphael Langerhorst)

+

+ Own Id: OTP-7505

+
+
+
+ + +
Improvements and New Features + + +

Memory information in + memsup:get_system_memory_data/0 now has additional + entries in its property list for linux.

+

+ Own Id: OTP-7409 Aux Id: seq10984

+
+
+
+ +
+ +
Os_Mon 2.1.6 + +
Fixed Bugs and Malfunctions + + +

+ System information retrieval on darwin platforms with + environments locales not conforming to the C locale + caused an error in cpu_sup resulting in process + termination.

+

+ Own Id: OTP-7320

+
+
+
+ +
+ +
Os_Mon 2.1.5 +
Improvements and New Features + + +

+ CPU utilization, on linux, is now measured via a port + program instead of os:cmd in erlang. This should enhance + performance.

+

+ Own Id: OTP-7108 Aux Id: OTP-6935

+
+
+
+
+ +
Os_Mon 2.1.3 +
Improvements and New Features + + +

+ Extended memsup memory probing on linux to use a port + program to probe memory usage. This is faster then the + previous implementation.

+

+ Own Id: OTP-6860 Aux Id: seq10616

+
+
+
+
+ + +
+ Os_Mon 2.1.2.1 + +
+ Improvements and New Features + + +

Minor Makefile changes.

+

Own Id: OTP-6689

+
+
+
+
+ +
+ OS_Mon 2.1.2 + +
+ Fixed Bugs and Malfunctions + + +

When the memsup_system_only flag was set to + true, a badmatch exception occurred in the + function os_mon_mib:get_load/1.

+

Own Id: OTP-6351 Aux Id: seq10517

+
+
+
+
+ +
+ OS_Mon 2.1.1 + +
+ Fixed Bugs and Malfunctions + + +

Did not build on Mac OS X.

+

Added support for IRIX. (Thanks to Michel Urvoy and + Daniel Solaz.)

+

Own Id: OTP-6136

+
+
+
+ +
+ Improvements and New Features + + +

disksup: Now using round(T*100) instead of + trunc(T*100) when setting the threshold value + given a float T.

+

Own Id: OTP-6153

+
+
+
+
+ +
+ Os_Mon 2.1 + +
+ Fixed Bugs and Malfunctions + + +

In 2.0, a call to alarm_handler:get_alarms/0 was + introduced in memsup and disksup. This will + lead to problems if the default alarm_handler + event handler is not used, however, and the call has now + been removed. (Thanks to Serge Aleynikov for pointing + this out.)

+

Own Id: OTP-6029

+
+ +

A bug that in rare cases caused cpu_sup to crash + has been corrected.

+

Own Id: OTP-6102 Aux Id: seq10312

+
+
+
+
+ +
+ OS_Mon 2.0 +

A note on backwards compatibility: The behaviour of OS_Mon 2.0 is + backwards compatible under normal operation, but has changed + somewhat in error situations: The services do not terminate + and the API functions do not raise exceptions in all cases where + they did before. Also, in the case where a service does terminate, + the exit reason may be different. See below for details.

+ +
+ Fixed Bugs and Malfunctions + + +

A call to a OS_Mon service (cpu_sup, disksup, ...) when + OS_Mon is not running, or when the service is not + available for the OS, or when the service is not started, + will no longer result in an exception. Instead a warning + is issued via error_logger and a dummy value is returned, + which one is specified in the man pages for the + respective service.

+

The reason is that it should not be necessary for a + service to be started on each and every node of a + distributed Erlang system for the OS-MON-MIB and other + OS_Mon users to work properly.

+

Own Id: OTP-4332 Aux Id: seq7358

+
+ +

References to the obsolete EVA application in + OTP-OS-MON-MIB has been removed.

+

Own Id: OTP-5699

+
+ +

Setting the option memsup_system_only to + true did not work, but would crash the + memsup process.

+

Own Id: OTP-5890 Aux Id: seq10185

+
+ +

cpu_sup:nprocs/0 returned 0 on FreeBsd.

+

Own Id: OTP-5901

+
+ +

If the OS_Mon service disksup or memsup was + restarted, the same alarm could be set twice. Also, set + alarms were not cleared when the application was stopped.

+

Own Id: OTP-5910

+
+
+
+ +
+ Improvements and New Features + + +

Thresholds and time intervals in disksup and + memsup are now configurable in run-time.

+

Own Id: OTP-4246 Aux Id: Seq7230

+
+ +

memsup can now handle systems with more than 4GB + of RAM.

+

Own Id: OTP-5800 Aux Id: seq10130

+
+ +

The entire OS_Mon application (code and documentation) + has been reviewed and consequently updated with the goal + to make the application more robust, homogeneous and + easier to configure.

+

The behaviour under normal operation is backwards + compatible. However, recoverable errors now no longer + terminate the affected service (and thus possible the + entire application), instead error_logger is used + to warn the user if/when such errors occurs. Also, in the + case of unrecoverable errors, the services have been made + more homogeneous with respect to behavior and exit + reasons. See below for more information and refer to the + man pages for details.

+

Port handling has been unified, meaning that if a port + program sends garbage or unexpectedly dies, this is now + handled the same way by all OS_Mon services, namely + immediate process termination with the exit reason + {port_error,Garbage} or {port_died,Reason}, + respectively.

+

Application configuration parameter handling has been + unified. Bad parameter values are no longer silently + ignored (disksup) or cause application termination + (memsup, os_sup). Instead a warning is + issued and the default value for the parameter is used. + Also, some cases where a bad parameter value accidentally + could be accepted have been corrected.

+

Message handling has been unified. Unknown + (gen_server-) calls cause process termination, + whereas unknown casts and messages are now ignored by all + OS_Mon services.

+

Own Id: OTP-5897

+
+ +

The following changes have been made to the os_sup + service:

+

It is now available on Windows, using nteventlog + as backend.

+

On Solaris, enabling the service (that is, installing a + new configuration file for syslogd etc.) can now + be done outside the os_sup process. The reason for + this is that the Erlang emulator should normally not be + run with root privileges, as is otherwise + required. The new application configuration parameter + os_sup_config must be set to false.

+

Also, os_sup can now be configured using a new + configuration parameter os_sup_mfa to call an + arbitrary Erlang function when a message is received from + the OS.

+

Own Id: OTP-5925

+
+ +

The memsup service has been rewritten, replacing + the supervised memsup_helper with a linked help + process. This gives the memsup process more + control and prevents a situation where it gets out of + synch with the received memory data and thus possibly + returns erroneous results.

+

Own Id: OTP-5927

+
+
+
+
+ +
+ OS_Mon 1.8.1 + +
+ Fixed Bugs and Malfunctions + + +

cpu_sup:util/0 failed with error reason + negative_diff when called the first time on a + machine (hw) that had been up for a very long time.

+

Own Id: OTP-5869 Aux Id: seq10166

+
+
+
+
+ +
+ OS_Mon 1.8 + +
+ Improvements and New Features + + +

The memsup part of the OS_Mon application has been made + more stable. If there are (possibly temporary) problems + collecting memory data, the interface functions + (get_memory_data/0, + get_system_memory_data/0) now do not fail, but + return the previously collected value, if any, or a dummy + value otherwise. Also, a warning message is printed.

+

*** POTENTIAL INCOMPATIBILITY ***

+

Own Id: OTP-5798

+
+
+
+
+ +
+ OS_Mon 1.7.4 + +
+ Fixed Bugs and Malfunctions + + +

Corrected several problems in the error handling/error + recovery (especially when OS_Mon is starting up).

+

Own Id: OTP-5559

+
+
+
+
+ +
+ OS_Mon 1.7.3 + +
+ Improvements and New Features + + +

memsup.c will now compile on OpenBSD. (Thanks to + Geoff White and Jay Nelson.)

+

The disksup and cpu_sup modules now work on + Mac OS X (tested on Mac OS 10.3.8).

+

The memsup module should now work on Linux 2.6.* as + well as on older Linuxes. (/proc/meminfo has + slightly different formats in different releases of + Linux.)

+

Own Id: OTP-5421

+ + Aux Id: OTP-5194, OTP-5228, OTP-5291

+
+
+
+
+ +
+ OS_Mon 1.7.2 +

This version is identical with 1.7.

+
+
+ diff --git a/lib/os_mon/doc/src/nteventlog.xml b/lib/os_mon/doc/src/nteventlog.xml new file mode 100644 index 0000000000..1e3fad90cd --- /dev/null +++ b/lib/os_mon/doc/src/nteventlog.xml @@ -0,0 +1,104 @@ + + + + +
+ + 19982009 + Ericsson AB. 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. + + + + nteventlog + + + + +
+ nteventlog + Interface to Windows Event Log + +

nteventlog provides a generic interface to the Windows + event log. It is part of the OS_Mon application, see + os_mon(6). Available for + Windows versions where the event log is available. That is, not + for Windows 98 and some other older Windows versions, but for most + (all?) newer Windows versions.

+

This module is used as the Windows backend for os_sup, see + os_sup(3).

+

To retain backwards compatibility, this module can also be used + to start a standalone nteventlog process which is not part + of the OS_Mon supervision tree. When starting such a process, + the user has to supply an identifier as well as a callback + function to handle the messages.

+

The identifier, an arbitrary string, should be reused whenever + the same application (or node) wants to start the process. + nteventlog is informed about all events that have arrived + to the eventlog since the last accepted message for the current + identifier. As long as the same identifier is used, the same + eventlog record will not be sent to nteventlog more than + once (with the exception of when graved system failures arise, in + which case the last records written before the failure may be + sent to Erlang again after reboot).

+

If the event log is configured to wrap around automatically, + records that have arrived to the log and been overwritten when + nteventlog was not running are lost. It however detects + this state and loses no records that are not overwritten.

+

The callback function works as described in os_sup(3).

+
+ + + start(Identifier, MFA) -> Result + start_link(Identifier, MFA) -> Result + Start the NT eventlog server + + Identifier = string() | atom() + MFA = {Mod, Func, Args} +  Mod = Func = atom() +  Args = [term()] + Result = {ok, Pid} | {error, {already_started, Pid}} + Pid = pid() + + +

This function starts the standalone nteventlog process + and, if start_link/2 is used, links to it.

+

Identifier is an identifier as described above.

+

MFA is the supplied callback function. When + nteventlog receives information about a new event, this + function will be called as apply(Mod, Func, [Event|Args]) where Event is a tuple

+
+
+ + stop() -> stopped + Stop the NT eventlog server + + Result = stopped + + +

Stops nteventlog. Usually only used during + development. The server does not have to be shut down + gracefully to maintain its state.

+
+
+
+ +
+ See Also +

os_mon(6), + os_sup(3)

+

Windows NT documentation

+
+
+ diff --git a/lib/os_mon/doc/src/os_mon_app.xml b/lib/os_mon/doc/src/os_mon_app.xml new file mode 100644 index 0000000000..8b44b70c5f --- /dev/null +++ b/lib/os_mon/doc/src/os_mon_app.xml @@ -0,0 +1,126 @@ + + + + +
+ + 19962009 + Ericsson AB. 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. + + + + os_mon + + + + +
+ os_mon + OS Monitoring Application + +

The operating system monitor, OS_Mon, provides the following + services:

+ + cpu_sup + CPU load and utilization supervision (Unix) + disksup + Disk supervision(Unix, Windows) + memsup + Memory supervision (Unix, Windows, VxWorks) + os_sup + Interface to OS system messages (Solaris, Windows) + +

To simplify usage of OS_Mon on distributed Erlang systems, it is + not considered an error trying to use a service at a node where it + is not available (either because OS_Mon is not running, or because + the service is not available for that OS, or because the service + is not started). Instead, a warning message is issued via + error_logger and a dummy value is returned, which one is + specified in the man pages for the respective services.

+
+ +
+ Configuration +

When OS_Mon is started, by default all services available for + the OS, except os_sup, are automatically started. This + configuration can be changed using the following application + configuration parameters:

+ + start_cpu_sup = bool() + +

Specifies if cpu_sup should be started. Defaults to + true.

+
+ start_disksup = bool() + +

Specifies if disksup should be started. Defaults to + true.

+
+ start_memsup = bool() + +

Specifies if memsup should be started. Defaults to + true.

+
+ start_os_sup = bool() + +

Specifies if os_sup should be started. Defaults to + false.

+
+
+

Configuration parameters effecting the different OS_Mon services + are described in the respective man pages.

+

See config(4) for + information about how to change the value of configuration + parameters.

+
+ +
+ SNMP MIBs +

The following MIBs are defined in OS_Mon:

+ + OTP-OS-MON-MIB + +

This MIB contains objects for instrumentation of disk, + memory and CPU usage of the nodes in the system.

+
+
+

The MIB is stored in the mibs directory. It is defined + in SNMPv2 SMI syntax. An SNMPv1 version of the MIB is delivered + in the mibs/v1 directory.

+

The compiled MIB is located under priv/mibs, and + the generated .hrl file under the include directory. + To compile a MIB that IMPORTS the OTP-OS-MON-MIB, give + the option {il, ["os_mon/priv/mibs"]} to the MIB compiler.

+

If the MIB should be used in a system, it should be loaded into + an agent with a call to os_mon_mib:load(Agent), where + Agent is the pid or registered name of an SNMP agent. Use + os_mon_mib:unload(Agent) to unload the MIB. + The implementation of this MIB uses Mnesia to store a cache with + data needed, which implicates that Mnesia must be up and running. + The MIB also use functions defined for the OTP-MIB, thus + that MIB must be loaded as well.

+
+ +
+ See Also +

cpu_sup(3), + disksup(3), + memsup(3), + os_sup(3), + nteventlog(3), + snmp(3).

+
+
+ diff --git a/lib/os_mon/doc/src/os_mon_mib.xml b/lib/os_mon/doc/src/os_mon_mib.xml new file mode 100644 index 0000000000..220d6a4a22 --- /dev/null +++ b/lib/os_mon/doc/src/os_mon_mib.xml @@ -0,0 +1,70 @@ + + + + +
+ + 20042009 + Ericsson AB. 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. + + + + os_mon_mib + Ingela Andin + + + + +
+ os_mon_mib + Loading and Unloading of OTP-OS-MON-MIB + +

Functions for loading and unloading the OTP-OS-MON-MIB into/from + an SNMP agent. The instrumentation of the OTP-OS-MON-MIB uses + Mnesia, hence Mnesia must be started prior to loading + the OTP-OS-MON-MIB.

+
+ + + load(Agent) -> ok | {error, Reason} + Load the OTP-OS-MON-MIB + + Agent = pid() | atom() + Reason = term() + + +

Loads the OTP-OS-MON-MIB.

+
+
+ + unload(Agent) -> ok | {error, Reason} + Unload the OTP-OS-MON-MIB + + Agent = pid() | atom() + Reason = term() + + +

Unloads the OTP-OS-MON-MIB.

+
+
+
+ +
+ See Also +

os_mon(6), + snmp(3)

+
+
+ diff --git a/lib/os_mon/doc/src/os_sup.xml b/lib/os_mon/doc/src/os_sup.xml new file mode 100644 index 0000000000..792d18ba14 --- /dev/null +++ b/lib/os_mon/doc/src/os_sup.xml @@ -0,0 +1,241 @@ + + + + +
+ + 19962009 + Ericsson AB. 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. + + + + os_sup + + + + +
+ os_sup + Interface to OS System Messages + +

os_sup is a process providing a message passing service + from the operating system to the error logger in the Erlang + runtime system. It is part of the OS_Mon application, see + os_mon(6). Available for + Solaris and Windows.

+

Messages received from the operating system results in an + user defined callback function being called. This function can do + whatever filtering and formatting is necessary and then deploy any + type of logging suitable for the user's application.

+
+ +
+ Solaris Operation +

The Solaris (SunOS 5.x) messages are retrieved from + the syslog-daemon, syslogd.

+

Enabling the service includes actions which require root + privileges, such as change of ownership and file privileges of an + executable binary file, and creating a modified copy of + the configuration file for syslogd. When os_sup is + terminated, the service must be disabled, meaning the original + configuration must be restored. Enabling/disabling can be done + either outside or inside os_sup, see + Configuration below.

+ +

This process cannot run in multiple instances on the same + hardware. OS_Mon must be configured to start os_sup on + one node only if two or more Erlang nodes execute on the same + machine.

+
+

The format of received events is not defined.

+
+ +
+ Windows Operation +

The Windows messages are retrieved from the eventlog file.

+

The nteventlog module is used to implement os_sup. + See nteventlog(3). Note + that the start functions of nteventlog does not need to be + used, in this case the process is started automatically as part of + the OS_Mon supervision tree.

+

OS messages are formatted as a tuple + {Time, Category, Facility, Severity, Message}:

+ + Time = {MegaSecs, Secs, MicroSecs} + +

A time stamp as returned by the BIF now().

+
+ Category = string() + +

Usually one of "System", "Application" or + "Security". Note that the NT eventlog viewer has + another notion of category, which in most cases is totally + meaningless and therefore not imported into Erlang. What is + called a category here is one of the main three types of + events occurring in a normal NT system.

+
+ Facility = string() + +

The source of the message, usually the name of + the application that generated it. This could be almost any + string. When matching messages from certain applications, + the version number of the application may have to be + accounted for. This is what the NT event viewer calls + "source".

+
+ Severity = string() + +

One of "Error", "Warning", + "Informational", "Audit_Success", + "Audit_Faulure" or, in case of a currently unknown + Windows NT version "Severity_Unknown".

+
+ Message = string() + +

Formatted exactly as it would be in the NT eventlog viewer. + Binary data is not imported into Erlang.

+
+
+
+ +
+ + Configuration + + os_sup_mfa = {Module, Function, Args} + +

The callback function to use. Module and + Function are atoms and Args is a list of terms. + When an OS message Msg is received, this function is + called as apply(Module, Function, [Msg | Args]).

+

Default is {os_sup, error_report, [Tag]} which will + send the event to the error logger using + error_logger:error_report(Tag, Msg). Tag is the value of + os_sup_errortag, see below.

+
+ os_sup_errortag = atom() + +

This parameter defines the error report type used when + messages are sent to error logger using the default callback + function. Default is std_error, which means the events + are handled by the standard event handler.

+
+ os_sup_enable = bool() + +

Solaris only. Defines if the service should be enabled (and + disabled) inside (true) or outside (false) + os_sup. For backwards compatibility reasons, + the default is true. The recommended value is + false, as the Erlang emulator should normally not be + run with root privileges, as is required for enabling + the service.

+
+ os_sup_own = string() + +

Solaris only. Defines the directory which contains + the backup copy and the Erlang specific configuration files + for syslogd, and a named pipe to receive the messages + from syslogd. Default is "/etc".

+
+ os_sup_syslogconf = string() + +

Solaris only. Defines the full name of the configuration file + for syslogd. Default is "/etc/syslog.conf".

+
+
+
+ + + enable() -> ok | {error, Res} + enable(Dir, Conf) -> ok | {error, Error} + Enable the service (Solaris only) + + Dir = Conf = Res = string() + + +

Enables the os_sup service. Needed on Solaris only.

+

If the configuration parameter os_sup_enable is + false, this function is called automatically by + os_sup, using the values of os_sup_own and + os_sup_syslogconf as arguments.

+

If os_sup_enable is true, this function must + be called before OS_Mon/os_sup is started. + Dir defines the directory which contains the backup + copy and the Erlang specific configuration files for + syslogd, and a named pipe to receive the messages + from syslogd. Defaults to "/etc". Conf + defines the full name of the configuration file for + syslogd. Default is "/etc/syslog.conf".

+

Results in a OS call to:

+ /bin/mod_syslog otp Dir Conf + ]]> +

where ]]> is the priv directory of + OS_Mon, code:priv_dir(os_mon).

+

Returns ok if this yields the expected result + "0", and {error, Res} if it yields anything + else.

+ +

This function requires root privileges to succeed.

+
+
+
+ + disable() -> ok | {error, Res} + disable(Dir, Conf) -> ok | {error, Error} + Disable the service (Solaris only) + + Dir = Conf = Res = string() + + +

Disables the os_sup service. Needed on Solaris only.

+

If the configuration parameter os_sup_enable is + false, this function is called automatically by + os_sup, using the same arguments as when + enable/2 was called.

+

If os_sup_enable is true, this function must + be called after OS_Mon/os_sup is stopped. + Dir defines the directory which contains the backup + copy and the Erlang specific configuration files for + syslogd, and a named pipe to receive the messages + from syslogd. Defaults to "/etc". Conf + defines the full name of the configuration file for + syslogd. Default is "/etc/syslog.conf".

+

Results in a OS call to:

+ /bin/mod_syslog nootp Dir Conf + ]]> +

where ]]> is the priv directory of + OS_Mon, code:priv_dir(os_mon).

+

Returns ok if this yields the expected result + "0", and {error, Res} if it yields anything + else.

+ +

This function requires root privileges to succeed.

+
+
+
+
+ +
+ See also +

error_logger(3), + os_mon(3)

+

syslogd(1M), syslog.conf(4) in the Solaris + documentation.

+

+
+
+ diff --git a/lib/os_mon/doc/src/part_notes.xml b/lib/os_mon/doc/src/part_notes.xml new file mode 100644 index 0000000000..8fe226f53e --- /dev/null +++ b/lib/os_mon/doc/src/part_notes.xml @@ -0,0 +1,36 @@ + + + + +
+ + 20042009 + Ericsson AB. 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. + + + + OS_Mon Release Notes + + + + +
+ +

The operating system monitor, OS_Mon, provides services for + monitoring CPU load, disk usage, memory usage and OS messages.

+
+ +
+ diff --git a/lib/os_mon/doc/src/ref_man.xml b/lib/os_mon/doc/src/ref_man.xml new file mode 100644 index 0000000000..5a50c122fd --- /dev/null +++ b/lib/os_mon/doc/src/ref_man.xml @@ -0,0 +1,42 @@ + + + + +
+ + 19962009 + Ericsson AB. 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. + + + + OS_Mon Reference Manual + + + + +
+ +

The operating system monitor, OS_Mon, provides services for + monitoring CPU load, disk usage, memory usage and OS messages.

+
+ + + + + + + +
+ diff --git a/lib/os_mon/doc/src/user_guide.gif b/lib/os_mon/doc/src/user_guide.gif new file mode 100644 index 0000000000..e6275a803d Binary files /dev/null and b/lib/os_mon/doc/src/user_guide.gif differ diff --git a/lib/os_mon/doc/src/warning.gif b/lib/os_mon/doc/src/warning.gif new file mode 100644 index 0000000000..96af52360e Binary files /dev/null and b/lib/os_mon/doc/src/warning.gif differ diff --git a/lib/os_mon/ebin/.gitignore b/lib/os_mon/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/include/memsup.hrl b/lib/os_mon/include/memsup.hrl new file mode 100644 index 0000000000..743d16522e --- /dev/null +++ b/lib/os_mon/include/memsup.hrl @@ -0,0 +1,43 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. 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% +%% +-ifndef(_memsup_hrl). +-define(_memsup_hrl,true). +%%% This file has to be kept consistent with ../c_src/memsup.h. +%%% Keep consistence manually. + +%% Defines + +-define( SHOW_MEM , 1 ). +-define( SHOW_SYSTEM_MEM , 2 ). +-define( SHOW_SYSTEM_MEM_END , 8#0 ). +%% tags for extended statistics +-define( MEM_SYSTEM_TOTAL , 1 ). +-define( MEM_TOTAL , 2 ). +-define( MEM_FREE , 3 ). +-define( MEM_LARGEST_FREE , 4 ). +-define( MEM_NUMBER_OF_FREE , 5 ). +%% extensions +-define( MEM_BUFFERS , 6 ). +-define( MEM_CACHED , 7 ). +-define( MEM_SHARED , 8 ). +-define( SWAP_TOTAL , 9 ). +-define( SWAP_FREE , 10 ). + +-endif. + diff --git a/lib/os_mon/info b/lib/os_mon/info new file mode 100644 index 0000000000..bc3f971c2a --- /dev/null +++ b/lib/os_mon/info @@ -0,0 +1,3 @@ +group: oam +short: A monitor which allows inspection of the underlying operating system + diff --git a/lib/os_mon/mibs/Makefile b/lib/os_mon/mibs/Makefile new file mode 100644 index 0000000000..cbbc337491 --- /dev/null +++ b/lib/os_mon/mibs/Makefile @@ -0,0 +1,97 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(OS_MON_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MIB_FILES= OTP-OS-MON-MIB.mib +FUNCS_FILES = OTP-OS-MON-MIB.funcs + +BIN_TARGETS= $(MIB_FILES:%.mib=$(SNMP_BIN_TARGET_DIR)/%.bin) +HRL_TARGETS= $(MIB_FILES:%.mib=$(SNMP_HRL_TARGET_DIR)/%.hrl) +V1_MIB_FILES= $(MIB_FILES:%.mib=v1/%.mib.v1) + +TARGET_FILES= $(SNMP_BIN_TARGET_DIR)/OTP-REG.bin \ + $(SNMP_BIN_TARGET_DIR)/OTP-TC.bin \ + $(SNMP_BIN_TARGET_DIR)/OTP-MIB.bin \ + $(BIN_TARGETS) $(HRL_TARGETS) $(V1_MIB_FILES) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +SNMP_FLAGS = -I $(SNMP_BIN_TARGET_DIR) + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +OTP_MIBDIR = $(shell if test -d ../../otp_mibs; then echo otp_mibs; \ + else echo sasl; fi) + +$(SNMP_BIN_TARGET_DIR)/OTP-REG.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-REG.mib + $(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $< + +$(SNMP_BIN_TARGET_DIR)/OTP-TC.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-TC.mib + $(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $< + +$(SNMP_BIN_TARGET_DIR)/OTP-MIB.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-MIB.mib + $(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $< + +v1/%.mib.v1: %.mib + $(ERL_TOP)/lib/snmp/bin/snmp-v2tov1 -o $@ $< + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/mibs + $(INSTALL_DIR) $(RELSYSDIR)/mibs/v1 + $(INSTALL_DATA) $(MIB_FILES) $(FUNCS_FILES) $(RELSYSDIR)/mibs + $(INSTALL_DATA) $(V1_MIB_FILES) $(RELSYSDIR)/mibs/v1 + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(HRL_TARGETS) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/priv/mibs + $(INSTALL_DATA) $(BIN_TARGETS) $(RELSYSDIR)/priv/mibs + +release_docs_spec: diff --git a/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs b/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs new file mode 100644 index 0000000000..7ed76517b9 --- /dev/null +++ b/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs @@ -0,0 +1,5 @@ +{loadMemorySystemWatermark, {os_mon_mib, mem_sys_mark, []}}. +{loadMemoryErlProcWatermark, {os_mon_mib, mem_proc_mark, []}}. +{loadTable, {os_mon_mib, load_table, []}}. +{diskAlmostFullThreshold, {os_mon_mib, disk_threshold, []}}. +{diskTable, {os_mon_mib, disk_table, []}}. diff --git a/lib/os_mon/mibs/OTP-OS-MON-MIB.mib b/lib/os_mon/mibs/OTP-OS-MON-MIB.mib new file mode 100644 index 0000000000..d6ababdcea --- /dev/null +++ b/lib/os_mon/mibs/OTP-OS-MON-MIB.mib @@ -0,0 +1,422 @@ +-- +-- %CopyrightBegin% +-- +-- Copyright Ericsson AB 1997-2009. 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% +-- + +OTP-OS-MON-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, NOTIFICATION-TYPE, OBJECT-TYPE, + Counter32, Gauge32, Integer32, Unsigned32, Counter64 + FROM SNMPv2-SMI + TEXTUAL-CONVENTION, DisplayString + FROM SNMPv2-TC + MODULE-COMPLIANCE, NOTIFICATION-GROUP, OBJECT-GROUP + FROM SNMPv2-CONF + otpModules, otpApplications + FROM OTP-REG + erlNodeId + FROM OTP-MIB + ; + + +otpOsMonModule MODULE-IDENTITY + LAST-UPDATED "0305090900Z" + ORGANIZATION "Ericsson" + CONTACT-INFO + "Contact: Erlang Support see license agreement for Erlang/OTP." + + DESCRIPTION + "This MIB is part of the OTP MIB. It defines MIB objects + for the os_mon application in OTP." + + REVISION "0508260900Z" + DESCRIPTION + "Removed dependeny on EVA." + REVISION "0305090900Z" + DESCRIPTION + "Changed CONTACT-INFO as it was outdated, made it more generic + to avoid such changes in the future." + + REVISION "9807080900Z" + DESCRIPTION + "Changed MAX-ACCESS for diskDescr from not-accessible to + read-only." + + REVISION "9801270900Z" + DESCRIPTION + "Changed erroneous name of this module to otpOsMonModule." + + REVISION "9712010900Z" + DESCRIPTION + "Converted to v2 SMI and placed in the OTP tree." + + REVISION "9608191700Z" + DESCRIPTION + "The initial revision of MIB module OTP-OS-MON-MIB." + ::= { otpModules 4 } + +OTPCounterBasedGauge64 ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION + "The CounterBasedGauge64 type represents a non-negative + integer, which may increase or decrease, but shall never + exceed a maximum value, nor fall below a minimum value. The + maximum value can not be greater than 2^64-1 + (18446744073709551615 decimal), and the minimum value can + + not be smaller than 0. The value of a CounterBasedGauge64 + has its maximum value whenever the information being modeled + is greater than or equal to its maximum value, and has its + minimum value whenever the information being modeled is + smaller than or equal to its minimum value. If the + information being modeled subsequently decreases below + (increases above) the maximum (minimum) value, the + CounterBasedGauge64 also decreases (increases). + + Note that this TC is not strictly supported in SMIv2, + because the 'always increasing' and 'counter wrap' semantics + associated with the Counter64 base type are not preserved. + It is possible that management applications which rely + solely upon the (Counter64) ASN.1 tag to determine object + semantics will mistakenly operate upon objects of this type + as they would for Counter64 objects. + + This textual convention represents a limited and short-term + solution, and may be deprecated as a long term solution is + defined and deployed to replace it." + SYNTAX Counter64 + +otpOsMonMIB OBJECT IDENTIFIER ::= { otpApplications 2 } +otpOsMonMIBConformance + OBJECT IDENTIFIER ::= { otpOsMonMIB 1 } +otpOsMonMIBObjects + OBJECT IDENTIFIER ::= { otpOsMonMIB 2 } +otpOsMonMIBAlarms + OBJECT IDENTIFIER ::= { otpOsMonMIB 4 } +otpOsMonMIBAlarmsV2 + OBJECT IDENTIFIER ::= { otpOsMonMIBAlarms 0 } + + +-- Datatypes + +-- Managed Objects + +load OBJECT IDENTIFIER ::= { otpOsMonMIBObjects 1 } +disk OBJECT IDENTIFIER ::= { otpOsMonMIBObjects 2 } + +loadMemorySystemWatermark OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Threshold in percent of the total available system + memory, which specifies how much memory can be allocated + by the system before an alarm is sent." + ::= { load 1 } + +loadMemoryErlProcWatermark OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Threshold in percent of the total available system + memory, which specifies how much memory can be allocated + by one Erlang process before an alarm is sent." + ::= { load 2 } + +loadTable OBJECT-TYPE + SYNTAX SEQUENCE OF LoadEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table with load and memory information + for each node." + ::= { load 3 } + +loadEntry OBJECT-TYPE + SYNTAX LoadEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row in the loadTable." + INDEX { loadErlNodeName } + ::= { loadTable 1 } + +LoadEntry ::= SEQUENCE { + loadErlNodeName DisplayString, + loadSystemTotalMemory Gauge32, + loadSystemUsedMemory Gauge32, + loadLargestErlProcess DisplayString, + loadLargestErlProcessUsedMemory Gauge32, + loadCpuLoad Integer32, + loadCpuLoad5 Integer32, + loadCpuLoad15 Integer32, + loadOsWordsize Unsigned32, + loadSystemTotalMemory64 OTPCounterBasedGauge64, + loadSystemUsedMemory64 OTPCounterBasedGauge64, + loadLargestErlProcessUsedMemory64 OTPCounterBasedGauge64 + } + +loadErlNodeName OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "The name of the erlang node, e.g. erlnode@host1." + ::= { loadEntry 1 } + +loadSystemTotalMemory OBJECT-TYPE + SYNTAX Gauge32 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of total memory in the system." + ::= { loadEntry 2 } + +loadSystemUsedMemory OBJECT-TYPE + SYNTAX Gauge32 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of used memory." + ::= { loadEntry 3 } + +loadLargestErlProcess OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The process identifier (Pid) of the largest Erlang + process." + ::= { loadEntry 4 } + +loadLargestErlProcessUsedMemory OBJECT-TYPE + SYNTAX Gauge32 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of memory used by the largest Erlang + process." + ::= { loadEntry 5 } + +loadCpuLoad OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The average load the last minute in percent of the CPU + where the Erlang node runs." + ::= { loadEntry 6 } + +loadCpuLoad5 OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The average load the last 5 minutes in percent of the CPU + where the Erlang node runs." + ::= { loadEntry 7} + +loadCpuLoad15 OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The average load the last 15 minutes in percent of the CPU + where the Erlang node runs." + ::= { loadEntry 8} + +loadOsWordsize OBJECT-TYPE + SYNTAX Unsigned32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The wordsize of the operating operating system." + ::= { loadEntry 9 } + +loadSystemTotalMemory64 OBJECT-TYPE + SYNTAX OTPCounterBasedGauge64 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of total memory in the system for 64-bit operating system." + ::= { loadEntry 10 } + +loadSystemUsedMemory64 OBJECT-TYPE + SYNTAX OTPCounterBasedGauge64 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of used memory for 64-bit operating system." + ::= { loadEntry 11 } + +loadLargestErlProcessUsedMemory64 OBJECT-TYPE + SYNTAX OTPCounterBasedGauge64 + UNITS "bytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of memory used by the largest Erlang + process for 64-bit operating system.." + ::= { loadEntry 12 } + +diskAlmostFullThreshold OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Threshold in percent of the available disk space, + which specifies how much disk space can be used by + a disk or partition before an alarm is sent." + ::= { disk 1 } + +diskTable OBJECT-TYPE + SYNTAX SEQUENCE OF DiskEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A table with all local disks or partitions on each + node." + ::= { disk 2 } + +diskEntry OBJECT-TYPE + SYNTAX DiskEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "A conceptual row in the diskTable." + INDEX { erlNodeId, diskId } + ::= { diskTable 1 } + +DiskEntry ::= SEQUENCE { + diskId Integer32, + diskDescr DisplayString, + diskKBytes Gauge32, + diskCapacity Integer32 + } + +diskId OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An integer that uniquely identifies the disk + or partition." + ::= { diskEntry 1 } + +diskDescr OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "A string that identifies the disk or partition." + ::= { diskEntry 2 } + +diskKBytes OBJECT-TYPE + SYNTAX Gauge32 + UNITS "kbytes" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The amount of total disk/partition space. " + ::= { diskEntry 3 } + +diskCapacity OBJECT-TYPE + SYNTAX Integer32 (0..100) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "How much of the disk's/partition's total capacity has + been used, in percent." + ::= { diskEntry 4 } + + +-- conformance information + +otpOsMonMIBCompliances + OBJECT IDENTIFIER ::= { otpOsMonMIBConformance 1 } +otpOsMonMIBGroups + OBJECT IDENTIFIER ::= { otpOsMonMIBConformance 2 } + + +-- compliance statements + +otpOsMonBasicCompliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for SNMPv2 entities which + implement the OTP-OS-MON-MIB." + MODULE -- this module + GROUP loadGroup + DESCRIPTION + "This group is mandatory for systems implementing the + load supervison functionality." + GROUP loadAlarmsGroup + DESCRIPTION + "This group is optional for systems implementing the + load supervison functionality." + GROUP diskGroup + DESCRIPTION + "This group is mandatory for system implementing the + disk supervison functionality." + GROUP diskAlarmsGroup + DESCRIPTION + "This group is optional for systems implementing the + disk supervison functionality." + ::= { otpOsMonMIBCompliances 1 } + + +-- units of conformance + +loadGroup OBJECT-GROUP + OBJECTS { loadMemorySystemWatermark, + loadMemoryErlProcWatermark, + loadSystemTotalMemory, + loadSystemUsedMemory, + loadLargestErlProcess, + loadLargestErlProcessUsedMemory, + loadCpuLoad, + loadCpuLoad5, + loadCpuLoad15, + loadOsWordsize, + loadSystemTotalMemory64, + loadSystemUsedMemory64, + loadLargestErlProcessUsedMemory64} + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + of the load of the OTP system." + ::= { otpOsMonMIBGroups 1 } + +diskGroup OBJECT-GROUP + OBJECTS { diskAlmostFullThreshold, + diskDescr, + diskKBytes, + diskCapacity } + STATUS current + DESCRIPTION + "A collection of objects providing basic instrumentation + of the disks in the OTP system." + ::= { otpOsMonMIBGroups 3 } + +END diff --git a/lib/os_mon/mibs/v1/.gitignore b/lib/os_mon/mibs/v1/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/priv/bin/.gitignore b/lib/os_mon/priv/bin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/priv/mibs/.gitignore b/lib/os_mon/priv/mibs/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/priv/obj/.gitignore b/lib/os_mon/priv/obj/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/os_mon/src/Makefile b/lib/os_mon/src/Makefile new file mode 100644 index 0000000000..9a75446a89 --- /dev/null +++ b/lib/os_mon/src/Makefile @@ -0,0 +1,110 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(OS_MON_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULES= disksup memsup cpu_sup os_mon os_mon_mib os_sup os_mon_sysinfo \ + nteventlog + +INCLUDE=../include +CSRC=../c_src + +MEMSUP_HRL=$(INCLUDE)/memsup.hrl + +HRL_FILES= $(MEMSUP_HRL) +ERL_FILES= $(MODULES:%=%.erl) + +APP_FILE= os_mon.app +APP_SRC= $(APP_FILE).src +APP_TARGET=$(EBIN)/$(APP_FILE) + +APPUP_FILE= os_mon.appup + +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= ../ebin/$(APPUP_FILE) + + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += +warn_obsolete_guard -I$(INCLUDE) + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +#------------------------------------------------------- +# Special dependencies +#------------------------------------------------------- +$(EBIN)/memsup.$(EMULATOR): $(MEMSUP_HRL) + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + + + + + + + diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl new file mode 100644 index 0000000000..742e20b1fa --- /dev/null +++ b/lib/os_mon/src/cpu_sup.erl @@ -0,0 +1,776 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. 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% +%% +-module(cpu_sup). + +%% API +-export([start_link/0, start/0, stop/0]). +-export([nprocs/0, avg1/0, avg5/0, avg15/0, util/0, util/1]). +-export([dummy_reply/1]). + +%% For testing +-export([ping/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Internal protocol with the port program +-define(nprocs,"n"). +-define(avg1,"1"). +-define(avg5,"5"). +-define(avg15,"f"). +-define(quit,"q"). +-define(ping,"p"). +-define(util,"u"). + +-define(cu_cpu_id, 0). +-define(cu_user, 1). +-define(cu_nice_user, 2). +-define(cu_kernel, 3). +-define(cu_io_wait, 4). +-define(cu_idle, 5). +-define(cu_hard_irq, 6). +-define(cu_soft_irq, 7). +-define(cu_steal, 8). + +-define(INT32(D3,D2,D1,D0), + (((D3) bsl 24) bor ((D2) bsl 16) bor ((D1) bsl 8) bor (D0))). + +-define(MAX_UINT32, ((1 bsl 32) - 1)). + +-record(cpu_util, {cpu, busy = [], non_busy = []}). + +-record(state, {server, os_type}). +%-record(state, {server, port = not_used, util = [], os_type}). + +-record(internal, {port = not_used, util = [], os_type}). + +%%---------------------------------------------------------------------- +%% Contract specifications +%%---------------------------------------------------------------------- + +-type(util_cpus() :: 'all' | integer() | [integer()]). +-type(util_state() :: + 'user' | + 'nice_user' | + 'kernel' | + 'wait' | + 'idle'). +-type(util_value() :: {util_state(), float()} | float()). +-type(util_desc() :: {util_cpus(), util_value(), util_value(), []}). + +%%---------------------------------------------------------------------- +%% Exported functions +%%---------------------------------------------------------------------- + +start() -> + gen_server:start({local, cpu_sup}, cpu_sup, [], []). + +start_link() -> + gen_server:start_link({local, cpu_sup}, cpu_sup, [], []). + +stop() -> + gen_server:call(cpu_sup, ?quit, infinity). + +-spec(nprocs/0 :: () -> integer() | {'error', any()}). + +nprocs() -> + os_mon:call(cpu_sup, ?nprocs, infinity). + +-spec(avg1/0 :: () -> integer() | {'error', any()}). + +avg1() -> + os_mon:call(cpu_sup, ?avg1, infinity). + +-spec(avg5/0 :: () -> integer() | {'error', any()}). + +avg5() -> + os_mon:call(cpu_sup, ?avg5, infinity). + +-spec(avg15/0 :: () -> integer() | {'error', any()}). + +avg15() -> + os_mon:call(cpu_sup, ?avg15, infinity). + +-spec(util/1 :: ([ 'detailed' | 'per_cpu']) -> + util_desc() | [util_desc()] | {'error', any()}). + +util(Args) when is_list (Args) -> + % Get arguments + case lists:foldl( + fun (detailed, {_ , PC}) -> {true, PC }; + (per_cpu , {D , _ }) -> {D , true}; + (_ , _ ) -> badarg + end, {false, false}, Args) of + badarg -> + erlang:error(badarg); + {Detailed, PerCpu} -> + os_mon:call(cpu_sup, {?util, Detailed, PerCpu}, infinity) + end; +util(_) -> + erlang:error(badarg). + +-spec(util/0 :: () -> float()). + +util() -> + case util([]) of + {all, Busy, _, _} -> Busy; + Error -> Error + end. + +dummy_reply(?nprocs) -> 0; +dummy_reply(?avg1) -> 0; +dummy_reply(?avg5) -> 0; +dummy_reply(?avg15) -> 0; +dummy_reply({?util,_,_}) -> {all, 0, 0, []}. + +%%---------------------------------------------------------------------- +%% For testing +%%---------------------------------------------------------------------- + +ping() -> + gen_server:call(cpu_sup,?ping). + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +%% init +init([]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + {ok, + #state{ os_type = os:type(), + server = measurement_server_start() + } + }. +handle_call(?quit, _From, State) -> + {stop, normal, ok, State}; +handle_call({?util, D, PC}, {Client, _Tag}, + #state{os_type = {unix, Flavor}} = State) + when Flavor == sunos; + Flavor == linux -> + case measurement_server_call(State#state.server, {?util, D, PC, Client}) of + {error, Reason} -> + { reply, + {error, Reason}, + State#state{server=measurement_server_restart(State#state.server)} + }; + Result -> {reply, Result, State} + end; +handle_call({?util, Detailed, PerCpu}, _From, State) -> + String = "OS_MON (cpu_sup), util/1 unavailable for this OS~n", + error_logger:warning_msg(String), + {reply, dummy_reply({?util, Detailed, PerCpu}), State}; +handle_call(Request, _From, State) when Request==?nprocs; + Request==?avg1; + Request==?avg5; + Request==?avg15; + Request==?ping -> + case measurement_server_call(State#state.server, Request) of + {error, Reason} -> + { reply, + {error, Reason}, + State#state{server=measurement_server_restart(State#state.server)} + }; + Result -> {reply, Result, State} + end. +handle_cast(_Msg, State) -> + {noreply, State}. +handle_info({'EXIT', _Port, Reason}, State) -> + {stop, {server_died, Reason}, State#state{server=not_used}}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + exit(State#state.server, normal). + +%% os_mon-2.0 +%% For live downgrade to/upgrade from os_mon-1.8[.1] +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + process_flag(trap_exit, false); + + %% Upgrade to this version + _Vsn -> + process_flag(trap_exit, true) + end, + {ok, PrevState}; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% internal functions +%%---------------------------------------------------------------------- + +get_uint32_measurement(Request, #internal{port = P, os_type = {unix, sunos}}) -> + port_server_call(P, Request); +get_uint32_measurement(Request, #internal{os_type = {unix, linux}}) -> + {ok,F} = file:open("/proc/loadavg",[read,raw]), + {ok,D} = file:read(F,24), + ok = file:close(F), + {ok,[Load1,Load5,Load15,_PRun,PTotal],_} = io_lib:fread("~f ~f ~f ~d/~d", D), + case Request of + ?avg1 -> sunify(Load1); + ?avg5 -> sunify(Load5); + ?avg15 -> sunify(Load15); + ?ping -> 4711; + ?nprocs -> PTotal + end; +get_uint32_measurement(Request, #internal{os_type = {unix, freebsd}}) -> + D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n", + {ok,[Load1,Load5,Load15],_} = io_lib:fread("{ ~f ~f ~f }", D), + %% We could count the lines from the ps command as well + case Request of + ?avg1 -> sunify(Load1); + ?avg5 -> sunify(Load5); + ?avg15 -> sunify(Load15); + ?ping -> 4711; + ?nprocs -> + Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"), + {ok, [N], _} = io_lib:fread("~d", Ps), + N-1 + end; +get_uint32_measurement(Request, #internal{os_type = {unix, dragonfly}}) -> + D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n", + {ok,[Load1,Load5,Load15],_} = io_lib:fread("{ ~f ~f ~f }", D), + %% We could count the lines from the ps command as well + case Request of + ?avg1 -> sunify(Load1); + ?avg5 -> sunify(Load5); + ?avg15 -> sunify(Load15); + ?ping -> 4711; + ?nprocs -> + Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"), + {ok, [N], _} = io_lib:fread("~d", Ps), + N-1 + end; +get_uint32_measurement(Request, #internal{os_type = {unix, openbsd}}) -> + D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n", + {ok, [L1, L5, L15], _} = io_lib:fread("~f ~f ~f", D), + case Request of + ?avg1 -> sunify(L1); + ?avg5 -> sunify(L5); + ?avg15 -> sunify(L15); + ?ping -> 4711; + ?nprocs -> + Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"), + {ok, [N], _} = io_lib:fread("~d", Ps), + N-1 + end; +get_uint32_measurement(Request, #internal{os_type = {unix, darwin}}) -> + %% Get the load average using uptime, overriding Locale setting. + D = os:cmd("LANG=C LC_ALL=C uptime") -- "\n", + %% Here is a sample uptime string from Mac OS 10.3.8 (C Locale): + %% "11:17 up 12 days, 20:39, 2 users, load averages: 1.07 0.95 0.66" + %% The safest way to extract the load averages seems to be grab everything + %% after the last colon and then do an fread on that. + Avg = lists:reverse(hd(string:tokens(lists:reverse(D), ":"))), + {ok,[L1,L5,L15],_} = io_lib:fread("~f ~f ~f", Avg), + + case Request of + ?avg1 -> sunify(L1); + ?avg5 -> sunify(L5); + ?avg15 -> sunify(L15); + ?ping -> 4711; + ?nprocs -> + Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"), + {ok, [N], _} = io_lib:fread("~d", Ps), + N-1 + end; +get_uint32_measurement(Request, #internal{os_type = {unix, Sys}}) when Sys == irix64; + Sys == irix -> + %% Get the load average using uptime. + %% "8:01pm up 2 days, 22:12, 4 users, load average: 0.70, 0.58, 0.43" + D = os:cmd("uptime") -- "\n", + Avg = lists:reverse(hd(string:tokens(lists:reverse(D), ":"))), + {ok, [L1, L5, L15], _} = io_lib:fread("~f, ~f, ~f", Avg), + case Request of + ?avg1 -> sunify(L1); + ?avg5 -> sunify(L5); + ?avg15 -> sunify(L15); + ?ping -> 4711; + ?nprocs -> + {ok, ProcList} = file:list_dir("/proc/pinfo"), + length(ProcList) + end; +get_uint32_measurement(_, _) -> + throw(not_implemented). + + +get_util_measurement(?util, #internal{port = P }) -> + case port_server_call(P, ?util) of + {error, Error} -> {error, Error}; + NewCpuUtil -> NewCpuUtil + end; +get_util_measurement(_,_) -> + throw(not_implemented). + +%%---------------------------------------------------------------------- +%% BEGIN: tainted internal functions +%%---------------------------------------------------------------------- + +sunify(Val) -> + round(Val*256). % Note that Solaris and Linux load averages are + % measured quite differently anyway + + +keysearchdelete(_, _, []) -> + {false, []}; +keysearchdelete(K, N, [T|Ts]) when element(N, T) == K -> + {{value, T}, Ts}; +keysearchdelete(K, N, [T|Ts]) -> + {X, NTs} = keysearchdelete(K, N, Ts), + {X, [T|NTs]}. + +%% Internal cpu utilization functions + +%% cpu_util_diff(New, Old) takes a list of new cpu_util records as first +%% argument and a list of old cpu_util records as second argument. The +%% two lists have to be sorted on cpu index in ascending order. +%% +%% The returned value is a difference list in descending order. +cpu_util_diff(New, Old) -> + cpu_util_diff(New, Old, []). + +cpu_util_diff([], [], Acc) -> + Acc; +cpu_util_diff([#cpu_util{cpu = Cpu, + busy = NewBusy, + non_busy = NewNonBusy} | NewCpuUtils], + [#cpu_util{cpu = Cpu, + busy = OldBusy, + non_busy = OldNonBusy} | OldCpuUtils], + Acc) -> + {PreBusy, GotBusy} = state_list_diff(NewBusy, OldBusy), + {NonBusy, GotNonBusy} = state_list_diff(NewNonBusy, OldNonBusy), + Busy = case GotBusy orelse GotNonBusy of + true -> + PreBusy; + false -> + %% This can happen if cpu_sup:util/[0,1] is called + %% again immediately after the previous call has + %% returned. Because the user obviously is doing + %% something we charge "user". + lists:map(fun ({user, 0}) -> {user, 1}; + ({_, 0} = StateTup) -> StateTup + end, + PreBusy) + end, +cpu_util_diff(NewCpuUtils, OldCpuUtils, [#cpu_util{cpu = Cpu, + busy = Busy, + non_busy = NonBusy} + | Acc]); + +%% A new cpu appeared +cpu_util_diff([#cpu_util{cpu = NC}|_] = New, + [#cpu_util{cpu = OC}|_] = Old, + Acc) when NC < OC -> +cpu_util_diff(New, [#cpu_util{cpu = NC}|Old], Acc); +cpu_util_diff([#cpu_util{cpu = NC}|_] = New, [], Acc) -> +cpu_util_diff(New, [#cpu_util{cpu = NC}], Acc); + +%% An old cpu disappeared +cpu_util_diff([#cpu_util{cpu = NC}|Ns], + [#cpu_util{cpu = OC}|_] = Old, + Acc) when NC > OC -> +cpu_util_diff(Ns, Old, Acc); +cpu_util_diff([], _Old, Acc) -> +cpu_util_diff([], [], Acc). + +cpu_util_rel(NewCpuUtils, OldCpuUtils, Detailed, PerCpu) -> + cpu_util_rel(cpu_util_diff(NewCpuUtils, OldCpuUtils), Detailed, PerCpu). + +%% +%% cpu_util_rel/3 takes a difference list of cpu_util records as first +%% argument, a boolean determining if the result should be detailed as +%% second argument, and a boolean determining if the result should be +%% per cpu as third argument. The first argument (the difference list) +%% has to be sorted on cpu index in descending order. +%% +cpu_util_rel(CUDiff, false, false) -> + {B, T} = lists:foldl(fun (#cpu_util{busy = BusyList, + non_busy = NonBusyList}, + {BusyAcc, TotAcc}) -> + Busy = state_list_sum(BusyList), + NonBusy = state_list_sum(NonBusyList), + {BusyAcc+Busy, TotAcc+Busy+NonBusy} + end, + {0, 0}, + CUDiff), + BRel = B/T*100, + {all, BRel, 100-BRel, []}; +cpu_util_rel(CUDiff, true, false) -> + cpu_util_rel_det(CUDiff, #cpu_util{cpu = [], busy = [], non_busy = []}); +cpu_util_rel(CUDiff, false, true) -> + cpu_util_rel_pcpu(CUDiff, []); +cpu_util_rel(CUDiff, true, true) -> + cpu_util_rel_det_pcpu(CUDiff, []). + +cpu_util_rel_pcpu([], Acc) -> + Acc; +cpu_util_rel_pcpu([#cpu_util{cpu = C, + busy = BusyList, + non_busy = NonBusyList} | Rest], Acc) -> + Busy = state_list_sum(BusyList), + NonBusy = state_list_sum(NonBusyList), + Tot = Busy + NonBusy, + cpu_util_rel_pcpu(Rest, [{C, Busy/Tot*100, NonBusy/Tot*100, []}|Acc]). + +cpu_util_rel_det([], #cpu_util{cpu = CpuAcc, + busy = BusyAcc, + non_busy = NonBusyAcc}) -> + Total = state_list_sum(BusyAcc) + state_list_sum(NonBusyAcc), + {CpuAcc, mk_rel_states(BusyAcc,Total), mk_rel_states(NonBusyAcc,Total), []}; +cpu_util_rel_det([#cpu_util{cpu = Cpu, + busy = Busy, + non_busy = NonBusy} | Rest], + #cpu_util{cpu = CpuAcc, + busy = BusyAcc, + non_busy = NonBusyAcc}) -> + cpu_util_rel_det(Rest, #cpu_util{cpu = [Cpu|CpuAcc], + busy = state_list_add(Busy, + BusyAcc), + non_busy = state_list_add(NonBusy, + NonBusyAcc)}). + +cpu_util_rel_det_pcpu([], Acc) -> + Acc; +cpu_util_rel_det_pcpu([#cpu_util{cpu = Cpu, + busy = Busy, + non_busy = NonBusy}| Rest], Acc) -> + Total = state_list_sum(Busy) + state_list_sum(NonBusy), + cpu_util_rel_det_pcpu(Rest, + [{Cpu, + mk_rel_states(Busy, Total), + mk_rel_states(NonBusy, Total), + []} | Acc]). + +mk_rel_states(States, Total) -> + lists:map(fun ({State, Value}) -> {State, 100*Value/Total} end, States). + +state_list_sum(StateList) -> + lists:foldl(fun ({_, X}, Acc) -> Acc+X end, 0, StateList). + +state_list_diff([],[]) -> + {[], false}; +state_list_diff([{State,ValueNew}|RestNew], []) -> + state_list_diff([{State, ValueNew} | RestNew], [{State, 0}]); +state_list_diff([{State,ValueNew}|RestNew], [{State,ValueOld}|RestOld]) -> + ValDiff = val_diff(State, ValueNew, ValueOld), + {RestStateDiff, FoundDiff} = state_list_diff(RestNew, RestOld), + {[{State, ValDiff} | RestStateDiff], FoundDiff orelse ValDiff /= 0}. + +state_list_add([],[]) -> + []; +state_list_add([{State, ValueA}|RestA], []) -> + [{State, ValueA} | state_list_add(RestA, [])]; +state_list_add([{State, ValueA} | RestA], [{State, ValueB} | RestB]) -> + [{State, ValueA + ValueB} | state_list_add(RestA, RestB)]. + +one_step_backwards(State, New, Old) -> + case os:type() of + {unix, linux} -> + %% This should never happen! But values sometimes takes a step + %% backwards on linux. We'll ignore it as long as it's only + %% one step... + 0; + _ -> + val_diff2(State, New, Old) + end. + +val_diff(State, New, Old) when New == Old - 1 -> + one_step_backwards(State, New, Old); +val_diff(State, ?MAX_UINT32, 0) -> + one_step_backwards(State, ?MAX_UINT32, 0); +val_diff(State, New, Old) -> + val_diff2(State, New, Old). + +val_diff2(State, New, Old) when New > ?MAX_UINT32; Old > ?MAX_UINT32 -> + %% We obviously got uints > 32 bits + ensure_positive_diff(State, New - Old); +val_diff2(State, New, Old) when New < Old -> + %% 32-bit integer wrapped + ensure_positive_diff(State, (?MAX_UINT32 + 1) + New - Old); +val_diff2(_State, New, Old) -> + New - Old. + +ensure_positive_diff(_State, Diff) when Diff >= 0 -> + Diff; +ensure_positive_diff(State, Diff) -> + throw({error, {negative_diff, State, Diff}}). +%%---------------------------------------------------------------------- +%% END: tainted internal functions +%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% cpu_sup measurement server wrapper +%%---------------------------------------------------------------------- + +measurement_server_call(Pid, Request) -> + Timeout = 5000, + Pid ! {self(), Request}, + receive + {data, Data} -> Data + after Timeout -> + {error, timeout} + end. + +measurement_server_restart(Pid) -> + exit(Pid, kill), + measurement_server_start(). + +measurement_server_start() -> + spawn(fun() -> measurement_server_init() end). + +measurement_server_init() -> + process_flag(trap_exit, true), + OS = os:type(), + Server = case OS of + {unix, Flavor} when Flavor==sunos; + Flavor==linux -> + port_server_start(); + {unix, Flavor} when Flavor==darwin; + Flavor==freebsd; + Flavor==dragonfly; + Flavor==openbsd; + Flavor==irix64; + Flavor==irix -> + not_used; + _ -> + exit({unsupported_os, OS}) + end, + measurement_server_loop(#internal{port=Server, os_type=OS}). + +measurement_server_loop(State) -> + receive + {_, quit} -> + State#internal.port ! {self(), ?quit}, + ok; + {'DOWN',Monitor,process,_,_} -> + measurement_server_loop(State#internal{ util = lists:keydelete( + Monitor, + 2, + State#internal.util)}); + {Pid, {?util, D, PC, Client}} -> + {Monitor, OldCpuUtil, Utils2} = case keysearchdelete(Client, 1, State#internal.util) of + {{value, {Client, Mon, U}}, Us} -> {Mon, U, Us}; + {false, Us} -> {erlang:monitor(process, Client), [], Us} + end, + try get_util_measurement(?util, State) of + NewCpuUtil -> + Result = cpu_util_rel(NewCpuUtil, OldCpuUtil, D, PC), + Pid ! {data, Result}, + measurement_server_loop(State#internal{util=[{Client,Monitor,NewCpuUtil}|Utils2]}) + catch + Error -> + Pid ! {error, Error}, + measurement_server_loop(State) + end; + {Pid, Request} -> + try get_uint32_measurement(Request, State) of + Result -> Pid ! {data, Result} + catch + Error -> Pid ! {error, Error} + end, + measurement_server_loop(State); + {'EXIT', Pid, _n} when State#internal.port == Pid -> + measurement_server_loop(State#internal{port = port_server_start()}); + _Other -> + measurement_server_loop(State) + end. + +%%---------------------------------------------------------------------- +%% cpu_sup port program server wrapper +%%---------------------------------------------------------------------- + +port_server_call(Pid, Command) -> + Pid ! {self(), Command}, + receive + {Pid, {data, Result}} -> Result; + {Pid, {error, Reason}} -> {error, Reason} + end. + +port_server_start() -> + Timeout = 6000, + Pid = spawn_link(fun() -> port_server_init(Timeout) end), + Pid ! {self(), ?ping}, + receive + {Pid, {data,4711}} -> Pid; + {error,Reason} -> {error, Reason} + after Timeout -> + {error, timeout} + end. + +port_server_init(Timeout) -> + Port = start_portprogram(), + port_server_loop(Port, Timeout). + +port_server_loop(Port, Timeout) -> + receive + + % Adjust timeout + {Pid, {timeout, Timeout}} -> + Pid ! {data, Timeout}, + port_server_loop(Port, Timeout); + % Number of processors + {Pid, ?nprocs} -> + port_command(Port, ?nprocs), + Result = port_receive_uint32(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + % Average load for the past minute + {Pid, ?avg1} -> + port_command(Port, ?avg1), + Result = port_receive_uint32(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + % Average load for the past five minutes + {Pid, ?avg5} -> + port_command(Port, ?avg5), + Result = port_receive_uint32(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + % Average load for the past 15 minutes + {Pid, ?avg15} -> + port_command(Port, ?avg15), + Result = port_receive_uint32(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + {Pid, ?util} -> + port_command(Port, ?util), + Result = port_receive_util(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + % Port ping + {Pid, ?ping} -> + port_command(Port, ?ping), + Result = port_receive_uint32(Port, Timeout), + Pid ! {self(), {data, Result}}, + port_server_loop(Port, Timeout); + + % Close port and this server + {Pid, ?quit} -> + port_command(Port, ?quit), + port_close(Port), + Pid ! {self(), {data, quit}}, + ok; + + % Ignore other commands + _ -> port_server_loop(Port, Timeout) + end. + +port_receive_uint32( Port, Timeout) -> port_receive_uint32(Port, Timeout, []). +port_receive_uint32(_Port, _Timeout, [D3,D2,D1,D0]) -> ?INT32(D3,D2,D1,D0); +port_receive_uint32(_Port, _Timeout, [_,_,_,_ | G]) -> exit({port_garbage, G}); +port_receive_uint32(Port, Timeout, D) -> + receive + {'EXIT', Port, Reason} -> exit({port_exit, Reason}); + {Port, {data, ND}} -> port_receive_uint32(Port, Timeout, D ++ ND) + after Timeout -> exit(timeout_uint32) end. + +port_receive_util(Port, Timeout) -> + receive + {Port, {data, [ NP3,NP2,NP1,NP0, % Number of processors + NE3,NE2,NE1,NE0 % Number of entries per processor + | CpuData]}} -> + port_receive_cpu_util( ?INT32(NP3,NP2,NP1,NP0), + ?INT32(NE3,NE2,NE1,NE0), + CpuData, []); + {'EXIT', Port, Reason} -> exit({port_exit, Reason}) + after Timeout -> exit(timeout_util) end. + +% per processor receive loop +port_receive_cpu_util(0, _NE, [], CpuList) -> + % Return in ascending cpu_id order + lists:reverse(CpuList); +port_receive_cpu_util(0, _NE, Garbage, _) -> + exit( {port_garbage, Garbage}); +port_receive_cpu_util(NP, NE, CpuData, CpuList) -> + {CpuUtil, Rest} = port_receive_cpu_util_entries(NE, #cpu_util{}, CpuData), + port_receive_cpu_util(NP - 1, NE, Rest, [ CpuUtil | CpuList]). + +% per entry receive loop +port_receive_cpu_util_entries(0, CU, Rest) -> + {CU, Rest}; +port_receive_cpu_util_entries(NE, CU, + [ CID3, CID2, CID1, CID0, + Val3, Val2, Val1, Val0 | + CpuData]) -> + + TagId = ?INT32(CID3,CID2,CID1,CID0), + Value = ?INT32(Val3,Val2,Val1,Val0), + + % Conversions from integers to atoms + case TagId of + ?cu_cpu_id -> + NewCU = CU#cpu_util{cpu = Value}, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_user -> + NewCU = CU#cpu_util{ + busy = [{user, Value} | CU#cpu_util.busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_nice_user -> + NewCU = CU#cpu_util{ + busy = [{nice_user, Value} | CU#cpu_util.busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_kernel -> + NewCU = CU#cpu_util{ + busy = [{kernel, Value} | CU#cpu_util.busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_io_wait -> + NewCU = CU#cpu_util{ + non_busy = [{wait, Value} | CU#cpu_util.non_busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_idle -> + NewCU = CU#cpu_util{ + non_busy = [{idle, Value} | CU#cpu_util.non_busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_hard_irq -> + NewCU = CU#cpu_util{ + busy = [{hard_irq, Value} | CU#cpu_util.busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_soft_irq -> + NewCU = CU#cpu_util{ + busy = [{soft_irq, Value} | CU#cpu_util.busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + ?cu_steal -> + NewCU = CU#cpu_util{ + non_busy = [{steal, Value} | CU#cpu_util.non_busy] }, + port_receive_cpu_util_entries(NE - 1, NewCU, CpuData); + Unhandled -> + exit({unexpected_type_id, Unhandled}) + end; +port_receive_cpu_util_entries(_, _, Data) -> + exit({data_mismatch, Data}). + +start_portprogram() -> + Command = filename:join([code:priv_dir(os_mon), "bin", "cpu_sup"]), + Port = open_port({spawn, Command}, [stream]), + port_command(Port, ?ping), + 4711 = port_receive_uint32(Port, 5000), + Port. diff --git a/lib/os_mon/src/disksup.erl b/lib/os_mon/src/disksup.erl new file mode 100644 index 0000000000..3340f7ee72 --- /dev/null +++ b/lib/os_mon/src/disksup.erl @@ -0,0 +1,369 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(disksup). +-behaviour(gen_server). + +%% API +-export([start_link/0]). +-export([get_disk_data/0, + get_check_interval/0, set_check_interval/1, + get_almost_full_threshold/0, set_almost_full_threshold/1]). +-export([dummy_reply/1, param_type/2, param_default/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Other exports +-export([format_status/2]). + +-record(state, {threshold, timeout, os, diskdata = [],port}). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, disksup}, disksup, [], []). + +get_disk_data() -> + os_mon:call(disksup, get_disk_data). + +get_check_interval() -> + os_mon:call(disksup, get_check_interval). +set_check_interval(Minutes) -> + case param_type(disk_space_check_interval, Minutes) of + true -> + os_mon:call(disksup, {set_check_interval, Minutes}); + false -> + erlang:error(badarg) + end. + +get_almost_full_threshold() -> + os_mon:call(disksup, get_almost_full_threshold). +set_almost_full_threshold(Float) -> + case param_type(disk_almost_full_threshold, Float) of + true -> + os_mon:call(disksup, {set_almost_full_threshold, Float}); + false -> + erlang:error(badarg) + end. + +dummy_reply(get_disk_data) -> + [{"none", 0, 0}]; +dummy_reply(get_check_interval) -> + minutes_to_ms(os_mon:get_env(disksup, disk_space_check_interval)); +dummy_reply({set_check_interval, _}) -> + ok; +dummy_reply(get_almost_full_threshold) -> + round(os_mon:get_env(disksup, disk_almost_full_threshold) * 100); +dummy_reply({set_almost_full_threshold, _}) -> + ok. + +param_type(disk_space_check_interval, Val) when is_integer(Val), + Val>=1 -> true; +param_type(disk_almost_full_threshold, Val) when is_number(Val), + 0= true; +param_type(_Param, _Val) -> false. + +param_default(disk_space_check_interval) -> 30; +param_default(disk_almost_full_threshold) -> 0.80. + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +init([]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + + OS = get_os(), + Port = case OS of + {unix, Flavor} when Flavor==sunos4; + Flavor==solaris; + Flavor==freebsd; + Flavor==dragonfly; + Flavor==darwin; + Flavor==linux; + Flavor==openbsd; + Flavor==irix64; + Flavor==irix -> + start_portprogram(); + {win32, _OSname} -> + not_used; + _ -> + exit({unsupported_os, OS}) + end, + + %% Read the values of some configuration parameters + Threshold = os_mon:get_env(disksup, disk_almost_full_threshold), + Timeout = os_mon:get_env(disksup, disk_space_check_interval), + + %% Initiation first disk check + self() ! timeout, + + {ok, #state{port=Port, os=OS, + threshold=round(Threshold*100), + timeout=minutes_to_ms(Timeout)}}. + +handle_call(get_disk_data, _From, State) -> + {reply, State#state.diskdata, State}; + +handle_call(get_check_interval, _From, State) -> + {reply, State#state.timeout, State}; +handle_call({set_check_interval, Minutes}, _From, State) -> + Timeout = minutes_to_ms(Minutes), + {reply, ok, State#state{timeout=Timeout}}; + +handle_call(get_almost_full_threshold, _From, State) -> + {reply, State#state.threshold, State}; +handle_call({set_almost_full_threshold, Float}, _From, State) -> + Threshold = round(Float * 100), + {reply, ok, State#state{threshold=Threshold}}; + +handle_call({set_threshold, Threshold}, _From, State) -> % test only + {reply, ok, State#state{threshold=Threshold}}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(timeout, State) -> + NewDiskData = check_disk_space(State#state.os, State#state.port, + State#state.threshold), + timer:send_after(State#state.timeout, timeout), + {noreply, State#state{diskdata = NewDiskData}}; +handle_info({'EXIT', _Port, Reason}, State) -> + {stop, {port_died, Reason}, State#state{port=not_used}}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + clear_alarms(), + case State#state.port of + not_used -> + ok; + Port -> + port_close(Port) + end, + ok. + +%% os_mon-2.0.1 +%% For live downgrade to/upgrade from os_mon-1.8[.1] +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + State = case PrevState#state.port of + not_used -> PrevState#state{port=noport}; + _ -> PrevState + end, + {ok, State}; + + %% Upgrade to this version + _Vsn -> + State = case PrevState#state.port of + noport -> PrevState#state{port=not_used}; + _ -> PrevState + end, + {ok, State} + end; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Other exports +%%---------------------------------------------------------------------- + +format_status(_Opt, [_PDict, #state{os = OS, threshold = Threshold, + timeout = Timeout, + diskdata = DiskData}]) -> + [{data, [{"OS", OS}, + {"Timeout", Timeout}, + {"Threshold", Threshold}, + {"DiskData", DiskData}]}]. + +%%---------------------------------------------------------------------- +%% Internal functions +%%---------------------------------------------------------------------- + +get_os() -> + case os:type() of + {unix, sunos} -> + case os:version() of + {5,_,_} -> {unix, solaris}; + {4,_,_} -> {unix, sunos4}; + V -> exit({unknown_os_version, V}) + end; + {unix, irix64} -> {unix, irix}; + OS -> + OS + end. + +%%--Port handling functions--------------------------------------------- + +start_portprogram() -> + open_port({spawn, "sh -s disksup 2>&1"}, [stream]). + +my_cmd(Cmd0, Port) -> + %% Insert a new line after the command, in case the command + %% contains a comment character + Cmd = io_lib:format("(~s\n) + receive + {Port, {data, N}} -> + case newline(N, O) of + {ok, Str} -> Str; + {more, Acc} -> get_reply(Port, Acc) + end; + {'EXIT', Port, Reason} -> + exit({port_died, Reason}) + end. + +newline([13|_], B) -> {ok, lists:reverse(B)}; +newline([H|T], B) -> newline(T, [H|B]); +newline([], B) -> {more, B}. + +%%--Check disk space---------------------------------------------------- + +check_disk_space({win32,_}, not_used, Threshold) -> + Result = os_mon_sysinfo:get_disk_info(), + check_disks_win32(Result, Threshold); +check_disk_space({unix, solaris}, Port, Threshold) -> + Result = my_cmd("/usr/bin/df -lk", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, irix}, Port, Threshold) -> + Result = my_cmd("/usr/sbin/df -lk",Port), + check_disks_irix(skip_to_eol(Result), Threshold); +check_disk_space({unix, linux}, Port, Threshold) -> + Result = my_cmd("/bin/df -lk", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, dragonfly}, Port, Threshold) -> + Result = my_cmd("/bin/df -k -t ufs,hammer", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, freebsd}, Port, Threshold) -> + Result = my_cmd("/bin/df -k -t ufs", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, openbsd}, Port, Threshold) -> + Result = my_cmd("/bin/df -k -t ffs", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, sunos4}, Port, Threshold) -> + Result = my_cmd("df", Port), + check_disks_solaris(skip_to_eol(Result), Threshold); +check_disk_space({unix, darwin}, Port, Threshold) -> + Result = my_cmd("/bin/df -k -t ufs,hfs", Port), + check_disks_solaris(skip_to_eol(Result), Threshold). + +% This code works for Linux and FreeBSD as well +check_disks_solaris("", _Threshold) -> + []; +check_disks_solaris("\n", _Threshold) -> + []; +check_disks_solaris(Str, Threshold) -> + case io_lib:fread("~s~d~d~d~d%~s", Str) of + {ok, [_FS, KB, _Used, _Avail, Cap, MntOn], RestStr} -> + if + Cap >= Threshold -> + set_alarm({disk_almost_full, MntOn}, []); + true -> + clear_alarm({disk_almost_full, MntOn}) + end, + [{MntOn, KB, Cap} | + check_disks_solaris(RestStr, Threshold)]; + _Other -> + check_disks_solaris(skip_to_eol(Str),Threshold) + end. + +%% Irix: like Linux with an extra FS type column and no '%'. +check_disks_irix("", _Threshold) -> []; +check_disks_irix("\n", _Threshold) -> []; +check_disks_irix(Str, Threshold) -> + case io_lib:fread("~s~s~d~d~d~d~s", Str) of + {ok, [_FS, _FSType, KB, _Used, _Avail, Cap, MntOn], RestStr} -> + if Cap >= Threshold -> set_alarm({disk_almost_full, MntOn}, []); + true -> clear_alarm({disk_almost_full, MntOn}) end, + [{MntOn, KB, Cap} | check_disks_irix(RestStr, Threshold)]; + _Other -> + check_disks_irix(skip_to_eol(Str),Threshold) + end. + +check_disks_win32([], _Threshold) -> + []; +check_disks_win32([H|T], Threshold) -> + case io_lib:fread("~s~s~d~d~d", H) of + {ok, [Drive,"DRIVE_FIXED",BAvail,BTot,_TotFree], _RestStr} -> + Cap = trunc((BTot-BAvail) / BTot * 100), + if + Cap >= Threshold -> + set_alarm({disk_almost_full, Drive}, []); + true -> + clear_alarm({disk_almost_full, Drive}) + end, + [{Drive, BTot div 1024, Cap} | + check_disks_win32(T, Threshold)]; % Return Total Capacity in Kbytes + {ok,_,_RestStr} -> + check_disks_win32(T,Threshold); + _Other -> + [] + end. + +%%--Alarm handling------------------------------------------------------ + +set_alarm(AlarmId, AlarmDescr) -> + case get(AlarmId) of + set -> + ok; + undefined -> + alarm_handler:set_alarm({AlarmId, AlarmDescr}), + put(AlarmId, set) + end. + +clear_alarm(AlarmId) -> + case get(AlarmId) of + set -> + alarm_handler:clear_alarm(AlarmId), + erase(AlarmId); + undefined -> + ok + end. + +clear_alarms() -> + lists:foreach(fun({{disk_almost_full, _MntOn} = AlarmId, set}) -> + alarm_handler:clear_alarm(AlarmId); + (_Other) -> + ignore + end, + get()). + +%%--Auxiliary----------------------------------------------------------- + +%% Type conversion +minutes_to_ms(Minutes) -> + trunc(60000*Minutes). + +skip_to_eol([]) -> + []; +skip_to_eol([$\n | T]) -> + T; +skip_to_eol([_ | T]) -> + skip_to_eol(T). diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl new file mode 100644 index 0000000000..822e1f939c --- /dev/null +++ b/lib/os_mon/src/memsup.erl @@ -0,0 +1,1022 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(memsup). +-behaviour(gen_server). + +%% API +-export([start_link/0]). % for supervisor +-export([get_memory_data/0, get_system_memory_data/0, + get_check_interval/0, set_check_interval/1, + get_procmem_high_watermark/0, set_procmem_high_watermark/1, + get_sysmem_high_watermark/0, set_sysmem_high_watermark/1, + get_helper_timeout/0, set_helper_timeout/1, + get_os_wordsize/0]). +-export([dummy_reply/1, param_type/2, param_default/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Other exports +-export([format_status/2]). + +-include("memsup.hrl"). + +-record(state, + {os, % {OSfamily,OSname} | OSfamily + port_mode, % bool() + + mem_usage, % undefined | {Alloc, Total} + worst_mem_user, % undefined | {Pid, Alloc} + + sys_only, % bool() memsup_system_only + timeout, % int() memory_check_interval, ms + helper_timeout, % int() memsup_helper_timeout, ms + sys_mem_watermark, % float() system_memory_high_watermark, % + proc_mem_watermark, % float() process_memory_high_watermark, % + + pid, % undefined | pid() + wd_timer, % undefined | TimerRef + ext_wd_timer, % undefined | TimerRef + pending = [], % [reg | {reg,From} | {ext,From}] + ext_pending = [] % [{ext,From}] + }). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +get_os_wordsize() -> + os_mon:call(memsup, get_os_wordsize, infinity). + +get_memory_data() -> + os_mon:call(memsup, get_memory_data, infinity). + +get_system_memory_data() -> + os_mon:call(memsup, get_system_memory_data, infinity). + +get_check_interval() -> + os_mon:call(memsup, get_check_interval, infinity). +set_check_interval(Minutes) -> + case param_type(memory_check_interval, Minutes) of + true -> + MS = minutes_to_ms(Minutes), % for backwards compatibility + os_mon:call(memsup, {set_check_interval, MS}, infinity); + false -> + erlang:error(badarg) + end. + +get_procmem_high_watermark() -> + os_mon:call(memsup, get_procmem_high_watermark, infinity). +set_procmem_high_watermark(Float) -> + case param_type(process_memory_high_watermark, Float) of + true -> + os_mon:call(memsup, {set_procmem_high_watermark, Float}, + infinity); + false -> + erlang:error(badarg) + end. + +get_sysmem_high_watermark() -> + os_mon:call(memsup, get_sysmem_high_watermark, infinity). +set_sysmem_high_watermark(Float) -> + case param_type(system_memory_high_watermark, Float) of + true -> + os_mon:call(memsup, {set_sysmem_high_watermark, Float}, + infinity); + false -> + erlang:error(badarg) + end. + +get_helper_timeout() -> + os_mon:call(memsup, get_helper_timeout, infinity). +set_helper_timeout(Seconds) -> + case param_type(memsup_helper_timeout, Seconds) of + true -> + os_mon:call(memsup, {set_helper_timeout, Seconds}); + false -> + erlang:error(badarg) + end. + +dummy_reply(get_memory_data) -> + dummy_reply(get_memory_data, + os_mon:get_env(memsup, memsup_system_only)); +dummy_reply(get_system_memory_data) -> + []; +dummy_reply(get_os_wordsize) -> + 0; +dummy_reply(get_check_interval) -> + minutes_to_ms(os_mon:get_env(memsup, memory_check_interval)); +dummy_reply({set_check_interval, _}) -> + ok; +dummy_reply(get_procmem_high_watermark) -> + trunc(100 * os_mon:get_env(memsup, process_memory_high_watermark)); +dummy_reply({set_procmem_high_watermark, _}) -> + ok; +dummy_reply(get_sysmem_high_watermark) -> + trunc(100 * os_mon:get_env(memsup, system_memory_high_watermark)); +dummy_reply({set_sysmem_high_watermark, _}) -> + ok; +dummy_reply(get_helper_timeout) -> + os_mon:get_env(memsup, memsup_helper_timeout); +dummy_reply({set_helper_timeout, _}) -> + ok. +dummy_reply(get_memory_data, true) -> + {0,0,undefined}; +dummy_reply(get_memory_data, false) -> + {0,0,{self(),0}}. + +param_type(memsup_system_only, Val) when Val==true; Val==false -> true; +param_type(memory_check_interval, Val) when is_integer(Val), + Val>0 -> true; +param_type(memsup_helper_timeout, Val) when is_integer(Val), + Val>0 -> true; +param_type(system_memory_high_watermark, Val) when is_number(Val), + 0= true; +param_type(process_memory_high_watermark, Val) when is_number(Val), + 0= true; +param_type(_Param, _Val) -> false. + +param_default(memsup_system_only) -> false; +param_default(memory_check_interval) -> 1; +param_default(memsup_helper_timeout) -> 30; +param_default(system_memory_high_watermark) -> 0.80; +param_default(process_memory_high_watermark) -> 0.05. + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +init([]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + + OS = os:type(), + PortMode = case OS of + {unix, darwin} -> false; + {unix, freebsd} -> false; + % Linux supports this. + {unix, linux} -> true; + {unix, openbsd} -> true; + {unix, irix64} -> true; + {unix, irix} -> true; + {unix, sunos} -> true; + {win32, _OSname} -> false; + vxworks -> true; + _ -> + exit({unsupported_os, OS}) + end, + Pid = if + PortMode -> + spawn_link(fun() -> port_init() end); + not PortMode -> + undefined + end, + + %% Read the values of some configuration parameters + SysOnly = os_mon:get_env(memsup, memsup_system_only), + Timeout = os_mon:get_env(memsup, memory_check_interval), + HelperTimeout = os_mon:get_env(memsup, memsup_helper_timeout), + SysMem = os_mon:get_env(memsup, system_memory_high_watermark), + ProcMem = os_mon:get_env(memsup, process_memory_high_watermark), + + %% Initiate first data collection + self() ! time_to_collect, + + {ok, #state{os=OS, port_mode=PortMode, + + sys_only = SysOnly, + timeout = minutes_to_ms(Timeout), + helper_timeout = sec_to_ms(HelperTimeout), + sys_mem_watermark = SysMem, + proc_mem_watermark = ProcMem, + + pid=Pid}}. + +handle_call(get_os_wordsize, _From, State) -> + Wordsize = get_os_wordsize(State#state.os), + {reply, Wordsize, State}; +handle_call(get_memory_data, From, State) -> + %% Return result of latest memory check + case State#state.mem_usage of + {Alloc, Total} -> + Worst = State#state.worst_mem_user, + {reply, {Total, Alloc, Worst}, State}; + + %% Special case: get_memory_data called before any memory data + %% has been collected + undefined -> + case State#state.wd_timer of + undefined -> + WDTimer = erlang:send_after(State#state.timeout, + self(), + reg_collection_timeout), + Pending = [{reg,From}], + if + State#state.port_mode -> + State#state.pid ! {self(), collect_sys}, + {noreply, State#state{wd_timer=WDTimer, + pending=Pending}}; + true -> + OS = State#state.os, + Self = self(), + Pid = spawn_link(fun() -> + MU = get_memory_usage(OS), + Self ! {collected_sys,MU} + end), + {noreply, State#state{pid=Pid, + wd_timer=WDTimer, + pending=Pending}} + end; + _TimerRef -> + Pending = [{reg,From} | State#state.pending], + {noreply, State#state{pending=Pending}} + end + end; + +handle_call(get_system_memory_data,From,#state{port_mode=true}=State) -> + %% When using a port, the extensive memory collection is slightly + %% different than a regular one + case State#state.ext_wd_timer of + undefined -> + WDTimer = erlang:send_after(State#state.helper_timeout, + self(), + ext_collection_timeout), + State#state.pid ! {self(), collect_ext_sys}, + {noreply, State#state{ext_wd_timer=WDTimer, + ext_pending=[{ext,From}]}}; + _TimerRef -> + Pending = [{ext,From} | State#state.ext_pending], + {noreply, State#state{ext_pending=Pending}} + end; +handle_call(get_system_memory_data, From, State) -> + %% When not using a port, the regular memory collection is used + %% for extensive memory data as well + case State#state.wd_timer of + undefined -> + WDTimer = erlang:send_after(State#state.helper_timeout, + self(), + reg_collection_timeout), + OS = State#state.os, + Self = self(), + Pid = spawn_link(fun() -> + MemUsage = get_memory_usage(OS), + Self ! {collected_sys, MemUsage} + end), + {noreply, State#state{pid=Pid, wd_timer=WDTimer, + pending=[{ext,From}]}}; + _TimerRef -> + Pending = [{ext,From} | State#state.pending], + {noreply, State#state{pending=Pending}} + end; + +handle_call(get_check_interval, _From, State) -> + {reply, State#state.timeout, State}; +handle_call({set_check_interval, MS}, _From, State) -> + {reply, ok, State#state{timeout=MS}}; + +handle_call(get_procmem_high_watermark, _From, State) -> + {reply, trunc(100 * State#state.proc_mem_watermark), State}; +handle_call({set_procmem_high_watermark, Float}, _From, State) -> + {reply, ok, State#state{proc_mem_watermark=Float}}; + +handle_call(get_sysmem_high_watermark, _From, State) -> + {reply, trunc(100 * State#state.sys_mem_watermark), State}; +handle_call({set_sysmem_high_watermark, Float}, _From, State) -> + {reply, ok, State#state{sys_mem_watermark=Float}}; + +handle_call(get_helper_timeout, _From, State) -> + {reply, ms_to_sec(State#state.helper_timeout), State}; +handle_call({set_helper_timeout, Seconds}, _From, State) -> + {reply, ok, State#state{helper_timeout=sec_to_ms(Seconds)}}; + +%% The following are only for test purposes (whitebox testing). +handle_call({set_sys_hw, HW}, _From, State) -> + {reply, ok, State#state{sys_mem_watermark=HW}}; +handle_call({set_pid_hw, HW}, _From, State) -> + {reply, ok, State#state{proc_mem_watermark=HW}}; +handle_call(get_state, _From, State) -> + {reply, State, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% It's time to check memory +handle_info(time_to_collect, State) -> + case State#state.wd_timer of + undefined -> + WDTimer = erlang:send_after(State#state.helper_timeout, + self(), + reg_collection_timeout), + if + State#state.port_mode -> + State#state.pid ! {self(), collect_sys}, + {noreply, State#state{wd_timer=WDTimer, + pending=[reg]}}; + true -> + OS = State#state.os, + Self = self(), + Pid = spawn_link(fun() -> + MU = get_memory_usage(OS), + Self ! {collected_sys,MU} + end), + {noreply, State#state{pid=Pid, wd_timer=WDTimer, + pending=[reg]}} + end; + _TimerRef -> + {noreply, State#state{pending=[reg|State#state.pending]}} + end; + +%% Memory data collected +handle_info({collected_sys, {Alloc,Total}}, State) -> + + %% Cancel watchdog timer (and as a security measure, + %% also flush any reg_collection_timeout message) + TimeSpent = case erlang:cancel_timer(State#state.wd_timer) of + false -> + State#state.helper_timeout; + TimeLeft -> + State#state.helper_timeout-TimeLeft + end, + flush(reg_collection_timeout), + + %% First check if this is the result of a periodic memory check + %% and update alarms and State if this is the case + State2 = + case lists:member(reg, State#state.pending) of + true -> + + %% Check if system alarm should be set/cleared + if + Alloc > State#state.sys_mem_watermark*Total -> + set_alarm(system_memory_high_watermark, []); + true -> + clear_alarm(system_memory_high_watermark) + end, + + %% Check if process data should be collected + case State#state.sys_only of + false -> + {Pid, Bytes} = get_worst_memory_user(), + Threshold= State#state.proc_mem_watermark*Total, + + %% Check if process alarm should be set/cleared + if + Bytes > Threshold -> + set_alarm(process_memory_high_watermark, + Pid); + true -> + clear_alarm(process_memory_high_watermark) + end, + + State#state{mem_usage={Alloc, Total}, + worst_mem_user={Pid, Bytes}}; + true -> + State#state{mem_usage={Alloc, Total}} + end; + false -> + State + end, + + %% Then send a reply to all waiting clients, in preserved time order + Worst = State2#state.worst_mem_user, + SysMemUsage = get_ext_memory_usage(State2#state.os, {Alloc,Total}), + reply(State2#state.pending, {Total,Alloc,Worst}, SysMemUsage), + + %% Last, if this was a periodic check, start a timer for the next + %% one. New timeout = interval-time spent collecting, + case lists:member(reg, State#state.pending) of + true -> + Time = case State2#state.timeout - TimeSpent of + MS when MS<0 -> + 0; + MS -> + MS + end, + erlang:send_after(Time, self(), time_to_collect); + false -> + ignore + end, + {noreply, State2#state{wd_timer=undefined, pending=[]}}; +handle_info({'EXIT', Pid, normal}, State) when is_pid(Pid) -> + %% Temporary pid terminating when job is done + {noreply, State}; + +%% Timeout during data collection +handle_info(reg_collection_timeout, State) -> + + %% Cancel memory collection (and as a security measure, + %% also flush any collected_sys message) + if + State#state.port_mode -> State#state.pid ! cancel; + true -> exit(State#state.pid, cancel) + end, + flush(collected_sys), + + %% Issue a warning message + Str = "OS_MON (memsup) timeout, no data collected~n", + error_logger:warning_msg(Str), + + %% Send a dummy reply to all waiting clients, preserving time order + reply(State#state.pending, + dummy_reply(get_memory_data, State#state.sys_only), + dummy_reply(get_system_memory_data)), + + %% If it is a periodic check which has timed out, start a timer for + %% the next one + %% New timeout = interval-helper timeout + case lists:member(reg, State#state.pending) of + true -> + Time = + case State#state.timeout-State#state.helper_timeout of + MS when MS<0 -> 0; + MS -> MS + end, + erlang:send_after(Time, self(), time_to_collect); + false -> + ignore + end, + {noreply, State#state{wd_timer=undefined, pending=[]}}; +handle_info({'EXIT', Pid, cancel}, State) when is_pid(Pid) -> + %% Temporary pid terminating as ordered + {noreply, State}; + +%% Extensive memory data collected (port_mode==true only) +handle_info({collected_ext_sys, SysMemUsage}, State) -> + + %% Cancel watchdog timer (and as a security mearure, + %% also flush any ext_collection_timeout message) + erlang:cancel_timer(State#state.ext_wd_timer), + flush(ext_collection_timeout), + + %% Send the reply to all waiting clients, preserving time order + reply(State#state.ext_pending, undef, SysMemUsage), + + {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}}; + +%% Timeout during ext memory data collection (port_mode==true only) +handle_info(ext_collection_timeout, State) -> + + %% Cancel memory collection (and as a security measure, + %% also flush any collected_ext_sys message) + State#state.pid ! ext_cancel, + flush(collected_ext_sys), + + %% Issue a warning message + Str = "OS_MON (memsup) timeout, no data collected~n", + error_logger:warning_msg(Str), + + %% Send a dummy reply to all waiting clients, preserving time order + SysMemUsage = dummy_reply(get_system_memory_data), + reply(State#state.ext_pending, undef, SysMemUsage), + + {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}}; + +%% Error in data collecting (port connected or temporary) process +handle_info({'EXIT', Pid, Reason}, State) when is_pid(Pid) -> + {stop, Reason, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + if + State#state.port_mode -> State#state.pid ! close; + true -> ok + end, + clear_alarms(), + ok. + +%% os_mon-2.0.1 +%% For live downgrade to/upgrade from os_mon-1.8[.1] and -2.0 +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + + %% Kill the helper process, if there is one, + %% and flush messages from it + case PrevState#state.pid of + Pid when is_pid(Pid) -> + unlink(Pid), % to prevent 'EXIT' message + exit(Pid, cancel); + undefined -> ignore + end, + flush(collected_sys), + flush(collected_ext_sys), + + %% Cancel timers, flush timeout messages + %% and send dummy replies to any pending clients + case PrevState#state.wd_timer of + undefined -> + ignore; + TimerRef1 -> + erlang:cancel_timer(TimerRef1), + SysOnly = PrevState#state.sys_only, + MemUsage = dummy_reply(get_memory_data, SysOnly), + SysMemUsage1 = dummy_reply(get_system_memory_data), + reply(PrevState#state.pending,MemUsage,SysMemUsage1) + end, + case PrevState#state.ext_wd_timer of + undefined -> + ignore; + TimerRef2 -> + erlang:cancel_timer(TimerRef2), + SysMemUsage2 = dummy_reply(get_system_memory_data), + reply(PrevState#state.pending, undef, SysMemUsage2) + end, + flush(reg_collection_timeout), + flush(ext_collection_timeout), + + %% Downgrade to old state record + State = {state, + PrevState#state.timeout, + PrevState#state.mem_usage, + PrevState#state.worst_mem_user, + PrevState#state.sys_mem_watermark, + PrevState#state.proc_mem_watermark, + not PrevState#state.sys_only, % collect_procmem + undefined, % wd_timer + [], % pending + undefined, % ext_wd_timer + [], % ext_pending + PrevState#state.helper_timeout}, + {ok, State}; + + %% Upgrade to this version + _Vsn -> + + %% Old state record + {state, + Timeout, MemUsage, WorstMemUser, + SysMemWatermark, ProcMemWatermark, CollProc, + WDTimer, Pending, ExtWDTimer, ExtPending, + HelperTimeout} = PrevState, + SysOnly = not CollProc, + + %% Flush memsup_helper messages + flush(collected_sys), + flush(collected_proc), + flush(collected_ext_sys), + + %% Cancel timers, flush timeout messages + %% and send dummy replies to any pending clients + case WDTimer of + undefined -> + ignore; + TimerRef1 -> + erlang:cancel_timer(TimerRef1), + MemUsage = dummy_reply(get_memory_data, SysOnly), + Pending2 = lists:map(fun(From) -> {reg,From} end, + Pending), + reply(Pending2, MemUsage, undef) + end, + case ExtWDTimer of + undefined -> + ignore; + TimerRef2 -> + erlang:cancel_timer(TimerRef2), + SysMemUsage = dummy_reply(get_system_memory_data), + ExtPending2 = lists:map(fun(From) -> {ext,From} end, + ExtPending), + reply(ExtPending2, undef, SysMemUsage) + end, + flush(reg_collection_timeout), + flush(ext_collection_timeout), + + OS = os:type(), + PortMode = case OS of + {unix, darwin} -> false; + {unix, freebsd} -> false; + {unix, linux} -> false; + {unix, openbsd} -> true; + {unix, sunos} -> true; + {win32, _OSname} -> false; + vxworks -> true + end, + Pid = if + PortMode -> spawn_link(fun() -> port_init() end); + not PortMode -> undefined + end, + + %% Upgrade to this state record + State = #state{os = OS, + port_mode = PortMode, + mem_usage = MemUsage, + worst_mem_user = WorstMemUser, + sys_only = SysOnly, + timeout = Timeout, + helper_timeout = HelperTimeout, + sys_mem_watermark = SysMemWatermark, + proc_mem_watermark = ProcMemWatermark, + pid = Pid, + wd_timer = undefined, + ext_wd_timer = undefined, + pending = [], + ext_pending = []}, + {ok, State} + end; +code_change(_Vsn, State, "2.0") -> + + %% Restart the port process (it must use new memsup code) + Pid = case State#state.port_mode of + true -> + State#state.pid ! close, + spawn_link(fun() -> port_init() end); + false -> + State#state.pid + end, + {ok, State#state{pid=Pid}}; + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Other exports +%%---------------------------------------------------------------------- + +format_status(_Opt, [_PDict, #state{timeout=Timeout, mem_usage=MemUsage, + worst_mem_user=WorstMemUser}]) -> + {Allocated, Total} = MemUsage, + WorstMemFormat = case WorstMemUser of + {Pid, Mem} -> + [{"Pid", Pid}, {"Memory", Mem}]; + undefined -> + undefined + end, + [{data, [{"Timeout", Timeout}]}, + {items, {"Memory Usage", [{"Allocated", Allocated}, + {"Total", Total}]}}, + {items, {"Worst Memory User", WorstMemFormat}}]. + + +%%---------------------------------------------------------------------- +%% Internal functions +%%---------------------------------------------------------------------- + +%%-- Fetching kernel bit support --------------------------------------- + +get_os_wordsize({unix, sunos}) -> + String = clean_string(os:cmd("isainfo -b")), + erlang:list_to_integer(String); +get_os_wordsize({unix, irix64}) -> 64; +get_os_wordsize({unix, irix}) -> 32; +get_os_wordsize({unix, linux}) -> get_os_wordsize_with_uname(); +get_os_wordsize({unix, darwin}) -> get_os_wordsize_with_uname(); +get_os_wordsize({unix, netbsd}) -> get_os_wordsize_with_uname(); +get_os_wordsize({unix, freebsd}) -> get_os_wordsize_with_uname(); +get_os_wordsize({unix, openbsd}) -> get_os_wordsize_with_uname(); +get_os_wordsize(_) -> unsupported_os. + +get_os_wordsize_with_uname() -> + String = clean_string(os:cmd("uname -m")), + case String of + "x86_64" -> 64; + "sparc64" -> 64; + _ -> 32 + end. + +clean_string(String) -> lists:flatten(string:tokens(String,"\r\n\t ")). + + +%%--Replying to pending clients----------------------------------------- + +reply(Pending, MemUsage, SysMemUsage) -> + lists:foreach(fun(reg) -> + ignore; + ({reg, From}) -> + gen_server:reply(From, MemUsage); + ({ext, From}) -> + gen_server:reply(From, SysMemUsage) + end, + lists:reverse(Pending)). + +%%--Collect memory data, no port---------------------------------------- + +%% get_memory_usage(OS) -> {Alloc, Total} + +%% Darwin: +%% Uses vm_stat command. This appears to lie about the page size in +%% Mac OS X 10.2.2 - the pages given are based on 4000 bytes, but +%% the vm_stat command tells us that it is 4096... +get_memory_usage({unix,darwin}) -> + Str1 = os:cmd("/usr/bin/vm_stat"), + + {[Free], Str2} = fread_value("Pages free:~d.", Str1), + {[Active], Str3} = fread_value("Pages active:~d.", Str2), + {[Inactive], Str4} = fread_value("Pages inactive:~d.", Str3), + {[_], Str5} = fread_value("Pages speculative:~d.", Str4), + {[Wired], _} = fread_value("Pages wired down:~d.", Str5), + + NMemUsed = (Wired + Active + Inactive) * 4000, + NMemTotal = NMemUsed + Free * 4000, + {NMemUsed,NMemTotal}; + +%% FreeBSD: Look in /usr/include/sys/vmmeter.h for the format of struct +%% vmmeter +get_memory_usage({unix,freebsd}) -> + PageSize = freebsd_sysctl("vm.stats.vm.v_page_size"), + PageCount = freebsd_sysctl("vm.stats.vm.v_page_count"), + FreeCount = freebsd_sysctl("vm.stats.vm.v_free_count"), + NMemUsed = (PageCount - FreeCount) * PageSize, + NMemTotal = PageCount * PageSize, + {NMemUsed, NMemTotal}; + +%% Win32: Find out how much memory is in use by asking +%% the os_mon_sysinfo process. +get_memory_usage({win32,_OSname}) -> + [Result|_] = os_mon_sysinfo:get_mem_info(), + {ok, [_MemLoad, TotPhys, AvailPhys, + _TotPage, _AvailPage, _TotV, _AvailV], _RestStr} = + io_lib:fread("~d~d~d~d~d~d~d", Result), + {TotPhys-AvailPhys, TotPhys}. + +fread_value(Format, Str0) -> + case io_lib:fread(Format, skip_to_eol(Str0)) of + {error, {fread, input}} -> {[0], Str0}; + {ok, Value, Str1} -> {Value, Str1} + end. + +skip_to_eol([]) -> []; +skip_to_eol([$\n | T]) -> T; +skip_to_eol([_ | T]) -> skip_to_eol(T). + +freebsd_sysctl(Def) -> + list_to_integer(os:cmd("/sbin/sysctl -n " ++ Def) -- "\n"). + +%% get_ext_memory_usage(OS, {Alloc, Total}) -> [{Tag, Bytes}] +get_ext_memory_usage(OS, {Alloc, Total}) -> + case OS of + {win32, _} -> + [{total_memory, Total}, {free_memory, Total-Alloc}, + {system_total_memory, Total}]; + {unix, linux} -> + [{total_memory, Total}, {free_memory, Total-Alloc}, + %% corr. unless setrlimit() set + {system_total_memory, Total}]; + {unix, freebsd} -> + [{total_memory, Total}, {free_memory, Total-Alloc}, + {system_total_memory, Total}]; + {unix, darwin} -> + [{total_memory, Total}, {free_memory, Total-Alloc}, + {system_total_memory, Total}]; + _ -> % OSs using a port + dummy % not sent anyway + end. + +%%--Collect memory data, using port------------------------------------- + +port_init() -> + process_flag(trap_exit, true), + Port = start_portprogram(), + port_idle(Port). + +start_portprogram() -> + Command = filename:join([code:priv_dir(os_mon), "bin", "memsup"]), + open_port({spawn, Command}, [{packet, 1}]). + +%% The connected process loops are a bit awkward (several different +%% functions doing almost the same thing) as +%% a) strategies for receiving regular memory data and extensive +%% memory data are different +%% b) memory collection can be cancelled, in which case the process +%% should still wait for port response (which should come +%% eventually!) but not receive any requests or cancellations +%% meanwhile to prevent getting out of synch. +port_idle(Port) -> + receive + {Memsup, collect_sys} -> + Port ! {self(), {command, [?SHOW_MEM]}}, + get_memory_usage(Port, undefined, Memsup); + {Memsup, collect_ext_sys} -> + Port ! {self(), {command, [?SHOW_SYSTEM_MEM]}}, + get_ext_memory_usage(Port, [], Memsup); + cancel -> + %% Received after reply already has been delivered... + port_idle(Port); + ext_cancel -> + %% Received after reply already has been delivered... + port_idle(Port); + close -> + port_close(Port); + {Port, {data, Data}} -> + exit({port_error, Data}); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. + +get_memory_usage(Port, Alloc, Memsup) -> + receive + {Port, {data, Data}} when Alloc==undefined -> + get_memory_usage(Port, erlang:list_to_integer(Data, 16), Memsup); + {Port, {data, Data}} -> + Total = erlang:list_to_integer(Data, 16), + Memsup ! {collected_sys, {Alloc, Total}}, + port_idle(Port); + cancel -> + get_memory_usage_cancelled(Port, Alloc); + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. +get_memory_usage_cancelled(Port, Alloc) -> + receive + {Port, {data, _Data}} when Alloc==undefined -> + get_memory_usage_cancelled(Port, 0); + {Port, {data, _Data}} -> + port_idle(Port); + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. + +get_ext_memory_usage(Port, Accum, Memsup) -> + Tab = [ + {?MEM_SYSTEM_TOTAL, system_total_memory}, + {?MEM_TOTAL, total_memory}, + {?MEM_FREE, free_memory}, + {?MEM_BUFFERS, buffered_memory}, + {?MEM_CACHED, cached_memory}, + {?MEM_SHARED, shared_memory}, + {?MEM_LARGEST_FREE, largest_free}, + {?MEM_NUMBER_OF_FREE, number_of_free}, + {?SWAP_TOTAL, total_swap}, + {?SWAP_FREE, free_swap} + ], + receive + {Port, {data, [?SHOW_SYSTEM_MEM_END]}} -> + Memsup ! {collected_ext_sys, Accum}, + port_idle(Port); + {Port, {data, [Tag]}} -> + case lists:keysearch(Tag, 1, Tab) of + {value, {Tag, ATag}} -> + get_ext_memory_usage(ATag, Port, Accum, Memsup); + _ -> + exit({memsup_port_error, {Port,[Tag]}}) + end; + ext_cancel -> + get_ext_memory_usage_cancelled(Port); + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. +get_ext_memory_usage_cancelled(Port) -> + Tab = [ + {?MEM_SYSTEM_TOTAL, system_total_memory}, + {?MEM_TOTAL, total_memory}, + {?MEM_FREE, free_memory}, + {?MEM_BUFFERS, buffered_memory}, + {?MEM_CACHED, cached_memory}, + {?MEM_SHARED, shared_memory}, + {?MEM_LARGEST_FREE, largest_free}, + {?MEM_NUMBER_OF_FREE, number_of_free}, + {?SWAP_TOTAL, total_swap}, + {?SWAP_FREE, free_swap} + ], + receive + {Port, {data, [?SHOW_SYSTEM_MEM_END]}} -> + port_idle(Port); + {Port, {data, [Tag]}} -> + case lists:keysearch(Tag, 1, Tab) of + {value, {Tag, ATag}} -> + get_ext_memory_usage_cancelled(ATag, Port); + _ -> + exit({memsup_port_error, {Port,[Tag]}}) + end; + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. + +get_ext_memory_usage(ATag, Port, Accum0, Memsup) -> + receive + {Port, {data, Data}} -> + Accum = [{ATag,erlang:list_to_integer(Data, 16)}|Accum0], + get_ext_memory_usage(Port, Accum, Memsup); + cancel -> + get_ext_memory_usage_cancelled(ATag, Port); + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. +get_ext_memory_usage_cancelled(_ATag, Port) -> + receive + {Port, {data, _Data}} -> + get_ext_memory_usage_cancelled(Port); + close -> + port_close(Port); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}); + {'EXIT', _Memsup, _Reason} -> + port_close(Port) + end. + +%%--Collect process data------------------------------------------------ + +%% get_worst_memory_user() -> {Pid, Bytes} +get_worst_memory_user() -> + get_worst_memory_user(processes(), self(), 0). + +get_worst_memory_user([Pid|Pids], MaxPid, MaxMemBytes) -> + case process_memory(Pid) of + undefined -> + get_worst_memory_user(Pids, MaxPid, MaxMemBytes); + MemoryBytes when MemoryBytes>MaxMemBytes -> + get_worst_memory_user(Pids, Pid, MemoryBytes); + _MemoryBytes -> + get_worst_memory_user(Pids, MaxPid, MaxMemBytes) + end; +get_worst_memory_user([], MaxPid, MaxMemBytes) -> + {MaxPid, MaxMemBytes}. + +process_memory(Pid) -> + case process_info(Pid, memory) of + {memory, Bytes} -> + Bytes; + undefined -> % Pid must have died + undefined + end. + +%%--Alarm handling------------------------------------------------------ + +set_alarm(AlarmId, AlarmDescr) -> + case get(AlarmId) of + set -> + ok; + undefined -> + alarm_handler:set_alarm({AlarmId, AlarmDescr}), + put(AlarmId, set) + end. + +clear_alarm(AlarmId) -> + case get(AlarmId) of + set -> + alarm_handler:clear_alarm(AlarmId), + erase(AlarmId); + _ -> + ok + end. + +clear_alarms() -> + lists:foreach(fun({system_memory_high_watermark = Id, set}) -> + alarm_handler:clear_alarm(Id); + ({process_memory_high_watermark = Id, set}) -> + alarm_handler:clear_alarm(Id); + (_Other) -> + ignore + end, + get()). + +%%--Auxiliary----------------------------------------------------------- + +%% Type conversions +minutes_to_ms(Minutes) -> trunc(60000*Minutes). +sec_to_ms(Sec) -> trunc(1000*Sec). +ms_to_sec(MS) -> MS div 1000. + +flush(Msg) -> + receive + {Msg, _} -> true; + Msg -> true + after 0 -> + true + end. diff --git a/lib/os_mon/src/nteventlog.erl b/lib/os_mon/src/nteventlog.erl new file mode 100644 index 0000000000..d624048c29 --- /dev/null +++ b/lib/os_mon/src/nteventlog.erl @@ -0,0 +1,162 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. 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% +%% +-module(nteventlog). +-behaviour(gen_server). + +%% API +-export([start_link/2, start/2, stop/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {port, mfa}). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +start_link(Ident, MFA) -> + gen_server:start_link({local, nteventlog}, nteventlog, + [Ident, MFA], []). + +start(Ident, MFA) -> + gen_server:start({local, nteventlog}, nteventlog, [Ident, MFA], []). + +stop() -> + gen_server:call(nteventlog, stop). + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +init([Identifier,MFA0]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + + Port = case os:type() of + {win32, _OSname} -> start_portprogram(Identifier); + OS -> exit({unsupported_os, OS}) + end, + + %% If we're using os_sup:error_report/2, + %% the setting of os_sup_errortag should be used as argument + MFA = case MFA0 of + {os_sup, error_report, [_Tag]} -> + Tag = os_mon:get_env(os_sup, os_sup_errortag), + {os_sup, error_report, [Tag]}; + _ -> + MFA0 + end, + + {ok, #state{port=Port, mfa=MFA}}. + +handle_call(stop, _From, State) -> + {stop, normal, stopped, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) -> + T = parse_log(Data), + apply(M, F, [T | A]), + State#state.port ! {self(), {command, "A"}}, + {noreply, State}; +handle_info({'EXIT', _Port, Reason}, State) -> + {stop, {port_died, Reason}, State#state{port=not_used}}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + case State#state.port of + not_used -> ignore; + Port -> + port_close(Port) + end, + ok. + +%% os_mon-2.0 +%% For live downgrade to/upgrade from os_mon-1.8[.1] +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + process_flag(trap_exit, false), + + %% Downgrade to old State tuple + State = {PrevState#state.port, PrevState#state.mfa}, + {ok, State}; + + %% Upgrade to this version + _Vsn -> + process_flag(trap_exit, true), + + %% Upgrade to this state record + {Port, MFA} = PrevState, + State = #state{port=Port, mfa=MFA}, + {ok, State} + end; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Internal functions +%%---------------------------------------------------------------------- + +start_portprogram(Identifier) -> + Command = + filename:join([code:priv_dir(os_mon),"bin","nteventlog.exe"]) ++ + " " ++ make_list(Identifier), + open_port({spawn,Command},[{packet,2}]). + +make_list(X) when is_atom(X) -> + atom_to_list(X); +make_list(X) -> + X. + +holl_len([$H | Rest], Sum) -> + {Sum, Rest}; +holl_len([ N | Rest], Sum) -> + NN = N - $0, + holl_len(Rest, Sum * 10 + NN). +holl_len(L) -> + holl_len(L,0). + +splitlist(L,N) -> + {lists:sublist(L,N),lists:nthtail(N,L)}. + +hollerith(Str) -> + {Len, Rest} = holl_len(Str), + splitlist(Rest,Len). + +holl_time(Str) -> + {Holl,Rest} = hollerith(Str), + Rev = lists:reverse(Holl), + B = list_to_integer(lists:reverse(lists:sublist(Rev,6))), + A = list_to_integer(lists:reverse(lists:nthtail(6,Rev))), + {{A,B,0},Rest}. + +parse_log(Str) -> + {Time, Rest1} = holl_time(Str), + {Category,Rest2} = hollerith(Rest1), + {Facility,Rest3} = hollerith(Rest2), + {Severity,Rest4} = hollerith(Rest3), + {Message,_} = hollerith(Rest4), + {Time,Category,Facility,Severity,Message}. diff --git a/lib/os_mon/src/os_mon.app.src b/lib/os_mon/src/os_mon.app.src new file mode 100644 index 0000000000..15bbd2663c --- /dev/null +++ b/lib/os_mon/src/os_mon.app.src @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% + +{application, os_mon, + [{description, "CPO CXC 138 46"}, + {vsn, "%VSN%"}, + {modules, [os_mon, os_mon_mib, os_sup, + disksup, memsup, cpu_sup, os_mon_sysinfo, nteventlog]}, + {registered, [os_mon_sup, os_mon_sysinfo, disksup, memsup, cpu_sup, + os_sup_server]}, + {applications, [kernel, stdlib, sasl]}, + {env, [{start_cpu_sup, true}, + {start_disksup, true}, + {start_memsup, true}, + {start_os_sup, false}]}, + {mod, {os_mon, []}}]}. diff --git a/lib/os_mon/src/os_mon.appup.src b/lib/os_mon/src/os_mon.appup.src new file mode 100644 index 0000000000..f8e09a7d87 --- /dev/null +++ b/lib/os_mon/src/os_mon.appup.src @@ -0,0 +1,41 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%% + +{"%VSN%", + [ + {"2.1", + [{load_module, cpu_sup}, + {load_module, disksup}, + {load_module, memsup}, + {load_module, os_mon}, + {load_module, os_mon_mib}]}, + {"2.1.1", + [{load_module, os_mon_mib}]} + ], + [ + {"2.1", + [{load_module, cpu_sup}, + {load_module, disksup}, + {load_module, memsup}, + {load_module, os_mon}, + {load_module, os_mon_mib}]}, + {"2.1.1", + [{load_module, os_mon_mib}]} + ] +}. diff --git a/lib/os_mon/src/os_mon.erl b/lib/os_mon/src/os_mon.erl new file mode 100644 index 0000000000..ef368571db --- /dev/null +++ b/lib/os_mon/src/os_mon.erl @@ -0,0 +1,179 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(os_mon). + +-behaviour(application). +-behaviour(supervisor). + +%% API +-export([call/2, call/3, get_env/2]). + +%% Application callbacks +-export([start/2, stop/1]). + +%% Supervisor callbacks +-export([init/1]). + +%%%----------------------------------------------------------------- +%%% API +%%%----------------------------------------------------------------- + +call(Service, Request) -> + call(Service, Request, 5000). + +call(Service, Request, Timeout) -> + try gen_server:call(server_name(Service), Request, Timeout) + catch + exit:{noproc, Call} -> + case lists:keysearch(os_mon, 1, + application:which_applications()) of + {value, _AppInfo} -> + case startp(Service) of + true -> + erlang:exit({noproc, Call}); + false -> + String = "OS_MON (~p) called by ~p, " + "unavailable~n", + error_logger:warning_msg(String, + [Service, self()]), + Service:dummy_reply(Request) + end; + false -> + String = "OS_MON (~p) called by ~p, not started~n", + error_logger:warning_msg(String, [Service, self()]), + Service:dummy_reply(Request) + end + end. + +get_env(Service, Param) -> + case application:get_env(os_mon, Param) of + {ok, Value} -> + case Service:param_type(Param, Value) of + true -> + Value; + false -> + String = "OS_MON (~p), ignoring " + "bad configuration parameter (~p=~p)~n" + "Using default value instead~n", + error_logger:warning_msg(String, + [Service, Param, Value]), + Service:param_default(Param) + end; + undefined -> + Service:param_default(Param) + end. + +%%%----------------------------------------------------------------- +%%% Application callbacks +%%%----------------------------------------------------------------- + +start(_, _) -> + supervisor:start_link({local, os_mon_sup}, os_mon, []). + +stop(_) -> + ok. + +%%%----------------------------------------------------------------- +%%% Supervisor callbacks +%%%----------------------------------------------------------------- + +init([]) -> + SupFlags = case os:type() of + {win32, _} -> + {one_for_one, 5, 3600}; + _ -> + {one_for_one, 4, 3600} + end, + SysInf = childspec(sysinfo, startp(sysinfo)), + DskSup = childspec(disksup, startp(disksup)), + MemSup = childspec(memsup, startp(memsup)), + CpuSup = childspec(cpu_sup, startp(cpu_sup)), + OsSup = childspec(os_sup, startp(os_sup)), + {ok, {SupFlags, SysInf ++ DskSup ++ MemSup ++ CpuSup ++ OsSup}}. + +childspec(_Service, false) -> + []; +childspec(cpu_sup, true) -> + [{cpu_sup, {cpu_sup, start_link, []}, + permanent, 2000, worker, [cpu_sup]}]; +childspec(disksup, true) -> + [{disksup, {disksup, start_link, []}, + permanent, 2000, worker, [disksup]}]; +childspec(memsup, true) -> + [{memsup, {memsup, start_link, []}, + permanent, 2000, worker, [memsup]}]; +childspec(os_sup, true) -> + OS = os:type(), + Mod = case OS of + {win32, _} -> nteventlog; % windows + _ -> os_sup % solaris + end, + [{os_sup, {os_sup, start_link, [OS]}, + permanent, 10000, worker, [Mod]}]; +childspec(sysinfo, true) -> + [{os_mon_sysinfo, {os_mon_sysinfo, start_link, []}, + permanent, 2000, worker, [os_mon_sysinfo]}]. + +%%%----------------------------------------------------------------- +%%% Internal functions (OS_Mon configuration) +%%%----------------------------------------------------------------- + +startp(Service) -> + %% Available for this platform? + case lists:member(Service, services(os:type())) of + true -> + %% Is there a start configuration parameter? + case start_param(Service) of + none -> + true; + Param -> + %% Is the start configuration parameter 'true'? + case application:get_env(os_mon, Param) of + {ok, true} -> + true; + _ -> + false + end + end; + false -> + false + end. + +services({unix, sunos}) -> + [cpu_sup, disksup, memsup, os_sup]; +services({unix, _}) -> % Other unix. + [cpu_sup, disksup, memsup]; +services({win32, _}) -> + [disksup, memsup, os_sup, sysinfo]; +services(vxworks) -> + [memsup]; +services(_) -> + []. + +server_name(cpu_sup) -> cpu_sup; +server_name(disksup) -> disksup; +server_name(memsup) -> memsup; +server_name(os_sup) -> os_sup_server; +server_name(sysinfo) -> os_mon_sysinfo. + +start_param(cpu_sup) -> start_cpu_sup; +start_param(disksup) -> start_disksup; +start_param(memsup) -> start_memsup; +start_param(os_sup) -> start_os_sup; +start_param(sysinfo) -> none. diff --git a/lib/os_mon/src/os_mon_mib.erl b/lib/os_mon/src/os_mon_mib.erl new file mode 100644 index 0000000000..a4ce274a16 --- /dev/null +++ b/lib/os_mon/src/os_mon_mib.erl @@ -0,0 +1,250 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(os_mon_mib). +%%%----------------------------------------------------------------- +%%% Description: This module implements the OS-MON-MIB. +%%% The tables are implemented as shadow tables with the module +%%% snmp_shadow_table. Here the update functions are implemented. +%%%----------------------------------------------------------------- + +-include("../../otp_mibs/include/OTP-MIB.hrl"). + +%% API +-export([load/1, unload/1]). + +%% Deprecated API +-export([init/1, stop/1]). + +-deprecated([{init,1,eventually}, + {stop,1,eventually}]). + +%% SNMP instrumentation +-export([load_table/1, load_table/3, disk_table/1, disk_table/3, + mem_sys_mark/1, mem_proc_mark/1, disk_threshold/1]). + +%% SNMP shadow functions +-export([update_load_table/0, update_disk_table/0]). + +%% Exported for internal use via rpc +-export([get_load/1, get_disks/1]). + +%% Shadow tables +-record(loadTable, { + loadErlNodeName, + loadSystemTotalMemory, + loadSystemUsedMemory, + loadLargestErlProcess, + loadLargestErlProcessUsedMemory, + loadCpuLoad, + loadCpuLoad5, + loadCpuLoad15, + loadOsWordsize, + loadSystemTotalMemory64, + loadSystemUsedMemory64, + loadLargestErlProcessUsedMemory64}). + +-record(diskTable, + {key, diskDescr, diskKBytes, diskCapacity}). + +%% Shadow argument macros +-define(loadShadowArgs, + {loadTable, string, record_info(fields, loadTable), 5000, + {os_mon_mib, update_load_table}}). + +-define(diskShadowArgs, + {diskTable, {integer, integer}, record_info(fields, diskTable), 5000, + {os_mon_mib, update_disk_table}}). + +%% Misc +-record(diskAlloc, {diskDescr, diskId}). + +%%%========================================================================= +%%% API +%%%========================================================================= + +%%------------------------------------------------------------------------- +%% load(Agent) -> ok | {error, Reason} +%% Agent - pid() | atom() +%% Reason - term() +%% Description: Loads the OTP-OS-MON-MIB +%%------------------------------------------------------------------------- +load(Agent) -> + MibDir = filename:join(code:priv_dir(os_mon), "mibs"), + snmpa:load_mibs(Agent, [filename:join(MibDir, "OTP-OS-MON-MIB")]). + +%%------------------------------------------------------------------------- +%% unload(Agent) -> ok | {error, Reason} +%% Agent - pid() | atom() +%% Reason - term() +%% Description: Unloads the OTP-OS-MON-MIB +%%------------------------------------------------------------------------- +unload(Agent) -> + snmpa:unload_mibs(Agent, ["OTP-OS-MON-MIB"]). + +%% To be backwards compatible +init(Agent) -> + load(Agent). +stop(Agent) -> + unload(Agent). + +%%%========================================================================= +%%% SNMP instrumentation +%%%========================================================================= +load_table(Op) -> + snmp_shadow_table:table_func(Op, ?loadShadowArgs). +load_table(Op, RowIndex, Cols) -> + snmp_shadow_table:table_func(Op, RowIndex, Cols, ?loadShadowArgs). + +disk_table(new) -> + Tab = diskAlloc, + Storage = ram_copies, + case lists:member(Tab, mnesia:system_info(tables)) of + true -> + case mnesia:table_info(Tab, storage_type) of + unknown -> + {atomic, ok}=mnesia:add_table_copy(Tab, node(), Storage); + Storage -> + catch delete_all(Tab) + end; + false -> + Nodes = [node()], + Props = [{type, set}, + {attributes, record_info(fields, diskAlloc)}, + {local_content, true}, + {Storage, Nodes}], + {atomic, ok} = mnesia:create_table(Tab, Props) + + end, + Rec = #diskAlloc{diskDescr = next_index, diskId = 1}, + ok = mnesia:dirty_write(Rec), + snmp_shadow_table:table_func(new, ?diskShadowArgs). + +disk_table(Op, RowIndex, Cols) -> + snmp_shadow_table:table_func(Op, RowIndex, Cols, ?diskShadowArgs). + +mem_sys_mark(get) -> + {value, memsup:get_sysmem_high_watermark()}; +mem_sys_mark(_) -> + ok. + +mem_proc_mark(get) -> + {value, memsup:get_procmem_high_watermark()}; +mem_proc_mark(_) -> + ok. + +disk_threshold(get) -> + {value, disksup:get_almost_full_threshold()}; +disk_threshold(_) -> + ok. + +%%%========================================================================= +%%% SNMP shadow functions +%%%========================================================================= +update_load_table() -> + delete_all(loadTable), + lists:foreach( + fun(Node) -> + case rpc:call(Node, os_mon_mib, get_load, [Node]) of + Load when is_record(Load,loadTable) -> + ok = mnesia:dirty_write(Load); + _Else -> + ok + end + end, [node() | nodes()]). + + +update_disk_table() -> + delete_all(diskTable), + node_update_disk_table( + otp_mib:erl_node_table(get_next, [], [?erlNodeName,?erlNodeOutBytes])). + +%%%======================================================================== +%%% Exported for internal use via rpc +%%%======================================================================== +get_load(Node) -> + {Total, Allocated, PidString, PidAllocated} = case memsup:get_memory_data() of + {MemTot, MemAlloc, undefined} -> {MemTot, MemAlloc, "undefined", 0}; + {MemTot, MemAlloc, {Pid, PidMem}} -> {MemTot, MemAlloc, pid_to_str(Pid), PidMem} + end, + OsWordsize = case memsup:get_os_wordsize() of + WS when is_integer(WS) -> WS; + _ -> 0 + end, + #loadTable{ + loadErlNodeName = atom_to_list(Node), + loadSystemTotalMemory = mask_int32(Total), + loadSystemUsedMemory = mask_int32(Allocated), + loadLargestErlProcess = PidString, + loadLargestErlProcessUsedMemory = mask_int32(PidAllocated), + loadCpuLoad = get_cpu_load(avg1), + loadCpuLoad5 = get_cpu_load(avg5), + loadCpuLoad15 = get_cpu_load(avg15), + loadOsWordsize = OsWordsize, + loadSystemTotalMemory64 = Total, + loadSystemUsedMemory64 = Allocated, + loadLargestErlProcessUsedMemory64 = PidAllocated + }. + +mask_int32(Value) -> Value band ((1 bsl 32) - 1). + +get_disks(NodeId) -> + element(1, + lists:mapfoldl( + fun({Descr, KByte, Capacity}, DiskId) -> + {#diskTable{key = {NodeId, DiskId}, + diskDescr = Descr, + diskKBytes = KByte, + diskCapacity = Capacity}, + DiskId + 1} + end, 1, disksup:get_disk_data())). + + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +node_update_disk_table([_, endOfTable]) -> + ok; + +node_update_disk_table([{[?erlNodeName | IndexList], NodeStr}, _]) -> + Disks = rpc:call(list_to_atom(NodeStr), os_mon_mib, get_disks, + IndexList), + lists:foreach(fun(Disk) -> + mnesia:dirty_write(Disk) + end, Disks), + node_update_disk_table(otp_mib:erl_node_table(get_next, + IndexList, + [?erlNodeName, + ?erlNodeOutBytes])). + +get_cpu_load(X) when X == avg1; X == avg5; X == avg15 -> + case erlang:round(apply(cpu_sup, X, [])/2.56) of + Large when Large > 100 -> + 100; + Load -> + Load + end. + +delete_all(Name) -> delete_all(mnesia:dirty_first(Name), Name). +delete_all('$end_of_table', _Name) -> done; +delete_all(Key, Name) -> + Next = mnesia:dirty_next(Name, Key), + ok = mnesia:dirty_delete({Name, Key}), + delete_all(Next, Name). + +pid_to_str(Pid) -> lists:flatten(io_lib:format("~w", [Pid])). diff --git a/lib/os_mon/src/os_mon_sysinfo.erl b/lib/os_mon/src/os_mon_sysinfo.erl new file mode 100644 index 0000000000..5d12bd95d1 --- /dev/null +++ b/lib/os_mon/src/os_mon_sysinfo.erl @@ -0,0 +1,147 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. 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% +%% +-module(os_mon_sysinfo). +-behaviour(gen_server). + +%% API +-export([start_link/0]). +-export([get_disk_info/0, get_disk_info/1, get_mem_info/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(DISK_INFO, $d). +-define(MEM_INFO, $m). +-define(OK, $o). + +-record(state, {port}). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local,os_mon_sysinfo}, os_mon_sysinfo, [],[]). + +get_disk_info() -> + gen_server:call(os_mon_sysinfo, get_disk_info). + +get_disk_info(DriveRoot) -> + gen_server:call(os_mon_sysinfo, {get_disk_info,DriveRoot}). + +get_mem_info() -> + gen_server:call(os_mon_sysinfo, get_mem_info). + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +init([]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + Port = case os:type() of + {win32, _OSname} -> start_portprogram(); + OS -> exit({unsupported_os, OS}) + end, + {ok, #state{port=Port}}. + +handle_call(get_disk_info, _From, State) -> + {reply, get_disk_info1(State#state.port), State}; +handle_call({get_disk_info,RootList}, _From, State) -> + {reply, get_disk_info1(State#state.port,RootList), State}; +handle_call(get_mem_info, _From, State) -> + {reply, get_mem_info1(State#state.port), State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({'EXIT', _Port, Reason}, State) -> + {stop, {port_died, Reason}, State#state{port=not_used}}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + case State#state.port of + not_used -> + ok; + Port -> + port_close(Port) + end, + ok. + +%% os_mon-2.0 +%% For live downgrade to/upgrade from os_mon-1.8[.1] +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + process_flag(trap_exit, false); + + %% Upgrade to this version + _Vsn -> + process_flag(trap_exit, true) + end, + {ok, PrevState}; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Internal functions +%%---------------------------------------------------------------------- + +start_portprogram() -> + Command = + filename:join([code:priv_dir(os_mon),"bin","win32sysinfo.exe"]), + Port = open_port({spawn,Command}, [{packet,1}]), + receive + {Port, {data, [?OK]}} -> + Port; + {Port, {data, Data}} -> + exit({port_error, Data}); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}) + after 5000 -> + exit({port_error, timeout}) + end. + +get_disk_info1(Port) -> + Port ! {self(),{command,[?DISK_INFO]}}, + get_data(Port,[]). + +get_disk_info1(Port,PathList) -> + Port ! {self(),{command,[?DISK_INFO|[P++[0]||P <- PathList]]}}, + get_data(Port,[]). + +get_mem_info1(Port) -> + Port ! {self(),{command,[?MEM_INFO]}}, + get_data(Port,[]). + +get_data(Port, Sofar) -> + receive + {Port, {data, [?OK]}} -> + lists:reverse(Sofar); + {Port, {data, Bytes}} -> + get_data(Port, [Bytes|Sofar]); + {'EXIT', Port, Reason} -> + exit({port_died, Reason}) + after 5000 -> + lists:reverse(Sofar) + end. diff --git a/lib/os_mon/src/os_sup.erl b/lib/os_mon/src/os_sup.erl new file mode 100644 index 0000000000..f5c6c138ba --- /dev/null +++ b/lib/os_mon/src/os_sup.erl @@ -0,0 +1,258 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(os_sup). +-behaviour(gen_server). + +%% API +-export([start_link/1, start/0, stop/0]). +-export([error_report/2]). +-export([enable/0, enable/2, disable/0, disable/2]). +-export([param_type/2, param_default/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {port, mfa, config, path, conf}). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +start_link({win32, _OSname}) -> + Identifier = os_sup, + MFA = os_mon:get_env(os_sup, os_sup_mfa), + gen_server:start_link({local, os_sup_server}, nteventlog, + [Identifier, MFA], []); +start_link(_OS) -> + gen_server:start_link({local, os_sup_server}, os_sup, [], []). + +start() -> % for testing + gen_server:start({local, os_sup_server}, os_sup, [], []). + +stop() -> + gen_server:call(os_sup_server, stop). + +error_report(LogData, Tag) -> + error_logger:error_report(Tag, LogData). + +enable() -> + command(enable). +enable(Path, Conf) -> + command(enable, Path, Conf). + +disable() -> + command(disable). +disable(Path, Conf) -> + command(disable, Path, Conf). + +param_type(os_sup_errortag, Val) when is_atom(Val) -> true; +param_type(os_sup_own, Val) -> io_lib:printable_list(Val); +param_type(os_sup_syslogconf, Val) -> io_lib:printable_list(Val); +param_type(os_sup_enable, Val) when Val==true; Val==false -> true; +param_type(os_sup_mfa, {Mod,Func,Args}) when is_atom(Mod), + is_atom(Func), + is_list(Args) -> true; +param_type(_Param, _Val) -> false. + +param_default(os_sup_errortag) -> std_error; +param_default(os_sup_own) -> "/etc"; +param_default(os_sup_syslogconf) -> "/etc/syslog.conf"; +param_default(os_sup_enable) -> true; +param_default(os_sup_mfa) -> {os_sup, error_report, [std_error]}. + +%%---------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------- + +init([]) -> + process_flag(trap_exit, true), + process_flag(priority, low), + + case os:type() of + {unix, sunos} -> + init2(); + OS -> {stop, {unsupported_os, OS}} + end. + +init2() -> % Enable service if configured to do so + ConfigP = os_mon:get_env(os_sup, os_sup_enable), + case ConfigP of + true -> % ..yes -- do enable + Path = os_mon:get_env(os_sup, os_sup_own), + Conf = os_mon:get_env(os_sup, os_sup_syslogconf), + case enable(Path, Conf) of + ok -> + init3(#state{config=ConfigP, path=Path, conf=Conf}); + {error, Error} -> + {stop, {mod_syslog, Error}} + end; + false -> % ..no -- skip directly to init3/1 + init3(#state{config=ConfigP}) + end. + +init3(State0) -> + Port = start_portprogram(), + + %% Read the values of some configuration parameters + MFA = case os_mon:get_env(os_sup, os_sup_mfa) of + {os_sup, error_report, _} -> + Tag = os_mon:get_env(os_sup, os_sup_errortag), + {os_sup, error_report, [Tag]}; + MFA0 -> + MFA0 + end, + + {ok, State0#state{port=Port, mfa=MFA}}. + +handle_call(stop, _From, State) -> + {stop, normal, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) -> + apply(M, F, [Data | A]), + {noreply, State}; +handle_info({'EXIT', _Port, Reason}, State) -> + {stop, {port_died, Reason}, State#state{port=not_used}}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{port=Port} = State) -> + case State#state.config of + true when is_port(Port) -> + Port ! {self(), {command, "only_stdin"}}, + Res = disable(State#state.path, State#state.conf), + port_close(Port), + if + Res/="0" -> exit({mod_syslog, Res}); + true -> ok + end; + true -> + Res = disable(State#state.path, State#state.conf), + if + Res/="0" -> exit({mod_syslog, Res}); + true -> ok + end; + false when is_port(Port) -> + Port ! {self(), {command, "only_stdin"}}, + port_close(Port); + false -> + ok + end. + +%% os_mon-2.0 +%% For live downgrade to/upgrade from os_mon-1.8[.1] +code_change(Vsn, PrevState, "1.8") -> + case Vsn of + + %% Downgrade from this version + {down, _Vsn} -> + + %% Find out the error tag used + {DefM, DefF, _} = param_default(os_sup_mfa), + Tag = case PrevState#state.mfa of + + %% Default callback function is used, then use + %% the corresponding tag + {DefM, DefF, [Tag0]} -> + Tag0; + + %% Default callback function is *not* used + %% (before the downgrade, that is) + %% -- check the configuration parameter + _ -> + case application:get_env(os_mon, + os_sup_errortag) of + {ok, Tag1} -> + Tag1; + + %% (actually, if it has no value, + %% the process should terminate + %% according to 1.8.1 version, but that + %% seems too harsh here) + _ -> + std_error + end + end, + + %% Downgrade to old state record + State = {state, PrevState#state.port, Tag}, + {ok, State}; + + %% Upgrade to this version + _Vsn -> + + {state, Port, Tag} = PrevState, + + {DefM, DefF, _} = param_default(os_sup_mfa), + MFA = {DefM, DefF, [Tag]}, + + %% We can safely assume the following configuration + %% parameters are defined, otherwise os_sup would never had + %% started in the first place. + %% (We can *not* safely assume they haven't been changed, + %% but that's a weakness inherited from the 1.8.1 version) + Path = application:get_env(os_mon, os_sup_own), + Conf = application:get_env(os_mon, os_sup_syslogconf), + + %% Upgrade to this state record + State = #state{port=Port, mfa=MFA, config=true, + path=Path, conf=Conf}, + {ok, State} + end; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Internal functions +%%---------------------------------------------------------------------- + +start_portprogram() -> + OwnPath = os_mon:get_env(os_sup, os_sup_own), + Command = + filename:join([code:priv_dir(os_mon), "bin", "ferrule"]) ++ + " " ++ OwnPath, + open_port({spawn, Command}, [{packet, 2}]). + +%% os:cmd(cmd_str(enable)) should be done BEFORE starting os_sup +%% os:cmd(cmd_str(disable)) should be done AFTER os_sup is terminated +%% Both commands return "0" if successful +command(Mode) -> + command(Mode, "/etc", "/etc/syslog.conf"). +command(Mode, Path, Conf) -> + case os:cmd(cmd_str(Mode, Path, Conf)) of + "0" -> + ok; + Error -> + {error, Error} + end. + +cmd_str(Mode, Path, Conf) -> + %% modpgm modesw ownpath syslogconf + PrivDir = code:priv_dir(os_mon), + ModeSw = + case Mode of + enable -> + " otp "; + disable -> + " nootp " + end, + PrivDir ++ "/bin/mod_syslog" ++ ModeSw ++ Path ++ " " ++ Conf. diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk new file mode 100644 index 0000000000..d9f56416bf --- /dev/null +++ b/lib/os_mon/vsn.mk @@ -0,0 +1 @@ +OS_MON_VSN = 2.2.4 -- cgit v1.2.3