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/runtime_tools/AUTHORS | 6 + lib/runtime_tools/Makefile | 38 + lib/runtime_tools/c_src/Makefile | 5 + lib/runtime_tools/c_src/Makefile.in | 151 ++ lib/runtime_tools/c_src/trace_file_drv.c | 638 +++++ lib/runtime_tools/c_src/trace_ip_drv.c | 938 +++++++ lib/runtime_tools/doc/html/.gitignore | 0 lib/runtime_tools/doc/man3/.gitignore | 0 lib/runtime_tools/doc/man6/.gitignore | 0 lib/runtime_tools/doc/pdf/.gitignore | 0 lib/runtime_tools/doc/src/Makefile | 123 + lib/runtime_tools/doc/src/book.xml | 46 + lib/runtime_tools/doc/src/dbg.xml | 1187 +++++++++ lib/runtime_tools/doc/src/erts_alloc_config.xml | 211 ++ lib/runtime_tools/doc/src/fascicules.xml | 15 + lib/runtime_tools/doc/src/make.dep | 20 + lib/runtime_tools/doc/src/note.gif | Bin 0 -> 1539 bytes lib/runtime_tools/doc/src/notes.xml | 503 ++++ lib/runtime_tools/doc/src/notes_history.xml | 48 + lib/runtime_tools/doc/src/part_notes.xml | 38 + lib/runtime_tools/doc/src/part_notes_history.xml | 38 + lib/runtime_tools/doc/src/ref_man.xml | 38 + lib/runtime_tools/doc/src/runtime_tools_app.xml | 56 + lib/runtime_tools/doc/src/warning.gif | Bin 0 -> 1498 bytes lib/runtime_tools/ebin/.gitignore | 0 lib/runtime_tools/include/observer_backend.hrl | 42 + lib/runtime_tools/info | 2 + lib/runtime_tools/priv/.gitignore | 0 lib/runtime_tools/src/Makefile | 109 + lib/runtime_tools/src/dbg.erl | 1749 +++++++++++++ lib/runtime_tools/src/erts_alloc_config.erl | 670 +++++ lib/runtime_tools/src/inviso_as_lib.erl | 155 ++ lib/runtime_tools/src/inviso_autostart.erl | 208 ++ lib/runtime_tools/src/inviso_autostart_server.erl | 311 +++ lib/runtime_tools/src/inviso_rt.erl | 2895 +++++++++++++++++++++ lib/runtime_tools/src/inviso_rt_lib.erl | 474 ++++ lib/runtime_tools/src/inviso_rt_meta.erl | 1207 +++++++++ lib/runtime_tools/src/observer_backend.erl | 320 +++ lib/runtime_tools/src/percept_profile.erl | 196 ++ lib/runtime_tools/src/runtime_tools.app.src | 32 + lib/runtime_tools/src/runtime_tools.appup.src | 19 + lib/runtime_tools/src/runtime_tools.erl | 49 + lib/runtime_tools/src/runtime_tools_sup.erl | 43 + lib/runtime_tools/vsn.mk | 1 + 44 files changed, 12581 insertions(+) create mode 100644 lib/runtime_tools/AUTHORS create mode 100644 lib/runtime_tools/Makefile create mode 100644 lib/runtime_tools/c_src/Makefile create mode 100644 lib/runtime_tools/c_src/Makefile.in create mode 100644 lib/runtime_tools/c_src/trace_file_drv.c create mode 100644 lib/runtime_tools/c_src/trace_ip_drv.c create mode 100644 lib/runtime_tools/doc/html/.gitignore create mode 100644 lib/runtime_tools/doc/man3/.gitignore create mode 100644 lib/runtime_tools/doc/man6/.gitignore create mode 100644 lib/runtime_tools/doc/pdf/.gitignore create mode 100644 lib/runtime_tools/doc/src/Makefile create mode 100644 lib/runtime_tools/doc/src/book.xml create mode 100644 lib/runtime_tools/doc/src/dbg.xml create mode 100644 lib/runtime_tools/doc/src/erts_alloc_config.xml create mode 100644 lib/runtime_tools/doc/src/fascicules.xml create mode 100644 lib/runtime_tools/doc/src/make.dep create mode 100644 lib/runtime_tools/doc/src/note.gif create mode 100644 lib/runtime_tools/doc/src/notes.xml create mode 100644 lib/runtime_tools/doc/src/notes_history.xml create mode 100644 lib/runtime_tools/doc/src/part_notes.xml create mode 100644 lib/runtime_tools/doc/src/part_notes_history.xml create mode 100644 lib/runtime_tools/doc/src/ref_man.xml create mode 100644 lib/runtime_tools/doc/src/runtime_tools_app.xml create mode 100644 lib/runtime_tools/doc/src/warning.gif create mode 100644 lib/runtime_tools/ebin/.gitignore create mode 100644 lib/runtime_tools/include/observer_backend.hrl create mode 100644 lib/runtime_tools/info create mode 100644 lib/runtime_tools/priv/.gitignore create mode 100644 lib/runtime_tools/src/Makefile create mode 100644 lib/runtime_tools/src/dbg.erl create mode 100644 lib/runtime_tools/src/erts_alloc_config.erl create mode 100644 lib/runtime_tools/src/inviso_as_lib.erl create mode 100644 lib/runtime_tools/src/inviso_autostart.erl create mode 100644 lib/runtime_tools/src/inviso_autostart_server.erl create mode 100644 lib/runtime_tools/src/inviso_rt.erl create mode 100644 lib/runtime_tools/src/inviso_rt_lib.erl create mode 100644 lib/runtime_tools/src/inviso_rt_meta.erl create mode 100644 lib/runtime_tools/src/observer_backend.erl create mode 100644 lib/runtime_tools/src/percept_profile.erl create mode 100644 lib/runtime_tools/src/runtime_tools.app.src create mode 100644 lib/runtime_tools/src/runtime_tools.appup.src create mode 100644 lib/runtime_tools/src/runtime_tools.erl create mode 100644 lib/runtime_tools/src/runtime_tools_sup.erl create mode 100644 lib/runtime_tools/vsn.mk (limited to 'lib/runtime_tools') diff --git a/lib/runtime_tools/AUTHORS b/lib/runtime_tools/AUTHORS new file mode 100644 index 0000000000..a8f6b7f82a --- /dev/null +++ b/lib/runtime_tools/AUTHORS @@ -0,0 +1,6 @@ +Original Authors and Contributors: + +Patrik Nyblom +Björn Gustavsson +Claes Wikström +Rickard Green diff --git a/lib/runtime_tools/Makefile b/lib/runtime_tools/Makefile new file mode 100644 index 0000000000..0094bb036d --- /dev/null +++ b/lib/runtime_tools/Makefile @@ -0,0 +1,38 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-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 +# + +SUB_DIRECTORIES = src c_src doc/src + +include vsn.mk +VSN = $(RUNTIME_TOOLS_VSN) + +SPECIAL_TARGETS = + +# +# Default Subdir Targets +# +include $(ERL_TOP)/make/otp_subdir.mk + + diff --git a/lib/runtime_tools/c_src/Makefile b/lib/runtime_tools/c_src/Makefile new file mode 100644 index 0000000000..19184f2e8d --- /dev/null +++ b/lib/runtime_tools/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/runtime_tools/c_src/Makefile.in b/lib/runtime_tools/c_src/Makefile.in new file mode 100644 index 0000000000..05b5598e51 --- /dev/null +++ b/lib/runtime_tools/c_src/Makefile.in @@ -0,0 +1,151 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-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=$(RUNTIME_TOOLS_VSN) + +# ---------------------------------------------------- +# The following variables differ on different systems, we set +# reasonable defaults, if something different is needed it should +# be set for that system only. +# ---------------------------------------------------- +CC = @CC@ +CFLAGS = @DED_CFLAGS@ +LD = @DED_LD@ +SHELL = /bin/sh +LIBS = @LIBS@ +LDFLAGS += @DED_LDFLAGS@ +ERLANG_OSTYPE = @ERLANG_OSTYPE@ + +SYSINCLUDE = -I$(ERL_TOP)/erts/emulator/beam \ + -I$(ERL_TOP)/erts/emulator/sys/$(ERLANG_OSTYPE) \ + -I$(ERL_TOP)/erts/include/internal \ + -I$(ERL_TOP)/erts/include/internal/$(ERLANG_OSTYPE) \ + -I$(ERL_TOP)/erts/include \ + -I$(ERL_TOP)/erts/include/$(ERLANG_OSTYPE) +ifeq ($(findstring vxworks,$(TARGET)),vxworks) + SYSINCLUDE += -I$(ERL_TOP)/erts/etc/vxworks +endif + +TRACE_DRV_INCLUDES = $(SYSINCLUDE) + +ALL_CFLAGS = $(CFLAGS) @DEFS@ $(TYPE_FLAGS) $(TRACE_DRV_INCLUDES) + + +ifeq ($(TYPE),debug) +TYPEMARKER = .debug +TYPE_FLAGS = -g -DDEBUG @DEBUG_FLAGS@ +else +TYPEMARKER = +TYPE_FLAGS = -O2 +endif + +ROOTDIR = $(ERL_TOP)/lib +PRIVDIR = ../priv +LIBDIR = $(PRIVDIR)/lib/$(TARGET) +OBJDIR = $(PRIVDIR)/obj/$(TARGET) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN) + +# ---------------------------------------------------- +# Misc Macros +# ---------------------------------------------------- +TRACE_IP_DRV_OBJS = \ + $(OBJDIR)/trace_ip_drv.o + +TRACE_FILE_DRV_OBJS = \ + $(OBJDIR)/trace_file_drv.o + +ifeq ($(findstring win32,$(TARGET)), win32) +SOLIBS = $(LIBDIR)/trace_ip_drv.dll $(LIBDIR)/trace_file_drv.dll +LN=cp +else +ifeq ($(findstring vxworks,$(TARGET)),vxworks) +SOLIBS = $(LIBDIR)/trace_ip_drv.eld $(LIBDIR)/trace_file_drv.eld +else +SOLIBS = $(LIBDIR)/trace_ip_drv.so $(LIBDIR)/trace_file_drv.so +endif +endif +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(OBJDIR) $(BINDIR) $(SOLIBS) + +$(OBJDIR): + -@mkdir -p $(OBJDIR) + +$(BINDIR): + -@mkdir -p $(BINDIR) + +$(OBJDIR)/%.o: %.c + $(INSTALL_DIR) $(OBJDIR) + $(CC) -c -o $@ $(ALL_CFLAGS) $< + +$(LIBDIR)/trace_ip_drv.so: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ -lc $(LIBS) + +$(LIBDIR)/trace_file_drv.so: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ -lc $(LIBS) + +$(LIBDIR)/trace_ip_drv.dll: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) +$(LIBDIR)/trace_file_drv.dll: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) +# +# VxWorks is simply to different from Unix in this sense. +# Here are the inference rules for VxWorks +# +$(LIBDIR)/trace_ip_drv.eld: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ + +$(LIBDIR)/trace_file_drv.eld: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ + +clean: + rm -f $(SOLIBS) $(TRACE_IP_DRV_OBJS) $(TRACE_FILE_DRV_OBJS) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/priv/lib + $(INSTALL_PROGRAM) $(SOLIBS) $(RELSYSDIR)/priv/lib + +release_docs_spec: + diff --git a/lib/runtime_tools/c_src/trace_file_drv.c b/lib/runtime_tools/c_src/trace_file_drv.c new file mode 100644 index 0000000000..482fcc0288 --- /dev/null +++ b/lib/runtime_tools/c_src/trace_file_drv.c @@ -0,0 +1,638 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1999-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: Send trace messages to a file. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#ifdef __WIN32__ +# include +# define write _write +# define open _open +# define close _close +# define unlink _unlink +#else +# include +#endif +#include +#include +#include +#ifdef VXWORKS +# include "reclaim.h" +#endif + + + +/* + * Deduce MAXPATHLEN, which is the one to use in this file, + * from any available definition. + */ +#ifndef MAXPATHLEN +# ifdef PATH_MAX /* Posix */ +# define MAXPATHLEN PATH_MAX +# else +# ifdef _POSIX_PATH_MAX /* Posix */ +# define MAXPATHLEN _POSIX_PATH_MAX +# else +# ifdef MAXPATH +# define MAXPATHLEN MAXPATH +# else +# ifdef MAX_PATH +# define MAXPATHLEN MAX_PATH +# else +# ifdef _MAX_PATH +# define MAXPATHLEN _MAX_PATH +# else +# error Could not define MAXPATHLEN +# endif +# endif +# endif +# endif +# endif +#endif + + + +#ifdef DEBUG +#ifndef __WIN32__ +#define ASSERT(X) do {if (!(X)) {erl_exit(1,"%s",#X);} } while(0) +#else +#include +#define ASSERT(X) assert(X) +#endif +#else +#define ASSERT(X) +#endif + + + +#include "erl_driver.h" + + + +/* +** Protocol from driver: +** '\0' -> ok +** '\1' ++ String -> {error, Atom} +** +** Protocol when opening (arguments to start): +** ["w "] "n " +** Where... +** , a string ('\0' terminated): +** The filename where the trace output is to be written. +** "w ...", if present orders a size limited wrapping log. +** , an unsigned integer: +** The size limit of each log file. +** , an unsigned integer: +** The number of log files. +** , an unsigned integer: +** The (zero based) index of where to insert the filename +** sequence count "a".."z","aa".."az","ba".."zz","aaa"... +** +** Port control messages handled: +** 'f' -> '\0' (ok) | '\1' ++ String (error) : Flush file. +** +** The package written to the file looks like this: +** +--+--------+-----------------------------------+ +** |Op|Size NBO|Term in external format or empty | +** +--+--------+-----------------------------------+ +** Op, a char, for conformance with the IP driver: +** 0 = binary, 1 = drop +** If Op is 1, then Size reflects the number of dropped messages. The +** op 1 is never used in this driver. +** Size, a 32 bit interger in network byte order: +** Either the size of the binary term, or the number of packet's dropped. +** Term, an array of bytes: +** An erlang term in the external format or simply empty if Op == 1, the +** term is Size long. +*/ + +typedef int FILETYPE; + +#define BUFFER_SIZE (BUFSIZ*8) + +#define OP_BINARY 0 +#define OP_DROP 1 + +/* +** State structures +*/ + +typedef struct trace_file_name { + char name[MAXPATHLEN+1]; /* Incl. space for terminating '\0' */ + unsigned suffix; /* Index of suffix start */ + unsigned tail; /* Index of tail start */ + unsigned len; /* Total length (strlen) */ + unsigned cnt; /* Current file count 0 <= cnt <= n */ + unsigned n; /* Number of files */ +} TraceFileName; + +typedef struct trace_file_wrap_data { + TraceFileName cur; /* Current trace file */ + TraceFileName del; /* Next file to delete when wrapping */ + unsigned size; /* File max size */ + int cnt; /* How many remains before starting to wrap */ + unsigned long time; /* Time to pass until starting to delete old files */ + unsigned len; /* Current file len */ +} TraceFileWrapData; + +typedef struct trace_file_data { + FILETYPE fd; + ErlDrvPort port; + struct trace_file_data *next, *prev; + TraceFileWrapData *wrap; /* == NULL => no wrap */ + int buff_siz; + int buff_pos; + unsigned char buff[1]; /* You guessed it, will be longer... */ +} TraceFileData; + +static TraceFileData *first_data; + +/* +** Interface routines +*/ +static ErlDrvData trace_file_start(ErlDrvPort port, char *buff); +static void trace_file_stop(ErlDrvData handle); +static void trace_file_output(ErlDrvData handle, char *buff, int bufflen); +static void trace_file_finish(void); +static int trace_file_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size); +static void trace_file_timeout(ErlDrvData handle); + +/* +** Internal routines +*/ +static unsigned digits(unsigned n); +static void next_name(TraceFileName *tfn); +static void *my_alloc(size_t size); +static int my_write(TraceFileData *data, unsigned char *buff, int siz); +static int my_flush(TraceFileData *data); +static void put_be(unsigned n, unsigned char *s); +static void close_unlink_port(TraceFileData *data); +static int wrap_file(TraceFileData *data); + +/* +** The driver struct +*/ +ErlDrvEntry trace_file_driver_entry = { + NULL, /* F_PTR init, N/A */ + trace_file_start, /* L_PTR start, called when port is opened */ + trace_file_stop, /* F_PTR stop, called when port is closed */ + trace_file_output, /* F_PTR output, called when erlang has sent */ + NULL, /* F_PTR ready_input, called when input descriptor + ready */ + NULL, /* F_PTR ready_output, called when output + descriptor ready */ + "trace_file_drv", /* char *driver_name, the argument to open_port */ + trace_file_finish, /* F_PTR finish, called when unloaded */ + NULL, /* void * that is not used (BC) */ + trace_file_control, /* F_PTR control, port_control callback */ + trace_file_timeout, /* F_PTR timeout, driver_set_timer callback */ + NULL /* F_PTR outputv, reserved */ +}; + +/* +** Driver initialization routine +*/ +DRIVER_INIT(trace_file_drv) +{ + first_data = NULL; + return &trace_file_driver_entry; +} + +/* +** Driver interface routines +*/ + +/* +** Open a port +*/ +static ErlDrvData trace_file_start(ErlDrvPort port, char *buff) +{ + unsigned size, cnt, time, tail, len; + char *p; + TraceFileData *data; + TraceFileWrapData *wrap; + FILETYPE fd; + int n, w; + static const char name[] = "trace_file_drv"; + +#ifdef HARDDEBUG + fprintf(stderr,"hello (%s)\r\n", buff); +#endif + w = 0; /* Index of where sscanf gave up */ + size = 0; /* Warning elimination */ + cnt = 0; /* -""- */ + time = 0; /* -""- */ + tail = 0; /* -""- */ + n = sscanf(buff, "trace_file_drv %n w %u %u %u %u %n", + &w, &size, &cnt, &time, &tail, &w); + + if (w < sizeof(name) || (n != 0 && n != 4)) + return ERL_DRV_ERROR_BADARG; + + /* Search for "n " in the rest of the string */ + p = buff + w; + for (p = buff + w; *p == ' '; p++); /* Skip space (necessary?) */ + if (*p++ != 'n') + return ERL_DRV_ERROR_BADARG; + if (*p++ != ' ') + return ERL_DRV_ERROR_BADARG; + /* Here we are at the start of the filename; p */ + len = strlen(p); + if (tail >= len) + /* Tail must start within filename */ + return ERL_DRV_ERROR_BADARG; + + data = my_alloc(sizeof(TraceFileData) - 1 + BUFFER_SIZE); + + /* We have to check the length in case we are running on + * VxWorks since too long pathnames may cause bus errors + * instead of error return from file operations. + */ + if (n == 4) { + /* Size limited wrapping log */ + unsigned d = digits(cnt); /* Nof digits in filename counter */ + if (len+d >= MAXPATHLEN) { + errno = ENAMETOOLONG; + return ERL_DRV_ERROR_ERRNO; + } + wrap = my_alloc(sizeof(TraceFileWrapData)); + wrap->size = size; + wrap->cnt = cnt; + wrap->time = time; + wrap->len = 0; + strcpy(wrap->cur.name, p); + wrap->cur.suffix = tail; + wrap->cur.tail = tail; + wrap->cur.len = len; + wrap->cur.cnt = cnt; + wrap->cur.n = cnt; + next_name(&wrap->cur); /* Incr to suffix "0" */ + wrap->del = wrap->cur; /* Struct copy! */ + p = wrap->cur.name; /* Use new name for open */ + } else { + /* Regular log */ + if (len >= MAXPATHLEN) { + errno = ENAMETOOLONG; + return ERL_DRV_ERROR_ERRNO; + } + wrap = NULL; + } + + if ((fd = open(p, O_WRONLY | O_TRUNC | O_CREAT +#ifdef O_BINARY + | O_BINARY +#endif + , 0777)) < 0) { + if (wrap) + driver_free(wrap); + driver_free(data); + return ERL_DRV_ERROR_ERRNO; + } + + data->fd = fd; + data->port = port; + data->buff_siz = BUFFER_SIZE; + data->buff_pos = 0; + data->wrap = wrap; + + if (first_data) { + data->prev = first_data->prev; + first_data->prev = data; + } else + data->prev = NULL; + data->next = first_data; + first_data = data; + + if (wrap && wrap->time > 0) + driver_set_timer(port, wrap->time); + + return (ErlDrvData) data; +} + + +/* +** Close a port +*/ +static void trace_file_stop(ErlDrvData handle) +{ + close_unlink_port((TraceFileData *) handle); +} + +/* +** Data sent from erlang to port. +*/ +static void trace_file_output(ErlDrvData handle, char *buff, int bufflen) +{ + int heavy = 0; + TraceFileData *data = (TraceFileData *) handle; + unsigned char b[5] = ""; + put_be((unsigned) bufflen, b + 1); + switch (my_write(data, b, sizeof(b))) { + case 1: + heavy = !0; + case 0: + switch (my_write(data, buff, bufflen)) { + case 1: + heavy = !0; + case 0: + break; + case -1: + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + break; + case -1: + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + if (data->wrap) { + TraceFileWrapData *wrap = data->wrap; + /* Size limited wrapping log files */ + wrap->len += sizeof(b) + bufflen; + if (wrap->time == 0 && wrap->len >= wrap->size) { + if (wrap_file(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + heavy = !0; + } + } + if (heavy) { + set_port_control_flags(data->port, PORT_CONTROL_FLAG_HEAVY); + } +} + +/* +** Control message from erlang, we handle $f, which is flush. +*/ +static int trace_file_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size) +{ + if (command == 'f') { + TraceFileData *data = (TraceFileData *) handle; + if (my_flush(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + } + if (res_size < 1) { + *res = my_alloc(1); + } + **res = '\0'; + return 1; + } + return -1; +} + +/* +** Timeout from driver_set_timer. +*/ +static void trace_file_timeout(ErlDrvData handle) { + TraceFileData *data = (TraceFileData *) handle; + if (data->wrap) { + if (wrap_file(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + return; + } else { + driver_set_timer(data->port, data->wrap->time); + } + } +} + +/* +** Driver unloaded +*/ +static void trace_file_finish(void) +{ + while (first_data != NULL) { + close_unlink_port(first_data); + } +} + +/* +** Internal helpers +*/ + +/* Calculate needed number of digits in filename counter. +**/ +static unsigned digits(unsigned n) { + unsigned m, i; + for (m = 10, i = 1; n >= m; i++, m *= 10) ; + return i; +} + +/* +** Increment filename. +** +** The filename counter counts "0"-"9","10"-"19"..."[n->n]","0"..., +** but also "","0" which is used for initialization. +*/ +static void next_name(TraceFileName *n) { + if (n->cnt >= n->n) { + n->cnt = 0; + /* Circular count from "[n->n]" to "0", or from "" to "0" */ + memmove(&n->name[n->suffix+1], + &n->name[n->tail], + n->len+1 - n->tail); /* Including '\0' */ + n->name[n->suffix] = '0'; + n->len -= n->tail - n->suffix - 1; + n->tail = n->suffix + 1; + } else { + int i = n->tail; + n->cnt++; + do { + i--; + /* Increment from the end, + * '0'..'1', carry propagate forward */ + if (n->name[i] < '9') { + n->name[i]++; + return; + } else + n->name[i] = '0'; + } while (i > n->suffix); + /* Wrapped around from "99..99" to "00..00", + * need one more character */ + memmove(&n->name[n->tail+1], + &n->name[n->tail], + n->len+1 - n->tail); /* Incl '\0' */ + n->name[n->tail++] = '0'; + n->name[n->suffix] = '1'; + n->len++; + } +} + +/* +** Yet another malloc wrapper +*/ +static void *my_alloc(size_t size) +{ + void *ret; + if ((ret = (void *) driver_alloc(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate %d bytes of memory in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** A write wrapper that regards it as an error if not all data was written. +*/ +static int do_write(FILETYPE fd, unsigned char *buff, int siz) { + int w = write(fd, buff, siz); + if (w != siz) { + if (w >= 0) { + errno = ENOSPC; + } + return -1; + } + return siz; +} + +/* +** Returns 0 if write to cache, 1 i write to file, and -1 if write failed. +*/ +static int my_write(TraceFileData *data, unsigned char *buff, int siz) +{ + int wrote, w; + + if (data->buff_siz - data->buff_pos >= siz) { + memcpy(data->buff + data->buff_pos, buff, siz); + data->buff_pos += siz; + return 0; + } + + wrote = data->buff_siz - data->buff_pos; + memcpy(data->buff + data->buff_pos, buff, wrote); + if (do_write(data->fd, data->buff, data->buff_siz) < 0) { + return -1; + } + data->buff_pos = 0; + if (siz - wrote >= data->buff_siz) { + /* Write directly, no need to buffer... */ + if (do_write(data->fd, buff + wrote, siz - wrote) < 0) { + return -1; + } + return 1; + } + memcpy(data->buff, buff + wrote, siz - wrote); + data->buff_pos = siz - wrote; + set_port_control_flags(data->port, PORT_CONTROL_FLAG_HEAVY); + return 1; +} + +/* +** Returns negative if it failed to write. + */ +static int my_flush(TraceFileData *data) +{ + if (do_write(data->fd, data->buff, data->buff_pos) < 0) { + return -1; + } + data->buff_pos = 0; + return 0; +} + +/* +** Write unsigned to buffer in big endian +*/ +static void put_be(unsigned n, unsigned char *s) +{ + s[0] = n >> 24; + s[1] = (n >> 16) & 0xFFFFU; + s[2] = (n >> 8) & 0xFFFFU; + s[3] = n & 0xFFFFU; +} + +/* +** Wrapper that only closes non-negative filehandles +*/ +static void do_close(FILETYPE fd) { + if (fd != -1) { + close(fd); + } +} + +/* +** Close the whole port and clean up +*/ +static void close_unlink_port(TraceFileData *data) +{ + my_flush(data); + do_close(data->fd); + + if (data->next) + data->next->prev = data->prev; + if (data->prev) + data->prev->next = data->next; + else + first_data = data->next; + + if (data->wrap) + driver_free(data->wrap); + driver_free(data); +} + +/* +** Wrap to new file - close the current, open a new and +** perhaps delete a too old one +** +** Returns negative if something failed. +*/ +static int wrap_file(TraceFileData *data) { + if (my_flush(data) < 0) { + close(data->fd); + data->fd = -1; + return -1; + } + close(data->fd); + data->fd = -1; + data->buff_pos = 0; + data->wrap->len = 0; + /* Count down before starting to remove old files */ + if (data->wrap->cnt > 0) + data->wrap->cnt--; + if (data->wrap->cnt == 0) { + /* Remove an old file */ + unlink(data->wrap->del.name); + next_name(&data->wrap->del); + } + next_name(&data->wrap->cur); + data->fd = open(data->wrap->cur.name, O_WRONLY | O_TRUNC | O_CREAT +#ifdef O_BINARY + | O_BINARY +#endif + , 0777); + if (data->fd < 0) { + data->fd = -1; + return -1; + } + return 0; +} + diff --git a/lib/runtime_tools/c_src/trace_ip_drv.c b/lib/runtime_tools/c_src/trace_ip_drv.c new file mode 100644 index 0000000000..d2ed1a294b --- /dev/null +++ b/lib/runtime_tools/c_src/trace_ip_drv.c @@ -0,0 +1,938 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1999-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 is a trace port, which means it cannot be busy and takes +** no opcodes. +** Send trace messages over the net. +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef __WIN32__ +# include +# include +#endif + +#include +#include +#include +#ifndef __WIN32__ +# ifdef VXWORKS +# include +# include +# include +# include +# include +# include +# include "reclaim.h" +# endif +# include +# include +# include +# include +# include +# include +#endif + +#ifdef DEBUG +# ifndef __WIN32__ + /* erl_exit is not available to dll_drivers on windows. */ + void erl_exit(int, char *, ...); +# define ASSERT(X) \ + do { \ + if (!(X)) { \ + erl_exit(1,"%s",#X); \ + } \ + } while(0) +# else +# include +# define ASSERT(X) assert(X) +# endif +#else +# define ASSERT(X) +#endif + +#define sock2event(s) ((ErlDrvEvent)(long)(s)) +#define event2sock(p) ((SOCKET)(long)(p)) + +#include "erl_driver.h" + +/* +** Protocol from driver: +** '\0' -> ok +** '\1' ++ String -> {error, Atom} +** Protocol when opening (arguments to start): +** +** Where... +** Portno, ascii string representing a number: +** The TCP port number to listen to. +** Quesize, ascii string representing a number: +** The number of messages to que up before dropping. +** Fl, ascii string representing a flag byte: +** 0x1 -> Drop oldest when que is full (instead of last arrived) +** 0x2 -> Fill the que even if noone is listening. +** +** The package sent over the network looks like this: +** +--+--------+-----------------------------------+ +** |Op|Size NBO|Term in external format or empty | +** +--+--------+-----------------------------------+ +** Op, a char: +** 0 = binary, 1 = drop +** If Op is 1, then Size reflects the number of dropped messages. +** Size, a 32 bit interger in network byte order: +** Either the size of the binary term, or the number of packet's dropped. +** Term, an array of bytes: +** An erlang term in the external format or simply empty if Op == 1, the +** term is Size long. +*/ + +/* +** SO, most of the differences between WinDoze and Posixish OS'es +** is handeled here, but the multiplexing (driver_select) is also quite +** interesting, see my_driver_select further down in the file... +*/ + +#ifndef __WIN32__ +# define FLAG_READ ERL_DRV_READ +# define FLAG_WRITE ERL_DRV_WRITE + typedef int SOCKET; +# define INVALID_SOCKET -1 +# define IS_INVALID_SOCKET(S) ((S) < 0) +# define closesocket(Sock) close(Sock) +# define ERRNO errno +# ifdef EWOULDBLOCK +# define ERRNO_BLOCK EWOULDBLOCK +# else +# ifdef EAGAIN +# define ERRNO_BLOCK EAGAIN +# else +# error "No EWOULDBLOCK found" +# endif +# endif +#else /* Win32 */ +# define FLAG_READ (FD_READ | FD_CONNECT | FD_ACCEPT) +# define FLAG_WRITE FD_WRITE +# define ERRNO WSAGetLastError() +# define ERRNO_BLOCK WSAEWOULDBLOCK +# define IS_INVALID_SOCKET(S) ((S) == INVALID_SOCKET) +#endif + + +/* +** Option flags +*/ +#define FLAG_DROP_OLDEST 1 +#define FLAG_FILL_ALWAYS 2 +#define FLAG_LISTEN_PORT 4 + +/* +** Op's in messages +*/ +#define OP_BINARY 0 +#define OP_DROP 1 + +/* +** State structure +*/ + +typedef struct trace_ip_message { + int siz; /* the size of the "binary data" */ + int written; /* if only a part was written, when == siz, all is written */ + unsigned char bin[1]; /* The opcode, the Size and optionally the binary. */ +} TraceIpMessage; + + +typedef struct trace_ip_data { + unsigned flags; + int listen_portno; + SOCKET listenfd; + SOCKET fd; +#ifdef __WIN32__ + unsigned listen_event_mask; + HANDLE listen_event; + unsigned event_mask; + HANDLE event; +#endif + ErlDrvPort port; + struct trace_ip_data *next; + int quesiz; + int questart; + int questop; + TraceIpMessage *que[1]; /* You guessed it, will be longer... */ +} TraceIpData; + +static TraceIpData *first_data; + +/* +** Interface routines +*/ +static ErlDrvData trace_ip_start(ErlDrvPort port, char *buff); +static void trace_ip_stop(ErlDrvData handle); +static void trace_ip_output(ErlDrvData handle, char *buff, int bufflen); +#ifdef __WIN32__ +static void trace_ip_event(ErlDrvData handle, ErlDrvEvent event); +#endif +static void trace_ip_ready_input(ErlDrvData handle, ErlDrvEvent fd); +static void trace_ip_ready_output(ErlDrvData handle, ErlDrvEvent fd); +static void trace_ip_finish(void); /* No arguments, despite what might be stated + in any documentation */ +static int trace_ip_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size); + +/* +** Internal routines +*/ +static void *my_alloc(size_t size); +static ErlDrvBinary *my_alloc_binary(int size); +static int write_until_done(SOCKET s, char *buff, int bufflen); +static unsigned get_be(unsigned char *s); +static void put_be32(unsigned n, unsigned char *s); +static void put_be16(unsigned n, unsigned char *s); +static TraceIpData *lookup_data_by_port(int portno); +static int set_nonblocking(SOCKET sock); +static TraceIpMessage *make_buffer(int datasiz, unsigned char op, + unsigned number); +static void enque_message(TraceIpData *data, unsigned char *buff, int bufflen, + int byteswritten); +static void clean_que(TraceIpData *data); +static void close_client(TraceIpData *data); +static int trywrite(TraceIpData *data, unsigned char *buff, int bufflen); +static SOCKET my_accept(SOCKET sock); +static void close_unlink_port(TraceIpData *data); +enum MySelectOp { SELECT_ON, SELECT_OFF, SELECT_CLOSE }; +static int my_driver_select(TraceIpData *desc, SOCKET fd, int flags, enum MySelectOp); +static void stop_select(ErlDrvEvent event, void*); + +/* +** The driver struct +*/ +ErlDrvEntry trace_ip_driver_entry = { + NULL, /* F_PTR init, N/A */ + trace_ip_start, /* L_PTR start, called when port is opened */ + trace_ip_stop, /* F_PTR stop, called when port is closed */ + trace_ip_output, /* F_PTR output, called when erlang has sent */ +#ifdef __WIN32__ + trace_ip_event, /* Anything happens on an associated event */ + NULL, /* Write selections not supported on win32 */ +#else + trace_ip_ready_input, /* F_PTR ready_input, called when input descriptor + ready */ + trace_ip_ready_output, /* F_PTR ready_output, called when output + descriptor ready */ +#endif + "trace_ip_drv", /* char *driver_name, the argument to open_port */ + trace_ip_finish, /* F_PTR finish, called when unloaded */ + NULL, /* void * that is not used */ + trace_ip_control, /* F_PTR control, port_control callback */ + NULL, /* F_PTR timeout, reserved */ + NULL, /* F_PTR outputv, reserved */ + NULL, /* ready_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, + NULL, /* process_exit */ + stop_select +}; + +/* +** Driver initialization routine +** +** No matter what's stated otherwise, this function shall return a pointer. +*/ + +DRIVER_INIT(trace_ip_drv) +{ + first_data = NULL; + /*trace_ip_driver_entry.handle = handle; ??? What is this, and why? It is no more! */ + return &trace_ip_driver_entry; +} + +/* +** Driver interface routines +*/ + +/* +** Open a port +*/ +static ErlDrvData trace_ip_start(ErlDrvPort port, char *buff) +{ + TraceIpData *ret; + int portno; + int quesiz; + int flags; + SOCKET s; + struct sockaddr_in sin; + int reuse = 1; + +#ifdef HARDDEBUG + fprintf(stderr,"trace_ip_drv/trace_ip_start (%s)\r\n", buff); +#endif + if (sscanf(buff,"trace_ip_drv %d %d %d",&portno, &quesiz, &flags) != 3) + return ERL_DRV_ERROR_GENERAL; + + if (flags > 3 || flags < 0 || portno < 0 || quesiz < 0) + return ERL_DRV_ERROR_GENERAL; + + if (lookup_data_by_port(portno) != NULL) + return ERL_DRV_ERROR_GENERAL; + + if (IS_INVALID_SOCKET(s = socket(AF_INET, SOCK_STREAM, 0))) + return ERL_DRV_ERROR_GENERAL; + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *) &reuse, sizeof(reuse)) < 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons((short) portno); + + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + if (portno == 0) { +#ifdef HAVE_SOCKLEN_T + socklen_t sinlen = sizeof(sin); +#else + int sinlen = (int) sizeof(sin); +#endif + if (getsockname(s, (struct sockaddr *)&sin, &sinlen) != 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } else { + portno = ntohs(sin.sin_port); + } + } + + if (listen(s, 1)) { /* No significant backlog needed */ + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + if (set_nonblocking(s) != 0){ + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + /* OK, the socket is created, lets build the structure. */ + /* Deliberately one more pointer than the quesize specified... */ + ret = my_alloc(sizeof(TraceIpData) + + quesiz * sizeof(TraceIpMessage *)); + + ret->flags = flags | FLAG_LISTEN_PORT; + ret->listen_portno = portno; + ret->listenfd = s; + ret->fd = INVALID_SOCKET; + ret->port = port; + ret->next = first_data; + ret->quesiz = quesiz+1; + ret->questart = ret->questop = 0; + memset(ret->que, 0, ret->quesiz); + + first_data = ret; +#ifdef __WIN32__ + ret->listen_event_mask = 0; + ret->listen_event = 0; + ret->event_mask = 0; + ret->event = 0; +#endif + my_driver_select(ret, s, FLAG_READ, SELECT_ON); + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); + + return (ErlDrvData) ret; +} + +/* +** Close a port +*/ +static void trace_ip_stop(ErlDrvData handle) +{ + close_unlink_port((TraceIpData *) handle); +} + +/* +** Data sent from erlang to port. +*/ +static void trace_ip_output(ErlDrvData handle, char *buff, int bufflen) +{ + TraceIpData *data = (TraceIpData *) handle; + if (data->flags & FLAG_LISTEN_PORT) { + if (data->flags & FLAG_FILL_ALWAYS) { + enque_message(data, buff, bufflen, 0); + } + return; + } + if (data->que[data->questart] != NULL) { + trace_ip_ready_output(handle, sock2event(data->fd)); + } + if (data->que[data->questart] == NULL) { + int written = trywrite(data, buff, bufflen); + if (written >= 0 && written < bufflen + 5) { + enque_message(data, buff, bufflen, written); + my_driver_select(data, data->fd, FLAG_WRITE, SELECT_ON); + } + return; + } + enque_message(data, buff, bufflen, 0); + return; +} + +/* +** We have something to read from a file descriptor +*/ +static void trace_ip_ready_input(ErlDrvData handle, ErlDrvEvent fd) +{ + TraceIpData *data = (TraceIpData *) handle; + int client; + + if (!(data->flags & FLAG_LISTEN_PORT) && event2sock(fd) == data->listenfd) { + /* + ** Someone tries to connect to already connected port, + ** just accept and close. + */ + if (!IS_INVALID_SOCKET((client = my_accept(data->listenfd)))) { + closesocket(client); + } + return; + } + + if (event2sock(fd) == data->listenfd) { + /* + ** Maybe accept, we are a listen port... + */ + if (!IS_INVALID_SOCKET((client = my_accept(data->listenfd)))) { + data->fd = client; + set_nonblocking(client); + if (data->que[data->questart] != NULL) { + my_driver_select(data, data->fd, FLAG_WRITE | FLAG_READ, SELECT_ON); + } else { + my_driver_select(data, data->fd, FLAG_READ, SELECT_ON); + } + data->flags &= ~(FLAG_LISTEN_PORT); + } + return; + } + + /* + * It is a probably EOF because the other end closed the socket, + * but better make sure. + */ + + if ((SOCKET)(long)fd == data->fd) { +#ifdef __WIN32__ + close_client(data); +#else + int res; + char sbuf[128]; + + if ((res = read(data->fd, sbuf, sizeof sbuf)) == 0) { + close_client(data); + } + + /* + * Something else. Just ignore it. + * + * When /dev/poll is used on Solaris, this callback can + * be called even if there is nothing to read. An attempt + * to read will result in an EAGAIN error. + */ +#ifdef DEBUG + if (res < 0) { + fprintf(stderr, "Read on fd %d failed with errno=%d\r\n", + data->fd, errno); + } +#endif +#endif + return; + } + + ASSERT(0); +} + +#ifdef __WIN32__ +static void trace_ip_event(ErlDrvData handle, ErlDrvEvent event) +{ + TraceIpData *data = (TraceIpData *) handle; + if ((HANDLE)event == data->event) { + WSANETWORKEVENTS netEv; + if (WSAEnumNetworkEvents(data->fd, data->event, &netEv) != 0) { + return; + } + if (netEv.lNetworkEvents & FLAG_WRITE) { + trace_ip_ready_output(handle, (ErlDrvEvent)data->fd); + } + if (netEv.lNetworkEvents & FLAG_READ) { + trace_ip_ready_input(handle, (ErlDrvEvent)data->fd); + } + } else if ((HANDLE)event == data->listen_event) { + trace_ip_ready_input(handle, (ErlDrvEvent)data->listenfd); + } +} +#endif /* ifdef __WIN32__ */ + +/* +** We can write to a file descriptor +*/ +static void trace_ip_ready_output(ErlDrvData handle, ErlDrvEvent fd) +{ + TraceIpData *data = (TraceIpData *) handle; + int res; + int towrite; + TraceIpMessage *tim; + + ASSERT(!(data->flags & FLAG_LISTEN_PORT) && + data->que[data->questart] != NULL && + (SOCKET)(long)fd == data->fd); + + tim = data->que[data->questart]; + towrite = tim->siz - tim->written; + while((res = write_until_done(data->fd, + tim->bin + tim->written, towrite)) + == towrite) { + driver_free(tim); + data->que[data->questart] = NULL; + if (data->questart == data->questop) { + /* + ** We wrote the last message in the que, dont + ** step forward, just 'deselect' + */ + my_driver_select(data, data->fd, FLAG_WRITE, SELECT_OFF); + /* + ** We are really done... + */ + return; + } + if (++(data->questart) == data->quesiz) + data->questart = 0; + tim = data->que[data->questart]; + ASSERT(tim != NULL); + towrite = tim->siz - tim->written; + } + if (res < 0) { + close_client(data); + return; + } + + tim->written += res; +} + +/* +** Control message from erlang, we handle $p, which is get_listen_port. +*/ +static int trace_ip_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size) +{ + register void *void_ptr; /* Soft type cast */ + + if (command == 'p') { + TraceIpData *data = (TraceIpData *) handle; + ErlDrvBinary *b = my_alloc_binary(3); + b->orig_bytes[0] = '\0'; /* OK */ + put_be16(data->listen_portno, &(b->orig_bytes[1])); + *res = void_ptr = b; + return 0; + } + return -1; +} + +/* +** Driver unloaded +*/ +static void trace_ip_finish(void) +{ + while (first_data != NULL) { + close_unlink_port(first_data); + } +} + +/* +** Internal helpers +*/ + +/* +** Yet another malloc wrapper +*/ +static void *my_alloc(size_t size) +{ + void *ret; + if ((ret = driver_alloc(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate %d bytes of memory in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** Yet another malloc wrapper +*/ +static ErlDrvBinary *my_alloc_binary(int size) +{ + ErlDrvBinary *ret; + if ((ret = driver_alloc_binary(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate a binary of %d bytes in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** Write to a nonblocking descriptor until it states EWOULDBLOCK +*/ +static int write_until_done(SOCKET s, char *buff, int bufflen) +{ + int ret = 0; + int res = 0; + +#ifdef HARDDEBUG + fprintf(stderr, "Writing %d characters.\r\n", bufflen); +#endif + + while(ret < bufflen && (res = send(s, buff + ret, bufflen - ret, 0)) > 0) { + ret += res; + } + if (ret < bufflen) { + if (res == 0) { + fprintf(stderr, "internal error in trace_ip_drv, " + "write to nonblocking " + "returned 0!"); + exit(1); + } else if (ERRNO != ERRNO_BLOCK) { + return -1; + } + } + return ret; +} + +/* +** Translate big endian integer in buffer to unsigned +*/ +static unsigned get_be(unsigned char *s) +{ + return s[3] | (s[2] << 8) | (s[1] << 16) | s[0] << 24; +} + +/* +** Write unsigned to buffer in big endian +*/ + +static void put_be32(unsigned n, unsigned char *s) +{ + s[0] = n >> 24U; + s[1] = n >> 16U; + s[2] = n >> 8U; + s[3] = n; +} + +static void put_be16(unsigned n, unsigned char *s) +{ + s[0] = n >> 8U; + s[1] = n; +} + +/* +** Lookup a port's data structure by the *TCP/IP port number* +*/ +static TraceIpData *lookup_data_by_port(int portno) +{ + TraceIpData *tmp = first_data; + while (tmp != NULL && tmp->listen_portno != portno) + tmp = tmp->next; + return tmp; +} + +/* +** Create a TraceIpMessage buffer (the binary data NOT filled in) +*/ +static TraceIpMessage *make_buffer(int datasiz, unsigned char op, + unsigned number) +{ + TraceIpMessage *ret = my_alloc(sizeof(TraceIpMessage) + + (datasiz + 4)); + ret->siz = datasiz + 5; + ret->written = 0; + ret->bin[0] = op; + put_be32(number, ret->bin + 1); + return ret; +} + +/* +** Add message to que, discarding in a politically correct way... +** The FLAG_DROP_OLDEST is currently ingored... +*/ +static void enque_message(TraceIpData *data, unsigned char *buff, int bufflen, + int byteswritten) +{ + int diff = data->questop - data->questart; + TraceIpMessage *tim; + + if (diff == -1 || diff == data->quesiz - 1) { + put_be32(get_be((data->que[data->questop])->bin + 1) + 1, + (data->que[data->questop])->bin + 1); + } else if (diff == -2 || diff == data->quesiz - 2) { + ASSERT(byteswritten == 0); + if (++(data->questop) == data->quesiz) { + data->questop = 0; + } + data->que[data->questop] = make_buffer(0, OP_DROP, 1); + } else { + if (data->que[data->questop] != NULL && + ++(data->questop) == data->quesiz) { + data->questop = 0; + } + tim = make_buffer(bufflen, OP_BINARY, bufflen); + tim->written = byteswritten; + memcpy(tim->bin + 5, buff, bufflen); + data->que[data->questop] = tim; + } +} + +/* +** Clean a que +*/ +static void clean_que(TraceIpData *data) +{ + int b = data->questart; + int e = data->questop; + + while (b != e) { + if (data->que[b] != NULL) { + driver_free(data->que[b]); + data->que[b] = NULL; + } + if (++b >= data->quesiz) { + b = 0; + } + } + if (data->que[b] != NULL) { + driver_free(data->que[b]); + data->que[b] = NULL; + } + data->questart = data->questop = 0; +} + +/* +** Cleanup closed client (or close the client and cleanup) +*/ +static void close_client(TraceIpData *data) +{ + my_driver_select(data, data->fd, FLAG_WRITE | FLAG_READ, SELECT_CLOSE); + data->flags |= FLAG_LISTEN_PORT; + if (!(data->flags & FLAG_FILL_ALWAYS)) { + clean_que(data); + } +} + +/* +** Try to write a message from erlang directly (only called when que is empty +** and client is connected) +*/ +static int trywrite(TraceIpData *data, unsigned char *buff, int bufflen) +{ + unsigned char op[5]; + int res; + + op[0] = OP_BINARY; + put_be32(bufflen, op + 1); + + if ((res = write_until_done(data->fd, op, 5)) < 0) { + close_client(data); + return -1; + } + if (res < 5) { + return res; + } + + if ((res = write_until_done(data->fd, buff, bufflen)) < 0) { + close_client(data); + return -1; + } + + return res + 5; +} + +/* +** accept wrapper +*/ +static SOCKET my_accept(SOCKET sock) +{ + struct sockaddr_in sin; + int sin_size = sizeof(sin); + + return accept(sock, (struct sockaddr *) &sin, &sin_size); +} + +/* +** Close the whole port and clean up +*/ +static void close_unlink_port(TraceIpData *data) +{ + TraceIpData **tmp; + + data->flags = 0; + if (!IS_INVALID_SOCKET(data->fd)) { + close_client(data); + } + my_driver_select(data, data->listenfd, FLAG_READ, SELECT_CLOSE); + + for(tmp = &first_data; *tmp != NULL && *tmp != data; + tmp = &((*tmp)->next)) + ; + if (*tmp != NULL) { + *tmp = (*tmp)->next; + } + driver_free(data); +} + + + + +#ifdef __WIN32__ +/* +** Mostly stolen from inet_drv in the emulator. +*/ +static int my_driver_select(TraceIpData *desc, SOCKET fd, + int flags, enum MySelectOp op) +{ + HANDLE *event; + unsigned *event_mask; + int ret = -1; + unsigned save_event_mask; + + if(fd == desc->listenfd) { + event = &(desc->listen_event); + event_mask = &(desc->listen_event_mask); + } else if(fd == desc->fd) { + event = &(desc->event); + event_mask = &(desc->event_mask); + } else { + return -1; + } + + save_event_mask = *event_mask; + + if (op==SELECT_ON) { + *event_mask |= flags; + } else { + *event_mask &= (~flags); + } + if (*event_mask != 0 && *event == 0) { + *event = WSACreateEvent(); + driver_select(desc->port, *event, ERL_DRV_READ|ERL_DRV_USE, 1); + } + + /* The RIGHT WAY (TM) to do this is to make sure: + A) The cancelling of all network events is done with + NULL as the event parameter (bug in NT's winsock), + B) The actual event handle is reset so that it is only + raised if one of the requested network events is active, + C) Avoid race conditions by making sure that the event cannot be set + while we are preparing to set the correct network event mask. + The simplest way to do it is to turn off all events, reset the + event handle and then, if event_mask != 0, turn on the appropriate + events again. */ + if (WSAEventSelect(fd, NULL, 0) != 0) { + *event_mask = save_event_mask; + goto error; + } + if (!ResetEvent(*event)) { + *event_mask = 0; + goto error; + } + if (*event_mask != 0) { + if (WSAEventSelect(fd, + *event, + *event_mask) != 0) { + *event_mask = 0; + goto error; + } + } + ret = 0; +error: + if (*event_mask == 0 && *event != 0 && (op==SELECT_CLOSE || ret!=0)) { + WSAEventSelect(fd, NULL, 0); /* Not necessary? + Well, actually I dont know, and + MS documentation states nothing + about what happens if WSAEventSelect + is called with empty event mask and + then the event is deleted. */ + driver_select(desc->port, *event, ERL_DRV_READ|ERL_DRV_USE, 0); + *event = 0; + if (op == SELECT_CLOSE) { + closesocket(fd); + } + } + return ret; +} + +static void stop_select(ErlDrvEvent event, void* _) +{ + WSACloseEvent((HANDLE)event); +} + +#else /* UNIX/VXWORKS */ + +static int my_driver_select(TraceIpData *desc, SOCKET fd, int flags, enum MySelectOp op) +{ + if (op != SELECT_OFF) { + flags |= ERL_DRV_USE; + } + return driver_select(desc->port, sock2event(fd), flags, (op==SELECT_ON)); +} + +static void stop_select(ErlDrvEvent event, void* _) +{ + closesocket((SOCKET)(long)event); +} + +#endif /* !__WIN32__ */ + +/* +** Set socket nonblocking, keep this at end of file. +*/ +#undef ERRNO_BLOCK +#undef ASSERT +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" +static int set_nonblocking(SOCKET sock) +{ + SET_NONBLOCKING(sock); + return 0; +} + + diff --git a/lib/runtime_tools/doc/html/.gitignore b/lib/runtime_tools/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/doc/man3/.gitignore b/lib/runtime_tools/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/doc/man6/.gitignore b/lib/runtime_tools/doc/man6/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/doc/pdf/.gitignore b/lib/runtime_tools/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/doc/src/Makefile b/lib/runtime_tools/doc/src/Makefile new file mode 100644 index 0000000000..dbbae81cfe --- /dev/null +++ b/lib/runtime_tools/doc/src/Makefile @@ -0,0 +1,123 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-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 + +# Make the docs target default. + +default: docs + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(RUNTIME_TOOLS_VSN) +APPLICATION=runtime_tools + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml +XML_REF3_FILES = dbg.xml erts_alloc_config.xml +XML_REF6_FILES = runtime_tools_app.xml + +XML_PART_FILES = part_notes.xml part_notes_history.xml +XML_CHAPTER_FILES = notes.xml notes_history.xml + +BOOK_FILES = book.xml + +XML_FILES = \ + $(BOOK_FILES) $(XML_CHAPTER_FILES) \ + $(XML_PART_FILES) $(XML_REF3_FILES) \ + $(XML_REF6_FILES) $(XML_APPLICATION_FILES) + +GIF_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 += +DVIPS_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 $(MAN6DIR)/* + 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) $(MAN6_FILES) $(RELEASE_PATH)/man/man6 + +release_spec: + diff --git a/lib/runtime_tools/doc/src/book.xml b/lib/runtime_tools/doc/src/book.xml new file mode 100644 index 0000000000..3f0dd7d55e --- /dev/null +++ b/lib/runtime_tools/doc/src/book.xml @@ -0,0 +1,46 @@ + + + + +
+ + 19992009 + 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. + + + + Runtime_Tools + Patrik Nyblom + + 1999-05-31 + A + book.sgml +
+ + + Runtime_Tools + + + + + + + + + + + +
+ diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml new file mode 100644 index 0000000000..0e63649c09 --- /dev/null +++ b/lib/runtime_tools/doc/src/dbg.xml @@ -0,0 +1,1187 @@ + + + + +
+ + 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. + + + + dbg + Patrik Nyblom (Claes Wikstrom) + + 1 + ETX/B/SFP (Kenneth Lundin) + + 96-11-07 + A + dbg.sgml +
+ dbg + The Text Based Trace Facility + +

This module implements a text based interface to the + trace/3 and the trace_pattern/2 BIFs. It makes it + possible to trace functions, processes and messages on text based + terminals. It can be used instead of, or as complement to, the + pman module. +

+

For some examples of how to use dbg from the Erlang + shell, see the simple example section. +

+

The utilities are also suitable to use in system testing on + large systems, where other tools have too much impact on the + system performance. Some primitive support for sequential tracing + is also included, see the advanced topics section. +

+
+ + + fun2ms(LiteralFun) -> MatchSpec + Pseudo function that transforms fun syntax to match_spec. + + LiteralFun = fun() literal + MatchSpec = term() + + +

Pseudo function that by means of a parse_transform + translates the literalfun() typed as parameter in + the function call to a match specification as described in + the match_spec manual of ERTS users guide. + (with literal I mean that the fun() needs to + textually be written as the parameter of the function, it + cannot be held in a variable which in turn is passed to the + function).

+

The parse transform is implemented in the module + ms_transform and the source must include the + file ms_transform.hrl in STDLIB for this + pseudo function to work. Failing to include the hrl file in + the source will result in a runtime error, not a compile + time ditto. The include file is easiest included by adding + the line + -include_lib("stdlib/include/ms_transform.hrl"). to + the source file.

+

The fun() is very restricted, it can take only a + single parameter (the parameter list to match), a sole variable or a + list. It needs to use the is_XXX guard tests and one + cannot use language constructs that have no representation + in a match_spec (like if, case, + receive etc). The return value from the fun will be + the return value of the resulting match_spec.

+

Example:

+
+1> dbg:fun2ms(fun([M,N]) when N > 3 -> return_trace() end).
+[{['$1','$2'],[{'>','$2',3}],[{return_trace}]}]
+

Variables from the environment can be imported, so that this + works:

+
+2> X=3.
+3
+3> dbg:fun2ms(fun([M,N]) when N > X  -> return_trace() end).
+[{['$1','$2'],[{'>','$2',{const,3}}],[{return_trace}]}]
+

The imported variables will be replaced by match_spec + const expressions, which is consistent with the + static scoping for Erlang fun()s. Local or global + function calls can not be in the guard or body of the fun + however. Calls to builtin match_spec functions of course is + allowed:

+
+4> dbg:fun2ms(fun([M,N]) when N > X, is_atomm(M)  -> return_trace() end).
+Error: fun containing local erlang function calls ('is_atomm' called in guard) cannot be translated into match_spec
+{error,transform_error}
+5> dbg:fun2ms(fun([M,N]) when N > X, is_atom(M)  -> return_trace() end).
+[{['$1','$2'],[{'>','$2',{const,3}},{is_atom,'$1'}],[{return_trace}]}]
+

As you can see by the example, the function can be called from + the shell too. The fun() needs to be literally in the + call when used from the shell as well. Other means than the + parse_transform are used in the shell case, but more or less + the same restrictions apply (the exception being records, + as they are not handled by the shell).

+ +

If the parse_transform is not applied to a module which calls this + pseudo function, the call will fail in runtime (with a + badarg). The module dbg actually exports a + function with this name, but it should never really be called + except for when using the function in the shell. If the + parse_transform is properly applied by including + the ms_transform.hrl header file, compiled code + will never call the function, but the function call is + replaced by a literal match_spec.

+
+

More information is provided by the ms_transform + manual page in STDLIB.

+
+
+ + h() -> ok + Give a list of available help items on standard output. + +

Gives a list of items for brief online help.

+
+
+ + h(Item) -> ok + Give brief help for an item. + + Item = atom() + + +

Gives a brief help text for functions in the dbg module. The + available items can be listed with dbg:h/0

+
+
+ + p(Item) -> {ok, MatchDesc} | {error, term()} + Trace messages to and from Item. + +

Equivalent to p(Item, [m]).

+
+
+ + p(Item, Flags) -> {ok, MatchDesc} | {error, term()} + Trace Item according to Flags. + + MatchDesc = [MatchNum] + MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError} + RPCError = term() + + +

Traces Item in accordance to the value specified + by Flags. The variation of Item is listed below:

+ + If the Item is a pid(), the corresponding + process is traced. The process may be a remote process + (on another Erlang node). The node must be in the list of + traced nodes (seen/1 and tracer/0/2/3). + If the Item is the atom all, all processes in the + system as well as all processes created hereafter are + to be traced. This also affects all nodes added with the + n/1 or tracer/0/2/3 function. + If the Item is the atom new, no currently existing + processes are affected, but every process created after the + call is.This also affects all nodes added with the + n/1 or tracer/0/2/3 function. + If the Item is the atom existing, all + existing processes are traced, but new processes will not + be affected.This also affects all nodes added with the + n/1 or tracer/0/2/3 function. + If the Item is an atom other than all, + new or existing, the process with the + corresponding registered name is traced.The process may be a + remote process (on another Erlang node). The node must be added + with the n/1 or tracer/0/2/3 function. + If the Item is an integer, the process ]]> is + traced. + If the Item is a tuple {X, Y, Z}, the + process ]]> is + traced. + If the Item is a string "]]> + as returned from pid_to_list/1, the process + ]]> is traced. + +

Flags can be a single atom, + or a list of flags. The available flags are: +

+ + s (send) + +

Traces the messages the process sends.

+
+ r (receive) + +

Traces the messages the process receives.

+
+ m (messages) + +

Traces the messages the process receives and sends.

+
+ c (call) + +

Traces global function calls for the process + according to the trace patterns set in the system (see tp/2).

+
+ p (procs) + +

Traces process related events to the process.

+
+ sos (set on spawn) + +

Lets all processes created by the traced + process inherit the trace flags + of the traced process.

+
+ sol (set on link) + +

Lets another process, P2, inherit the + trace flags of the traced + process whenever the traced process links to P2.

+
+ sofs (set on first spawn) + +

This is the same as sos, but only + for the first process spawned by the traced process.

+
+ sofl (set on first link) + +

This is the same as sol, but only for + the first call to + link/1 by the traced process.

+
+ all + +

Sets all flags.

+
+ clear + +

Clears all flags. +

+
+
+

The list can also include any of the flags allowed in + erlang:trace/3

+

The function returns either an error tuple or a tuple + {ok, List}. The List consists of + specifications of how many processes that matched (in the + case of a pure pid() exactly 1). The specification of + matched processes is {matched, Node, N}. If the + remote processor call,rpc, to a remote node fails, + the rpc error message is delivered as a fourth + argument and the number of matched processes are 0. Note + that the result {ok, List} may contain a list where + rpc calls to one, several or even all nodes failed.

+
+
+ + c(Mod, Fun, Args) + Evaluate apply(M,F,Args)with alltrace flags set. + +

Equivalent to c(Mod, Fun, Args, all).

+
+
+ + c(Mod, Fun, Args, Flags) + Evaluate apply(M,F,Args)with Flagstrace flags set. + +

Evaluates the expression apply(Mod, Fun, Args) with the trace + flags in Flags set. This is a convenient way to trace processes + from the Erlang shell.

+
+
+ + i() -> ok + Display information about all traced processes. + +

Displays information about all traced processes.

+
+
+ + tp(Module,MatchSpec) + Set pattern for traced global function calls + +

Same as tp({Module, '_', '_'}, MatchSpec)

+
+
+ + tp(Module,Function,MatchSpec) + Set pattern for traced global function calls + +

Same as tp({Module, Function, '_'}, MatchSpec)

+
+
+ + tp(Module, Function, Arity, MatchSpec) + Set pattern for traced global function calls + +

Same as tp({Module, Function, Arity}, MatchSpec)

+
+
+ + tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()} + Set pattern for traced global function calls + + Module = atom() | '_' + Function = atom() | '_' + Arity = integer() |'_' + MatchSpec = integer() | atom() | [] | match_spec() + MatchDesc = [MatchInfo] + MatchInfo = {saved, integer()} | MatchNum + MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError} + + +

This function enables call trace for one or more + functions. All exported functions matching the {Module, Function, Arity} argument will be concerned, but the + match_spec() may further narrow down the set of function + calls generating trace messages.

+

For a description of the match_spec() syntax, + please turn to the + User's guide part of the online + documentation for the runtime system (erts). The + chapter Match Specification in Erlang explains the + general match specification "language".

+

The Module, Function and/or Arity parts of the tuple may + be specified as the atom '_' which is a "wild-card" + matching all modules/functions/arities. Note, if the + Module is specified as '_', the Function and Arity + parts have to be specified as '_' too. The same holds for the + Functions relation to the Arity.

+

All nodes added with n/1 or tracer/0/2/3 will + be affected by this call, and if Module is not '_' + the module will be loaded on all nodes.

+

The function returns either an error tuple or a tuple + {ok, List}. The List consists of specifications of how + many functions that matched, in the same way as the processes + are presented in the return value of p/2.

+

There may be a tuple {saved, N} in the return value, + if the MatchSpec is other + than []. The integer N may then be used in + subsequent calls to this function and will stand as an + "alias" for the given expression. There are also built-in + aliases named with atoms (see also ltp/0 below).

+

If an error is returned, it can be due to errors in + compilation of the match specification. Such errors are + presented as a list of tuples {error, string()} where + the string is a textual explanation of the compilation + error. An example:

+
+(x@y)4> dbg:tp({dbg,ltp,0},[{[],[],[{message, two, arguments}, {noexist}]}]).
+{error,
+ [{error,"Special form 'message' called with wrong number of
+          arguments in {message,two,arguments}."},
+  {error,"Function noexist/1 does_not_exist."}]}
+
+
+ + tpl(Module,MatchSpec) + Set pattern for traced local (as well as global) function calls + +

Same as tpl({Module, '_', '_'}, MatchSpec)

+
+
+ + tpl(Module,Function,MatchSpec) + Set pattern for traced local (as well as global) function calls + +

Same as tpl({Module, Function, '_'}, MatchSpec)

+
+
+ + tpl(Module, Function, Arity, MatchSpec) + Set pattern for traced local (as well as global) function calls + +

Same as tpl({Module, Function, Arity}, MatchSpec)

+
+
+ + tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()} + Set pattern for traced local (as well as global) function calls + +

This function works as tp/2, but enables + tracing for local calls (and local functions) as well as for + global calls (and functions).

+
+
+ + ctp() + Clear call trace pattern for the specified functions + +

Same as ctp({'_', '_', '_'})

+
+
+ + ctp(Module) + Clear call trace pattern for the specified functions + +

Same as ctp({Module, '_', '_'})

+
+
+ + ctp(Module, Function) + Clear call trace pattern for the specified functions + +

Same as ctp({Module, Function, '_'})

+
+
+ + ctp(Module, Function, Arity) + Clear call trace pattern for the specified functions + +

Same as ctp({Module, Function, Arity})

+
+
+ + ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()} + Clear call trace pattern for the specified functions + + Module = atom() | '_' + Function = atom() | '_' + Arity = integer() | '_' + MatchDesc = [MatchNum] + MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError} + + +

This function disables call tracing on the specified + functions. The semantics of the parameter is the same + as for the corresponding function specification in + tp/2 or tpl/2. Both local and global call trace + is disabled.

+

The return value reflects how many functions that matched, + and is constructed as described in tp/2. No tuple + {saved, N} is however ever returned (for obvious reasons).

+
+
+ + ctpl() + Clear call trace pattern for the specified functions + +

Same as ctpl({'_', '_', '_'})

+
+
+ + ctpl(Module) + Clear call trace pattern for the specified functions + +

Same as ctpl({Module, '_', '_'})

+
+
+ + ctpl(Module, Function) + Clear call trace pattern for the specified functions + +

Same as ctpl({Module, Function, '_'})

+
+
+ + ctpl(Module, Function, Arity) + Clear call trace pattern for the specified functions + +

Same as ctpl({Module, Function, Arity})

+
+
+ + ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()} + Clear call trace pattern for the specified functions + +

This function works as ctp/1, but only disables + tracing set up with tpl/2 (not with tp/2).

+
+
+ + ctpg() + Clear call trace pattern for the specified functions + +

Same as ctpg({'_', '_', '_'})

+
+
+ + ctpg(Module) + Clear call trace pattern for the specified functions + +

Same as ctpg({Module, '_', '_'})

+
+
+ + ctpg(Module, Function) + >Clear call trace pattern for the specified functions + +

Same as ctpg({Module, Function, '_'})

+
+
+ + ctpg(Module, Function, Arity) + >Clear call trace pattern for the specified functions + +

Same as ctpg({Module, Function, Arity})

+
+
+ + ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()} + Clear call trace pattern for the specified functions + +

This function works as ctp/1, but only disables + tracing set up with tp/2 (not with tpl/2).

+
+
+ + ltp() -> ok + List saved and built-in match specifications on the console. + +

Use this function to recall all match specifications previously + used in the session (i. e. previously saved during calls + to tp/2, and built-in match specifications. + This is very useful, as a complicated + match_spec can be quite awkward to write. Note that the + match specifications are lost if stop/0 is called.

+

Match specifications used can be saved in a file (if a + read-write file system is present) for use in later + debugging sessions, see wtp/1 and rtp/1

+
+
+ + dtp() -> ok + Delete all saved match specifications. + +

Use this function to "forget" all match specifications + saved during calls to tp/2. + This is useful when one wants to restore other match + specifications from a file with rtp/1. Use + dtp/1 to delete specific saved match specifications.

+
+
+ + dtp(N) -> ok + Delete a specific saved match_spec. + + N = integer() + + +

Use this function to "forget" a specific match specification + saved during calls to tp/2.

+
+
+ + wtp(Name) -> ok | {error, IOError} + Write all saved and built-in match specifications to a file + + Name = string() + IOError = term() + + +

This function will save all match specifications saved + during the session (during calls to tp/2) + and built-in match specifications in a text + file with the name designated by Name. The format + of the file is textual, why it can be edited with an + ordinary text editor, and then restored with + rtp/1.

+

Each match spec in the file ends with a full stop + (.) and new (syntactically correct) match + specifications can be added to the file manually.

+

The function returns ok or an error tuple where the + second element contains the I/O error that made the + writing impossible.

+
+
+ + rtp(Name) -> ok | {error, Error} + Read saved match specifications from file. + + Name = string() + Error = term() + + +

This function reads match specifications from a file + (possibly) generated by the wtp/1 function. It checks + the syntax of all match specifications and verifies that + they are correct. The error handling principle is "all or + nothing", i. e. if some of the match specifications are + wrong, none of the specifications are added to the list of + saved match specifications for the running system.

+

The match specifications in the file are merged + with the current match specifications, so that no duplicates + are generated. Use ltp/0 to see what numbers were + assigned to the specifications from the file.

+

The function will return an error, either due to I/O + problems (like a non existing or non readable file) or due + to file format problems. The errors from a bad format file + are in a more or less textual format, which will give a hint + to what's causing the problem. +

+
+
+ + n(Nodename) -> {ok, Nodename} | {error, Reason} + Add a remote node to the list of traced nodes + + Nodename = atom() + Reason = term() + + +

The dbg server keeps a list of nodes where tracing + should be performed. Whenever a tp/2 call or a + p/2 call is made, it is executed for all nodes in this + list including the local node (except for p/2 with a + specific pid() as first argument, in which case the + command is executed only on the node where the designated + process resides). +

+

This function adds a remote node (Nodename) to the + list of nodes where tracing is performed. It starts a tracer + process on the remote node, which will send all trace messages + to the tracer process on the local node (via the Erlang + distribution). If no tracer process is running on the local + node, the error reason no_local_tracer is returned. The + tracer process on the local node must be started with the + tracer/0/2 function. +

+

If Nodename is the local node, the error reason + cant_add_local_node is returned. +

+

If a trace port (seetrace_port/2) is + running on the local node, remote nodes can not be traced with + a tracer process. The error reason + cant_trace_remote_pid_to_local_port is returned. A + trace port can however be started on the remote node with the + tracer/3 function. +

+

The function will also return an error if the node + Nodename is not reachable.

+
+
+ + cn(Nodename) -> ok + Clear a node from the list of traced nodes. + + Nodename = atom() + + +

Clears a node from the list of traced nodes. Subsequent + calls to tp/2 and p/2 will not consider that + node, but tracing already activated on the node will continue + to be in effect.

+

Returns ok, cannot fail.

+
+
+ + ln() -> ok + Show the list of traced nodes on the console. + +

Shows the list of traced nodes on the console.

+
+
+ + tracer() -> {ok, pid()} | {error, already_started} + Start a tracer server that handles trace messages. + +

This function starts a server on the local node that will + be the recipient of all trace messages. All subsequent calls + to p/2 will result in messages sent to the newly + started trace server.

+

A trace server started in this way will simply display the + trace messages in a formatted way in the Erlang shell + (i. e. use io:format). See tracer/2 for a description + of how the trace message handler can be customized. +

+

To start a similar tracer on a remote node, use n/1.

+
+
+ + tracer(Type, Data) -> {ok, pid()} | {error, Error} + Start a tracer server with additional parameters + + Type = port | process + Data = PortGenerator | HandlerSpec + HandlerSpec = {HandlerFun, InitialData} + HandlerFun = fun() (two arguments) + InitialData = term() + PortGenerator = fun() (no arguments) + Error = term() + + +

This function starts a tracer server with additional + parameters on the local node. The first parameter, the + Type, indicates if trace messages should be handled + by a receiving process (process) or by a tracer port + (port). For a description about tracer ports see + trace_port/2. +

+

If Type is a process, a message handler function can + be specified (HandlerSpec). The handler function, which + should be a fun taking two arguments, will be called + for each trace message, with the first argument containing the + message as it is and the second argument containing the return + value from the last invocation of the fun. The initial value + of the second parameter is specified in the InitialData + part of the HandlerSpec. The HandlerFun may + chose any appropriate action to take when invoked, and can + save a state for the next invocation by returning it. +

+

If Type is a port, then the second parameter should + be a fun which takes no arguments and returns a + newly opened trace port when called. Such a fun is + preferably generated by calling trace_port/2. +

+

If an error is returned, it can either be due to a tracer + server already running ({error,already_started}) or + due to the HandlerFun throwing an exception. +

+

To start a similar tracer on a remote node, use + tracer/3. +

+
+
+ + tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason} + Start a tracer server on given node with additional parameters + + Nodename = atom() + + +

This function is equivalent to tracer/2, but acts on + the given node. A tracer is started on the node + (Nodename) and the node is added to the list of traced + nodes. +

+ +

This function is not equivalent to n/1. While + n/1 starts a process tracer which redirects all trace + information to a process tracer on the local node (i.e. the + trace control node), tracer/3 starts a tracer of any + type which is independent of the tracer on the trace control + node.

+
+

For details, seetracer/2.

+
+
+ + trace_port(Type, Parameters) -> fun() + Create and returns a trace port generatingfun + + Type = ip | file + Parameters = Filename | WrapFilesSpec | IPPortSpec + Filename = string() | [string()] | atom() + WrapFilesSpec = {Filename, wrap, Suffix} | {Filename, wrap, Suffix, WrapSize} | {Filename, wrap, Suffix, WrapSize, WrapCnt} + Suffix = string() + WrapSize = integer() >= 0 | {time, WrapTime} + WrapTime = integer() >= 1 + WrapCnt = integer() >= 1 + IpPortSpec = PortNumber | {PortNumber, QueSize} + PortNumber = integer() + QueSize = integer() + + +

This function creates a trace port generating fun. + The fun takes no arguments and returns a newly opened + trace port. The return value from this function is suitable as + a second parameter to tracer/2, i. e. dbg:tracer(port, dbg:trace_port(ip, 4711)).

+

A trace port is an + Erlang port to a dynamically linked in driver that handles + trace messages directly, without the overhead of sending them + as messages in the Erlang virtual machine.

+

Two trace drivers are currently implemented, the + file and the ip trace drivers. The file driver + sends all trace messages into one or several binary files, + from where they later can be fetched and processed with the + trace_client/2 function. The ip driver opens a TCP/IP + port where it listens for connections. When a client + (preferably started by calling trace_client/2 on + another Erlang node) connects, all trace messages are sent + over the IP network for further processing by the remote + client.

+

Using a trace port significantly lowers the overhead + imposed by using tracing.

+

The file trace driver expects a filename or a wrap files + specification as parameter. A file is written with a high + degree of buffering, why all trace messages are not + guaranteed to be saved in the file in case of a system + crash. That is the price to pay for low tracing overhead.

+

A wrap files specification is used to limit the disk + space consumed by the trace. The trace is written to a + limited number of files each with a limited size. + The actual filenames are Filename ++ SeqCnt ++ Suffix, where SeqCnt counts as a decimal string + from 0 to WrapCnt and then around again from + 0. When a trace term written to + the current file makes it longer than WrapSize, + that file is closed, if the number of files in this + wrap trace is as many as WrapCnt the oldest file + is deleted then a new file is opened to become the current. + Thus, when a wrap trace has been stopped, there are at most + WrapCnt trace files saved with a size of at least + WrapSize (but not much bigger), except for + the last file that might even be empty. The default values + are WrapSize = 128*1024 and WrapCnt = 8.

+

The SeqCnt values in the filenames are all in the + range 0 through WrapCnt with a gap in the + circular sequence. The gap is needed to find the end of the + trace.

+

If the WrapSize is specified as + {time, WrapTime}, the current file is closed when it + has been open more than WrapTime milliseconds, + regardless of it being empty or not.

+

The ip trace driver has a queue of QueSize messages + waiting to be delivered. If the driver cannot deliver messages + as fast as they are produced by the runtime system, a special + message is sent, which indicates how many messages that are + dropped. That message will arrive at the handler function + specified in trace_client/3 as the tuple {drop, N} where N is the number of consecutive messages + dropped. In case of heavy tracing, drop's are likely to occur, + and they surely occur if no client is reading the trace + messages.

+
+
+ + flush_trace_port() + Equivalent to flush_trace_port(node()). + +

Equivalent to flush_trace_port(node()).

+
+
+ + flush_trace_port(Nodename) -> ok | {error, Reason} + Flush internal data buffers in a trace driver on the given node. + +

Equivalent to trace_port_control(Nodename,flush).

+
+
+ + trace_port_control(Operation) + Equivalent to trace_port_control(node(),Operation). + +

Equivalent to trace_port_control(node(),Operation).

+
+
+ + trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason} + Perform a control operation on the active trace port driver on the given node. + + Nodename = atom() + + +

This function is used to do a control operation on the + active trace port driver on the given node + (Nodename). Which operations that are allowed as well + as their return values are depending on which trace driver + that is used.

+

Returns either ok or {ok, Result} + if the operation was successful, or {error, Reason} + if the current tracer is a process + or if it is a port not supporting the operation.

+

The allowed values for Operation are:

+ + flush + +

This function is used to flush the internal buffers + held by a trace port driver. Currently only the + file trace driver supports this operation. + Returns ok.

+
+ get_listen_port + +

Returns {ok, IpPort} where IpPortis + the IP port number used by the driver listen socket. + Only the ip trace driver supports this operation.

+
+
+
+
+ + trace_client(Type, Parameters) -> pid() + Start a trace client that reads messages created by a trace port driver + + Type = ip | file | follow_file + Parameters = Filename | WrapFilesSpec | IPClientPortSpec + Filename = string() | [string()] | atom() + WrapFilesSpec = see trace_port/2 + Suffix = string() + IpClientPortSpec = PortNumber | {Hostname, PortNumber} + PortNumber = integer() + Hostname = string() + + +

This function starts a trace client that reads the output + created by a trace port driver and handles it in mostly the + same way as a tracer process created by the tracer/0 + function.

+

If Type is file, the client reads all trace + messages stored in the file named Filename or + specified by WrapFilesSpec (must be the same as used + when creating the trace, see trace_port/2) + and let's the default handler function format the + messages on the console. This is one way to interpret the data + stored in a file by the file trace port driver.

+

If Type is follow_file, the client behaves as + in the file case, but keeps trying to read (and + process) more data + from the file until stopped by stop_trace_client/1. + WrapFilesSpec is not allowed as second argument + for this Type.

+

If Type is ip, the client connects to the + TCP/IP port PortNumber on the host Hostname, + from where it reads trace messages until the TCP/IP connection + is closed. If no Hostname is specified, the local host + is assumed.

+

As an example, one can let trace messages be sent over the + network to another Erlang node (preferably not + distributed), where the formatting occurs:

+

On the node stack there's an Erlang node + ant@stack, in the shell, type the following:

+
+ant@stack> dbg:tracer(port, dbg:trace_port(ip,4711)).
+<0.17.0>
+ant@stack> dbg:p(self(), send).
+{ok,1}
+

All trace messages are now sent to the trace port driver, + which in turn listens for connections on the TCP/IP port + 4711. If we want to see the messages on another node, + preferably on another host, we do like this:

+
+-> dbg:trace_client(ip, {"stack", 4711}).
+<0.42.0>
+

If we now send a message from the shell on the node + ant@stack, where all sends from the shell are traced:

+
+ant@stack> self() ! hello.
+hello
+

The following will appear at the console on the node that + started the trace client:

+
+(<0.23.0>) <0.23.0> ! hello
+(<0.23.0>) <0.22.0> ! {shell_rep,<0.23.0>,{value,hello,[],[]}}
+

The last line is generated due to internal message passing + in the Erlang shell. The process id's will vary.

+
+
+ + trace_client(Type, Parameters, HandlerSpec) -> pid() + Start a trace client that reads messages created by a trace port driver, with a user defined handler + + Type = ip | file | follow_file + Parameters = Filename | WrapFilesSpec | IPClientPortSpec + Filename = string() | [string()] | atom() + WrapFilesSpec = see trace_port/2 + Suffix = string() + IpClientPortSpec = PortNumber | {Hostname, PortNumber} + PortNumber = integer() + Hostname = string() + HandlerSpec = {HandlerFun, InitialData} + HandlerFun = fun() (two arguments) + InitialData = term() + + +

This function works exactly as trace_client/2, but + allows you to write your own handler function. The handler + function works mostly as the one described in + tracer/2, but will also have to be prepared to handle + trace messages of the form {drop, N}, where N is + the number of dropped messages. This pseudo trace message will + only occur if the ip trace driver is used.

+

For trace type file, the pseudo trace message + end_of_trace will appear at the end of the trace. The + return value from the handler function is in this case + ignored.

+
+
+ + stop_trace_client(Pid) -> ok + Stop a trace client gracefully. + + Pid = pid() + + +

This function shuts down a previously started trace + client. The Pid argument is the process id returned + from the trace_client/2 or trace_client/3 call.

+
+
+ + get_tracer() + Equivalent to get_tracer(node()). + +

Equivalent to get_tracer(node()).

+
+
+ + get_tracer(Nodename) -> {ok, Tracer} + Return the process or port to which all trace messages are sent. + + Nodename = atom() + Tracer = port() | pid() + + +

Returns the process or port to which all trace + messages are sent.

+
+
+ + stop() -> stopped + Stop the dbgserver and the tracing of all processes. + +

Stops the dbg server and clears all trace flags for + all processes and all trace patterns for all functions. Also + shuts down all trace clients and closes all trace ports.

+

Note that no trace patterns are affected by this + function.

+
+
+ + stop_clear() -> stopped + Stop the dbgserver and the tracing of all processes, and clears trace patterns. + +

Same as stop/0, but also clears all trace patterns on local + and global functions calls.

+
+
+
+ +
+ + Simple examples - tracing from the shell +

The simplest way of tracing from the Erlang shell is to use + dbg:c/3 or dbg:c/4, e.g. tracing the function + dbg:get_tracer/0:

+
+(tiger@durin)84> dbg:c(dbg,get_tracer,[]).
+(<0.154.0>) <0.152.0> ! {<0.154.0>,{get_tracer,tiger@durin}}
+(<0.154.0>) out {dbg,req,1}
+(<0.154.0>) << {dbg,{ok,<0.153.0>}}
+(<0.154.0>) in {dbg,req,1}
+(<0.154.0>) << timeout
+{ok,<0.153.0>}
+(tiger@durin)85>
+

Another way of tracing from the shell is to explicitly start a + tracer and then set the trace flags of your + choice on the processes you want to trace, e.g. trace messages and + process events:

+
+(tiger@durin)66> Pid = spawn(fun() -> receive {From,Msg} -> From ! Msg end end).
+<0.126.0>
+(tiger@durin)67> dbg:tracer().
+{ok,<0.128.0>}
+(tiger@durin)68> dbg:p(Pid,[m,procs]).
+{ok,[{matched,tiger@durin,1}]}
+(tiger@durin)69> Pid ! {self(),hello}.
+(<0.126.0>) << {<0.116.0>,hello}
+{<0.116.0>,hello}
+(<0.126.0>) << timeout
+(<0.126.0>) <0.116.0> ! hello
+(<0.126.0>) exit normal
+(tiger@durin)70> flush().
+Shell got hello
+ok
+(tiger@durin)71>
+

If you set the call trace flag, you also have to set a + trace pattern for the functions you want to trace:

+
+(tiger@durin)77> dbg:tracer().
+{ok,<0.142.0>}
+(tiger@durin)78> dbg:p(all,call).
+{ok,[{matched,tiger@durin,3}]}
+(tiger@durin)79> dbg:tp(dbg,get_tracer,0,[]).
+{ok,[{matched,tiger@durin,1}]}
+(tiger@durin)80> dbg:get_tracer().
+(<0.116.0>) call dbg:get_tracer()
+{ok,<0.143.0>}
+(tiger@durin)81> dbg:tp(dbg,get_tracer,0,[{'_',[],[{return_trace}]}]).
+{ok,[{matched,tiger@durin,1},{saved,1}]}
+(tiger@durin)82> dbg:get_tracer().
+(<0.116.0>) call dbg:get_tracer()
+(<0.116.0>) returned from dbg:get_tracer/0 -> {ok,<0.143.0>}
+{ok,<0.143.0>}
+(tiger@durin)83>
+
+ +
+ + Advanced topics - combining with seq_trace +

The dbg module is primarily targeted towards + tracing through the erlang:trace/3 function. It is + sometimes desired to trace messages in a more delicate way, which + can be done with the help of the seq_trace module. +

+

seq_trace implements sequential tracing (known in the + AXE10 world, and sometimes called "forlopp tracing"). dbg + can interpret messages generated from seq_trace and the + same tracer function for both types of tracing can be used. The + seq_trace messages can even be sent to a trace port for + further analysis. +

+

As a match specification can turn on sequential tracing, the + combination of dbg and seq_trace can be quite + powerful. This brief example shows a session where sequential + tracing is used:

+
+1> dbg:tracer().
+{ok,<0.30.0>}
+2> {ok, Tracer} = dbg:get_tracer().
+{ok,<0.31.0>}
+3> seq_trace:set_system_tracer(Tracer).
+false
+4> dbg:tp(dbg, get_tracer, 0, [{[],[],[{set_seq_token, send, true}]}]).
+{ok,[{matched,nonode@nohost,1},{saved,1}]}
+5> dbg:p(all,call).
+{ok,[{matched,nonode@nohost,22}]}
+6> dbg:get_tracer(), seq_trace:set_token([]).
+(<0.25.0>) call dbg:get_tracer()
+SeqTrace [0]: (<0.25.0>) <0.30.0> ! {<0.25.0>,get_tracer} [Serial: {2,4}]
+SeqTrace [0]: (<0.30.0>) <0.25.0> ! {dbg,{ok,<0.31.0>}} [Serial: {4,5}]
+{1,0,5,<0.30.0>,4}
+

This session sets the system_tracer to the same process as + the ordinary tracer process (i. e. <0.31.0>) and sets the + trace pattern for the function dbg:get_tracer to one that + has the action of setting a sequential token. When the function + is called by a traced process (all processes are traced in this + case), the process gets "contaminated" by the token and + seq_trace messages are sent both for the server request + and the response. The seq_trace:set_token([]) after the + call clears the seq_trace token, why no messages are sent + when the answer propagates via the shell to the console port. + The output would otherwise have been more noisy.

+
+ +
+ Note of caution +

When tracing function calls on a group leader process (an IO process), there is risk + of causing a deadlock. This will happen if a group leader process generates a trace + message and the tracer process, by calling the trace handler function, sends an IO + request to the same group leader. The problem can only occur if the trace handler + prints to tty using an io function such as format/2. Note that when + dbg:p(all,call) is called, IO processes are also traced. + Here's an example:

+
+%% Using a default line editing shell
+1> dbg:tracer(process, {fun(Msg,_) -> io:format("~p~n", [Msg]), 0 end, 0}).
+{ok,<0.37.0>}
+2> dbg:p(all, [call]).
+{ok,[{matched,nonode@nohost,25}]}
+3> dbg:tp(mymod,[{'_',[],[]}]).
+{ok,[{matched,nonode@nohost,0},{saved,1}]}
+4> mymod: % TAB pressed here
+%% -- Deadlock --
+

Here's another example:

+
+%% Using a shell without line editing (oldshell)
+1> dbg:tracer(process).
+{ok,<0.31.0>}
+2> dbg:p(all, [call]).
+{ok,[{matched,nonode@nohost,25}]}
+3> dbg:tp(lists,[{'_',[],[]}]).
+{ok,[{matched,nonode@nohost,0},{saved,1}]}
+% -- Deadlock --
+

The reason we get a deadlock in the first example is because when TAB is pressed + to expand the function name, the group leader (which handles character input) calls + mymod:module_info(). This generates a trace message which, in turn, causes the + tracer process to send an IO request to the group leader (by calling io:format/2). + We end up in a deadlock.

+

In the second example we use the default trace handler function. This handler + prints to tty by sending IO requests to the user process. When Erlang is + started in oldshell mode, the shell process will have user as its + group leader and so will the tracer process in this example. Since user calls + functions in lists we end up in a deadlock as soon as the first IO request is sent.

+

Here are a few suggestions for how to avoid deadlock:

+ + Don't trace the group leader of the tracer process. If tracing has been switched on + for all processes, call dbg:p(TracerGLPid,clear) to stop tracing the group leader + (TracerGLPid). process_info(TracerPid,group_leader) tells you + which process this is (TracerPid is returned from dbg:get_tracer/0). + Don't trace the user process if using the default trace handler function. + In your own trace handler function, call erlang:display/1 instead of an + io function or, if user is not used as group leader, print to + user instead of the default group leader. Example: + io:format(user,Str,Args). + +
+
+ diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml new file mode 100644 index 0000000000..5e7cbe4172 --- /dev/null +++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml @@ -0,0 +1,211 @@ + + + + +
+ + 20072009 + 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. + + + + erts_alloc_config + Rickard Green + 1 + 07-05-30 + 1 + erts_alloc_config.sgml +
+ erts_alloc_config + Configuration tool for erts_alloc + + +

erts_alloc_config is currently an experimental + tool and might be subject to backward incompatible + changes.

+
+

erts_alloc(3) is an + Erlang Run-Time System internal memory allocator library. + erts_alloc_config is intended to be used to aid creation + of an erts_alloc(3) + configuration that is suitable for a limited number of runtime + scenarios. The configuration that erts_alloc_config + produce is intended as a suggestion, and may need to be + adjusted manually.

+

The configuration is created based on information about a number + of runtime scenarios. It is obviously impossible to foresee every + runtime scenario that can occur. The important scenarios are + those that cause maximum or minimum load on specific memory + allocators. Load in this context is total size of memory blocks + allocated.

+

The current implementation of erts_alloc_config concentrate + on configuration of multi-block carriers. Information gathered + when a runtime scenario is saved is mainly current and maximum use + of multi-block carriers. If a parameter that change the use of + multi-block carriers is changed, a previously generated + configuration is invalid and erts_alloc_config needs + to be run again. It is mainly the single block carrier threshold + that effects the use of multi-block carriers, but other + single-block carrier parameters might as well. If another value of + a single block carrier parameter than the default is desired, use + the desired value when running erts_alloc_config.

+

A configuration is created in the following way:

+ + +

Pass the +Mea config + command-line flag to the Erlang runtime system you are going + to use for creation of the allocator configuration. It will + disable features that prevent erts_alloc_config from + doing it's job. Note, you should not use this flag + when using the created configuration. Also note that it is + important that you use the same + amount of schedulers + when creating the configuration as you are going the use on + the system using the configuration.

+
+ +

Run your applications with different scenarios (the more + the better) and save information about each scenario by calling + save_scenario/0. + It may be hard to know when the applications are at an (for + erts_alloc_config) important runtime scenario. A good + approach may therefore be to call + save_scenario/0 + repeatedly, e.g. once every tenth second. Note that it is + important that your applications reach the runtime scenarios + that are important for erts_alloc_config when you are + saving scenarios; otherwise, the configuration may perform + bad.

+
+ +

When you have covered all scenarios, call + make_config/1 + in order to create a configuration. The configuration is + written to a file that you have chosen. This configuration + file can later be read by an Erlang runtime-system at + startup. Pass the command line argument + -args_file FileName + to the erl(1) command.

+
+ +

The configuration produced by erts_alloc_config may + need to be manually adjusted as already stated. Do not modify the + file produced by erts_alloc_config; instead, put your + modifications in another file and load this file after the + file produced by erts_alloc_config. That is, put the + -args_file FileName + argument that reads your modification file later on the + command-line than the + -args_file FileName + argument that reads the configuration file produced by + erts_alloc_config. If a memory allocation parameter + appear multiple times, the last version of will be used, i.e., + you can override parameters in the configuration file produced + by erts_alloc_config. Doing it this way simplifies + things when you want to rerun erts_alloc_config.

+
+
+ +

The configuration created by erts_alloc_config may + perform bad, ever horrible, for runtime scenarios that are very + different from the ones saved when creating the + configuration. You are, therefore, advised to rerun + erts_alloc_config if the applications run when the + configuration was made are changed, or if the load on the + applications have changed since the configuration was made. You + are also advised to rerun erts_alloc_config if the Erlang + runtime system used is changed.

+
+

erts_alloc_config saves information about runtime scenarios + and performs computations in a server that is automatically + started. The server register itself under the name + '__erts_alloc_config__'.

+
+ + + save_scenario() -> ok | {error, Error} + Saves information about current runtime scenario + + Error = term() + + +

save_scenario/0 saves information about the current + runtime scenario. This information will later be used when + make_config/0, + or make_config/1 + is called.

+

The first time save_scenario/0 is called a server + will be started. This server will save runtime scenarios. All + saved scenarios can be removed by calling + stop/0.

+
+
+ + make_config() -> ok | {error, Error} + Creates an erts_alloc configuration + + Error = term() + + +

This is the same as calling + make_config(group_leader()).

+
+
+ + make_config(FileNameOrIODev) -> ok | {error, Error} + Creates an erts_alloc configuration + + FileNameOrIODev = string() | io_device() + Error = term() + + +

make_config/1 uses the information previously saved by + save_scenario/0 + in order to produce an erts_alloc configuration. At + least one scenario have had to be saved. All scenarios + previously saved will be used when creating the + configuration.

+

If FileNameOrIODev is a string(), + make_config/1 will use FileNameOrIODev as a + filename. A file named FileNameOrIODev is created and + the configuration will be written to that file. If + FileNameOrIODev is an + io_device() (see the + documentation of the module + io), the configuration + will be written to the io device.

+
+
+ + stop() -> ok | {error, Error} + + + Error = term() + + +

Stops the server that saves runtime scenarios.

+
+
+
+ +
+ See Also +

erts_alloc(3), + erl(1), + io(3)

+
+
+ + diff --git a/lib/runtime_tools/doc/src/fascicules.xml b/lib/runtime_tools/doc/src/fascicules.xml new file mode 100644 index 0000000000..5230dc7e90 --- /dev/null +++ b/lib/runtime_tools/doc/src/fascicules.xml @@ -0,0 +1,15 @@ + + + + + + Reference Manual + + + Release Notes + + + Off-Print + + + diff --git a/lib/runtime_tools/doc/src/make.dep b/lib/runtime_tools/doc/src/make.dep new file mode 100644 index 0000000000..85eae88adf --- /dev/null +++ b/lib/runtime_tools/doc/src/make.dep @@ -0,0 +1,20 @@ +# ---------------------------------------------------- +# >>>> 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 dbg.tex erts_alloc_config.tex refman.tex \ + runtime_tools_app.tex + +# ---------------------------------------------------- +# Source inlined when transforming from source to LaTeX +# ---------------------------------------------------- + +book.tex: refman.xml + diff --git a/lib/runtime_tools/doc/src/note.gif b/lib/runtime_tools/doc/src/note.gif new file mode 100644 index 0000000000..6fffe30419 Binary files /dev/null and b/lib/runtime_tools/doc/src/note.gif differ diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml new file mode 100644 index 0000000000..3262e4819b --- /dev/null +++ b/lib/runtime_tools/doc/src/notes.xml @@ -0,0 +1,503 @@ + + + + +
+ + 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. + + + + Runtime_Tools Release Notes + + + + + notes.xml +
+

This document describes the changes made to the Runtime_Tools + application.

+ +
Runtime_Tools 1.8.2 + +
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

+
+
+
+ +
+ +
Runtime_Tools 1.8.1 + +
Fixed Bugs and Malfunctions + + +

+ Makefile.in has been updated to use the LDFLAGS + environment variable (if set). (Thanks to Davide + Pesavento.)

+

+ Own Id: OTP-8157

+
+
+
+ +
+ +
Runtime_Tools 1.8 + +
Improvements and New Features + + +

+ etop would crash if the emulator's custom + allocators had been turned off (e.g. using the + +Meamin option).

+

+ Own Id: OTP-7519

+
+ +

The copyright notices have been updated.

+

+ Own Id: OTP-7851

+
+ +

Now, dbg:p/2 accepts {X,Y,Z} process specification as + stated in the documentation. It also now accepts + "<X.Y.Z>" like from erlang:pid_to_list/1.

+

There is now a pre-saved match spec in dbg that saves + a lot of typing. Use dbg:ltp/0 to find out more...

+

+ Own Id: OTP-7867

+
+
+
+ +
+ +
Runtime_Tools 1.7.3 + +
Fixed Bugs and Malfunctions + + +

Fixed a timestamp problem where some events could be + sent out of order. Minor fixes to presentation of + data.

+

+ Own Id: OTP-7544 Aux Id: otp-7442

+
+
+
+ +
+ +
Runtime_Tools 1.7.2 + +
Fixed Bugs and Malfunctions + + +

etop now collects process information faster + and more reliably than before (a race condition reported + by Igor Goryachev has been eliminated).

+

Trace messages could be lost when ttb:stop/0 + was called.

+

+ Own Id: OTP-7164

+
+
+
+ +
+ +
Runtime_Tools 1.7.1 + +
Improvements and New Features + + +

The documentation has been updated so as to reflect + the last updates of the Erlang shell as well as the minor + modifications of the control sequence p of the + io_lib module.

Superfluous empty lines have + been removed from code examples and from Erlang shell + examples.

+

+ Own Id: OTP-6944 Aux Id: OTP-6554, OTP-6911

+
+ +

Memory management improvements especially for the + runtime system with SMP support:

The + runtime system with SMP support can now use multiple, + thread specific instances of most memory allocators. This + improves performance since it reduces lock contention in + the memory allocators. It may however increase memory + usage for some applications. The runtime system with SMP + support will by default enable this feature on most + allocators. The amount of instances used can be + configured. driver_alloc(), + driver_realloc(), and driver_free() now use + their own erts specific memory allocator instead of the + default malloc() implementation on the system. + The default configuration of some + allocators have been changed to fit applications that use + much memory better. Some new + erts_alloc configuration parameters have been + added. erts_alloc_config has been + modified to be able to create configurations suitable for + multiple instances of allocators. The + returned value from erlang:system_info({allocator, + Alloc}) has been changed. This since an allocator may + now run in multiple instances.

If you + for some reason want the memory allocators to be + configured as before, you can pass the +Mea r11b + command-line argument to erl.

For more + information see the erts_alloc(3), the + erts_alloc_config(3), and the erlang(3) + documentation.

+

+ *** POTENTIAL INCOMPATIBILITY ***

+

+ Own Id: OTP-7100

+
+
+
+ +
+ +
Runtime_Tools 1.7 + +
Fixed Bugs and Malfunctions + + +

+ dbg could leave traced processes in a suspended + state if the tracer process was killed with exit reason + kill.

+

+ erlang:suspend_process/2 has been introduced which + accepts an option list as second argument. For more + information see the erlang(3) documentation.

+

+ Processes suspended via + erlang:suspend_process/[1,2] will now be + automatically resumed if the process that called + erlang:suspend_process/[1,2] terminates.

+

+ Processes could previously be suspended by one process + and resumed by another unless someone was tracing the + suspendee. This is not possible anymore. The + process resuming a process has to be the one + that suspended it.

+

+ *** POTENTIAL INCOMPATIBILITY ***

+

+ Own Id: OTP-6946

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

+ The undocumented and unsupported function + dbg:tracer/1 has been removed. The undocumented, + unsupported, and broken function dbg:i/1 has been + removed.

+

+ Own Id: OTP-6939

+
+
+
+ +
+ +
Runtime_Tools 1.6.8 + +
Fixed Bugs and Malfunctions + + +

+ In this release the following has been fixed and + enhanced: Autostart: It is now possible to configure + modules that shall be loaded by the autostart mechanism. + This because it is not certain that all application + systems make use of the OTP boot script to set up paths + to all Erlang modules. Runtime_tools/Inviso: A bug in the + fetch_log functionality has been fixed. Further a bug + that was (supposedly) fixed in a previous patch + concerning meta-tracer write_ti has been fixed (again) in + this patch. A bug in inviso_as_lib making remote + autostart config file updates fail has been fixed. + Inviso: inviso_tool has been given a flush API.

+

+ Own Id: OTP-6918

+
+
+
+ +
+
Runtime_Tools 1.6.7 + +
Improvements and New Features + + +

+ The following bugs/improvements have been done: Internal + interworking between inviso_rt and inviso_rt_meta. The + call function used by inviso_rt to call inviso_rt_meta is + now protected with a monitor. Inviso_rt_meta now includes + the timestamp of the incoming meta trace message when + calling the call-callback. (Makes it possible to add a + "better" timestamp to the ti-file.) Bug in inviso_tool + making it not remove trace patterns when terminating. Bug + in internal function h_start_session making inviso_tool + crash if there were no active nodes to start the session + on. The user-inviso_tool and inviso API-inviso control + component request/response gen_server protocols had + default time-out. Since many trace operations can be time + consuming, a longer time-out is necessary. Improved + overload protection. It is now possible to let the + overload protection renew itself (e.g after an exit from + an external overload protector). Inviso_rt_meta now fully + uses the exception_trace match spec action term. Run + Trace Case API (as in contrast to activate and deactivate + trace case APIs) in inviso_tool. Flush trace-port API + added to inviso. Get_session_data API added to + inviso_tool. Improved inviso_tool:stop making it possible + to name nodes which shall not have their trace patterns + removed when inviso_tool terminates. Bug in handling of + writing multiple ti-entries if returned from a + call/return_from call-back in inviso_rt_meta Process + trace flags are no longer explicitly removed by the + inviso_tool when it terminates. Not necessary. + Inviso_tool get_autostart_data adopted to standard + autostarter.

+

+ *** INCOMPATIBILITY with Meta trace call-backs are called + with different arguments now. ***

+

+ Own Id: OTP-6881

+
+
+
+ +
+ +
+ Runtime_Tools 1.6.6 + +
+ Fixed Bugs and Malfunctions + + +

A bug in inviso_rt_meta caused an ETS table containing + information on initiated (init_tpm) functions to be lost + when suspending tracing. Further an enhancement to + inviso_rt has been introduced making it possible to + activate process trace flags based on globally registered + names. It is then not an error to activate a global name + on a node where the name does not reside. The process + count in the return value will simply be set to zero + (hence exactly one node in the NodeResult part of the + return value will indicate one matching process found). A + bug was found in fetch_log API. At the same time the + fetch_log functionality was enhanced to also offer flow + control making fetcher processes send chunks of + transferred file data at a slower pace.

+

Own Id: OTP-6703

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

Minor Makefile changes.

+

Own Id: OTP-6689 Aux Id: OTP-6742

+
+ +

An experimental tool called erts_alloc_config + has been added. erts_alloc_config is intended to + be used to aid creation of an erts_alloc + configuration that is suitable for a limited number of + runtime scenarios. For more information see the + erts_alloc_config(3) documentation.

+

Own Id: OTP-6700

+
+
+
+
+ +
+ Runtime_Tools 1.6.5 + +
+ Improvements and New Features + + +

Misc improvements.

+

Own Id: OTP-6576

+
+
+
+
+ +
+ Runtime_Tools 1.6.4 + +
+ Improvements and New Features + + +

This application has been updated to eliminate warnings + by Dialyzer.

+

Own Id: OTP-6551

+
+
+
+
+ +
+ Runtime_Tools 1.6.3 + +
+ Fixed Bugs and Malfunctions + + +

This ticket includes several improvements and bugfixes to + both runtime_tools and inviso. The overload mechanism can + now also react to incoming messages. This is useful if + an external overload watch-dog is used. Some improvements + of variable bindings has been done to the default + autostart mechanism - inviso_autostart_server. Autostart + "jobs" can now be done in parallel, allowing for some + jobs to hang waiting for some parts of the traced system + to become ready before proceeding. Previously when using + named meta-match-specs (tpm_ms) ending up with zero + match-specs still kept the meta trace pattern active. + This caused zero match-specs to be equal to unlimited + meta tracing on that particular function. If the internal + database becomes empty of meta match specs, meta trace + pattern is removed for that function. Standard public + loop data in the inviso runtime meta tracer process is + now extended to a 2-tuple. The functions ctp/1 and ctpl/1 + are added making it possible to remove trace patterns for + a list of functions rather than one by one. + Inviso_rt_meta will now accept a list of binaries to be + output into the trace information file, in additions to a + single binary. Further it is also possible to make own + output to the trace information file using the write_ti/1 + function. An error was discovered in inviso_rt making the + inviso_rt_meta remain rather than terminate if the + inviso_rt terminated due to "running alone" (not allowed + to run without a control component). A new tool, + inviso_tool, has been added to the inviso application.

+

Own Id: OTP-6426

+
+
+
+
+ +
+ Runtime_Tools 1.6.2 + +
+ Fixed Bugs and Malfunctions + + +

Several minor bugs and race conditions eliminated in the + runtime_tools and observer applications.

+

Own Id: OTP-6265

+
+
+
+
+ +
+ Runtime_Tools 1.6.1 + +
+ Improvements and New Features + + +

There are new BIFs erlang:spawn_monitor/1,3, + and the new option monitor for + spawn_opt/2,3,4,5.

+

The observer_backend module has been updated to + handle the new BIFs.

+

Own Id: OTP-6281

+
+
+
+
+ +
+ Runtime_Tools 1.6 +

Added the runtime part of the Inviso tracer, see the new Inviso + application for more information. This also meant adding an + application callback module and an application supervisor tree + for Runtime_Tools.

+
+ +
+ Runtime_Tools 1.5.1.1 + +
+ Improvements and New Features + + +

The dbg manual page has been updated with information + about how to avoid deadlock when tracing.

+

Own Id: OTP-5373 Aux Id: seq9729

+
+
+
+
+ +
+ Runtime_Tools 1.5.1 + +
+ Fixed Bugs and Malfunctions + + +

Linked in drivers in the Crypto, and Asn1 applications + are now compiled with the -D_THREAD_SAFE and + -D_REENTRANT switches on unix when the emulator has + thread support enabled.

+

Linked in drivers on MacOSX are not compiled with the + undocumented -lbundle1.o switch anymore. Thanks to + Sean Hinde who sent us a patch.

+

Linked in driver in Crypto, and port programs in SSL, now + compiles on OSF1.

+

Minor Makefile improvements in Runtime_Tools.

+

Own Id: OTP-5346

+
+
+
+
+
+ diff --git a/lib/runtime_tools/doc/src/notes_history.xml b/lib/runtime_tools/doc/src/notes_history.xml new file mode 100644 index 0000000000..587d935e0a --- /dev/null +++ b/lib/runtime_tools/doc/src/notes_history.xml @@ -0,0 +1,48 @@ + + + + +
+ + 2006 + 2007 + 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. + + The Initial Developer of the Original Code is Ericsson AB. + + + Runtime_Tools Release Notes History + + + + +
+ +
+ Runtime_Tools 1.5 + +
+ Fixed Bugs and Malfunctions + + +

Trace IP port with port number 0 can now be opened on + 64-bit.

+

Own Id: OTP-5023

+
+
+
+
+
+ diff --git a/lib/runtime_tools/doc/src/part_notes.xml b/lib/runtime_tools/doc/src/part_notes.xml new file mode 100644 index 0000000000..6f7f610da0 --- /dev/null +++ b/lib/runtime_tools/doc/src/part_notes.xml @@ -0,0 +1,38 @@ + + + + +
+ + 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. + + + + Runtime_Tools Release Notes + + + + +
+ +

Runtime_Tools provides low footprint tracing/debugging + tools suitable for inclusion in a production system.

+

For information about older versions, see + Release Notes History.

+
+ +
+ diff --git a/lib/runtime_tools/doc/src/part_notes_history.xml b/lib/runtime_tools/doc/src/part_notes_history.xml new file mode 100644 index 0000000000..cdd727780c --- /dev/null +++ b/lib/runtime_tools/doc/src/part_notes_history.xml @@ -0,0 +1,38 @@ + + + + +
+ + 2006 + 2007 + 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. + + The Initial Developer of the Original Code is Ericsson AB. + + + Runtime_Tools Release Notes History + + + + +
+ +

Runtime_Tools provides low footprint tracing/debugging + tools suitable for inclusion in a production system.

+
+ +
+ diff --git a/lib/runtime_tools/doc/src/ref_man.xml b/lib/runtime_tools/doc/src/ref_man.xml new file mode 100644 index 0000000000..579efcd969 --- /dev/null +++ b/lib/runtime_tools/doc/src/ref_man.xml @@ -0,0 +1,38 @@ + + + + +
+ + 19992009 + 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. + + + + Runtime_Tools Reference Manual + Patrik Nyblom + + 1999-05-27 + A +
+ +

Runtime_Tools provides low footprint tracing/debugging + tools suitable for inclusion in a production system.

+
+ + + +
+ diff --git a/lib/runtime_tools/doc/src/runtime_tools_app.xml b/lib/runtime_tools/doc/src/runtime_tools_app.xml new file mode 100644 index 0000000000..e31c8cb5f7 --- /dev/null +++ b/lib/runtime_tools/doc/src/runtime_tools_app.xml @@ -0,0 +1,56 @@ + + + + +
+ + 1999 + 2007 + 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. + + The Initial Developer of the Original Code is Ericsson AB. + + + runtime_tools + Patrik Nyblom + Patrik Nyblom + + + + 1999-05-27 + PA1 + runtime_tools_app.sgml +
+ runtime_tools + The Runtime tools Application + +

This chapter describes the Runtime_Tools application in OTP, + which provides low footprint tracing/debugging tools suitable + for inclusion in a production system.

+
+ +
+ Configuration +

There are currently no configuration parameters available for + this application. +

+
+ +
+ SEE ALSO +

application(3)

+
+
+ diff --git a/lib/runtime_tools/doc/src/warning.gif b/lib/runtime_tools/doc/src/warning.gif new file mode 100644 index 0000000000..96af52360e Binary files /dev/null and b/lib/runtime_tools/doc/src/warning.gif differ diff --git a/lib/runtime_tools/ebin/.gitignore b/lib/runtime_tools/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/include/observer_backend.hrl b/lib/runtime_tools/include/observer_backend.hrl new file mode 100644 index 0000000000..4be9baca5b --- /dev/null +++ b/lib/runtime_tools/include/observer_backend.hrl @@ -0,0 +1,42 @@ +%% ``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 via the world wide web 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. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id$ +%% + +-record(etop_info, + {now = {0, 0, 0}, + n_procs = 0, + wall_clock = {0, 0}, + runtime = {0, 0}, + run_queue = 0, + alloc_areas = [], + memi = [{total, 0}, + {processes, 0}, + {ets, 0}, + {atom, 0}, + {code, 0}, + {binary, 0}], + procinfo = [] + }). + +-record(etop_proc_info, + {pid, + mem=0, + reds=0, + name, + runtime=0, + cf, + mq=0}). diff --git a/lib/runtime_tools/info b/lib/runtime_tools/info new file mode 100644 index 0000000000..87f01602c6 --- /dev/null +++ b/lib/runtime_tools/info @@ -0,0 +1,2 @@ +group: tools +short: Runtime tools, tools to include in a production system diff --git a/lib/runtime_tools/priv/.gitignore b/lib/runtime_tools/priv/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/runtime_tools/src/Makefile b/lib/runtime_tools/src/Makefile new file mode 100644 index 0000000000..4f831f3dd8 --- /dev/null +++ b/lib/runtime_tools/src/Makefile @@ -0,0 +1,109 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-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=$(RUNTIME_TOOLS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN) + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +MODULES= \ + erts_alloc_config \ + inviso_rt \ + inviso_rt_meta \ + inviso_rt_lib \ + inviso_as_lib \ + inviso_autostart \ + inviso_autostart_server \ + runtime_tools \ + runtime_tools_sup \ + dbg \ + percept_profile \ + observer_backend +HRL_FILES= ../include/observer_backend.hrl + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +APP_FILE= runtime_tools.app + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) + +APPUP_FILE= runtime_tools.appup + +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -I../include + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f errs core *~ + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + + + + + + + diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl new file mode 100644 index 0000000000..66ac0422eb --- /dev/null +++ b/lib/runtime_tools/src/dbg.erl @@ -0,0 +1,1749 @@ +%% +%% %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(dbg). +-export([p/1,p/2,c/3,c/4,i/0,start/0,stop/0,stop_clear/0,tracer/0, + tracer/2, tracer/3, get_tracer/0, get_tracer/1, tp/2, tp/3, tp/4, + ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4, ctpl/0, ctpl/1, + ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3, ltp/0, wtp/1, rtp/1, + dtp/0, dtp/1, n/1, cn/1, ln/0, h/0, h/1]). + +-export([trace_port/2, flush_trace_port/0, flush_trace_port/1, + trace_port_control/1, trace_port_control/2, trace_client/2, + trace_client/3, stop_trace_client/1]). + +-export([transform_flags/1,dhandler/2]). + +-export([fun2ms/1]). + +%% Local exports +-export([erlang_trace/3,get_info/0]). + +%% Debug exports +-export([wrap_presort/2, wrap_sort/2, wrap_postsort/1, wrap_sortfix/2, + match_front/2, match_rear/2, + match_0_9/1]). + + +%%% Shell callable utility +fun2ms(ShellFun) when is_function(ShellFun) -> + % Check that this is really a shell fun... + case erl_eval:fun_data(ShellFun) of + {fun_data,ImportList,Clauses} -> + case ms_transform:transform_from_shell( + ?MODULE,Clauses,ImportList) of + {error,[{_,[{_,_,Code}|_]}|_],_} -> + io:format("Error: ~s~n", + [ms_transform:format_error(Code)]), + {error,transform_error}; + Else -> + Else + end; + false -> + exit({badarg,{?MODULE,fun2ms, + [function,called,with,real,'fun', + should,be,transformed,with, + parse_transform,'or',called,with, + a,'fun',generated,in,the, + shell]}}) + end. + + +%%% Client functions. + +%% +%% n(Node) -> {ok, Node} | {error, Reason} +%% Adds Node to the list of traced nodes. +%% +n(Node) when Node =:= node() -> + {error, cant_add_local_node}; +n(Node) -> + case (catch net_adm:ping(Node)) of + {'EXIT',_} -> + {error, {bad_node, Node}}; + pang -> + {error, {nodedown, Node}}; + pong -> + req({add_node, Node}); + Other -> + {error, Other} + end. + +%% +%% cn(Node) -> ok +%% Remove Node from the list of traced nodes. +%% +cn(Node) -> + req({remove_node, Node}). + +%% +%% ln() -> ok +%% List traced nodes +%% +ln() -> + lists:foreach(fun(X) -> + io:format("~p~n",[X]) + end, + req(get_nodes)), + ok. + +%% +%% tp/tpl(Module, Pattern) | tp/tpl(Module,Function,Pattern) | +%% tp/tpl(Module,Function,Arity,Pattern) | tp/tpl({M,F,A},Pattern) +%% -> {ok, [{matched,Node,N}]} | +%% {ok, [{matched,Node,N}, {saved,M}]} | +%% {ok, [{saved,M}]} | +%% {error, Reason} +%% Set trace pattern for function or group of functions. +%% +tp(Module, Function, Pattern) -> + do_tp({Module, Function, '_'}, Pattern, []). +tp(Module, Function, Arity, Pattern) -> + do_tp({Module, Function, Arity}, Pattern, []). +tp(Module, Pattern) when is_atom(Module) -> + do_tp({Module, '_', '_'}, Pattern, []); +tp({_Module, _Function, _Arity} = X, Pattern) -> + do_tp(X,Pattern,[]). +tpl(Module, Function, Pattern) -> + do_tp({Module, Function, '_'}, Pattern, [local]). +tpl(Module, Function, Arity, Pattern) -> + do_tp({Module, Function, Arity}, Pattern, [local]). +tpl(Module, Pattern) when is_atom(Module) -> + do_tp({Module, '_', '_'}, Pattern, [local]); +tpl({_Module, _Function, _Arity} = X, Pattern) -> + do_tp(X,Pattern,[local]). +do_tp({_Module, _Function, _Arity} = X, Pattern, Flags) + when is_integer(Pattern); + is_atom(Pattern) -> + case ets:lookup(get_pattern_table(), Pattern) of + [{_,NPattern}] -> + do_tp(X, binary_to_term(NPattern), Flags); + _ -> + {error, unknown_pattern} + end; +do_tp({Module, _Function, _Arity} = X, Pattern, Flags) when is_list(Pattern) -> + Nodes = req(get_nodes), + case Module of + '_' -> + ok; + M when is_atom(M) -> + %% Try to load M on all nodes + lists:foreach(fun(Node) -> + rpc:call(Node, M, module_info, []) + end, + Nodes) + end, + case lint_tp(Pattern) of + {ok,_} -> + SaveInfo = case save_pattern(Pattern) of + N when is_integer(N), N > 0; is_atom(N) -> + [{saved, N}]; + _ -> + [] + end, + {ok, do_tp_on_nodes(Nodes, X, Pattern, Flags) ++ SaveInfo}; + Other -> + Other + end. + +%% All nodes are handled the same way - also the local node if it is traced +do_tp_on_nodes(Nodes, MFA, P, Flags) -> + lists:map(fun(Node) -> + case rpc:call(Node,erlang,trace_pattern,[MFA,P, Flags]) of + N when is_integer(N) -> + {matched, Node, N}; + Else -> + {matched, Node, 0, Else} + end + end, + Nodes). + +%% +%% ctp/ctpl(Module) | ctp/ctpl(Module,Function) | +%% ctp/ctpl(Module,Function,Arity) | ctp/ctpl({M,F,A}) -> +%% {ok, [{matched, N}]} | {error, Reason} +%% Clears trace pattern for function or group of functions. +%% +ctp() -> + do_ctp({'_','_','_'},[]). +ctp(Module, Function) -> + do_ctp({Module, Function, '_'}, []). +ctp(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, []). +ctp(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, []); +ctp({_Module, _Function, _Arity} = X) -> + do_ctp(X,[]). +ctpl() -> + do_ctp({'_', '_', '_'}, [local]). +ctpl(Module, Function) -> + do_ctp({Module, Function, '_'}, [local]). +ctpl(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, [local]). +ctpl(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, [local]); +ctpl({_Module, _Function, _Arity} = X) -> + do_ctp(X,[local]). +ctpg() -> + do_ctp({'_', '_', '_'}, [global]). +ctpg(Module, Function) -> + do_ctp({Module, Function, '_'}, [global]). +ctpg(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, [global]). +ctpg(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, [global]); +ctpg({_Module, _Function, _Arity} = X) -> + do_ctp(X,[global]). +do_ctp({Module, Function, Arity},[]) -> + do_ctp({Module, Function, Arity},[global]), + do_ctp({Module, Function, Arity},[local]); +do_ctp({_Module, _Function, _Arity}=MFA,Flags) -> + Nodes = req(get_nodes), + {ok,do_tp_on_nodes(Nodes,MFA,false,Flags)}. + +%% +%% ltp() -> ok +%% List saved and built-in trace patterns. +%% +ltp() -> + pt_doforall(fun({X, El},_Ignore) -> + io:format("~p: ~p~n", [X,El]) + end,[]). + +%% +%% dtp() | dtp(N) -> ok +%% Delete saved pattern with number N or all saved patterns +%% +%% Do not delete built-in trace patterns. +dtp() -> + pt_doforall(fun ({Key, _}, _) when is_integer(Key) -> + dtp(Key); + ({_, _}, _) -> + ok + end, + []). +dtp(N) when is_integer(N) -> + ets:delete(get_pattern_table(), N), + ok; +dtp(_) -> + ok. + +%% +%% wtp(FileName) -> ok | {error, Reason} +%% Writes all current saved trace patterns to a file. +%% +%% Actually write the built-in trace patterns too. +wtp(FileName) -> + case file:open(FileName,[write]) of + {error, Reason} -> + {error, Reason}; + {ok, File} -> + pt_doforall(fun ({_, Val}, _) when is_list(Val) -> + io:format(File, "~p.~n", [Val]); + ({_, _}, _) -> + ok + end, + []), + file:close(File), + ok + end. + +%% +%% rtp(FileName) -> ok | {error, Reason} +%% Reads in previously saved pattern file and merges the contents +%% with what's there now. +%% +%% So the saved built-in trace patterns will merge with +%% the already existing, which should be the same. +rtp(FileName) -> + T = get_pattern_table(), + case file:consult(FileName) of + {error, Reason1} -> + {error, {read_error, Reason1}}; + {ok, Data} -> + case check_list(Data) of + ok -> + lists:foreach(fun(X) -> + save_pattern(X,T) + end, Data), + ok; + {error, Reason2} -> + {error, {file_format_error, Reason2}} + end + end. + +tracer() -> + tracer(process, {fun dhandler/2,user}). + +tracer(port, Fun) when is_function(Fun) -> + start(Fun); + +tracer(port, Port) when is_port(Port) -> + start(fun() -> Port end); + +tracer(process, {Handler,HandlerData}) -> + start(fun() -> start_tracer_process(Handler, HandlerData) end). + + +remote_tracer(port, Fun) when is_function(Fun) -> + remote_start(Fun); + +remote_tracer(port, Port) when is_port(Port) -> + remote_start(fun() -> Port end); + +remote_tracer(process, {Handler,HandlerData}) -> + remote_start(fun() -> start_tracer_process(Handler, HandlerData) end). + +remote_start(StartTracer) -> + case (catch StartTracer()) of + {'EXIT', Reason} -> + {error, Reason}; + Tracer -> + {ok,Tracer} + end. + +%% +%% tracer(Node,Type,Data) -> {ok, Node} | {error, Reason} +%% Add Node to the list of traced nodes and a trace port defined by +%% Type and Data is started on Node. +%% +tracer(Node,Type,Data) when Node =:= node() -> + case tracer(Type,Data) of + {ok,_Dbg} -> {ok,Node}; + Error -> Error + end; +tracer(Node,Type,Data) -> + case (catch net_adm:ping(Node)) of + {'EXIT',_} -> + {error, {bad_node, Node}}; + pang -> + {error, {nodedown, Node}}; + pong -> + req({add_node, Node, Type, Data}); + Other -> + {error, Other} + end. + +flush_trace_port() -> + trace_port_control(flush). +flush_trace_port(Node) -> + trace_port_control(Node, flush). + +trace_port_control(Operation) -> + trace_port_control(node(), Operation). + +trace_port_control(Node, flush) -> + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> ok + end, + case trace_port_control(Node, $f, "") of + {ok, [0]} -> + ok; + {ok, _} -> + {error, not_supported_by_trace_driver}; + Other -> + Other + end; +trace_port_control(Node,get_listen_port) -> + case trace_port_control(Node,$p, "") of + {ok, <<0, IpPort:16>>} -> + {ok, IpPort}; + {ok, _Other} -> + {error, not_supported_by_trace_driver}; + Other -> + Other + end. + +trace_port_control(Node, Command, Arg) -> + case get_tracer(Node) of + {ok, Port} when is_port(Port) -> + {ok, catch rpc:call(Node,erlang,port_control,[Port, Command, Arg])}; + _ -> + {error, no_trace_driver} + end. + + + + +trace_port(file, {Filename, wrap, Tail}) -> + trace_port(file, {Filename, wrap, Tail, 128*1024}); +trace_port(file, {Filename, wrap, Tail, WrapSize}) -> + trace_port(file, {Filename, wrap, Tail, WrapSize, 8}); +trace_port(file, {Filename, wrap, Tail, WrapSize, WrapCnt}) + when is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, WrapSize, WrapCnt, 0}); +trace_port(file, {Filename, wrap, Tail, {time, WrapTime}, WrapCnt}) + when is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, 0, WrapCnt, WrapTime}); +trace_port(file, Filename) -> + trace_port1(file, Filename, nowrap); + +trace_port(ip, Portno) when is_integer(Portno) -> + trace_port(ip,{Portno,50}); + +trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) -> + fun() -> + Driver = "trace_ip_drv", + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + L = lists:flatten( + io_lib:format("~s ~p ~p 2", + [Driver, Portno, Qsiz])), + open_port({spawn, L}, [eof]) + end. + +trace_port1(file, Filename, Options) -> + Driver = "trace_file_drv", + fun() -> + Name = filename:absname(Filename), + %% Absname is needed since the driver uses + %% the supplied name without further investigations, + %% and if the name is relative the resulting path + %% might be too long which can cause a bus error + %% on vxworks instead of a nice error code return. + %% Also, the absname must be found inside the fun, + %% in case the actual node where the port shall be + %% started is on another node (or even another host) + {Wrap, Tail} = + case Options of + {wrap, T, WrapSize, WrapCnt, WrapTime} -> + {lists:flatten( + io_lib:format("w ~p ~p ~p ~p ", + [WrapSize, WrapCnt, WrapTime, + length(Name)])), + T}; + nowrap -> + {"", ""} + end, + Command = Driver ++ " " ++ Wrap ++ "n " ++ Name ++ Tail, + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + if element(1, Options) == wrap -> + %% Delete all files from any previous wrap log + Files = wrap_postsort(wrap_presort(Name, Tail)), + lists:foreach( + fun(N) -> file:delete(N) end, + Files); + true -> ok + end, + open_port({spawn, Command}, [eof]) + end. + + +trace_client(file, Filename) -> + trace_client(file, Filename, {fun dhandler/2,user}); +trace_client(follow_file, Filename) -> + trace_client(follow_file, Filename, {fun dhandler/2,user}); +trace_client(ip, Portno) when is_integer(Portno) -> + trace_client1(ip, {"localhost", Portno}, {fun dhandler/2,user}); +trace_client(ip, {Host, Portno}) when is_integer(Portno) -> + trace_client1(ip, {Host, Portno}, {fun dhandler/2,user}). + +trace_client(file, {Filename, wrap, Tail}, FD) -> + trace_client(file, {Filename, wrap, Tail, 128*1024}, FD); +trace_client(file, {Filename, wrap, Tail, WrapSize}, FD) -> + trace_client(file, {Filename, wrap, Tail, WrapSize, 8}, FD); +trace_client(file, + {_Filename, wrap, Tail, _WrapSize, WrapCnt} = WrapSpec, + {Fun, _Data} = FD) + when is_list(Tail), is_function(Fun), is_integer(WrapCnt), WrapCnt >= 1 -> + trace_client1(file, WrapSpec, FD); +trace_client(file, Filename, {Fun, Data} ) when is_function(Fun) -> + trace_client1(file, Filename, {Fun, Data}); +trace_client(follow_file, Filename, {Fun, Data} ) when is_function(Fun) -> + trace_client1(follow_file, Filename, {Fun, Data}); +trace_client(ip, Portno, {Fun, Data}) when is_integer(Portno), is_function(Fun) -> + trace_client1(ip, {"localhost", Portno}, {Fun, Data}); +trace_client(ip, {Host, Portno}, {Fun, Data}) when is_integer(Portno), + is_function(Fun) -> + trace_client1(ip, {Host, Portno}, {Fun, Data}). + +trace_client1(Type, OpenData, {Handler,HData}) -> + case req({link_to, + spawn( + fun() -> + tc_loop(gen_reader(Type, OpenData), Handler, HData) + end)}) of + {ok, Pid} -> + Pid; + Other -> + Other + end. + +stop_trace_client(Pid) when is_pid(Pid) -> + process_flag(trap_exit,true), + link(Pid), + exit(to_pidspec(Pid),abnormal), + Res = receive + {'EXIT', Pid, _} -> + ok + after 5000 -> + {error, timeout} + end, + process_flag(trap_exit,false), + Res. + +p(Pid) -> + p(Pid, [m]). + +p(Pid, Flags) when is_atom(Flags) -> + p(Pid, [Flags]); +p(Pid, Flags) -> + req({p,Pid,Flags}). + +i() -> req(i). + +c(M, F, A) -> + c(M, F, A, all). +c(M, F, A, Flags) when is_atom(Flags) -> + c(M, F, A, [Flags]); +c(M, F, A, Flags) -> + case transform_flags(Flags) of + {error,Reason} -> {error,Reason}; + Flags1 -> + tracer(), + {ok, Tracer} = get_tracer(), + S = self(), + Pid = spawn(fun() -> c(S, M, F, A, [{tracer, Tracer} | Flags1]) end), + Mref = erlang:monitor(process, Pid), + receive + {'DOWN', Mref, _, _, Reason} -> + stop_clear(), + {error, Reason}; + {Pid, Res} -> + erlang:demonitor(Mref), + receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end, + %% 'sleep' prevents the tracer (recv_all_traces) from + %% receiving garbage {'EXIT',...} when dbg i stopped. + timer:sleep(1), + stop_clear(), + Res + end + end. + +c(Parent, M, F, A, Flags) -> + %% The trace BIF is used directly here instead of the existing function + %% p/2. The reason is that p/2 (when stopping trace) sends messages which + %% we don't want to show up in this simple tracing from the shell. + erlang:trace(self(), true, Flags), + Res = apply(M, F, A), + erlang:trace(self(), false, [all]), + Parent ! {self(), Res}. + +stop() -> + Mref = erlang:monitor(process, dbg), + catch dbg ! {self(),stop}, + receive + {'DOWN',Mref,_,_,_} -> + ok + end. + +stop_clear() -> + ctp(), + stop(). + +%%% Calling the server. + +req(R) -> + P = ensure(), % The pid or perhaps the name of the server + Mref = erlang:monitor(process, P), + catch P ! {self(), R}, % May crash if P = atom() and server died + receive + {'DOWN', Mref, _, _, _} -> % If server died + exit(dbg_server_crash); + {dbg, Reply} -> + erlang:demonitor(Mref), + receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end, + Reply + end. + +%% Returns the pid of the dbg server, or in worst case the name. +%% Starts a new server if necessary. +ensure() -> + case whereis(dbg) of + undefined -> + case start() of + {ok, P} -> + P; + {error, already_started} -> + dbg + end; + Pid -> + Pid + end. + + +%%% Server implementation. +start() -> + start(no_tracer). + +start(TracerFun) -> + S = self(), + case whereis(dbg) of + undefined -> + Dbg = spawn(fun() -> init(S) end), + receive {Dbg,started} -> ok end, + case TracerFun of + no_tracer -> + {ok, Dbg}; + Fun when is_function(Fun) -> + req({tracer,TracerFun}) + end; + Pid when is_pid(Pid), is_function(TracerFun) -> + req({tracer,TracerFun}) + end. + +init(Parent) -> + process_flag(trap_exit, true), + register(dbg, self()), + Parent ! {self(),started}, + loop({[],[]},[]). + +% +% SurviveLinks = Processes we should take with us while falling, +% but not get killed by if they die (i. e. trace clients +% and relay processes on other nodes) +% SurviveLinks = {TraceClients,Relays} +% +loop({C,T}=SurviveLinks, Table) -> + receive + {From,i} -> + reply(From, display_info(lists:map(fun({N,_}) -> N end,get()))), + loop(SurviveLinks, Table); + {From,{p,Pid,Flags}} -> + reply(From, trace_process(Pid, Flags)), + loop(SurviveLinks, Table); + {From,{tracer,TracerFun}} when is_function(TracerFun) -> + case get(node()) of + undefined -> + case (catch TracerFun()) of + {'EXIT', Reason} -> + reply(From, {error, Reason}); + Tracer when is_pid(Tracer); is_port(Tracer) -> + put(node(),{self(),Tracer}), + reply(From, {ok,self()}) + end; + {_Relay,_Tracer} -> + reply(From, {error, already_started}) + end, + loop(SurviveLinks,Table); + {From,{get_tracer,Node}} -> + case get(Node) of + undefined -> reply(From,{error, {no_tracer_on_node,Node}}); + {_Relay,Tracer} -> reply(From, {ok,Tracer}) + end, + loop(SurviveLinks, Table); + {From, get_table} -> + Tab = case Table of + [] -> + new_pattern_table(); + _exists -> + Table + end, + reply(From, {ok, Tab}), + loop(SurviveLinks, Tab); + {_From,stop} -> + %% We want to make sure that all trace messages have been delivered + %% on all nodes that might be traced. Since dbg:cn/1 does not turn off + %% tracing on the node it removes from the list of active trace nodes, + %% we will call erlang:trace_delivered/1 on ALL nodes that we have + %% connections to. + Delivered = fun() -> + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> ok + end + end, + catch rpc:multicall(nodes(), erlang, apply, [Delivered,[]]), + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> + exit(done) + end; + {From, {link_to, Pid}} -> + case (catch link(Pid)) of + {'EXIT', Reason} -> + reply(From, {error, Reason}), + loop(SurviveLinks, Table); + _ -> + reply(From, {ok, Pid}), + loop({[Pid|C],T}, Table) + end; + {From, {add_node, Node}} -> + case get(node()) of + undefined -> + reply(From, {error, no_local_tracer}), + loop(SurviveLinks, Table); + {_LocalRelay,Tracer} when is_port(Tracer) -> + reply(From, {error, cant_trace_remote_pid_to_local_port}), + loop(SurviveLinks, Table); + {_LocalRelay,Tracer} when is_pid(Tracer) -> + case (catch relay(Node, Tracer)) of + {ok,Relay} -> + reply(From, {ok, Node}), + loop({C,[Relay|T]}, Table); + {'EXIT', Something} -> + reply(From, {error, Something}), + loop(SurviveLinks, Table); + Error -> + reply(From, Error), + loop(SurviveLinks, Table) + end + end; + {From, {add_node, Node, Type, Data}} -> + case (catch relay(Node, {Type,Data})) of + {ok,Relay} -> + reply(From, {ok, Node}), + loop({C,[Relay|T]}, Table); + {'EXIT', Something} -> + reply(From, {error, Something}), + loop(SurviveLinks, Table); + Error -> + reply(From, Error), + loop(SurviveLinks, Table) + end; + {From, {remove_node, Node}} -> + erase(Node), + reply(From, ok), + loop(SurviveLinks, Table); + {From, get_nodes} -> + reply(From, lists:map(fun({N,_}) -> N end, get())), + loop(SurviveLinks, Table); + {'EXIT', Pid, Reason} -> + case lists:delete(Pid, C) of + C -> + case lists:delete(Pid,T) of + T -> + io:format(user,"** dbg got EXIT - terminating: ~p~n", + [Reason]), + exit(done); + NewT -> + erase(node(Pid)), + loop({C,NewT}, Table) + end; + NewC -> + loop({NewC,T}, Table) + end; + Other -> + io:format(user,"** dbg got garbage: ~p~n", + [{Other,SurviveLinks,Table}]), + loop(SurviveLinks, Table) + end. + +reply(Pid, Reply) -> + Pid ! {dbg,Reply}. + + +%%% A process-based tracer. + +start_tracer_process(Handler, HandlerData) -> + spawn_opt(fun() -> tracer_init(Handler, HandlerData) end, + [link,{priority,max}]). + + +tracer_init(Handler, HandlerData) -> + process_flag(trap_exit, true), + tracer_loop(Handler, HandlerData). + +tracer_loop(Handler, Hdata) -> + receive + Msg -> + %% Don't match in receive to avoid giving EXIT message higher + %% priority than the trace messages. + case Msg of + {'EXIT',_Pid,_Reason} -> + ok; + Trace -> + NewData = recv_all_traces(Trace, Handler, Hdata), + tracer_loop(Handler, NewData) + end + end. + +recv_all_traces(Trace, Handler, Hdata) -> + Suspended = suspend(Trace, []), + recv_all_traces(Suspended, Handler, Hdata, [Trace]). + +recv_all_traces(Suspended0, Handler, Hdata, Traces) -> + receive + Trace when is_tuple(Trace), element(1, Trace) == trace -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == trace_ts -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == seq_trace -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == drop -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Other -> + %%% Is this really a good idea? + io:format(user,"** tracer received garbage: ~p~n", [Other]), + recv_all_traces(Suspended0, Handler, Hdata, Traces) + after 0 -> + case catch invoke_handler(Traces, Handler, Hdata) of + {'EXIT',Reason} -> + resume(Suspended0), + exit({trace_handler_crashed,Reason}); + NewHdata -> + resume(Suspended0), + NewHdata + end + end. + +invoke_handler([Tr|Traces], Handler, Hdata0) -> + Hdata = invoke_handler(Traces, Handler, Hdata0), + Handler(Tr, Hdata); +invoke_handler([], _Handler, Hdata) -> + Hdata. + +suspend({trace,From,call,_Func}, Suspended) when node(From) == node() -> + case (catch erlang:suspend_process(From, [unless_suspending, + asynchronous])) of + true -> + [From | Suspended]; + _ -> + Suspended + end; +suspend(_Other, Suspended) -> Suspended. + +resume([Pid|Pids]) when node(Pid) == node() -> + (catch erlang:resume_process(Pid)), + resume(Pids); +resume([]) -> ok. + + + +%%% Utilities. + +trac(Proc, How, Flags) when is_atom(Proc) -> + %% Proc = all | new | existing | RegisteredName + %% Must go to all nodes + case get() of + [] -> + {error,no_tracers}; + Nodes -> + Matched = [trac(Node, NodeInfo, Proc, How, Flags) + || {Node, NodeInfo} <- Nodes], + {ok,Matched} + end; +trac(Proc, How, Flags) -> + %% Proc = Pid | Integer | {X,Y,Z} | "" + %% One node only + Pid = to_pid(Proc), + case Pid of + {badpid,_} -> + {error,Pid}; + _ -> + Node = if is_pid(Pid) -> node(Pid); true -> node() end, + case get(Node) of + undefined -> + {error,{no_tracer_on_node,Node}}; + NodeInfo -> + Match = trac(Node, NodeInfo, Pid, How, Flags), + {ok,[Match]} + end + end. + +trac(Node, {_Relay, Tracer}, AtomPid, How, Flags) -> + case rpc:call(Node, ?MODULE, erlang_trace, + [AtomPid, How, [{tracer, Tracer} | Flags]]) of + N when is_integer(N) -> + {matched, Node, N}; + {badrpc,Reason} -> + {matched, Node, 0, Reason}; + Else -> + {matched, Node, 0, Else} + end. + +erlang_trace(AtomPid, How, Flags) -> + case to_pidspec(AtomPid) of + {badpid,_} -> + {no_proc,AtomPid}; + P -> + erlang:trace(P, How, Flags) + end. + +%% Since we are not allowed to do erlang:trace/3 on a remote +%% process, we create a relay process at the remote node. + +relay(Node,To) when Node /= node() -> + case get(Node) of + undefined -> + S = self(), + Pid = spawn_link(Node, fun() -> do_relay(S,To) end), + receive {started,Remote} -> put(Node, {Pid,Remote}) end, + {ok,Pid}; + {_Relay,PortOrPid} -> + {error, {already_started, PortOrPid}} + end. + +do_relay(Parent,RelP) -> + process_flag(trap_exit, true), + case RelP of + {Type,Data} -> + {ok,Tracer} = remote_tracer(Type,Data), + Parent ! {started,Tracer}; + Pid when is_pid(Pid) -> + Parent ! {started,self()} + end, + do_relay_1(RelP). + +do_relay_1(RelP) -> + %% In the case of a port tracer, this process exists only so that + %% dbg know that the node is alive... should maybe use monitor instead? + receive + {'EXIT', _P, _} -> + exit(normal); + TraceInfo when is_pid(RelP) -> % Here is the normal case for trace i/o + RelP ! TraceInfo, + do_relay_1(RelP); + Other -> + io:format(user,"** relay got garbage: ~p~n", [Other]), + do_relay_1(RelP) + end. + +dhandler(end_of_trace, Out) -> + Out; +dhandler(Trace, Out) when element(1, Trace) == trace, tuple_size(Trace) >= 3 -> + dhandler1(Trace, tuple_size(Trace), Out); +dhandler(Trace, Out) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> + dhandler1(Trace, tuple_size(Trace)-1, Out); +dhandler(Trace, Out) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 -> + io:format(Out, "*** Dropped ~p messages.~n", [element(2,Trace)]), + Out; +dhandler(Trace, Out) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 -> + SeqTraceInfo = case Trace of + {seq_trace, Lbl, STI, TS} -> + io:format(Out, "SeqTrace ~p [~p]: ", + [TS, Lbl]), + STI; + {seq_trace, Lbl, STI} -> + io:format(Out, "SeqTrace [~p]: ", + [Lbl]), + STI + end, + case SeqTraceInfo of + {send, Ser, Fr, To, Mes} -> + io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n", + [Fr, To, Mes, Ser]); + {'receive', Ser, Fr, To, Mes} -> + io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n", + [To, Mes, Ser, Fr]); + {print, Ser, Fr, _, Info} -> + io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n", + [Info, Ser, Fr]); + Else -> + io:format(Out, "~p~n", [Else]) + end, + Out; +dhandler(_Trace, Out) -> + Out. + +dhandler1(Trace, Size, Out) -> +%%%! Self = self(), + From = element(2, Trace), + case element(3, Trace) of + 'receive' -> + case element(4, Trace) of + {dbg,ok} -> ok; + Message -> io:format(Out, "(~p) << ~p~n", [From,Message]) + end; + 'send' -> + Message = element(4, Trace), + case element(5, Trace) of +%%%! This causes messages to disappear when used by ttb (observer). Tests +%%%! so far show that there is no difference in results with dbg even if I +%%%! comment it out, so I hope this is only some old code which isn't +%%%! needed anymore... /siri +%%%! Self -> ok; + To -> io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message]) + end; + call -> + case element(4, Trace) of + MFA when Size == 5 -> + Message = element(5, Trace), + io:format(Out, "(~p) call ~s (~p)~n", [From,ffunc(MFA),Message]); + MFA -> + io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)]) + end; + return -> %% To be deleted... + case element(4, Trace) of + MFA when Size == 5 -> + Ret = element(5, Trace), + io:format(Out, "(~p) old_ret ~s -> ~p~n", [From,ffunc(MFA),Ret]); + MFA -> + io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)]) + end; + return_from -> + MFA = element(4, Trace), + Ret = element(5, Trace), + io:format(Out, "(~p) returned from ~s -> ~p~n", [From,ffunc(MFA),Ret]); + return_to -> + MFA = element(4, Trace), + io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]); + spawn when Size == 5 -> + Pid = element(4, Trace), + MFA = element(5, Trace), + io:format(Out, "(~p) spawn ~p as ~s~n", [From,Pid,ffunc(MFA)]); + Op -> + io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)]) + end, + Out. + + + +%%% These f* functions returns non-flat strings + +%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" +%% {M,F,A} -> "M:F/A" +ffunc({M,F,Argl}) when is_list(Argl) -> + io_lib:format("~p:~p(~s)", [M, F, fargs(Argl)]); +ffunc({M,F,Arity}) -> + io_lib:format("~p:~p/~p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~p", [X]). + +%% Integer -> "Integer" +%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" +fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); +fargs([]) -> []; +fargs([A]) -> io_lib:format("~p", [A]); %% last arg +fargs([A|Args]) -> [io_lib:format("~p,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~p", [A]). % last or only arg + +%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" +ftup(Trace, Index, Index) -> + io_lib:format("~p", [element(Index, Trace)]); +ftup(Trace, Index, Size) -> + [io_lib:format("~p ", [element(Index, Trace)]) + | ftup(Trace, Index+1, Size)]. + + + +trace_process(Pid, [clear]) -> + trac(Pid, false, all()); +trace_process(Pid, Flags0) -> + case transform_flags(Flags0) of + {error,Reason} -> {error,Reason}; + Flags -> trac(Pid, true, Flags) + end. + +transform_flags(Flags0) -> + transform_flags(Flags0,[]). +transform_flags([],Acc) -> Acc; +transform_flags([m|Tail],Acc) -> transform_flags(Tail,[send,'receive'|Acc]); +transform_flags([s|Tail],Acc) -> transform_flags(Tail,[send|Acc]); +transform_flags([r|Tail],Acc) -> transform_flags(Tail,['receive'|Acc]); +transform_flags([c|Tail],Acc) -> transform_flags(Tail,[call|Acc]); +transform_flags([call|Tail],Acc) -> transform_flags(Tail,[call|Acc]); +transform_flags([p|Tail],Acc) -> transform_flags(Tail,[procs|Acc]); +transform_flags([sos|Tail],Acc) -> transform_flags(Tail,[set_on_spawn|Acc]); +transform_flags([sol|Tail],Acc) -> transform_flags(Tail,[set_on_link|Acc]); +transform_flags([sofs|Tail],Acc) -> transform_flags(Tail,[set_on_first_spawn|Acc]); +transform_flags([sofl|Tail],Acc) -> transform_flags(Tail,[set_on_first_link|Acc]); +transform_flags([all|_],_Acc) -> all(); +transform_flags([F|Tail]=List,Acc) when is_atom(F) -> + case lists:member(F, all()) of + true -> transform_flags(Tail,[F|Acc]); + false -> {error,{bad_flags,List}} + end; +transform_flags(Bad,_Acc) -> {error,{bad_flags,Bad}}. + +all() -> + [send,'receive',call,procs,garbage_collection,running, + set_on_spawn,set_on_first_spawn,set_on_link,set_on_first_link, + timestamp,arity,return_to]. + +display_info([Node|Nodes]) -> + io:format("~nNode ~w:~n",[Node]), + io:format("~-12s ~-21s Trace ~n", ["Pid", "Initial call"]), + List = rpc:call(Node,?MODULE,get_info,[]), + display_info1(List), + display_info(Nodes); +display_info([]) -> + ok. + +display_info1([{Pid,Call,Flags}|T]) -> + io:format("~-12s ~-21s ~s~n", + [io_lib:format("~w",[Pid]), + io_lib:format("~p", [Call]), + format_trace(Flags)]), + display_info1(T); +display_info1([]) -> + ok. + +get_info() -> + get_info(processes(),[]). + +get_info([Pid|T],Acc) -> + case pinfo(Pid, initial_call) of + undefined -> + get_info(T,Acc); + {initial_call, Call} -> + case tinfo(Pid, flags) of + undefined -> + get_info(T,Acc); + {flags,[]} -> + get_info(T,Acc); + {flags,Flags} -> + get_info(T,[{Pid,Call,Flags}|Acc]) + end + end; +get_info([],Acc) -> Acc. + +format_trace([]) -> []; +format_trace([Item]) -> [ts(Item)]; +format_trace([Item|T]) -> [ts(Item) ," | ", format_trace(T)]. + +ts(send) -> "s"; +ts('receive') -> "r"; +ts(call) -> "c"; +ts(procs) -> "p"; +ts(set_on_spawn) -> "sos"; +ts(set_on_first_spawn) -> "sofs"; +ts(set_on_link) -> "sol"; +ts(set_on_first_link) -> "sofl"; +ts(Other) -> atom_to_list(Other). + +%% +%% Turn (pid or) atom into a PidSpec for erlang:trace, +%% return {badpid,X} on failure +%% + +to_pidspec(X) when is_pid(X) -> + case erlang:is_process_alive(X) of + true -> X; + false -> {badpid,X} + end; +to_pidspec(new) -> new; +to_pidspec(all) -> all; +to_pidspec(existing) -> existing; +to_pidspec(X) when is_atom(X) -> + case whereis(X) of + undefined -> {badpid,X}; + Pid -> Pid + end; +to_pidspec(X) -> {badpid,X}. + +%% +%% Turn (pid or) integer or tuple or list into pid +%% + +to_pid(X) when is_pid(X) -> X; +to_pid(X) when is_integer(X) -> to_pid({0,X,0}); +to_pid({X,Y,Z}) -> + to_pid(lists:concat(["<",integer_to_list(X),".", + integer_to_list(Y),".", + integer_to_list(Z),">"])); +to_pid(X) when is_list(X) -> + try list_to_pid(X) of + Pid -> Pid + catch + error:badarg -> {badpid,X} + end; +to_pid(X) -> {badpid,X}. + + +pinfo(P, X) when node(P) == node() -> erlang:process_info(P, X); +pinfo(P, X) -> check(rpc:call(node(P), erlang, process_info, [P, X])). + +tinfo(P, X) when node(P) == node() -> erlang:trace_info(P, X); +tinfo(P, X) -> check(rpc:call(node(P), erlang, trace_info, [P, X])). + +check({badrpc, _}) -> undefined; +check(X) -> X. + +%% Process loop that processes a trace. Reads the trace with +%% the reader Reader, and feeds the trace terms +%% to handler Handler, keeping a state variable for the +%% handler. +%% +%% Exits 'normal' at end of trace, other exits due to errors. +%% +%% Reader is a lazy list, i.e either a list or a fun/0. +%% If it is a fun, it is evaluated for rest of the lazy list. +%% A list head is considered to be a trace term. End of list +%% is interpreted as end of trace. + +tc_loop([Term|Tail], Handler, HData0) -> + HData = Handler(Term, HData0), + tc_loop(Tail, Handler, HData); +tc_loop([], Handler, HData) -> + Handler(end_of_trace, HData), + exit(normal); +tc_loop(Reader, Handler, HData) when is_function(Reader) -> + tc_loop(Reader(), Handler, HData); +tc_loop(Other, _Handler, _HData) -> + io:format("~p:tc_loop ~p~n", [?MODULE, Other]), + exit({unknown_term_from_reader, Other}). + + + +%% Returns a reader (lazy list of trace terms) for tc_loop/2. +gen_reader(ip, {Host, Portno}) -> + case gen_tcp:connect(Host, Portno, [{active, false}, binary]) of + {ok, Sock} -> + mk_reader(fun ip_read/2, Sock); + Error -> + exit(Error) + end; +gen_reader(file, {Filename, wrap, Tail, _, WrapCnt}) -> + mk_reader_wrap(wrap_sort(wrap_presort(Filename, Tail), WrapCnt)); +gen_reader(file, Filename) -> + gen_reader_file(fun file_read/2, Filename); +gen_reader(follow_file, Filename) -> + gen_reader_file(fun follow_read/2, Filename). + +%% Opens a file and returns a reader (lazy list). +gen_reader_file(ReadFun, Filename) -> + case file:open(Filename, [read, raw, binary]) of + {ok, File} -> + mk_reader(ReadFun, File); + Error -> + exit({client_cannot_open, Error}) + end. + +%% Creates and returns a reader (lazy list). +mk_reader(ReadFun, Source) -> + fun() -> + case read_term(ReadFun, Source) of + {ok, Term} -> + [Term | mk_reader(ReadFun, Source)]; + eof -> + [] % end_of_trace + end + end. + +%% Creates and returns a reader (lazy list) for a wrap log. +%% The argument is a sorted list of sort converted +%% wrap log file names, see wrap_presort/2. + +mk_reader_wrap([]) -> + []; +mk_reader_wrap([Hd | _] = WrapFiles) -> + case file:open(wrap_name(Hd), [read, raw, binary]) of + {ok, File} -> + mk_reader_wrap(WrapFiles, File); + Error -> + exit({client_cannot_open, Error}) + end. + +mk_reader_wrap([_Hd | Tail] = WrapFiles, File) -> + fun() -> + case read_term(fun file_read/2, File) of + {ok, Term} -> + [Term | mk_reader_wrap(WrapFiles, File)]; + eof -> + file:close(File), + case Tail of + [_|_] -> + mk_reader_wrap(Tail); + [] -> + [] % end_of_trace + end + end + end. + + + +%% Generic read term function. +%% Returns {ok, Term} | 'eof'. Exits on errors. + +read_term(ReadFun, Source) -> + case ReadFun(Source, 5) of + Bin when is_binary(Bin) -> + read_term(ReadFun, Source, Bin); + List when is_list(List) -> + read_term(ReadFun, Source, list_to_binary(List)); + eof -> + eof + end. + +read_term(ReadFun, Source, <> = Tag) -> + case Op of + 0 -> + case ReadFun(Source, Size) of + eof -> + exit({'trace term missing', + binary_to_list(Tag)}); + Bin when is_binary(Bin) -> + {ok, binary_to_term(Bin)}; + List when is_list(List) -> + {ok, binary_to_term(list_to_binary(List))} + end; + 1 -> + {ok, {drop, Size}}; + Junk -> + exit({'bad trace tag', Junk}) + end. + + + +%% Read functions for different source types, for read_term/2. +%% +%% Returns a binary of length N, an I/O-list of +%% effective length N or 'eof'. Exits on errors. + +file_read(File, N) -> + case file:read(File, N) of + {ok, Bin} when byte_size(Bin) =:= N -> + Bin; + {ok, Bin} when is_binary(Bin) -> + exit({'truncated file', binary_to_list(Bin)}); + eof -> + eof; + {error, Reason} -> + exit({'file read error', Reason}) + end. + +follow_read(File, N) -> + follow_read(File, N, cur). + +follow_read(File, N, Pos) -> + case file:position(File, Pos) of + {ok, Offset} -> + case file:read(File, N) of + {ok, Bin} when byte_size(Bin) =:= N -> + Bin; + {ok, Bin} when is_binary(Bin) -> + follow_read(File, N, Offset); + eof -> + follow_read(File, N, Offset); + {error, Reason} -> + exit({'file read error', Reason}) + end; + {error, Reason} -> + exit({'file position error', Reason}) + end. + +ip_read(Socket, N) -> + case gen_tcp:recv(Socket, N) of + {ok, Bin} when byte_size(Bin) < N -> + [Bin | ip_read(Socket, N-byte_size(Bin))]; + {ok, Bin} when byte_size(Bin) == N -> + [Bin]; + {ok, Bin} when is_binary(Bin) -> + exit({'socket read too much data', Bin}); + {error, closed} -> + eof; + {error, _Reason} = Error -> + exit({'socket read error', Error}) + end. + +get_tracer() -> + req({get_tracer,node()}). +get_tracer(Node) -> + req({get_tracer,Node}). + +save_pattern([]) -> + 0; +save_pattern(P) -> + (catch save_pattern(P, get_pattern_table())). + +save_pattern(Pattern, PT) -> + Last = last_pattern(ets:last(PT), PT), + BPattern = term_to_binary(Pattern), + case ets:match_object(PT, {'_', BPattern}) of + [] -> + ets:insert(PT, {Last + 1, BPattern}), + Last + 1; + [{N, BPattern}] -> + N + end. + +last_pattern('$end_of_table', _PT) -> + 0; +last_pattern(I, PT) when is_atom(I) -> + last_pattern(ets:prev(PT, I), PT); +last_pattern(I, _PT) when is_integer(I) -> + I; +last_pattern(_, _) -> + throw({error, badtable}). + + +get_pattern_table() -> + {ok, Ret} = req(get_table), + Ret. + +new_pattern_table() -> + PT = ets:new(dbg_tab, [ordered_set, public]), + ets:insert(PT, + {x, + term_to_binary([{'_',[],[{exception_trace}]}])}), + ets:insert(PT, + {exception_trace, + term_to_binary(x)}), + PT. + + +pt_doforall(Fun, Ld) -> + T = get_pattern_table(), + pt_doforall(T, Fun, ets:first(T), Ld). + +pt_doforall(_, _, '$end_of_table', _Ld) -> + ok; +pt_doforall(T, Fun, Key, Ld) -> + [{A,B}] = ets:lookup(T,Key), + NLd = Fun({A,binary_to_term(B)},Ld), + pt_doforall(T,Fun,ets:next(T,Key),NLd). + +lint_tp([]) -> + {ok,[]}; +lint_tp(Pattern) -> + case erlang:match_spec_test([],Pattern,trace) of + {ok,_Res,Warnings,_Flags} -> + {ok, Warnings}; + {error, Reasons} -> + {error, Reasons} + end. + +check_list(T) -> + case (catch lists:foldl( + fun(Val,_) -> + {ok,_,_,_} = + erlang:match_spec_test([],Val,trace), + ok + end, + ok, T)) of + {'EXIT',_} -> + {error, bad_match_spec}; + ok -> + ok; + _Else -> + {error, badfile} + end. + + + +%% Find all possible wrap log files. +%% Returns: a list of sort converted filenames. +%% +%% The sort conversion is done by extracting the wrap sequence counter +%% from the filename, and calling wrap_encode/2. +wrap_presort(Filename, Tail) -> + Name = filename:basename(Filename), + Dirname = filename:dirname(Filename), + case file:list_dir(Dirname) of + {ok, Files} -> + lists:zf( + fun(N) -> + case match_front(N, Name) of + false -> + false; + X -> + case match_rear(X, Tail) of + false -> + false; + C -> % Counter + case match_0_9(C) of + true -> + {true, + wrap_encode( + filename:join(Dirname, N), + C)}; + false -> + false + end + end + end + end, + Files); + _ -> + [] + end. + + + +%% Sorts a list of sort converted files +wrap_sort(Files, N) -> + wrap_sortfix(lists:sort(Files), N). + +%% Finish the sorting, since the lists:sort order is not the correct order. +%% Cut the list of files at the gap (at least one file is supposed +%% to be 'missing') and create a new list by cons'ing the two parts +%% in the right order. +wrap_sortfix([], N) when N >= 1 -> + []; +wrap_sortfix([], _N) -> + exit(inconsistent_wrap_file_trace_set); +%% file 0, gap 1..N +wrap_sortfix([{0, _}] = Files, N) when N >= 1 -> + Files; +wrap_sortfix([{0, _}], _N) -> + exit(inconsistent_wrap_file_trace_set); +%% files 0, ... +wrap_sortfix([{0, _} | _] = Files, N) when N >= 1-> + wrap_sortfix_1(Files, N, [], Files); +%% gap 0, files 1, ... +wrap_sortfix([{1, _} | _] = Files, N) when N >= 1 -> + wrap_sortfix_2(Files, N, [], Files); +wrap_sortfix([{_C, _} | _], _N) -> + exit(inconsistent_wrap_file_trace_set). + +%% files 0..C, gap C+1..N +wrap_sortfix_1([{C, _}], N, _R, Files) + when C < N -> + Files; +%% files 0..C1, C1+1==C2, ... +wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, Files) + when C1+1 == C2, C2 < N -> + wrap_sortfix_1(Tail, N, [F1 | R], Files); +%% files 0..C1, gap C1+1, files C1+2==C2, ... +wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, _Files) + when C1+2 == C2, C2 =< N -> + wrap_sortfix_2(Tail, N, lists:reverse([F1 | R]), Tail); +wrap_sortfix_1([_F1 | [_F2 | _]], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set). + +%% M == length(R); files 0..M-1, gap M, files M+1..N +wrap_sortfix_2([{N, _}], N, R, Files) -> + Files ++ R; +wrap_sortfix_2([{_C, _}], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set); +%% M == length(R); files 0..M-1, gap M, files M+1..C1, C1+1==C2, ... +wrap_sortfix_2([{C1, _} | [{C2, _} | _] = Tail], N, R, Files) + when C1+1 == C2, C2 =< N -> + wrap_sortfix_2(Tail, N, R, Files); +wrap_sortfix_2([{_C1, _} | [{_C2, _} | _]], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set). + + + +%% Extract the filenames from a list of sort converted ones. +wrap_postsort(Files) -> + lists:map(fun wrap_name/1, Files). + +wrap_encode(N, C) -> + {list_to_integer(C), N}. + +wrap_name({_C, N}) -> + N. + +%% Returns what is left of ListA when removing all matching +%% elements from ListB, or false if some element did not match, +%% or if ListA runs out of elements before ListB. +match_front(ListA, []) when is_list(ListA) -> + ListA; +match_front([], ListB) when is_list(ListB) -> + false; +match_front([Hd|TlA], [Hd|TlB]) -> + match_front(TlA,TlB); +match_front([_HdA|_], [_HdB|_]) -> + false. + +%% Reversed version of match_front/2 +match_rear(ListA, ListB) when is_list(ListA), is_list(ListB) -> + case match_front(lists:reverse(ListA), lists:reverse(ListB)) of + false -> + false; + List -> + lists:reverse(List) + end. + +%% Returns true if the non-empty list arguments contains all +%% characters $0 .. $9. +match_0_9([]) -> + false; +match_0_9([H]) when is_integer(H), $0 =< H, H =< $9 -> + true; +match_0_9([H|T]) when is_integer(H), $0 =< H, H =< $9 -> + match_0_9(T); +match_0_9(L) when is_list(L) -> + false. + +%%%%%%%%%%%%%%%%%% +%% Help... +%%%%%%%%%%%%%%%%%% + +help_display([]) -> + io:format("~n",[]), + ok; +help_display([H|T]) -> + io:format("~s~n",[H]), + help_display(T). + +h() -> + help_display( + [ + "The following help items are available:", + " p, c", + " - Set trace flags for processes", + " tp, tpl, ctp, ctpl, ctpg, ltp, dtp, wtp, rtp", + " - Manipulate trace patterns for functions", + " n, cn, ln", + " - Add/remove traced nodes.", + " tracer, trace_port, trace_client, get_tracer, stop, stop_clear", + " - Manipulate tracer process/port", + " i", + " - Info", + "", + "call dbg:h(Item) for brief help a brief description", + "of one of the items above."]). +h(p) -> + help_display(["p(Item) -> {ok, MatchDesc} | {error, term()}", + " - Traces messages to and from Item.", + "p(Item, Flags) -> {ok, MatchDesc} | {error, term()}", + " - Traces Item according to Flags.", + " Flags can be one of s,r,m,c,p,sos,sol,sofs,", + " sofl,all,clear or any flag accepted by erlang:trace/3"]); +h(c) -> + help_display(["c(Mod, Fun, Args)", + " - Evaluates apply(M,F,Args) with all trace flags set.", + "c(Mod, Fun, Args, Flags)", + " - Evaluates apply(M,F,Args) with Flags trace flags set."]); +h(i) -> + help_display(["i() -> ok", + " - Displays information about all traced processes."]); +h(tp) -> + help_display( + ["tp(Module,MatchSpec)", + " - Same as tp({Module, '_', '_'}, MatchSpec)", + "tp(Module,Function,MatchSpec)", + " - Same as tp({Module, Function, '_'}, MatchSpec)", + "tp(Module, Function, Arity, MatchSpec)", + " - Same as tp({Module, Function, Arity}, MatchSpec)", + "tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} " + "| {error, term()}", + " - Set pattern for traced global function calls."]); +h(tpl) -> + help_display( + ["tpl(Module,MatchSpec)", + " - Same as tpl({Module, '_', '_'}, MatchSpec)", + "tpl(Module,Function,MatchSpec)", + " - Same as tpl({Module, Function, '_'}, MatchSpec)", + "tpl(Module, Function, Arity, MatchSpec)", + " - Same as tpl({Module, Function, Arity}, MatchSpec)", + "tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} " + "| {error, term()}", + " - Set pattern for traced local (as well as global) function calls."]); +h(ctp) -> + help_display( + ["ctp()", + " - Same as ctp({'_', '_', '_'})", + "ctp(Module)", + " - Same as ctp({Module, '_', '_'})", + "ctp(Module, Function)", + " - Same as ctp({Module, Function, '_'})", + "ctp(Module, Function, Arity)", + " - Same as ctp({Module, Function, Arity})", + "ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear call trace pattern for the specified functions"]); +h(ctpl) -> + help_display( + ["ctpl()", + " - Same as ctpl({'_', '_', '_'})", + "ctpl(Module)", + " - Same as ctpl({Module, '_', '_'})", + "ctpl(Module, Function)", + " - Same as ctpl({Module, Function, '_'})", + "ctpl(Module, Function, Arity)", + " - Same as ctpl({Module, Function, Arity})", + "ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear local call trace pattern for the specified functions"]); +h(ctpg) -> + help_display( + ["ctpg()", + " - Same as ctpg({'_', '_', '_'})", + "ctpg(Module)", + " - Same as ctpg({Module, '_', '_'})", + "ctpg(Module, Function)", + " - Same as ctpg({Module, Function, '_'})", + "ctpg(Module, Function, Arity)", + " - Same as ctpg({Module, Function, Arity})", + "ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear global call trace pattern for the specified functions"]); +h(ltp) -> + help_display(["ltp() -> ok", + " - Lists saved and built-in match_spec's on the console."]); +h(dtp) -> + help_display(["dtp() -> ok", + " - Deletes all saved match_spec's.", + "dtp(N) -> ok", + " - Deletes a specific saved match_spec."]); +h(wtp) -> + help_display(["wtp(Name) -> ok | {error, IOError}", + " - Writes all saved match_spec's to a file"]); +h(rtp) -> + help_display(["rtp(Name) -> ok | {error, Error}", + " - Read saved match specifications from file."]); +h(n) -> + help_display( + ["n(Nodename) -> {ok, Nodename} | {error, Reason}", + " - Starts a tracer server on the given node.", + "n(Nodename,Type,Data) -> {ok, Nodename} | {error, Reason}", + " - Starts a tracer server with additional args on the given node."]); +h(cn) -> + help_display(["cn(Nodename) -> ok", + " - Clears a node from the list of traced nodes."]); +h(ln) -> + help_display(["ln() -> ok", + " - Shows the list of traced nodes on the console."]); +h(tracer) -> + help_display(["tracer() -> {ok, pid()} | {error, already_started}", + " - Starts a tracer server that handles trace messages.", + "tracer(Type, Data) -> {ok, pid()} | {error, Error}", + " - Starts a tracer server with additional parameters"]); +h(trace_port) -> + help_display(["trace_port(Type, Parameters) -> fun()", + " - Creates and returns a trace port generating fun"]); +h(trace_client) -> + help_display(["trace_client(Type, Parameters) -> pid()", + " - Starts a trace client that reads messages created by " + "a trace port driver", + "trace_client(Type, Parameters, HandlerSpec) -> pid()", + " - Starts a trace client that reads messages created by a", + " trace port driver, with a user defined handler"]); +h(get_tracer) -> + help_display( + ["get_tracer() -> {ok, Tracer}", + " - Returns the process or port to which all trace messages are sent.", + "get_tracer(Node) -> {ok, Tracer}", + " - Returns the process or port to which all trace messages are sent."]); +h(stop) -> + help_display( + ["stop() -> stopped", + " - Stops the dbg server and the tracing of all processes.", + " Does not clear any trace patterns."]); +h(stop_clear) -> + help_display( + ["stop_clear() -> stopped", + " - Stops the dbg server and the tracing of all processes,", + " and clears all trace patterns."]). + diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl new file mode 100644 index 0000000000..0bcb202fd8 --- /dev/null +++ b/lib/runtime_tools/src/erts_alloc_config.erl @@ -0,0 +1,670 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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. +%% +%% The Initial Developer of the Original Code is Ericsson AB. +%% +%% %CopyrightEnd% + +%%%------------------------------------------------------------------- +%%% File : erts_alloc_config.erl +%%% Author : Rickard Green +%%% Description : Generate an erts_alloc configuration suitable for +%%% a limited amount of runtime scenarios. +%%% +%%% Created : 9 May 2007 by Rickard Green +%%%------------------------------------------------------------------- + +-module(erts_alloc_config). + +-record(state, {have_scenario = false, + alloc}). + + +-record(alloc, {name, + enabled, + need_config_change, + alloc_util, + instances, + low_mbc_blocks_size, + high_mbc_blocks_size, + sbct, + segments}). + +-record(conf, + {segments, + format_to}). + +-record(segment, {size,number}). + +-define(PRINT_WITDH, 76). + +-define(SERVER, '__erts_alloc_config__'). + +-define(MAX_ALLOCATOR_INSTANCES, 16). + +-define(KB, 1024). +-define(MB, 1048576). + +-define(B2KB(B), ((((B) - 1) div ?KB) + 1)). +-define(ROUNDUP(V, R), ((((V) - 1) div (R)) + 1)*(R)). + +-define(LARGE_GROWTH_ABS_LIMIT, 20*?MB). +-define(MBC_MSEG_LIMIT, 150). +-define(FRAG_FACT, 1.25). +-define(GROWTH_SEG_FACT, 2). +-define(MIN_SEG_SIZE, 1*?MB). +-define(SMALL_GROWTH_SEGS, 5). + +-define(ALLOC_UTIL_ALLOCATOR(A), + A == binary_alloc; + A == std_alloc; + A == ets_alloc; + A == eheap_alloc; + A == ll_alloc; + A == sl_alloc; + A == temp_alloc; + A == driver_alloc). + +-define(ALLOCATORS, + [binary_alloc, + ets_alloc, + eheap_alloc, + fix_alloc, + ll_alloc, + mseg_alloc, + sl_alloc, + std_alloc, + sys_alloc, + temp_alloc, + driver_alloc]). + +-define(MMBCS_DEFAULTS, + [{binary_alloc, 131072}, + {std_alloc, 131072}, + {ets_alloc, 131072}, + {eheap_alloc, 524288}, + {ll_alloc, 2097152}, + {sl_alloc, 131072}, + {temp_alloc, 131072}, + {driver_alloc, 131072}]). + +-define(MMMBC_DEFAULTS, + [{binary_alloc, 10}, + {std_alloc, 10}, + {ets_alloc, 10}, + {eheap_alloc, 10}, + {ll_alloc, 0}, + {sl_alloc, 10}, + {temp_alloc, 10}, + {driver_alloc, 10}]). + + +%%% +%%% Exported interface +%%% + +-export([save_scenario/0, + make_config/0, + make_config/1, + stop/0]). + +%% Test and debug export +-export([state/0]). + + +save_scenario() -> + req(save_scenario). + +make_config() -> + make_config(group_leader()). + +make_config(FileName) when is_list(FileName) -> + case file:open(FileName, [write]) of + {ok, IODev} -> + Res = req({make_config, IODev}), + file:close(IODev), + Res; + Error -> + Error + end; +make_config(IODev) -> + req({make_config, IODev}). + +stop() -> + req(stop). + + +%% state() is intentionally undocumented, and is for testing +%% and debugging only... + +state() -> + req(state). + +%%% +%%% Server +%%% + +req(Req) -> + Ref = make_ref(), + ReqMsg = {request, self(), Ref, Req}, + req(ReqMsg, Ref, true). + +req(ReqMsg, Ref, TryStart) -> + req(ReqMsg, Ref, TryStart, erlang:monitor(process, ?SERVER)). + +req(ReqMsg, Ref, TryStart, Mon) -> + (catch ?SERVER ! ReqMsg), + receive + {response, Ref, Res} -> + erlang:demonitor(Mon, [flush]), + Res; + {'DOWN', Mon, _, _, noproc} -> + case TryStart of + true -> start_server(Ref, ReqMsg); + false -> {error, server_died} + end; + {'DOWN', Mon, _, _, Reason} -> + {error, Reason} + end. + +start_server(Ref, ReqMsg) -> + Starter = self(), + Pid = spawn(fun () -> + register(?SERVER, self()), + Starter ! {Ref, self(), started}, + server_loop(make_state()) + end), + Mon = erlang:monitor(process, Pid), + receive + {Ref, Pid, started} -> + req(ReqMsg, Ref, false, Mon); + {'DOWN', Mon, _, _, _} -> + req(ReqMsg, Ref, false) + end. + +server_loop(State) -> + NewState = receive + {request, From, Ref, save_scenario} -> + Alloc = save_scenario(State#state.alloc), + From ! {response, Ref, ok}, + State#state{alloc = Alloc, have_scenario = true}; + {request, From, Ref, {make_config, IODev}} -> + case State#state.have_scenario of + true -> + Conf = #conf{segments = ?MBC_MSEG_LIMIT, + format_to = IODev}, + Res = mk_config(Conf, State#state.alloc), + From ! {response, Ref, Res}; + _ -> + From ! {response, Ref, no_scenario_saved} + end, + State; + {request, From, Ref, stop} -> + From ! {response, Ref, ok}, + exit(normal); + {request, From, Ref, state} -> + From ! {response, Ref, State}, + State; + {request, From, Ref, Req} -> + From ! {response, Ref, {unknown_request, Req}}, + State; + _ -> + State + end, + server_loop(NewState). + +allocator_instances(temp_alloc) -> + erlang:system_info(schedulers) + 1; +allocator_instances(ll_alloc) -> + 1; +allocator_instances(_Allocator) -> + case erlang:system_info(schedulers) of + Schdlrs when Schdlrs =< ?MAX_ALLOCATOR_INSTANCES -> Schdlrs; + _Schdlrs -> ?MAX_ALLOCATOR_INSTANCES + end. + +make_state() -> + #state{alloc = lists:map(fun (A) -> + #alloc{name = A, + instances = allocator_instances(A)} + end, + ?ALLOCATORS)}. + +%% +%% Save scenario +%% + +ai_value(Key1, Key2, AI) -> + case lists:keysearch(Key1, 1, AI) of + {value, {Key1, Value1}} -> + case lists:keysearch(Key2, 1, Value1) of + {value, Result} -> Result; + _ -> undefined + end; + _ -> undefined + end. + + +chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = undefined, + high_mbc_blocks_size = undefined} = Alc, + Min, + Max) -> + Alc#alloc{low_mbc_blocks_size = Min, + high_mbc_blocks_size = Max, + enabled = true}; +chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = LowBS, + high_mbc_blocks_size = HighBS} = Alc, + Min, + Max) -> + true = is_integer(LowBS), + true = is_integer(HighBS), + Alc1 = case Min < LowBS of + true -> Alc#alloc{low_mbc_blocks_size = Min}; + false -> Alc + end, + case Max > HighBS of + true -> Alc1#alloc{high_mbc_blocks_size = Max}; + false -> Alc1 + end. + +set_alloc_util(#alloc{alloc_util = AU} = Alc, AU) -> + Alc; +set_alloc_util(Alc, Val) -> + Alc#alloc{alloc_util = Val}. + +chk_sbct(#alloc{sbct = undefined} = Alc, AI) -> + case ai_value(options, sbct, AI) of + {sbct, Bytes} when is_integer(Bytes) -> Alc#alloc{sbct = b2kb(Bytes)}; + _ -> Alc + end; +chk_sbct(Alc, _AI) -> + Alc. + +save_scenario(AlcList) -> + %% The high priority is not really necessary. It is + %% used since it will make retrieval of allocator + %% information less spread out in time on a highly + %% loaded system. + OP = process_flag(priority, high), + Res = do_save_scenario(AlcList), + process_flag(priority, OP), + Res. + +save_ai2(Alc, AI) -> + Alc1 = chk_sbct(Alc, AI), + case ai_value(mbcs, blocks_size, AI) of + {blocks_size, MinBS, _, MaxBS} -> + set_alloc_util(chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true); + _ -> + set_alloc_util(Alc, false) + end. + +save_ai(Alc, [{instance, 0, AI}]) -> + save_ai2(Alc, AI); +save_ai(Alc, [{instance, _, _}, {instance, _, _}| _]) -> + Alc#alloc{enabled = true, need_config_change = true}; +save_ai(Alc, AI) -> + save_ai2(Alc, AI). % Non erts_alloc_util allocator + +do_save_scenario(AlcList) -> + lists:map(fun (#alloc{enabled = false} = Alc) -> + Alc; + (#alloc{name = Name} = Alc) -> + case erlang:system_info({allocator, Name}) of + undefined -> + exit({bad_allocator_name, Name}); + false -> + Alc#alloc{enabled = false}; + AI when is_list(AI) -> + save_ai(Alc, AI) + end + end, + AlcList). + +%% +%% Make configuration +%% + +conf_size(Bytes) when is_integer(Bytes), Bytes < 0 -> + exit({bad_value, Bytes}); +conf_size(Bytes) when is_integer(Bytes), Bytes < 1*?MB -> + ?ROUNDUP(?B2KB(Bytes), 128); +conf_size(Bytes) when is_integer(Bytes), Bytes < 10*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(1*?MB)); +conf_size(Bytes) when is_integer(Bytes), Bytes < 100*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(2*?MB)); +conf_size(Bytes) when is_integer(Bytes), Bytes < 256*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(5*?MB)); +conf_size(Bytes) when is_integer(Bytes) -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(10*?MB)). + +sbct(#conf{format_to = FTO}, #alloc{name = A, sbct = SBCT}) -> + fc(FTO, "Sbc threshold size of ~p kilobytes.", [SBCT]), + format(FTO, " +M~csbct ~p~n", [alloc_char(A), SBCT]). + +default_mmbcs(temp_alloc = A, _Insts) -> + {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS), + MMBCS_Default; +default_mmbcs(A, Insts) -> + {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS), + I = case Insts > 4 of + true -> 4; + _ -> Insts + end, + ?ROUNDUP(MMBCS_Default div I, ?B2KB(1*?KB)). + +mmbcs(#conf{format_to = FTO}, + #alloc{name = A, instances = Insts, low_mbc_blocks_size = BlocksSize}) -> + BS = case A of + temp_alloc -> BlocksSize; + _ -> BlocksSize div Insts + end, + case BS > default_mmbcs(A, Insts) of + true -> + MMBCS = conf_size(BS), + fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]), + format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]); + false -> + ok + end. + +smbcs_lmbcs_mmmbc(#conf{format_to = FTO}, + #alloc{name = A, instances = Insts, segments = Segments}) -> + MMMBC = case {A, Insts} of + {_, 1} -> Segments#segment.number; + {temp_alloc, _} -> Segments#segment.number; + _ -> (Segments#segment.number div Insts) + 1 + end, + MBCS = Segments#segment.size, + AC = alloc_char(A), + fc(FTO, "Mseg mbc size of ~p kilobytes.", [MBCS]), + format(FTO, " +M~csmbcs ~p +M~clmbcs ~p~n", [AC, MBCS, AC, MBCS]), + fc(FTO, "Max ~p mseg mbcs.", [MMMBC]), + format(FTO, " +M~cmmmbc ~p~n", [AC, MMMBC]), + ok. + +alloc_char(binary_alloc) -> $B; +alloc_char(std_alloc) -> $D; +alloc_char(ets_alloc) -> $E; +alloc_char(fix_alloc) -> $F; +alloc_char(eheap_alloc) -> $H; +alloc_char(ll_alloc) -> $L; +alloc_char(mseg_alloc) -> $M; +alloc_char(driver_alloc) -> $R; +alloc_char(sl_alloc) -> $S; +alloc_char(temp_alloc) -> $T; +alloc_char(sys_alloc) -> $Y; +alloc_char(Alloc) -> + exit({bad_allocator, Alloc}). + +conf_alloc(#conf{format_to = FTO}, + #alloc{name = A, enabled = false}) -> + fcl(FTO, A), + fcp(FTO, + "WARNING: ~p has been disabled. Consider enabling ~p by passing " + "the \"+M~ce true\" command line argument and rerun " + "erts_alloc_config.", + [A, A, alloc_char(A)]); +conf_alloc(#conf{format_to = FTO}, + #alloc{name = A, need_config_change = true}) -> + fcl(FTO, A), + fcp(FTO, + "WARNING: ~p has been configured in a way that prevents " + "erts_alloc_config from creating a configuration. The configuration " + "will be automatically adjusted to fit erts_alloc_config if you " + "use the \"+Mea config\" command line argument while running " + "erts_alloc_config.", + [A]); +conf_alloc(#conf{format_to = FTO} = Conf, + #alloc{name = A, alloc_util = true} = Alc) -> + fcl(FTO, A), + chk_xnote(Conf, Alc), + au_conf_alloc(Conf, Alc), + format(FTO, "#~n", []); +conf_alloc(#conf{format_to = FTO} = Conf, #alloc{name = A} = Alc) -> + fcl(FTO, A), + chk_xnote(Conf, Alc). + +chk_xnote(#conf{format_to = FTO}, + #alloc{name = fix_alloc}) -> + fcp(FTO, "Cannot be configured."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = sys_alloc}) -> + fcp(FTO, "Cannot be configured. Default malloc implementation used."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = mseg_alloc}) -> + fcp(FTO, "Default configuration used."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = ll_alloc}) -> + fcp(FTO, + "Note, blocks allocated with ll_alloc are very " + "seldom deallocated. Placing blocks in mseg " + "carriers is therefore very likely only a waste " + "of resources."); +chk_xnote(#conf{}, #alloc{}) -> + ok. + +au_conf_alloc(#conf{format_to = FTO} = Conf, + #alloc{name = A, + alloc_util = true, + instances = Insts, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc) -> + fcp(FTO, "Usage of mbcs: ~p - ~p kilobytes", [?B2KB(Low), ?B2KB(High)]), + case Insts of + 1 -> + fc(FTO, "One instance used."), + format(FTO, " +M~ct false~n", [alloc_char(A)]); + _ -> + fc(FTO, "~p instances used.", + [Insts]), + format(FTO, " +M~ct ~p~n", [alloc_char(A), Insts]) + end, + mmbcs(Conf, Alc), + smbcs_lmbcs_mmmbc(Conf, Alc), + sbct(Conf, Alc). + +large_growth(Low, High) -> + High - Low >= ?LARGE_GROWTH_ABS_LIMIT. + +calc_seg_size(Growth, Segs) -> + conf_size(round(Growth*?FRAG_FACT*?GROWTH_SEG_FACT) div Segs). + +calc_growth_segments(Conf, AlcList0) -> + CalcSmall = fun (#alloc{name = ll_alloc} = Alc, Acc) -> + {Alc#alloc{segments = #segment{size = 0, + number = 0}}, + Acc}; + (#alloc{alloc_util = true, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc, + {SL, AL}) -> + Growth = High - Low, + case large_growth(Low, High) of + true -> + {Alc, {SL, AL+1}}; + false -> + Segs = ?SMALL_GROWTH_SEGS, + SegSize = calc_seg_size(Growth, Segs), + {Alc#alloc{segments + = #segment{size = SegSize, + number = Segs}}, + {SL - Segs, AL}} + + end; + (Alc, Acc) -> {Alc, Acc} + end, + {AlcList1, {SegsLeft, AllocsLeft}} + = lists:mapfoldl(CalcSmall, {Conf#conf.segments, 0}, AlcList0), + case AllocsLeft of + 0 -> + AlcList1; + _ -> + SegsPerAlloc = case (SegsLeft div AllocsLeft) + 1 of + SPA when SPA < ?SMALL_GROWTH_SEGS -> + ?SMALL_GROWTH_SEGS; + SPA -> + SPA + end, + CalcLarge = fun (#alloc{alloc_util = true, + segments = undefined, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc) -> + Growth = High - Low, + SegSize = calc_seg_size(Growth, + SegsPerAlloc), + Alc#alloc{segments + = #segment{size = SegSize, + number = SegsPerAlloc}}; + (Alc) -> + Alc + end, + lists:map(CalcLarge, AlcList1) + end. + +mk_config(#conf{format_to = FTO} = Conf, AlcList) -> + format_header(FTO), + Res = lists:foreach(fun (Alc) -> conf_alloc(Conf, Alc) end, + calc_growth_segments(Conf, AlcList)), + format_footer(FTO), + Res. + +format_header(FTO) -> + {Y,Mo,D} = erlang:date(), + {H,Mi,S} = erlang:time(), + fcl(FTO), + fcl(FTO, "erts_alloc configuration"), + fcl(FTO), + fcp(FTO, + "This erts_alloc configuration was automatically " + "generated at ~w-~2..0w-~2..0w ~2..0w:~2..0w.~2..0w by " + "erts_alloc_config.", + [Y, Mo, D, H, Mi, S]), + fcp(FTO, + "~s was used when generating the configuration.", + [string:strip(erlang:system_info(system_version), both, $\n)]), + case erlang:system_info(schedulers) of + 1 -> ok; + Schdlrs -> + MinSchdlrs = case Schdlrs > ?MAX_ALLOCATOR_INSTANCES of + true -> ?MAX_ALLOCATOR_INSTANCES; + false -> Schdlrs + end, + fcp(FTO, + "NOTE: This configuration was made for ~p schedulers. " + "It is very important that at least ~p schedulers " + "are used.", + [Schdlrs, MinSchdlrs]) + end, + fcp(FTO, + "This configuration is intended as a suggestion and " + "may need to be adjusted manually. Instead of modifying " + "this file, you are advised to write another configuration " + "file and override values that you want to change. " + "Doing it this way simplifies things when you want to " + "rerun erts_alloc_config."), + fcp(FTO, + "This configuration is based on the actual use of " + "multi-block carriers (mbcs) for a set of different " + "runtime scenarios. Note that this configuration may " + "perform bad, ever horrible, for other runtime " + "scenarios."), + fcp(FTO, + "You are advised to rerun erts_alloc_config if the " + "applications run when the configuration was made " + "are changed, or if the load on the applications have " + "changed since the configuration was made. You are also " + "advised to rerun erts_alloc_config if the Erlang runtime " + "system used is changed."), + fcp(FTO, + "Note, that the singel-block carrier (sbc) parameters " + "very much effects the use of mbcs. Therefore, if you " + "change the sbc parameters, you are advised to rerun " + "erts_alloc_config."), + fcp(FTO, + "For more information see the erts_alloc_config(3) " + "documentation."), + ok. + +format_footer(FTO) -> + fcl(FTO). + +%%% +%%% Misc. +%%% + +b2kb(B) when is_integer(B) -> + MaxKB = (1 bsl erlang:system_info(wordsize)*8) div 1024, + case ?B2KB(B) of + KB when KB > MaxKB -> MaxKB; + KB -> KB + end. + +format(false, _Frmt) -> + ok; +format(IODev, Frmt) -> + io:format(IODev, Frmt, []). + +format(false, _Frmt, _Args) -> + ok; +format(IODev, Frmt, Args) -> + io:format(IODev, Frmt, Args). + +%% fcp: format comment paragraf +fcp(IODev, Frmt, Args) -> + fc(IODev, Frmt, Args), + format(IODev, "#~n"). + +fcp(IODev, Frmt) -> + fc(IODev, Frmt), + format(IODev, "#~n"). + +%% fc: format comment +fc(IODev, Frmt, Args) -> + fc(IODev, lists:flatten(io_lib:format(Frmt, Args))). + +fc(IODev, String) -> + fc_aux(IODev, string:tokens(String, " "), 0). + +fc_aux(_IODev, [], 0) -> + ok; +fc_aux(IODev, [], _Len) -> + format(IODev, "~n"); +fc_aux(IODev, [T|Ts], 0) -> + Len = 2 + length(T), + format(IODev, "# ~s", [T]), + fc_aux(IODev, Ts, Len); +fc_aux(IODev, [T|_Ts] = ATs, Len) when (length(T) + Len) >= ?PRINT_WITDH -> + format(IODev, "~n"), + fc_aux(IODev, ATs, 0); +fc_aux(IODev, [T|Ts], Len) -> + NewLen = Len + 1 + length(T), + format(IODev, " ~s", [T]), + fc_aux(IODev, Ts, NewLen). + +%% fcl: format comment line +fcl(FTO) -> + EndStr = "# ", + Precision = length(EndStr), + FieldWidth = -1*(?PRINT_WITDH), + format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, EndStr]). + +fcl(FTO, A) when is_atom(A) -> + fcl(FTO, atom_to_list(A)); +fcl(FTO, Str) when is_list(Str) -> + Str2 = "# --- " ++ Str ++ " ", + Precision = length(Str2), + FieldWidth = -1*(?PRINT_WITDH), + format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, Str2]). diff --git a/lib/runtime_tools/src/inviso_as_lib.erl b/lib/runtime_tools/src/inviso_as_lib.erl new file mode 100644 index 0000000000..75f3d9d004 --- /dev/null +++ b/lib/runtime_tools/src/inviso_as_lib.erl @@ -0,0 +1,155 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% + +%%% File : inviso_as_lib.erl +%%% Author : Lennart Öhman +%%% Description : +%% The purpose of the inviso autostart library is to provide useful functions +%% for anyone wanting to customize the autostart mechanism in the inviso +%% tracer. It is intended to work well with the example 'inviso_autostart_server'. +%%% +%%% Created : 15 Dec 2005 by Lennart Öhman +%% ----------------------------------------------------------------------------- + +-module(inviso_as_lib). + +-export([setup_autostart/7,setup_autostart/8,setup_autostart/9, + inhibit_autostart/1, + set_repeat/2,set_repeat_2/2]). +%% ----------------------------------------------------------------------------- + +%% setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings) = ok|{error,Reason}. +%% Repeat=integer(), where 0 means no (more) autostarts. +%% Options=List of options as taken by the runtime component at start-up. +%% TracerData= Tracerdata as given to inviso_rt:init_tracing. +%% CmdFiles=[FileName,...] list of string(), files that will be executed +%% by the subprocess started during autostart. +%% Bindings=[{VarName,Value},...] Variable bindings for CmdFiles. +%% VarName=atom(), +%% +%% This function creates the inviso_autostart.config file on Erlang node Node. +%% This is useful when you wish to prepare for an autostarted trace. +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations) -> + setup_autostart(Node,Repeat,Options,TracerData,CmdFiles, + Bindings,Translations,inviso_std_ref,off). +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations,RTtag) -> + setup_autostart(Node,Repeat,Options,TracerData,CmdFiles, + Bindings,Translations,RTtag,off). +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations,RTtag,Dbg) -> + case rpc:call(Node,inviso_autostart,which_config_file,[]) of + FileName when is_list(FileName) -> % Write to this file then. + {String,Args}=format_config_file(Repeat,TracerData,Options,CmdFiles, + Bindings,Translations,RTtag,Dbg), + Bytes=list_to_binary(io_lib:format(String,Args)), + case rpc:call(Node,file,write_file,[FileName,Bytes]) of + ok -> + ok; + {error,Reason} -> + {error,{write_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{write_file,Reason}}} + end; + {error,Reason} -> + {error,{which_config_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{which_config_file,Reason}}} + end. +%% ----------------------------------------------------------------------------- + +%% inhibit_autostart(Node) = ok|{error,Reason} +%% +%% Inhibits autostart by simply making the repeat parameter zero in the +%% configuration file at node Node. All other parameters are left untouched. +inhibit_autostart(Node) -> + set_repeat(Node,0). +%% ----------------------------------------------------------------------------- + +%% set_repeat(Node,N)=ok | {error,Reason} +%% N=integer(), the number of time autostart shall be allowed. +set_repeat(Node,N) -> + case examine_config_file(Node) of + {ok,FileName,Terms} -> + NewTerms=[{repeat,N}|lists:keydelete(repeat,1,Terms)], + case rpc:call(Node,?MODULE,set_repeat_2,[FileName,NewTerms]) of + {badrpc,Reason} -> + {error,{badrpc,{open,Reason}}}; + Result -> + Result + end; + {error,Reason} -> + {error,Reason} + end. + +%% Must be a sepparate function to do rpc on. The entire function must be done +%% in one rpc call. Otherwise the FD will die since it is linked to the opening +%% process. +set_repeat_2(FileName,NewTerms) -> + case file:open(FileName,[write]) of + {ok,FD} -> + String=lists:flatten(lists:map(fun(_)->"~w.~n" end,NewTerms)), + case catch io:format(FD,String,NewTerms) of + ok -> + file:close(FD), + ok; + {'EXIT',Reason} -> + file:close(FD), + {error,{format,Reason}} + end; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +examine_config_file(Node) -> + case rpc:call(Node,inviso_autostart,which_config_file,[]) of + FileName when is_list(FileName) -> % Read this file, and then modify it. + case rpc:call(Node,file,consult,[FileName]) of + {ok,Terms} -> + {ok,FileName,Terms}; + {error,Reason} -> + {error,{consult,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{consult,Reason}}} + end; + {error,Reason} -> + {error,{which_config_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{which_config_file,Reason}}} + end. +%% ----------------------------------------------------------------------------- + +format_config_file(Repeat,TracerData,Options,CmdFiles,Bindings,Translations,RTtag,Dbg) -> + String="~w.~n~w.~n~w.~n~w.~n", + Args=[{repeat,Repeat}, + {mfa,{inviso_autostart_server,init,[[{tracerdata,TracerData}, + {cmdfiles,CmdFiles}, + {bindings,Bindings}, + {translations,Translations}, + {debug,Dbg}]]}}, + {options,Options}, + {tag,RTtag}], + {String,Args}. +%% ----------------------------------------------------------------------------- + + + + + + + diff --git a/lib/runtime_tools/src/inviso_autostart.erl b/lib/runtime_tools/src/inviso_autostart.erl new file mode 100644 index 0000000000..134133ad1f --- /dev/null +++ b/lib/runtime_tools/src/inviso_autostart.erl @@ -0,0 +1,208 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% +%% Author: Lennart Öhman, lennart.ohman@st.se +-module(inviso_autostart). + +-export([autostart/1,which_config_file/0]). + +%% This module implements the default autostart module for the inviso runtime +%% component. +%% It will: +%% (1) Open the autostart configuration file (either the default or the one +%% pointed out by the runtime_tools application parameter inviso_autostart_config). +%% (2) Check that the incarnation counter has not reached 0. If so, we do not +%% allow (yet) one autostart. +%% (3) Rewrite the configuration file if there was an incarnation counter. +%% (With the counter decreased). +%% (4) Inspect the content of the configuration file and pass paramters in the +%% return value (which is interpreted by the runtime component). +%% +%% CONTENT OF A CONFIGURATION FILE: +%% A plain text file containing erlang tuple terms, each ended with a period(.). +%% The following parameters are recognized: +%% {repeat,N} N=interger(), +%% The number of remaining allowed autostart incarnations of inviso. +%% {options,Options} Options=list() +%% The options which controls the runtime component, such as overload and +%% dependency. +%% {mfa,{Mod,Func,Args}} Args=list() +%% Controls how a spy process initiating tracing, patterns and flags shall +%% be started. +%% {tag,Tag} +%% The tag identifying the runtime component to control components. +%% ============================================================================= + +%% This function is run in the runtime component's context during autostart +%% to determine whether to continue and if, then how. +autostart(_AutoModArgs) -> + ConfigFile= + case application:get_env(inviso_autostart_conf) of + {ok,FileName} when is_list(FileName) -> % Use this filename then. + FileName; + {ok,{load,FileNames,{M,F}}} -> % First load the module, then... + case try_load_module(FileNames) of + ok -> + autostart_apply(M,F); + false -> % No such module available + "inviso_autostart.config" + end; + {ok,{gettia_asc,asc_file}} -> % Uggly hack to not have to change in GSN-CPS. + case try_load_module(["/tmp/DPE_COMMONLOG/gettia_asc", + "/tmp/DPE_COMMONLOG/gettia_overload"]) of + ok -> + autostart_apply(gettia_asc,asc_file); + false -> % No such module available + false + end; + {ok,{M,F}} -> % Use M:F(node()) + autostart_apply(M,F); + {ok,no_autostart} -> + false; + _ -> % Use a default name, in CWD! + "inviso_autostart.config" + end, + if + is_list(ConfigFile) -> + case file:consult(ConfigFile) of + {ok,Terms} -> % There is a configuration. + case handle_repeat(ConfigFile,Terms) of + ok -> % Handled or not, we shall continue. + {get_mfa(Terms),get_options(Terms),get_tag(Terms)}; + stop -> % We are out of allowed starts. + true % Then no autostart. + end; + {error,_} -> % There is no config file + true % Then no autostart! + end; + true -> % Skip it then. + true + end. + +autostart_apply(M,F) -> + case catch M:F(node()) of + FileName when is_list(FileName) -> + FileName; + no_autostart -> % No autostart after all. + false; + _ -> + "inviso_autostart.config" + end. + +%% This function is necessary since it is not always the case that all code-paths +%% are set at the time of an autostart. +try_load_module([AbsFileName|Rest]) when is_list(AbsFileName) -> + case catch code:load_abs(AbsFileName) of % May not be a proper filename. + {module,_Mod} -> + try_load_module(Rest); + _ -> + false + end; +try_load_module([]) -> % Load all beam files successfully. + ok; +try_load_module(AbsFileName) when is_list(AbsFileName) -> + try_load_module([AbsFileName]). +%% ----------------------------------------------------------------------------- + +%% Function returning the filename probably used as autostart config file. +%% Note that this function must be executed at the node in question. +which_config_file() -> + case application:get_env(runtime_tools,inviso_autostart_conf) of + {ok,FileName} when is_list(FileName) -> % Use this filename then. + FileName; + {ok,{M,F}} -> % Use M:F(node()) + case catch M:F(node()) of + FileName when is_list(FileName) -> + FileName; + _ -> + {ok,CWD}=file:get_cwd(), + filename:join(CWD,"inviso_autostart.config") + end; + _ -> % Use a default name, in CWD! + {ok,CWD}=file:get_cwd(), + filename:join(CWD,"inviso_autostart.config") + end. +%% ----------------------------------------------------------------------------- + + +%% Help function which finds out if there is a limit on the number of times +%% we shall autostart. If there is a repeat parameter and it is greater than +%% zero, the file must be rewritten with the parameter decreased with one. +%% Returns 'ok' or 'stop'. +handle_repeat(FileName,Terms) -> + case lists:keysearch(repeat,1,Terms) of + {value,{_,N}} when N>0 -> % Controlls how many time more. + handle_repeat_rewritefile(FileName,Terms,N-1), + ok; % Indicate that we shall continue. + {value,_} -> % No we have reached the limit. + stop; + false -> % There is no repeat parameter. + ok % No restrictions then! + end. + +%% Help function which writes the configuration file again, but with the +%% repeat parameter set to NewN. +%% Returns nothing significant. +handle_repeat_rewritefile(FileName,Term,NewN) -> + case file:open(FileName,[write]) of + {ok,FD} -> + NewTerm=lists:keyreplace(repeat,1,Term,{repeat,NewN}), + handle_repeat_rewritefile_2(FD,NewTerm), + file:close(FD); + {error,_Reason} -> % Not much we can do then?! + error + end. + +handle_repeat_rewritefile_2(FD,[Tuple|Rest]) -> + io:format(FD,"~w.~n",[Tuple]), + handle_repeat_rewritefile_2(FD,Rest); +handle_repeat_rewritefile_2(_,[]) -> + true. +%% ----------------------------------------------------------------------------- + +%% Three help functions finding the parameters possible to give to the runtime +%% component. Note that some of them have default values, should the parameter +%% not exist. +get_mfa(Terms) -> + case lists:keysearch(mfa,1,Terms) of + {value,{_,MFA}} -> + MFA; + false -> + false + end. + +get_options(Terms) -> + case lists:keysearch(options,1,Terms) of + {value,{_,Options}} -> + Options; + false -> + [] + end. + +get_tag(Terms) -> + case lists:keysearch(tag,1,Terms) of + {value,{_,Tag}} -> + Tag; + false -> + default_tag + end. +%% ----------------------------------------------------------------------------- + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + diff --git a/lib/runtime_tools/src/inviso_autostart_server.erl b/lib/runtime_tools/src/inviso_autostart_server.erl new file mode 100644 index 0000000000..5af96e4e39 --- /dev/null +++ b/lib/runtime_tools/src/inviso_autostart_server.erl @@ -0,0 +1,311 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% +%% Author: Lennart Öhman, lennart.ohman@st.se +%% +-module(inviso_autostart_server). +-export([init/1]). + +%% ----------------------------------------------------------------------------- +%% Internal exports +%% ----------------------------------------------------------------------------- +-export([cmd_file_interpreter_init/4]). +%% ----------------------------------------------------------------------------- + + +%% This module provides a (well working) example of how to program an +%% autostart server responsible for initializing trace, setting patterns +%% and flags. +%% +%% The general idea is that this code spawns interpreter processes in order to +%% execute commands concurrently. Each of the interpreter processes opens one or +%% several files (in sequence) containing erlang function calls which are evaluated +%% in the interpreter process context. +%% The argument provided to init shall be a list of options controlling +%% how to initialize tracing, which file(s) to open and variable bindings. +%% +%% This autostart_server interpreters understands standard inviso trace case files. +%% +%% The runtime component provides an API very similar to the API provided +%% by the control component. It is therefore easy to translate inviso calls to +%% inviso_rt calls. +%% +%% This process may be killed by the inviso_rt process if stop_tracing is called. +%% The reason is that there is no time limit to the interpreter processes. Hence +%% they should be killed if tracing is not possible anylonger. +%% ============================================================================= + + +%% ----------------------------------------------------------------------------- + +%% The independent autostart process spawned by the runtime component to carry +%% out initializations is spawened on this function (if using the example +%% autostart which comes with inviso). +%% ArgsFromConfig is as can be heard from the name comming from a paramater in +%% the autostart configuration file. Here it is supposed to be: +%% ArgsFromConfig=[ServerParam,...] +%% ServerParam={tracerdata,TracerData}|{cmdfiles,Files}|{bindings,Bindings}| +%% {translations,Translations}|{debug,DbgLevel} +%% TracerData=tracerdata given to inviso_rt:init_tracing/1 function. +%% Files=[FileNameSpecs,...] where each FileNameSpecs will be executed in +%% a separate process. Making each FileNameSpec parallel. +%% FileNameSpecs=[FileNameSpec,...] +%% FileNameSpec=FileName | {FileName,Bindings} +%% Bindings=[{Var,Value},...] variable environment understood by +%% erl_eval:exprs/2. +%% Translations=[Translation,...] +%% A translation file is a text-file with following tuples +%% Translation={{Mod,Func,Arity,{Mod2,Func2,ParamMF}}}| +%% {{Func,Arity,{Mod2,Func2,ParamMF}}} +%% ParamMF={M,F} | any() +%% Translates Mod:Func/Arity to Mod2:Func2 with the arguments to +%% Mod:Func translated using M:F/1. Note that ParamMF is not +%% necessarily an MF. If no translation shall be done, ParamMF +%% shall be anything else but an MF. +%% Also note that Mod is optional in a Translation. That means that +%% function calls without a module in the trace case file will +%% be translated according to that translation. +init(ArgsFromConfig) -> + case get_tracerdata_opts(ArgsFromConfig) of + {ok,TracerData} -> % Otherwise we can not start a trace! + case inviso_rt:init_tracing(TracerData) of + {ok,_} -> % Ok, tracing has been initiated. + case get_cmdfiles_opts(ArgsFromConfig) of + {ok,CmdFiles} -> % List of cmd-files. + Bindings=get_initialbindings_opts(ArgsFromConfig), + Translations=get_translations_opts(ArgsFromConfig), + Dbg=get_dbg_opts(ArgsFromConfig), + Procs=start_cmd_file_interpreters(CmdFiles, + Bindings, + Translations, + Dbg), + loop(Procs,Dbg); % Wait for procs to be done. + false -> % Then we can terminate normally. + true + end; + {error,Reason} -> % This is fault, lets terminate abnormally. + exit({inviso,{error,Reason}}) + end; + false -> % Then there is not much use then. + true % Just terminate normally. + end. +%% ----------------------------------------------------------------------------- + +%% Help function which starts a process for each item found in the FileNames +%% list. The idea is that each item will be processed concurrently. The items +%% them selves may be a sequence of filenames. +%% Returns a list of spawned interpret processes. +start_cmd_file_interpreters([FileNames|Rest],Bindings,Translations,Dbg) -> + P=spawn_link(?MODULE,cmd_file_interpreter_init,[FileNames,Bindings,Translations,Dbg]), + MRef=erlang:monitor(process,P), % Can't trap exits in this process. + [{P,MRef}|start_cmd_file_interpreters(Rest,Bindings,Translations,Dbg)]; +start_cmd_file_interpreters([],_,_,_) -> + []. +%% ----------------------------------------------------------------------------- + + +%% The loop where this process simply waits for all of the interpreters to be +%% done. Note that that may take som time. An interpreter may take as long time +%% necessary to do its task. +loop(Procs,Dbg) -> + receive + {'DOWN',MRef,process,Pid,_Reason} -> + case lists:keysearch(MRef,1,Procs) of + {value,{Pid,_}} -> % It was an interpreter that terminated. + case lists:keydelete(MRef,1,Procs) of + [] -> % No more interpreters. + true; % Then terminate. + NewProcs -> + loop(NewProcs,Dbg) + end; + false -> + loop(Procs,Dbg) + end; + _ -> + loop(Procs,Dbg) + end. + + +%% ----------------------------------------------------------------------------- +%% The interpret process. +%% +%% An interpreter process executes trace case files. Several interpreter processes +%% may be running in parallel. It is not within the scoop of this implementation +%% of an autostart server to solve conflicts. (You may implement your own autostart +%% server!). +%% An interpret process may run for as long as necessary. Hence the function called +%% within the trace case file can contain wait functions, waiting for a certain +%% system state to occure before continuing. +%% Note that this process also mixes global and local bindings. GlobalBindings +%% is a binding() structure, where LocalBindings is a list of {Var,Value}. +%% Further it is possible to let FileName be a {inviso,Func,Args} tuple instead. +%% ----------------------------------------------------------------------------- + +%% Init function for an interpreter process instance. +cmd_file_interpreter_init(FileNames,GlobalBindings,Translations,Dbg) -> + interpret_cmd_files(FileNames,GlobalBindings,Translations,Dbg). + +interpret_cmd_files([{FileName,LocalBindings}|Rest],GlobalBindings,Translations,Dbg) -> + Bindings=join_local_and_global_vars(LocalBindings,GlobalBindings), + interpret_cmd_files_1(FileName,Bindings,Translations,Dbg), + interpret_cmd_files(Rest,GlobalBindings,Translations,Dbg); +interpret_cmd_files([FileName|Rest],GlobalBindings,Translations,Dbg) -> + interpret_cmd_files_1(FileName,GlobalBindings,Translations,Dbg), + interpret_cmd_files(Rest,GlobalBindings,Translations,Dbg); +interpret_cmd_files([],_,_,_) -> % Done, return nothing significant! + true. + +%% This is "inline" inviso calls. +interpret_cmd_files_1({inviso,F,Args},Bindings,Translations,Dbg) -> + {ok,Tokens1,_}=erl_scan:string("inviso:"++atom_to_list(F)++"("), + Tokens2=tokenize_args(Args), + {ok,Tokens3,_}=erl_scan:string(")."), + case erl_parse:parse_exprs(Tokens1++Tokens2++Tokens3) of + {ok,Exprs} -> + interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg); + {error,_Reason} -> + error + end; +interpret_cmd_files_1({Mod,Func,Args},_Bindings,_Translations,_Dbg) -> + catch apply(Mod,Func,Args); +%% This is the case when it actually is a trace case file. +interpret_cmd_files_1(FileName,Bindings,Translations,Dbg) -> + case file:open(FileName,[read]) of + {ok,FD} -> + interpret_cmd_files_2(FD,Bindings,io:parse_erl_exprs(FD,""),Translations,Dbg), + file:close(FD); + {error,Reason} -> % Something wrong with the file. + inviso_rt_lib:debug(Dbg,interpret_cmd_files,[FileName,{error,Reason}]) + end. + +%% Help function which handles Exprs returned from io:parse_erl_exprs and +%% tries to eval them. It is the side-effects we are interested in, like +%% setting flags and patterns. Note that we will get a failure should there +%% be a variable conflict. +%% Also note that there is logic to translate control component API calls to +%% corresponding runtime component calls. +%% Returns nothing significant. +interpret_cmd_files_2(FD,Bindings,{ok,Exprs,_},Translations,Dbg) -> + {next,NewBindings}=interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg), + interpret_cmd_files_2(FD,NewBindings,io:parse_erl_exprs(FD,""),Translations,Dbg); +interpret_cmd_files_2(FD,Bindings,{error,ErrorInfo,Line},Translations,Dbg) -> + inviso_rt_lib:debug(Dbg,parse_erl_exprs,[ErrorInfo,Line]), + interpret_cmd_files_2(FD,Bindings,io:parse_erl_exprs(FD,""),Translations,Dbg); +interpret_cmd_files_2(_,_,{eof,_},_,_) -> % End of file. + true. + +interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg) -> + case catch inviso_rt_lib:transform(Exprs,Translations) of + NewExprs when is_list(NewExprs) -> % We may have translated the API. + case catch erl_eval:exprs(NewExprs,Bindings) of + {'EXIT',Reason} -> + inviso_rt_lib:debug(Dbg,exprs,[Exprs,Bindings,{'EXIT',Reason}]), + {next,Bindings}; + {value,_Val,NewBindings} -> % Only interested in the side effects! + {next,NewBindings} + end; + {'EXIT',Reason} -> + inviso_rt_lib:debug(Dbg,translate2runtime_funcs,[Exprs,Reason]), + {next,Bindings} + end. + +%% Help function adding variables to a bindings structure. If the variable already +%% is assigned in the structure, it will be overridden. Returns a new +%% bindings structure. +join_local_and_global_vars([{Var,Val}|Rest],Bindings) when is_atom(Var) -> + join_local_and_global_vars(Rest,erl_eval:add_binding(Var,Val,Bindings)); +join_local_and_global_vars([_|Rest],Bindings) -> + join_local_and_global_vars(Rest,Bindings); +join_local_and_global_vars([],Bindings) -> + Bindings. + +%% Help function returning a string of tokens, including "," separation +%% between the arguments. +tokenize_args(Args=[Arg|Rest]) when length(Args)>1 -> + AbsTerm=erl_parse:abstract(Arg), + Tokens=erl_parse:tokens(AbsTerm), + {ok,Token,_}=erl_scan:string(","), + Tokens++Token++tokenize_args(Rest); +tokenize_args([Arg]) -> + AbsTerm=erl_parse:abstract(Arg), + erl_parse:tokens(AbsTerm); +tokenize_args([]) -> + "". +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Help functions working on the options given as argument to init during spawn. +%% ----------------------------------------------------------------------------- + +get_tracerdata_opts(ArgsFromConfig) -> + case lists:keysearch(tracerdata,1,ArgsFromConfig) of + {value,{_,{mfa,{M,F,CompleteTDGargs}}}} -> % Dynamic tracerdata. + case catch apply(M,F,CompleteTDGargs) of + {'EXIT',_Reason} -> + false; + TracerData -> + {ok,TracerData} + end; + {value,{_,TracerData}} -> % Interpret this as static tracerdata. + {ok,TracerData}; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_cmdfiles_opts(ArgsFromConfig) -> + case lists:keysearch(cmdfiles,1,ArgsFromConfig) of + {value,{_,CmdFiles}} -> + {ok,CmdFiles}; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_initialbindings_opts(ArgsFromConfig) -> + case lists:keysearch(bindings,1,ArgsFromConfig) of + {value,{_,Bindings}} -> + Bindings; + false -> % Then we use empty bindings. + erl_eval:new_bindings() + end. +%% ----------------------------------------------------------------------------- + +get_translations_opts(ArgsFromConfig) -> + case lists:keysearch(translations,1,ArgsFromConfig) of + {value,{_,Translations}} -> + Translations; + false -> % This becomes nearly point less. + [] + end. +%% ----------------------------------------------------------------------------- + +get_dbg_opts(ArgsFromConfig) -> + case lists:keysearch(debug,1,ArgsFromConfig) of + {value,{_,DbgLevel}} -> + DbgLevel; + false -> + off + end. +%% ----------------------------------------------------------------------------- + +%% EOF + + + diff --git a/lib/runtime_tools/src/inviso_rt.erl b/lib/runtime_tools/src/inviso_rt.erl new file mode 100644 index 0000000000..dfab70b42e --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt.erl @@ -0,0 +1,2895 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% +%% Description: +%% The runtime component of the trace tool Inviso. +%% +%% Authors: +%% Ann-Marie Löf, ann-marie.lof@st.se +%% Lennart Öhman, lennart.ohman@st.se +%% ----------------------------------------------------------------------------- + +-module(inviso_rt). + + +%% ----------------------------------------------------------------------------- +%% interface for supervisor +%% ----------------------------------------------------------------------------- +-export([start_link_man/3,start_link_auto/1]). + +%% API for controll component. +-export([start/4,stop/1, + init_tracing/2,stop_tracing_parallel/1, + try_to_adopt/3,confirm_connection/2,get_node_info/1, + suspend/2,call_suspend/2,cancel_suspension/1,change_options/2, + clear/2,clear_all_tp/1, + flush/1, + trace_patterns_parallel/3, + trace_flags_parallel/3,trace_flags_parallel/2,trace_flags_parallel/1, + meta_tracer_call_parallel/2, + get_status/1,get_tracerdata/1,list_logs/1,list_logs/2,fetch_log/2,fetch_log/3, + delete_log/1,delete_log/2, + state/1]). +%% ----------------------------------------------------------------------------- + +%% API mostly for autostart scripts, instead of corresponding control component +%% apis not available doing local function calls. +-export([init_tracing/1,tp/4,tp/5,tp/1,tpg/4,tpg/5,tpg/1, + tpl/4,tpl/5,tpl/1, + ctp/1,ctp/3,ctpg/1,ctpg/3,ctpl/1,ctpl/3, + init_tpm/4,init_tpm/7, + tpm/4,tpm/5,tpm/8,tpm_tracer/4,tpm_tracer/5,tpm_tracer/8, + tpm_ms/5,tpm_ms_tracer/5, + ctpm_ms/4, + local_register/0,global_register/0, + ctpm/3,remove_local_register/0,remove_global_register/0, + tf/2,tf/1,ctf/2,ctf/1]). +%% ----------------------------------------------------------------------------- + +%% Internal exports. +-export([init/4,auto_init/2,fetch_init/4]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Constants. +%% ----------------------------------------------------------------------------- + +-define(DEFAULT_OVERLOAD_FUNC,default_overload_func). +-define(NO_LOADCHECK,no_loadcheck). + +-define(RT_SUP,runtime_tools_sup). % Refers to the registered name. +-define(CTRL,inviso_c). % Refers to the registered name. +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Record definition. +%% ----------------------------------------------------------------------------- + +%% #rt +%% All record fields must be bound to listed values when leaving init or +%% auto_init. +%% dependency: Timeout accepting being without control component. +%% overload : Controlls which module to call, if any, when time for a check. +%% timer_ref: Used when timing delayed shutdown due to lost control component. +-record(rt,{state = new, % new | idle | tracing + status = running, % running | {suspended, Reason} + next_loadcheck = now(), % now | "No Loadcheck" + parent, % pid() + tracerdata, % undefined|{fun(),term()}|{file,Param}|{ip,Param} + tracer_port, % port() | undefined + handler, % {fun(), term()} | undefined + auto_starter, % pid() | undefined; proc starting interpreters. + meta_tracer, % undefined | pid() + fetchers=[], % [pid(),...] processes transfering logfiles. +% spies = [], + dependency={infinity,node()}, % {TOut,Node} | TOut; TOut=int()|infinity + overload=no_loadcheck, % ?NO_LOADCHECK|{LoadMF,Interval,InitMFA,RemoveMFA} + overload_data=void, % Datastructure given to LoadMF and RemoveMFA. + timer_ref, % undefined | reference() + ctrl, % undefined | pid() + ctrl_ref, % undefined | reference() + vsn, % list() + tag % term() + }). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================== +%% Start API +%% ============================================================================== + +%% Note that the runtime component may be started in many different ways. +%% It can be autostarted by the runtime_tools_sup during initial start-up of the +%% system. It is actually most likely that it will be started that way. However +%% if there are no autostart trace-cases to run, the inviso_rt runtime component +%% will terminate. It will then however remain as a child of the runtime_tools_sup +%% supervisor. This means that if the runtime component is started again, manually, +%% by the control component, some actions must be taken. +%% For instance is it very likely that the child already exists. But since it +%% must be started with different arguments when started manually, the child-spec +%% must be changed. +%% +%% The runtime component is not a proper gen_server, to allow full control of +%% what happens. It however mimcs gen_server behaviour to be managed by the +%% runtime_tools_sup supervisor. + + +%% start_link_auto(AutoModArgs)={ok,Pid} +%% +%% This function is entered into the child-spec when planning on doing autostart +%% of the runtime component. The autostart is controlled by the so called +%% inviso_autostart_mod. It is an application environment parameter of the +%% runtime_tools application. If it exists, it shall point out a module name. +%% If it does not exist, the default 'inviso_autostart' module will be tried. +%% Note that these start_link functions do not implement proper otp-behaviour. +%% For instance they return {ok,Pid} immediately making the init-phase of the +%% runtime component process empty. +%% +%% The inviso_autostart_mod shall export one function: +%% autostart(AutoModArgs) -> {MFA,Options,Tag}, where +%% AutoModArgs=term(), comes from the application start parameters in the +%% runtime_tools application resource file. +%% MFA={Mod,Func,Args} | term(). +%% If it is MFA it will cause a trace initiator process to start spawning +%% on spawn_link(Mod,Func,Args). The trace initiator may for instance +%% initiate the wanted tracing. +start_link_auto(AutoModArgs) -> + {ok,spawn_link(?MODULE,auto_init,[AutoModArgs,self()])}. +%% ------------------------------------------------------------------------------ + +%% This function is entered into the child-specification of the runtime_tools_sup +%% if the runtime component shall be started manually via the control component. +start_link_man(Ctrl,Options,Tag) -> + {ok,spawn_link(?MODULE,init,[Ctrl,Options,Tag,self()])}. +%% ------------------------------------------------------------------------------ + +%% start(Node,Options,Tag,Condition)=tbd +%% Node=The node where the runtime component shall be started. +%% Options=[Opt]; List of options to the runtime component. +%% Opt={dependency,Val}|{dependency,{Val,Node}} +%% Val=int()|infinity +%% If the runtime component may run on its own or not. Val=0 means a runtime +%% component which will terminate immediately without its control component. +%% Note that if the runtime component is started manually, the Node part +%% is never used. The runtime is supposed to be dependent of the Ctrl mentioned +%% in the start_link_man parameters. +%% Opt={overload,OverLoad} | overload +%% The latter means no loadcheck. Necessary if changing the options. +%% Overload=Iterval (int() in milliseconds) | +%% {LoadMF,Interval}|{LoadMF,Interval,InitMFA,RemoveMFA} +%% LoadMF={Mod,Func}|function() +%% InitMFA,RemoveMFA={Mod,Func,ArgList} where +%% apply(InitM,InitF,InitArgs) -> {ok,DataStruct}|'void'. +%% apply(RemoveM,RemoveF,[DataStruct|Args]) -> don't care +%% LoadMF is called each time loadcheck is performed. +%% Mod:Func(DataStruct)->ok|{suspend,Reason} +%% If just Interval is used, it means using a default overload check. +%% Tag=term(), used to identify an incarnation of a runtime component so that +%% a control component reconnecting will know if it was its own incarnation +%% still alive, or some elses. +%% Condition='if_ref'|term(). Controls if we want to adopt the runtime component. +%% If 'if_ref' is stated it means that we only want to adopt a runtime component +%% with the suggested Tag. +%% +%% This is the API used by the control component when tries to start a runtime +%% component. Note that it will try to adopt an already running, if possible. +%% Adoptions are only possible if the runtime component at hand is running +%% without control component. +start(Node, Options, Tag, Condition) when Node == node() -> + ChildSpec = {?MODULE, {?MODULE, start_link_man, [self(), Options, Tag]}, + temporary, 5000, worker, [?MODULE]}, + case catch supervisor:start_child(?RT_SUP, ChildSpec) of + {ok, Pid} when is_pid(Pid) -> + {node_info, _Node, Pid, VSN, State, Status, _Tag} = + get_node_info(Pid), + {node_info, Node, Pid, VSN, State, Status, new}; + {error, already_present} -> + supervisor:delete_child(?RT_SUP, ?MODULE), + start(Node, Options, Tag, Condition); + {error, {already_started, Pid}} -> + try_to_adopt(Pid, Tag, Condition); + {error,Reason} -> + {error,Reason}; + {'EXIT',Reason} -> + {error,Reason} + end; +start(Node, Options, Tag, Condition) -> + case rt_version(Node) of + {error,Error} -> + {error,Error}; + _VSN -> + ChildSpec = {?MODULE, {?MODULE, start_link_man, + [self(), Options, Tag]}, + temporary, 5000, worker, [?MODULE]}, + case catch rpc:call(Node, supervisor, start_child, + [?RT_SUP, ChildSpec]) of + {ok, Pid} when is_pid(Pid) -> + {node_info, _Node, Pid, + VSN, State, Status, _Tag} = get_node_info(Pid), + {node_info, Node, Pid, VSN, State, Status, new}; + {error, already_present} -> + rpc:call(Node, supervisor, delete_child, + [?RT_SUP, ?MODULE]), + start(Node, Options, Tag, Condition); + {error, {already_started, Pid}} -> + try_to_adopt(Pid, Tag, Condition); + {error,Reason} -> % Could not start child. + {error,Reason}; + {badrpc,nodedown} -> + {error,nodedown}; + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end + end. + +rt_version(Node) -> + case catch rpc:call(Node,application,loaded_applications,[]) of + List when is_list(List) -> + case lists:keysearch(runtime_tools,1,List) of + {value,{_,_,VSN}} -> + VSN; + false -> + {error,not_loaded} + end; + {badrpc,nodedown} -> + {error,nodedown}; + {'EXIT',Reason} -> + {error,Reason} + end. +%% ------------------------------------------------------------------------------ + +%% stop(Node)=ok|{error,Reason} +%% Stops the runtim component on node Node. Note that this is mearly calling the +%% supervisor API to shutdown the inviso_rt child belonging to the runtime_tools_sup. +stop(Node) when Node==node() -> + supervisor:terminate_child(?RT_SUP,?MODULE), + supervisor:delete_child(?RT_SUP,?MODULE), + ok; +stop(Node) -> + case catch rpc:call(Node,supervisor,terminate_child,[?RT_SUP,?MODULE]) of + ok -> + stop_delete_child(Node); + {error,_} -> % No child running. + stop_delete_child(Node); % Make sure we remove it also. + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end. + +stop_delete_child(Node) -> + case catch rpc:call(Node,supervisor,delete_child,[?RT_SUP,?MODULE]) of + ok -> + ok; + {error,_} -> % No child running. + ok; + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% API for the control component. +%% ============================================================================== + +%% init_tracing(TracerData) -> +%% TracerData = LogTD | [{trace,LogTD},{ti,TiTD}] +%% LogTD = {HandlerFun, Data} | collector | +%% {relayer, pid()} | {ip, IPPortParameters} | +%% {file, FilePortParameters} +%% TiTD = {file,FileName} | {file,FileName,{InitPublLD,RemovePublLD,CleanPublLD}} +%% | {relay,Node} | {relay,Node,{InitPublLD,RemovePublLD,CleanPublLD}} +%% HandlerFun=fun(TraceMsg,Data)->NewData +%% IPPortParameters = Portno | {Portno, Qsiz} +%% Qsiz = +%% FilePortParameters = {Filename, wrap, Tail, {time, WrapTime}, WrapCnt} | +%% {FileName, wrap, Tail, WrapSize, WrapCnt} | +%% {FileName, wrap, Tail, WrapSize} | +%% {FileName, wrap, Tail} | FileName +%% Defines a tracer: +%% {HandlerFun, Data} - will be used as handler inside the runtime component for +%% every incomming trace message. +%% relayer - the runtime component will relay all comming trace messages to +%% the runtime component Pid. +%% collector - the runtime component is used as tracer or collector of relayed +%% trace messages using the default handler writing them to io. +%% ip | file - will start a tracer port using PortParameters +init_tracing(Pid,TracerData) -> + call(Pid,{init_tracing,TracerData}). +%% ------------------------------------------------------------------------------ + +%% stop_tracing(RTpids)=[{Node,NodeResult},...] +%% RTpids=[RTinfo,...] +%% RTinfo={RTpid,Node} | {{error,Reason},Node} +%% NodeResult={ok,State} | {error,Reason} +%% Sends a request to stop tracing to all nodes in RTpids, in parallel. Stop +%% tracing means that all trace flags are removed and the nodes go to idle +%% state. +stop_tracing_parallel(RTpids) -> + call_parallel(lists:map(fun({Pid,Node})->{Pid,Node,stop_tracing}; + (Error)->Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% try_to_adopt(Pid,NewTag,Condition)= +%% {node_info,node(),self(),VSN,State,Status,{tag,PreviousTag}}|{error,Reason} +%% NewTag=term(), the identification tag we want the runtime component to use +%% from now on if adoption was successful. +%% Condition='if_ref', only adopt if current tag is NewTag. +%% PreviousTag= the tag the runtime component had before it accepted the +%% adoption. +%% This function shall only be used by a control component wishing to adopt this +%% runtime component. +try_to_adopt(Pid, Tag, Condition) -> + call(Pid,{try_to_adopt,Tag,Condition}). +%% ------------------------------------------------------------------------------ + +%% confirm_connection(Pid,Tag)= {node_info,node(),self(),VSN,State,Status,Tag}| +%% {error,refused}. +%% Must only be used by a control component having been contacted by the runtime +%% component Pid. It confirms to the runtime component that the control component +%% has accepted the connect request. +confirm_connection(Pid,Tag) -> + call(Pid,{confirm_connection,Tag}). +%% ------------------------------------------------------------------------------ + +%% get_node_info(Pid)={node_info,Node,Pid,VSN,State,Status,Tag}. +get_node_info(Pid) -> + call(Pid,get_node_info). +%% ------------------------------------------------------------------------------ + +%% suspend(NodeOrPid,Reason)=ok +%% call_suspend(NodeOrPid,Reason)=ok +%% Makes the runtime component and all of its helpers suspend. suspend/2 is +%% assynchronous. +suspend(NodeOrPid,Reason) -> + cast(NodeOrPid,{suspend,Reason}). + +call_suspend(NodeOrPid,Reason) -> + call(NodeOrPid,{suspend,Reason}). +%% ------------------------------------------------------------------------------ + +%% cancel_suspension(Pid)=ok +%% Function moving the runtime component to status running. Regardless of its +%% current status. +cancel_suspension(Pid) -> + call(Pid,cancel_suspension). +%% ------------------------------------------------------------------------------ + +%% change_options(Pid,Options)=ok +%% Options=list(); see the start_link_XXX functions. +%% Changes options according to Options list. +%% Changing the control component we shall be depending on has no effect. The +%% dependency value in self can however be changed, and takes effect immediately. +change_options(Pid,Options) -> + call(Pid,{change_options,Options}). +%% ------------------------------------------------------------------------------ + +%% clear_all_tp(Pid)=ok +%% Function removing all, both local and global trace-patterns from the node. +clear_all_tp(Pid) -> + call(Pid,clear_all_tp). +%% ------------------------------------------------------------------------------ + +%% clear(Pid,Options)={ok,{new,Status}} +%% Options=[Opt,...] +%% Opt=keep_trace_patterns | keep_log_files +%% Resets the runtime component to state 'new' by stopping all ongoing tracing, +%% closing and removing all associated logfiles. The Options can be used to +%% prevent the runtime component from being totally erased. +clear(Pid,Options) -> + call(Pid,{clear,Options}). +%% ------------------------------------------------------------------------------ + +%% flush(Pid)=ok | {error,Reason} +%% Sends the flush command to the trace-port, if we are using a trace-port and +%% are tracing. +flush(Pid) -> + call(Pid,flush). +%% ------------------------------------------------------------------------------ + +%% trace_patterns_parallel(RTpids,Args,Flags)=[{Node,Answer},...] +%% RTpids=[{RTpid,Node},...] or [{Error,Node},...] +%% Args=[Arg,...] +%% Arg={Mod,Func,Arity,MS}|{Mod,Func,Arity,MS,Opts} +%% Mod=atom()|reg_exp()|{Dir,reg_exp()} +%% Dir=reg_exp() +%% Answer=[Answer,...] +%% Answer=int()|{error,Reason} +%% API function for the control component sending trace-patterns to a list of +%% runtime components. Returns a [{Node,Answer},...] list in the same order. +trace_patterns_parallel(RTpids,Args,Flags) -> % Same args and flags for all. + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)->{Pid,Node,{tp,Args,Flags}}; + (Error)-> Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% trace_flags_parallel(RTpids,Args,How)= +%% trace_flags_parallel(RTpidsArgs,How)= +%% trace_flags_parallel(RTpidsArgsHow)=[{Node,Reply},...] +%% RTpids=[RTpidEntry,...] +%% RTpidEntry={RTpid,Node}|{Error,Node} +%% Error=term(), any term you wish to have as reply in Answer assoc. to Node. +%% Args=[{Process,Flags},...] +%% Process=pid()|registeredname()|'all'|'new'|'existing' +%% Flags=List of the allowed process trace flags. +%% RTpidsArgs=[RTpidArgEntry,...] +%% RTpidArgEntry={RTpid,Node,Args}|{Error,Node} +%% RTpidsArgsHow=[RTpidArgsHowEntry,...] +%% RTpidArgsHowEntry={RTpid,Node,Args,How}|{Error,Node} +%% How=true|false +%% Reply={ok,Answers} +%% Answers=[Answer,...], one for each Args and in the same order. +%% Answer=int()|{error,Reason} +%% API function used by the control component to send flags to a list of runtime +%% components. Returns a list of [{Node,Answer},... ] in the same order. +trace_flags_parallel(RTpids,Args,How) -> % Same args for every node! + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)->{Pid,Node,{tf,Args,How}}; + (Error)-> Error + end, + RTpids)). + +trace_flags_parallel(RTpidArgs,How) -> % Different args but same how. + call_parallel(lists:map(fun({Pid,Node,Args})when is_pid(Pid)-> + {Pid,Node,{tf,Args,How}}; + (Error)-> + Error + end, + RTpidArgs)). + +trace_flags_parallel(RTpidArgsHow) -> % Both different args and hows. + call_parallel(lists:map(fun({Pid,Node,Args,How})when is_pid(Pid)-> + {Pid,Node,{tf,Args,How}}; + (Error)-> + Error + end, + RTpidArgsHow)). +%% ------------------------------------------------------------------------------ + +%% meta_pattern(RTpids,Args)=[{Node,Answer},...] +%% RTpids=[{RTpid,Node},...] or [{Error,Node},...] +%% Args={FunctionName,ArgList} +%% FunctionName=atom() +%% ArgList=list(), list of the arguments to FunctionName. +%% Answer=[Answer,...] +%% Answer=int()|{error,Reason} +%% Makes a call to the meta-tracer through its runtime component. Returns a list +%% a answers in the same order as RTpids. Note that if "someone" has discovered +%% that there is an error with a particular node, the error answer can be placed +%% in the RTpids list from the start. +meta_tracer_call_parallel(RTpids,Args) -> % Same args for all nodes. + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)-> + {Pid,Node,{meta_tracer_call,Args}}; + (Error)-> + Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% get_status(Pid)={ok,{State,Status}} +%% State=new|tracing|idle +%% Status=running|{suspended,Reason} +get_status(Pid) -> + call(Pid,get_status). +%% ------------------------------------------------------------------------------ + +%% get_tracerdata(Pid)={ok,TracerData} | {ok,no_tracerdata} | {error,Reason} +%% TracerData=see init_tracing +%% Fetches the current tracerdata from the runtime component. +get_tracerdata(Pid) -> + call(Pid,get_tracerdata). +%% ------------------------------------------------------------------------------ + +%% list_log(Pid)={ok,no_log}|{ok,LogCollection}|{error,Reason} +%% list_log(Pid,TracerData)= +%% LogCollection=[LogTypes,...] +%% LogTypes={trace_log,Dir,Files}|{ti_log,Dir,Files} +%% Dir=string() +%% Files=[FileNameWithoutDir,...] +%% Lists all files associated with the current tracerdata. Or finds out which +%% files there are stored in this node given a tracerdata. +list_logs(Pid) -> + call(Pid,list_logs). +list_logs(Pid,TD) -> + call(Pid,{list_logs,TD}). +%% ------------------------------------------------------------------------------ + +%% fetch_log(Pid,CollectPid)={ok,FetcherPid}|{complete,no_log}|{error,Reason} +%% fetch_log(Pid,CollectPid,Spec)= +%% CollectPid=pid(), the process which will be given the transfered logs. +%% Spec=TracerData|LogCollection +%% Transferes a number of files using ditributed Erlang to CollectPid. This +%% function is supposed to be used internally by a control component. It returns +%% when the transfer is initiated and does not mean it is done or successful. +fetch_log(Pid,CollectPid) -> + call(Pid,{fetch_log,CollectPid}). +fetch_log(Pid,CollectPid,Spec) -> + call(Pid,{fetch_log,CollectPid,Spec}). +%% ------------------------------------------------------------------------------ + +%% delete_log(Pid,TracerDataOrLogList)={ok,Results}|{error,Reason} +%% TracerDataOrLogList=[FileNameWithPath,...]|LogCollection|TracerData +%% Results=[LogType,...] +%% LogType={trace_log,FileSpecs}|{ti_log,FilesSpecs} +%% FilesSpecs=[FileSpec,...] +%% FileSpec={ok,FileName}|{error,{Posix,FileName}} +%% Filename=string(), the filename without dir-path. +delete_log(Pid) -> + call(Pid,delete_logs). +delete_log(Pid,TracerDataOrLogList) -> + call(Pid,{delete_logs,TracerDataOrLogList}). +%% ------------------------------------------------------------------------------ + +%% state(NodeOrPid)=LoopData +%% Returns the loopdata of the runtime component. Only meant for debugging. +state(NodeOrPid) -> + call(NodeOrPid,state). +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% API for local calls made from the same node. E.g autostart. +%% ============================================================================== + +%% init_tracing(TracerData)= +%% See init_tracing/2. +init_tracing(TracerData) -> + call_regname(?MODULE,{init_tracing,TracerData}). +%% ------------------------------------------------------------------------------ + + +%% Meaning that these function does most often not have to be called by a +%% control component because there are more efficient ones above. + +%% tp(Module,Function,Arity,MatchSpec) -> +%% tp(Module,Function,Arity,MatchSpec,Opts) -> +%% tp(PatternList) -> +%% Module = '_'|atom()|ModRegExp|{DirRegExp,ModRegExp} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% MatchSpec = true | false | [] | matchspec() see ERTS User's guide for a +%% description of match specifications. +%% Opts=list(); 'only_loaded' +%% PatternList = [Pattern], +%% Pattern = {Module,Function,Arity,MatchSpec,Opts}, +%% Set trace pattern (global). +tp(Module,Function,Arity,MatchSpec) -> + tp(Module,Function,Arity,MatchSpec,[]). +tp(Module,Function,Arity,MatchSpec,Opts) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,Opts}],[global]}). +tp(PatternList) -> + call_regname(?MODULE,{tp,PatternList,[global]}). +%% ------------------------------------------------------------------------------ + +tpg(Mod,Func,Arity,MatchSpec) -> + tp(Mod,Func,Arity,MatchSpec). +tpg(Mod,Func,Arity,MatchSpec,Opts) -> + tp(Mod,Func,Arity,MatchSpec,Opts). +tpg(PatternList) -> + tp(PatternList). +%% ------------------------------------------------------------------------------ + +%% tpl(Module,Function,Arity,MatchSpec) -> +%% tpl(Module,Function,Arity,MatchSpec,Opts) -> +%% tpl(PatternList) -> +%% Module = Function == atom() | '_' | RegExpMod | {RegExpDir,RegExpMod} +%% Arity = integer() | '_' +%% MatchSpec = true | false | [] | matchspec() see ERTS User's guide for a +%% Opts=list(); 'only_loaded' +%% description of match specifications. +%% PatternList = [Pattern], +%% Pattern = {Module, Function, Arity, MatchSpec}, +%% Set trace pattern (local). +tpl(Module,Function,Arity,MatchSpec) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,[]}],[local]}). +tpl(Module,Function,Arity,MatchSpec,Opts) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,Opts}],[local]}). +tpl(PatternList) -> + call_regname(?MODULE,{tp,PatternList,[local]}). +%% ------------------------------------------------------------------------------ + +%% ctp(Module,Function,Arity) -> +%% ctp(PatternList)= +%% Module = atom()|'_'|RegExpMod|{RegExpDir,RegExpMod} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% PatternList=[{Mod,Func,Arity},...] +%% Clear trace pattern (global). +%% Note that it is possible to clear patterns using regexps. But we can for +%% natural reasons only clear patterns for loaded modules. Further more there +%% seems to be a fault in the emulator (<=R10B) crashing if we remove patterns +%% for deleted modules. Therefore we use the only_loaded option. +ctp(Module,Function,Arity) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,false,[only_loaded]}],[global]}). +ctp(PatternList) -> + call_regname(?MODULE, + {tp, + lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList), + [global]}). +%% ------------------------------------------------------------------------------ + +ctpg(Mod,Func,Arity) -> + ctp(Mod,Func,Arity). +ctpg(PatternList) -> + ctp(PatternList). +%% ------------------------------------------------------------------------------ + +%% ctpl(Module,Function,Arity) -> +%% Module = atom()|'_'|RegExpMod|{RegExpDir,RegExpMod} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% PatternList=[{Mod,Func,Arity},...] +%% Clear trace pattern (local). +ctpl(Module,Function,Arity) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,false,[only_loaded]}],[local]}). +ctpl(PatternList) -> + call_regname(?MODULE, + {tp, + lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList), + [local]}). +%% ------------------------------------------------------------------------------ + +init_tpm(Mod,Func,Arity,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{init_tpm,[Mod,Func,Arity,CallFunc]}}). + +init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {init_tpm, + [Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm(Mod,Func,Arity,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm,[Mod,Func,Arity,MS]}}). +tpm(Mod,Func,Arity,MS,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{tpm,[Mod,Func,Arity,MS,CallFunc]}}). +tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {tpm, + [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm_tracer(Mod,Func,Arity,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_tracer,[Mod,Func,Arity,MS]}}). +tpm_tracer(Mod,Func,Arity,MS,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_tracer,[Mod,Func,Arity,MS,CallFunc]}}). +tpm_tracer(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {tpm_tracer, + [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm_ms(Mod,Func,Arity,MSname,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_ms,[Mod,Func,Arity,MSname,MS]}}). +%% ------------------------------------------------------------------------------ + +tpm_ms_tracer(Mod,Func,Arity,MSname,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_ms_tracer,[Mod,Func,Arity,MSname,MS]}}). +%% ------------------------------------------------------------------------------ + +ctpm_ms(Mod,Func,Arity,MSname) -> + call_regname(?MODULE,{meta_tracer_call,{ctpm_ms,[Mod,Func,Arity,MSname]}}). +%% ------------------------------------------------------------------------------ + +local_register() -> + call_regname(?MODULE,{meta_tracer_call,{local_register,[]}}). +%% ------------------------------------------------------------------------------ + +global_register() -> + call_regname(?MODULE,{meta_tracer_call,{global_register,[]}}). +%% ------------------------------------------------------------------------------ + +ctpm(Mod,Func,Arity) -> + call_regname(?MODULE,{meta_tracer_call,{ctpm,[Mod,Func,Arity]}}). +%% ------------------------------------------------------------------------------ + +remove_local_register() -> + call_regname(?MODULE,{meta_tracer_call,{remove_local_register,[]}}). +%% ------------------------------------------------------------------------------ + +remove_global_register() -> + call_regname(?MODULE,{meta_tracer_call,{remove_global_register,[]}}). +%% ------------------------------------------------------------------------------ + +%% tf(PidSpec, FlagList) -> +%% tf(TraceConfList) -> +%% TraceConfList = [{PidSpec, FlagList}], +%% FlagList = [Flags], +%% PidSpec = all | new | existing | pid() | registeredname() +%% Flags = all | send | 'receive' | procs | call | silent | return_to | +%% running | garbage_collection | timestamp | cpu_timestamp | arity | +%% set_on_spawn | set_on_first_spawn | set_on_link | set_on_first_link +%% Set trace flags. +tf(PidSpec, FlagList) -> + call_regname(?MODULE,{tf,[{PidSpec,FlagList}],true}). + +tf(TraceConfList) -> + call_regname(?MODULE,{tf,TraceConfList,true}). +%% ------------------------------------------------------------------------------ + +%% ctf(PidSpec, FlagList) -> +%% ctf(TraceConfList) -> +%% TraceConfList = [{PidSpec, FlagList}], +%% FlagList = [Flags], +%% PidSpec = all | new | existing | pid() | registeredname() +%% Flags = all | send | 'receive' | procs | call | silent | return_to | +%% running | garbage_collection | timestamp | cpu_timestamp | arity | +%% set_on_spawn | set_on_first_spawn | set_on_link | set_on_first_link +%% Clear trace flags. +ctf(PidSpec, FlagList) -> + call_regname(?MODULE,{tf,[{PidSpec,FlagList}],false}). + +ctf(TraceConfList) -> + call_regname(?MODULE,{tf_as,TraceConfList,false}). +%% ------------------------------------------------------------------------------ + + +%% ------------------------------------------------------------------------------ +%% Client side functions. +%% ------------------------------------------------------------------------------ + +%% Call function managing the client to server communication. This function may +%% be run by a client on a different node. +%% Note that we must use two different functions for calling a named process and +%% calling the runtime component at a specified node. +call(Pid,Request) when is_pid(Pid) -> + call_2(Pid,Request); +call(Node,Request) when Node==node() -> % To our node! + call_2(?MODULE,Request); +call(Node,Request) when is_atom(Node) -> + call_2({?MODULE,Node},Request); +call(To,_Request) -> + {error,{badarg,To}}. + +call_regname(Name,Request) when is_atom(Name) -> % To a registered name. + call_2(Name,Request). + +call_2(To,Request) -> + MRef=erlang:monitor(process,To), % Use a monitor to avoid waiting for ever. + Ref=make_ref(), + case catch To ! {Request,self(),Ref} of % Can be a remote pid. + {'EXIT',_} -> % If we use registered name. + erlang:demonitor(MRef), % Maybe not necessary!? + receive + {'DOWN',MRef,_Type,_Obj,_Info} -> + true + after + 0 -> + true + end, + {error,not_started}; + _ -> % At least no obvious error. + receive + {Msg,Ref} -> + erlang:demonitor(MRef), + Msg; + {'DOWN',MRef,_Type,_Obj,Info} -> % The runtime component disapeared. + {error,{no_response,Info}} + end + end. +%% ----------------------------------------------------------------------------- + +%% Multicall function taking a list of [{Pid,Node,Request},...] and sends +%% a request to every Pid. This function then also allows you to send multiple +%% requests to the same Pid since it will sit and wait for all replies. +%% Note that RTspec may also be an [{{error,Reason},Node},...]. That tuple will +%% then be used as reply in the reply list. +%% Returns [{Node,Reply},...] for every element in RTspec, in the same order. +call_parallel(RTspec) -> + Ref=make_ref(), + {Nr,Pending}=call_parallel_2(RTspec,Ref,0,[]), + Replies=call_parallel_3(Ref,Pending,Nr,[],[]), + call_parallel_build_reply(RTspec,1,Replies). + +call_parallel_2([{Pid,Node,Request}|Rest],Ref,Nr,Pending) when is_pid(Pid) -> + Pid ! {Request,self(),{Ref,Nr+1}}, + MRef=erlang:monitor(process,Pid), % So we won't wait for ever for it. + call_parallel_2(Rest,Ref,Nr+1,[{Nr+1,Node,MRef}|Pending]); +call_parallel_2([{{error,_Reason},_Node}|Rest],Ref,Nr,Pending) -> + call_parallel_2(Rest,Ref,Nr,Pending); % Just skip it. This is no process. +call_parallel_2([_Faulty|Rest],Ref,Nr,Pending) -> % Should not happend. + call_parallel_2(Rest,Ref,Nr,Pending); % But we choose to skip it instead of crash. +call_parallel_2([],_,Nr,Pending) -> + {Nr,Pending}. + +%% Help function collecting reply-messages sent from the runtime components. We +%% count down until we got a reply for every pending request. Or if we get a DOWN +%% message indicating that the runtime component is no longer present. Note that +%% we can by accident read away DOWN messages not belonging to this procedure. +%% They are collected to be reissued after we are done. +call_parallel_3(_Ref,_Pending,0,Replies,DownMsgs) -> % All expected received. + lists:foreach(fun({MRef,Pid,Info}) -> self() ! {'DOWN',MRef,process,Pid,Info} end, + DownMsgs), % Reissue the down messages! + Replies; +call_parallel_3(Ref,Pending,NrOfPending,Replies,DownMsgs) -> + receive + {Reply,{Ref,Nr}} -> + case lists:keysearch(Nr,1,Pending) of + {value,{_Nr,Node,MRef}} -> + erlang:demonitor(MRef), + call_parallel_3(Ref,Pending,NrOfPending-1, + [{Nr,Node,Reply}|Replies],DownMsgs); + false -> % Really strange! + call_parallel_3(Ref,Pending,NrOfPending,Replies,DownMsgs) + end; + {'DOWN',MRef,process,Pid,Info} -> % Probably process we monitor terminated. + case lists:keysearch(MRef,3,Pending) of + {value,{Nr,Node,_}} -> % Yes it was one of our processes. + call_parallel_3(Ref,Pending,NrOfPending-1, + [{Nr,Node,{error,no_reponse}}|Replies],DownMsgs); + false -> % We picked up a DOWN msg by misstake. + call_parallel_3(Ref,Pending,NrOfPending,Replies, + [{MRef,Pid,Info}|DownMsgs]) + end + end. + +%% Help function which build up the [{Node,Reply},...] list in the same order as RTspec. +call_parallel_build_reply([],_,_) -> + []; +call_parallel_build_reply([{Pid,Node,_Request}|Rest],Nr,Replies) when is_pid(Pid) -> + {value,{_Nr,_Node,Reply}}=lists:keysearch(Nr,1,Replies), + [{Node,Reply}|call_parallel_build_reply(Rest,Nr+1,Replies)]; +call_parallel_build_reply([{{error,Reason},Node}|Rest],Nr,Replies) -> + [{Node,{error,Reason}}|call_parallel_build_reply(Rest,Nr,Replies)]; +call_parallel_build_reply([_Faulty|Rest],Nr,Replies) -> + call_parallel_build_reply(Rest,Nr,Replies). +%% ------------------------------------------------------------------------------ + +cast(Pid,Request) when is_pid(Pid) -> + cast2(Pid,Request); +cast(Node,Request) when Node==node() -> + catch cast2(?MODULE,Request), + ok; +cast(Node,Request) when is_atom(Node) -> + catch cast2({?MODULE,Node},Request), + ok; +cast(BadAddress,_Request) -> + {error,{badarg,BadAddress}}. + +cast2(To,Request) -> + To ! {Request,void,void}. % Mimics the call protocol. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Implementation of the runtime component (server side). +%% ============================================================================== + +%% Since the runtime component is not implemented using gen_sever we are "free" +%% to use what ever functionnames we like. + +%% Initial function on which the runtime component is spawned on if started by +%% a controlcomponent. +init(Ctrl, Options, Tag, Parent) when is_list(Options) -> + %% started from controller + process_flag(trap_exit,true), + register(?MODULE,self()), % Will crash if rt is already running + do_clear_trace_patterns(), % Remove potential old patterns left. + LD1=read_option_list(Options, + #rt{state=new, + parent=Parent, + ctrl=Ctrl, + vsn=get_application_vsn(), + tag=Tag}), + OverloadData=initialize_overload(LD1), + CtrlRef=erlang:monitor(process,Ctrl), % Monitor our control component. + loop1(LD1#rt{ctrl_ref=CtrlRef,overload_data=OverloadData}). +%% ---------------------------------------------------------------------------- + +%% Initial function on which the runtime component is spawned on if started +%% by the runtime_tools supervisor. It is here it is determined if we shall +%% autostart. +auto_init(AutoModArgs,Parent) -> + %% autostart + process_flag(trap_exit, true), + register(?MODULE, self()), % Will crash if a rt is already running + AutoMod=get_autostart_module(), % Determine which module to use! + case catch AutoMod:autostart(AutoModArgs) of + {MFA,Options,Tag} -> + do_clear_trace_patterns(), % Remove previously left patterns. + LD1=read_option_list(Options,#rt{state=new, + parent=Parent, + vsn=get_application_vsn(), + tag=Tag}), + case auto_init_connect_control(LD1) of + {ok,LD2} -> % Either connected or running_alone. + OverloadData=initialize_overload(LD2), + case auto_init_check_mfa(MFA) of + {ok,{M,F,A}} -> % We shall start somekind of tracing! + P=spawn_link(M,F,A), % It lives its own life, only link! + loop1(LD2#rt{auto_starter=P,overload_data=OverloadData}); + false -> + loop1(LD2#rt{overload_data=OverloadData}) + end; + stop -> % Not allowed to run alone! + true % Simply terminate. + end; + _ -> % Non existent or faulty autostart mod! + true % Terminate normally. + end. + +auto_init_connect_control(LD1) -> + case auto_init_connect_find_pid(LD1#rt.dependency) of + Pid when is_pid(Pid) -> % There is a control component. + CtrlRef=erlang:monitor(process,Pid), + Pid ! {connect,node(),self(),LD1#rt.vsn,LD1#rt.tag}, + {ok,LD1#rt{ctrl_ref=CtrlRef,ctrl=Pid}}; + _ -> % There is no control component. + do_down_message(LD1) % Will return 'stop' or a LoopData. + end. + +%% Help function which finds the pid of the control component. +auto_init_connect_find_pid({_TimeOut,Node}) when Node==node() -> + whereis(?CTRL); +auto_init_connect_find_pid({_TimeOut,Node}) when is_atom(Node) -> + rpc:call(Node,erlang,whereis,[?CTRL]); +auto_init_connect_find_pid(_) -> % Node is not a proper node. + undefined. % Act as could not find control comp. + +%% Help function checking that the parameter is reasonable to be used as +%% spawn_link argument. +auto_init_check_mfa({M,F,A}) when is_atom(M),is_atom(F),is_list(A) -> + {ok,{M,F,A}}; +auto_init_check_mfa(_) -> + false. + +%% Help function to init_auto which finds out which module to call for +%% guidance on how to proceed. Returns an atom. +get_autostart_module() -> + case application:get_env(inviso_autostart_mod) of + {ok,Mod} when is_atom(Mod) -> + Mod; + _ -> + inviso_autostart % The default autostart module. + end. +%% ---------------------------------------------------------------------------- + + +%% This is the preloop function which performs loadcheck if necessary. Note +%% that it calculates the timeout used in the after in the real loop. There is +%% further no use doing overload checks if we are not tracing or already +%% suspended. There is yet one more situation, we do not want to perform +%% overload checks if the interval is set to infinity. This can be the case if +%% we are using an external source pushing overload information instead. +loop1(LD=#rt{overload=Overload}) -> + if + Overload/=?NO_LOADCHECK,element(2,Overload)/=infinity -> + Now=now(), + if + LD#rt.status==running, + LD#rt.state==tracing, + Now>LD#rt.next_loadcheck -> % Do loadcheck only then! + {NewLD,TimeOut}=do_check_overload(LD,{timeout,LD#rt.overload_data}), + loop(NewLD,TimeOut); + LD#rt.status==running,LD#rt.state==tracing -> + Timeout=calc_diff_to_now(Now,LD#rt.next_loadcheck), + loop(LD,Timeout); + true -> % Do not spend CPU on this! :-) + loop(LD,infinity) + end; + true -> % Either no check or infinity. + loop(LD,infinity) + end. + +loop(LoopData,Timeout) -> + receive + Msg when element(1,Msg)==trace_ts; + element(1,Msg)==trace; + element(1,Msg)==drop; + element(1,Msg)==seq_trace -> + case LoopData#rt.handler of + {HandlerFun,Data} -> + NewData=HandlerFun(Msg,Data), + loop1(LoopData#rt{handler={HandlerFun,NewData}}); + _ -> + loop1(LoopData) + end; + {{tp,Args,Flags},From,Ref} -> + if + LoopData#rt.status==running -> % Not when suspended. + Reply=do_set_trace_patterns(Args,Flags), + if + LoopData#rt.state==new -> % No longer new when tp set. + reply_and_loop({ok,Reply},From,Ref,LoopData#rt{state=idle}); + true -> + reply_and_loop({ok,Reply},From,Ref,LoopData) + end; + true -> % We are suspended! + reply_and_loop({error,suspended},From,Ref,LoopData) + end; + {{tf,Args,How},From,MRef} -> + Reply= + case How of + true -> + if + LoopData#rt.status==running -> + case {LoopData#rt.tracer_port,LoopData#rt.handler} of + {Port,_} when is_port(Port) -> + do_set_trace_flags(Port,Args,How); + {_,{Handler,_D}} when is_function(Handler) -> + do_set_trace_flags(self(),Args,How); + _ -> + {error,no_tracer} + end; + true -> % Can't turn *on* flags if suspended. + {error, suspended} + end; + false -> % No tracer needed when turning off. + do_set_trace_flags(void,Args,How) + end, + reply_and_loop(Reply,From,MRef,LoopData); + {{meta_tracer_call,Args},From,MRef} -> + if + LoopData#rt.status==running -> + case LoopData#rt.meta_tracer of + MPid when is_pid(MPid) -> + Reply=do_meta_pattern(MPid,Args), + reply_and_loop(Reply,From,MRef,LoopData); + _ -> + reply_and_loop({error,no_metatracer},From,MRef,LoopData) + end; + true -> + reply_and_loop({error,suspended},From,MRef,LoopData) + end; + {clear_all_tp,From,MRef} -> + do_clear_trace_patterns(), + reply_and_loop(ok,From,MRef,LoopData); + {{init_tracing,TracerData},From,MRef} -> + {NewLoopData,Reply}= + if + LoopData#rt.status==running -> + if + LoopData#rt.state==tracing -> + {LoopData,{error,already_initiated}}; + true -> % Otherwise, try to init-tracing! + case translate_td(TracerData) of + {ok,LogTD,MetaTD} -> + do_init_tracing(LoopData,TracerData,LogTD,MetaTD); + Error -> + {LoopData,Error} + end + end; + true -> % Can't init tracing if not running. + {LoopData,{error,suspended}} + end, + reply_and_loop(Reply,From,MRef,NewLoopData); + {stop_tracing,From,MRef} -> + case LoopData#rt.state of + tracing -> % Only case we need to do anything. + reply_and_loop({ok,idle},From,MRef,do_stop_tracing(LoopData)); + idle -> % Already idle! + reply_and_loop({ok,idle},From,MRef,LoopData); + new -> % Have actually never traced! + reply_and_loop({ok,new},From,MRef,LoopData) + end; + {{suspend,Reason},From,MRef} -> + if + LoopData#rt.status==running -> + NewLD=do_suspend(LoopData,Reason), + reply_and_loop(ok,From,MRef,NewLD); + true -> % No need suspend if not running! + reply_and_loop(ok,From,MRef,LoopData) + end; + {cancel_suspension,From,MRef} -> + NewLoopData=LoopData#rt{status=running,next_loadcheck=now()}, + send_event(state_change,NewLoopData), + reply_and_loop(ok,From,MRef,NewLoopData); + {{clear,Options},From,MRef} -> + NewLoopData=do_clear(LoopData,Options), + reply_and_loop({ok,{new,NewLoopData#rt.status}},From,MRef,NewLoopData); + {flush,From,MRef} -> + case LoopData#rt.state of + tracing -> % Can only flush if we are tracing. + if + is_port(LoopData#rt.tracer_port) -> + trace_port_control(LoopData#rt.tracer_port,flush), + reply_and_loop(ok,From,MRef,LoopData); + true -> % Not necessary but lets pretend. + reply_and_loop(ok,From,MRef,LoopData) + end; + State -> + reply_and_loop({error,{not_tracing,State}},From,MRef,LoopData) + end; + {list_logs,From,MRef} -> + TracerData=LoopData#rt.tracerdata, % Current tracerdata. + if + TracerData/=undefined -> % There is tracerdata! + reply_and_loop(do_list_logs(TracerData),From,MRef,LoopData); + true -> % Have no current tracerdata! + reply_and_loop({error,no_tracerdata},From,MRef,LoopData) + end; + {{list_logs,TracerData},From,MRef} -> + reply_and_loop(do_list_logs(TracerData),From,MRef,LoopData); + {{fetch_log,CollectPid},From,MRef} -> % Fetch according to current tracerdata. + TracerData=LoopData#rt.tracerdata, % Current tracerdata. + if + TracerData/=undefined -> % There is tracerdata! + {Reply,NewLD}=do_fetch_log(LoopData,CollectPid,TracerData), + reply_and_loop(Reply,From,MRef,NewLD); + true -> % No tracerdata! + reply_and_loop({error,no_tracerdata},From,MRef,LoopData) + end; + {{fetch_log,CollectPid,Spec},From,MRef} -> % Either list of files or tracerdata. + {Reply,NewLD}=do_fetch_log(LoopData,CollectPid,Spec), + reply_and_loop(Reply,From,MRef,NewLD); + {delete_logs,From,MRef} -> + if + LoopData#rt.state==tracing -> % Can't remove then! + reply_and_loop({error,tracing},From,MRef,LoopData); + true -> + TracerData=LoopData#rt.tracerdata, + reply_and_loop(do_delete_logs(TracerData),From,MRef,LoopData) + end; + {{delete_logs,TracerDataOrLogList},From,MRef} -> + if + LoopData#rt.state==tracing -> % Can't remove then! + reply_and_loop({error,tracing},From,MRef,LoopData); + true -> + reply_and_loop(do_delete_logs(TracerDataOrLogList),From,MRef,LoopData) + end; + {get_node_info,From,MRef} -> + Reply=collect_node_info(LoopData), + reply_and_loop(Reply,From,MRef,LoopData); + {{try_to_adopt,Tag,Condition},From,MRef} -> + if + LoopData#rt.ctrl_ref==undefined -> % We have no control component. + {Reply,NewLoopData}=do_try_to_adopt(Tag,Condition,LoopData,From), + reply_and_loop(Reply,From,MRef,NewLoopData); + true -> % We already have a control component. + reply_and_loop({error,refused},From,MRef,LoopData) + end; + {{confirm_connection,_Tag},From,MRef} -> + if + LoopData#rt.ctrl==From -> % It must be from this process! + Reply=collect_node_info(LoopData), + reply_and_loop(Reply,From,MRef,LoopData); + true -> % Strange, some one is joking? + reply_and_loop({error,refused},From,MRef,LoopData) + end; + {{change_options,Options},From,MRef} -> + case do_change_options(Options,LoopData) of + stop -> % Can't run alone with these options! + terminate_overload(LoopData), + From ! {ok,MRef}; % Don't care if From not a proper pid! + NewLoopData when is_record(NewLoopData,rt) -> + reply_and_loop(ok,From,MRef,NewLoopData) + end; + {get_status,From,MRef} -> + Reply={ok,{LoopData#rt.state,LoopData#rt.status}}, + reply_and_loop(Reply,From,MRef,LoopData); + {get_tracerdata,From,MRef} -> + case LoopData#rt.tracerdata of + undefined -> + reply_and_loop({ok,no_tracerdata},From,MRef,LoopData); + TracerData -> + reply_and_loop({ok,TracerData},From,MRef,LoopData) + end; + {state,From,MRef} -> % For debugging purposes. + reply_and_loop(LoopData,From,MRef,LoopData); + + {'DOWN',CtrlRef,process,_,_} when CtrlRef==LoopData#rt.ctrl_ref -> + case do_down_message(LoopData) of + stop -> % inviso_c gone and we must stop! + terminate_overload(LoopData), + exit(running_alone); + {ok,NewLoopData} -> + loop1(NewLoopData) + end; + {'EXIT',Pid,Reason} -> + case act_on_exit(Pid,Reason,LoopData) of + exit -> + terminate_overload(LoopData), + exit(Reason); + NewLoopData when is_record(NewLoopData,rt) -> + loop1(NewLoopData); + {NewLoopData,NewTimeOut} when is_record(NewLoopData,rt) -> + loop(NewLoopData,NewTimeOut) + end; + Other -> % Check if it concerns overload. + if + LoopData#rt.overload/=?NO_LOADCHECK, + LoopData#rt.status==running, + LoopData#rt.state==tracing -> + {NewLD,NewTimeOut}= + do_check_overload(LoopData, + {msg,{Other,LoopData#rt.overload_data}}), + loop(NewLD,NewTimeOut); + true -> + NewTimeOut=calc_diff_to_now(now(),LoopData#rt.next_loadcheck), + loop(LoopData,NewTimeOut) + end + after + Timeout -> + loop1(LoopData) + end. + +reply_and_loop(Reply,To,MRef,LoopData) when is_pid(To) -> + To ! {Reply,MRef}, + loop1(LoopData); +reply_and_loop(_,_,_,LoopData) -> % Used together with incoming casts. + loop1(LoopData). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% File transfer process implementation. +%% ============================================================================= + +%% Files that are to to be transfered from the runtime component to the control +%% component are done so by reading them as binaries and sending them with +%% normal message passing (over distributed Erlang). +%% Reading the files are done in a process separate to the runtime component, +%% to both make the code more simple. But also to free up the runtime component. +%% +%% This help process must be capable of recognizing the fact that the runtime +%% component has been suspended, and then of course also discontinue any file +%% transfere. +fetch_init(Parent,Files,CollectPid,ChunkSize) -> + process_flag(trap_exit,true), % We must clean-up. + process_flag(priority,low), % Lets be careful. + case fetch_open_file(Files,CollectPid) of + {ok,FileName,FD,RestFiles} -> + MRef=erlang:monitor(process,CollectPid), + fetch_loop(Parent,RestFiles,CollectPid,ChunkSize,FileName,FD,MRef); + done -> + fetch_end(CollectPid); + error -> + fetch_incomplete(CollectPid) + end. + +fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef) -> + receive + {suspend,Parent} -> % The runtime component is suspended. + file:close(FD), % We must clean-up. + fetch_incomplete(CollectPid); + {'DOWN',MRef,process,_,_} -> % The CollectPid terminated! + file:close(FD); % Close file and terminate. + {'EXIT',Parent,_Reason} -> % The runtime component terminated. + file:close(FD), + fetch_incomplete(CollectPid); + _ -> + fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef) + after + 0 -> % If non of the above, get to work! + case file:read(FD,ChunkSize) of + {ok,Bin} -> + fetch_send_chunk(CollectPid,Bin), + case fetch_wait_for_chunk_ack(CollectPid,MRef) of + ok -> % Collector ready to receive next chunk. + fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef); + cancel -> % Send no more files! + file:close(FD), % Close file, send incomplete, terminate! + fetch_incomplete(CollectPid); + 'DOWN' -> % Collector has terminate, stop! + file:close(FD) % Close file and terminate. + end; + eof -> % Ok, go on with the next file. + file:close(FD), + fetch_send_eof(CollectPid), + case fetch_open_file(Files,CollectPid) of + {ok,NewFName,NewFD,RestFiles} -> + fetch_loop(Parent,RestFiles,CollectPid, + ChunkSize,NewFName,NewFD,MRef); + done -> + fetch_end(CollectPid); + error -> + fetch_incomplete(CollectPid) + end; + {error,Reason} -> % Do not continue. + file:close(FD), + fetch_send_readerror(CollectPid,FName,Reason), + fetch_incomplete(CollectPid) + end + end. +%% ----------------------------------------------------------------------------- + +%% Help function which opens the next file to be transferred. It also communicates +%% the opening of the file to the collector process. +%% We know here that it will be a list of three-tuples. But there is no guarantee +%% that Dir or FileName are proper strings. +%% Returns {ok,FileName,FileDescriptor,RemainingFiles} or 'done'. +fetch_open_file([{FType,Dir,FileName}|RestFiles],CollectPid) -> + case catch file:open(filename:join(Dir,FileName),[read,raw,binary]) of + {ok,FD} -> + CollectPid ! {node(),open,{FType,FileName}}, + {ok,FileName,FD,RestFiles}; + {error,_Reason} -> + CollectPid ! {node(),open_failure,{FType,FileName}}, + error; + {'EXIT',_Reason} -> % Faulty Dir or FileName. + CollectPid ! {node(),open_failure,{FType,FileName}}, + error + end; +fetch_open_file([],_CollectPid) -> + done. +%% ----------------------------------------------------------------------------- + +%% A group of help functions sending information to the collector process. +%% Returns nothing significant. +fetch_send_chunk(CollectPid,Bin) -> + CollectPid ! {node(),payload,Bin,self()}. +%% ----------------------------------------------------------------------------- + +fetch_send_eof(CollectPid) -> + CollectPid ! {node(),end_of_file}. +%% ----------------------------------------------------------------------------- + +fetch_end(CollectPid) -> + CollectPid ! {node(),end_of_transmission}. +%% ----------------------------------------------------------------------------- + +fetch_send_readerror(CollectPid,FName,Reason) -> + CollectPid ! {node(),{error,{file_read,{Reason,FName}}}}. +%% ----------------------------------------------------------------------------- + +fetch_incomplete(CollectPid) -> + CollectPid ! {node(),incomplete}. +%% ----------------------------------------------------------------------------- + +%% Help function waiting for the collector to respond that it is ready to receive +%% the next chunk. This is in order to exercise flow control protecting the +%% collector to get swamped if the node where the collector runs is busy. +fetch_wait_for_chunk_ack(CollectPid,MRef) -> + receive + {CollectPid,chunk_ack} -> + ok; + {CollectPid,cancel_transmission} -> % Some problem at collector side. + cancel; + {'DOWN',MRef,process,_,_} -> % The collector terminated. + 'DOWN' + end. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% First level do-functions, called from the main server loop on incomming +%% requests. +%% ============================================================================= + +%% Function performing the overload check. Returns {NewLoopData,TimeOut}. +%% Note that this function may also cause a suspend to be carried out if the +%% loadcheck turns out negative. +do_check_overload(LD,Data) -> + case do_check_overload_2(LD#rt.overload,Data) of + ignore -> % Load check not performed. + {LD,calc_diff_to_now(now(),LD#rt.next_loadcheck)}; + {ok,Interval} -> % No problem, continue. + NextLoadCheck=add_to_now(now(),Interval), + {LD#rt{next_loadcheck=NextLoadCheck},Interval}; + {suspend,Reason} -> % Emergency! suspend, suspend! + NewLD=do_suspend(LD,Reason), + {NewLD,infinity}; % No need to do load-checks now! + {new,NewData,Interval} -> % The overload was restarted or something. + NextLoadCheck=add_to_now(now(),Interval), + {LD#rt{overload_data=NewData,next_loadcheck=NextLoadCheck},Interval}; + error -> % Inhibit overload check then. + {LD#rt{overload=?NO_LOADCHECK},infinity} + end. + +%% Help function performing an overload check. Returns {ok,Interval}, +%% {suspend,Reason}, 'error' ir 'ignore'. +do_check_overload_2({{Mod,Func},Interval,_,_},Data) -> + do_check_overload_3(Interval,catch Mod:Func(Data)); +do_check_overload_2({Fun,Interval,_,_},Data) when is_function(Fun) -> + do_check_overload_3(Interval,catch Fun(Data)); +do_check_overload_2(_,_) -> % Bad loadcheck configuration. + error. % Stop using load checks then. + +do_check_overload_3(Interval,ok) -> + {ok,Interval}; +do_check_overload_3(Interval,{new,NewData}) -> + {new,NewData,Interval}; +do_check_overload_3(_Interval,{suspend,Reason}) -> + {suspend,Reason}; +do_check_overload_3(_Interval,ignore) -> % Loadcheck not triggered. + ignore; +do_check_overload_3(_Interval,_) -> % Failure or other return value. + error. % Stop doing loadchecks from now on. +%% ------------------------------------------------------------------------------ + +%% Function setting the trace-pattern according to Args and Flags. Note that +%% Args can contain regexps which must be expanded here. +%% Returns a list: [Result], where Result can be: int()|{error,Reason}. +%% Sometimes an error tuple will represent an entire pattern, sometimes the +%% pattern will expand to a number of error-tuples. +do_set_trace_patterns(Args,Flags) -> + Replies=do_set_trace_patterns_2(Args,Flags,[]), + lists:reverse(Replies). + +do_set_trace_patterns_2([{M,F,Arity,MS}|Rest],Flags,Replies) -> % Option-less. + do_set_trace_patterns_2([{M,F,Arity,MS,[]}|Rest],Flags,Replies); +do_set_trace_patterns_2([{M,F,Arity,MS,Opts}|Rest],Flags,Replies) when is_atom(M) -> + case load_module_on_option(M,Opts) of + true -> % Already present, loaded or no option! + case catch erlang:trace_pattern({M,F,Arity},MS,Flags) of + No when is_integer(No) -> + do_set_trace_patterns_2(Rest,Flags,[No|Replies]); + {'EXIT',Reason} -> + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,[{M,F,Arity,MS},Reason]}}| + Replies]) + end; + false -> % Module not present, or not found! + do_set_trace_patterns_2(Rest,Flags,[0|Replies]) + end; +do_set_trace_patterns_2([{M,F,Arity,MS,Opts}|Rest],Flags,Replies) when is_list(M) -> + case check_pattern_parameters(void,F,Arity,MS) of % We don't want to repeat bad params. + ok -> + case inviso_rt_lib:expand_regexp(M,Opts) of % Get a list of real modulnames. + Mods when is_list(Mods) -> + MoreReplies= + do_set_trace_patterns_2(lists:map(fun(Mod)-> + {Mod,F,Arity,MS,Opts} + end, + Mods), + Flags, + Replies), + do_set_trace_patterns_2(Rest,Flags,MoreReplies); + {error,Reason} -> + do_set_trace_patterns_2(Rest,Flags,[{error,Reason}|Replies]) + end; + error -> % Bad pattern parameters. + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,{M,F,Arity,MS}}}|Replies]) + end; +do_set_trace_patterns_2([{{Dir,M},F,Arity,MS,Opts}|Rest],Flags,Replies) + when is_list(Dir),is_list(M) -> + case check_pattern_parameters(void,F,Arity,MS) of % We don't want to repeat bad params. + ok -> + case inviso_rt_lib:expand_regexp(Dir,M,Opts) of % Get a list of real modulnames. + Mods when is_list(Mods) -> + MoreReplies= + do_set_trace_patterns_2(lists:map(fun(Mod)-> + {Mod,F,Arity,MS,Opts} + end, + Mods), + Flags, + Replies), + do_set_trace_patterns_2(Rest,Flags,MoreReplies); + {error,Reason} -> + do_set_trace_patterns_2(Rest,Flags,[{error,Reason}|Replies]) + end; + error -> % Bad pattern parameters. + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,{M,F,Arity,MS}}}|Replies]) + end; +do_set_trace_patterns_2([Arg|Rest],Flags,Replies) -> + do_set_trace_patterns_2(Rest,Flags,[{error,{bad_trace_args,Arg}}|Replies]); +do_set_trace_patterns_2([],_Flags,Replies) -> + Replies. +%% ----------------------------------------------------------------------------- + +%% Help function which sets the trace flags for all processes specifed in Args. +%% Args shall be a list of {ProcessSpecification,ProcessTraceFlags}. +%% Returns {ok,Answers} where Answers is a list of integer and error descriptions. +%% Note that a process specification may be a particular pid or a {global,Name}. +%% In the case the process does not exist we will fake a zero instead of an +%% error. +do_set_trace_flags(Tracer,Args,How) -> + Fun=fun({Proc,Flags}) -> + case check_traceflag_pidspec(Proc) of + {ok,Proc2} -> % Reg-names converted. + case check_flags(Flags) of + Flags2 when is_list(Flags2) -> % No error! + case (catch + case How of + true -> + erlang:trace(Proc2, + true, + [{tracer,Tracer}|Flags2]); + false -> % No tracer of turning off. + erlang:trace(Proc2, + false, + Flags2) + end) of + N when is_integer(N) -> + N; + {'EXIT',Reason} -> + if + is_pid(Proc2) -> + 0; % Proc2 not alive or not at this node! + true -> % Otherwise, just error! + {error, + {bad_trace_args, + [Reason,Proc2,How,Flags2,Tracer]}} + end + end; + FlagError -> + FlagError + end; + false -> % Skip it. + 0; % Indicate that zero processes matched. + {error,Reason} -> % Bad process specification. + {error,{bad_process,[Reason,Proc]}} + end; + (Faulty) -> + {error,{bad_process,Faulty}} + end, + {ok,lists:map(Fun,Args)}. +%% ------------------------------------------------------------------------------ + +%% Function calling API:s in the trace information server. Note that we have +%% given the responsibility to form a correct functionsname and argument list +%% to the caller. +%% Returns whatever the called function returns. +do_meta_pattern(MPid,{FuncName,ArgList}) -> + case catch apply(inviso_rt_meta,FuncName,[MPid|ArgList]) of + {'EXIT',_Reason} -> + {error,{badarg,{FuncName,ArgList}}}; + Result -> + Result + end; +do_meta_pattern(_MPid,BadArgs) -> + {error,{bad_args,BadArgs}}. +%% ------------------------------------------------------------------------------ + +%% Function removing *all* patterns. Beaware that the one for local patterns +%% causes a walkthrough of all loaded modules. +do_clear_trace_patterns() -> + erlang:trace_pattern({'_','_','_'},false,[local]), %% inc. meta, call_count + erlang:trace_pattern({'_','_','_'},false,[global]). +%% ------------------------------------------------------------------------------ + +%% Function that takes TracerData and initializes the tracing. That can be +%% opening appropriate logfiles, starting meta-tracer. There must be one +%% clause here for every "type" of logging we want to be able to do. +%% Returns the Reply to be forwarded to the caller. +do_init_tracing(LoopData,TD,{HandlerFun,Data},TiTD) when is_function(HandlerFun) -> + {NewLoopData,Reply}= + case do_init_metatracing(TiTD,self()) of + {ok,MetaPid} -> + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + meta_tracer=MetaPid, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,ok}]}}; + false -> % No meta tracing requested. + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok}]}}; + {error,Reason} -> % Problems starting meta tracing. + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,{error,Reason}}]}} + end, + send_event(state_change,NewLoopData), % Send to subscribing processes. + {NewLoopData,Reply}; +do_init_tracing(LoopData,TD,{Type,Parameters},TiTD) when Type==ip;Type==file -> + case check_traceport_parameters(Type,Parameters) of + ok -> + case catch trace_port(Type,Parameters) of + Fun when is_function(Fun) -> + case catch Fun() of + Port when is_port(Port) -> % Ok, our trace-port is open. + {NewLoopData,Reply}= + case do_init_metatracing(TiTD,Port) of + {ok,MetaPid} -> + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + meta_tracer=MetaPid, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,ok}]}}; + false -> % No meta tracing requested. + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok}]}}; + {error,Reason} -> % Problems starting meta tracing. + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,{error,Reason}}]}} + end, + send_event(state_change,NewLoopData), + {NewLoopData,Reply}; + {'EXIT',Reason} -> + {LoopData,{error,{bad_port_fun,[Parameters,Reason]}}} + end; + {'EXIT',Reason} -> + {LoopData,{error,{bad_port_args,[Parameters,Reason]}}} + end; + {error,Reason} -> % Bad traceport parameters. + {LoopData,{error,Reason}} + end. + +%% Help function that starts the meta-tracing. Note that the runtime component +%% will becom linked to it. +%% Currently the meta tracer handles two types, 'file' and 'relay'. +%% Note that Tracer tells the meta tracer where regular trace messages shall be +%% sent. This is because the meta tracer is capable of appending a {tracer,Tracer} +%% action term to meta match specs. +do_init_metatracing(LogSpec={_Type,_Arg},Tracer) -> + case inviso_rt_meta:start(LogSpec,Tracer) of + {ok,MetaPid} -> + {ok,MetaPid}; + {error,Reason} -> + {error,Reason} + end; +do_init_metatracing({Type,Arg,{InitPublLDmfa,RemovePublLDmf,CleanPublLDmf}},Tracer)-> + case inviso_rt_meta:start({Type,Arg},Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) of + {ok,MetaPid} -> + {ok,MetaPid}; + {error,Reason} -> + {error,Reason} + end; +do_init_metatracing(void,_) -> % Means no meta tracer. + false. +%% ----------------------------------------------------------------------------- + +%% Function that stops all tracing and closes all open files. This function +%% can't fail :-) It tries as hard as it can. +%% This function also kills the autostarter process if one exists. Otherwise it +%% will not be possible from a control component to end an ongoing autostarted +%% tracing. +%% Returns a new loopdata structure since stopping tracing involves updating it. +do_stop_tracing(LoopData) -> + do_stop_tracing_kill_autostarter(LoopData#rt.auto_starter), + do_clear_trace_flags(), % Do not generate any more traces. + NewLoopData1=do_stop_tracing_tracelog(LoopData), + NewLoopData2=do_stop_tracing_metatracing(NewLoopData1), + NewLoopData3=NewLoopData2#rt{state=idle,auto_starter=undefined}, + send_event(state_change,NewLoopData3), + NewLoopData3. + +do_stop_tracing_tracelog(LoopData=#rt{tracer_port=Port}) when is_port(Port) -> + trace_port_control(Port,flush), % Write buffered trace messages. + catch port_close(Port), + LoopData#rt{tracer_port=undefined}; +do_stop_tracing_tracelog(LoopData) -> + LoopData#rt{handler=undefined}. + +do_stop_tracing_metatracing(LoopData=#rt{meta_tracer=MPid}) when is_pid(MPid) -> + inviso_rt_meta:stop(MPid), + LoopData#rt{meta_tracer=undefined}; +do_stop_tracing_metatracing(LoopData) -> % No meta tracer running! + LoopData. + +%% Help function killing the autostarter, if one is active. +do_stop_tracing_kill_autostarter(P) when is_pid(P) -> + exit(P,stop_tracing); +do_stop_tracing_kill_autostarter(_) -> % No autostarter, do nothing. + true. +%% ----------------------------------------------------------------------------- + +%% Help function implementing suspending the runtime component. +%% Returns a new loopdata structure. +do_suspend(LD,Reason) -> + do_clear_trace_flags(), % If no process flags, no output! + do_suspend_metatracer(LD#rt.meta_tracer), + do_suspend_fetchers(LD#rt.fetchers), + do_stop_tracing_kill_autostarter(LD#rt.auto_starter), + NewLD=LD#rt{fetchers=[],status={suspended,Reason},auto_starter=undefined}, + send_event(state_change,NewLD), % Notify subscribers. + NewLD. + +do_suspend_metatracer(MetaTracer) when is_pid(MetaTracer) -> + inviso_rt_meta:suspend(MetaTracer); % This makes it suspended. +do_suspend_metatracer(_) -> + true. + +do_suspend_fetchers([FetcherPid|Rest]) -> + FetcherPid ! {suspend,self()}, % This makes it terminate. + do_suspend_fetchers(Rest); +do_suspend_fetchers([]) -> + true. +%% ------------------------------------------------------------------------------ + +%% Function that stops all tracing, removes all trace-patterns and removes all +%% logfiles. The idea is to return the runtime component to the 'new' state. +do_clear(LoopData,Opts) when is_list(Opts) -> + NewLoopData=do_stop_tracing(LoopData), % First stop tracing, if tracing. + case lists:member(keep_trace_patterns,Opts) of + false -> + do_clear_trace_patterns(); + _ -> + true + end, + case lists:member(keep_log_files,Opts) of + false -> + if + NewLoopData#rt.tracerdata/=undefined -> + do_delete_logs(NewLoopData#rt.tracerdata); + true -> % If no tracerdata, nothing to remove! + true % Do nothing then. + end; + _ -> + true + end, + NewLoopData#rt{state=new,tracerdata=undefined}; +do_clear(LoopData,_Opts) -> % Faulty Opts. + do_clear(LoopData,[]). % Then just ignore the options. +%% ----------------------------------------------------------------------------- + +%% Function which takes a tracerdata, either our own or a "suggested" +%% and tries to find the corresponding files. Note that the return value only +%% contains "types" of logs that the tracerdata is pointing out. Hence +%% is there no ti-log, no one will be mentioned in the return value. +do_list_logs(TracerData) -> % Handles both list and tuple. + case translate_td(TracerData) of + {ok,LogTD,TiTD} -> + {TraceDir,TraceLogs}=list_logs_tracelog(LogTD), + {TiDir,TiLogs}=list_logs_tilog(TiTD), + case {TraceLogs,TiLogs} of + {no_log,no_log} -> % Tracerdata not generating logs! + {ok,no_log}; + {_,no_log} -> % No ti logs. + {ok,[{trace_log,TraceDir,TraceLogs}]}; + {no_log,_} -> % Only ti-logs, unusual! + {ok,[{ti_log,TiDir,TiLogs}]}; + _ -> % Both trace and ti logs. + {ok,[{trace_log,TraceDir,TraceLogs},{ti_log,TiDir,TiLogs}]} + end; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +%% Help function implementing fetching logfiles using distributed Erlang. +%% This function works for both situations, a list of specific files are +%% requested, or a tracerdata is specified. +%% Returns {Reply,NewLoopData}. +do_fetch_log(LD,CollectPid,What) -> + if + LD#rt.state/=tracing -> + case is_list_of_files_or_tracerdata(What) of + files -> + FetcherPid=do_fetch_log_listoffiles(CollectPid,What), + {{ok,FetcherPid},add_fetcher_ld(FetcherPid,LD)}; + tracerdata -> + case do_fetch_log_tracerdata(CollectPid,What) of + {Reply,FetcherPid} when is_pid(FetcherPid) -> + {Reply,add_fetcher_ld(FetcherPid,LD)}; + {Reply,_} -> % No fetch process was started. + {Reply,LD} + end; + false -> % It is an empty list! + {{complete,no_log},LD}; + error -> % Incorrect parameter. + {{error,badarg},LD} + end; + true -> % No transfere during tracing. + {{error,tracing},LD} + end. + +%% Function taking tracerdata to find out what files to send over to the RemotePid. +%% Note that we will not go back to the loop function from here but rather call +%% the fetch_loop instead, prepresenting the fetch-log state. Unless we encounter +%% a problem. +do_fetch_log_tracerdata(CollectPid,TracerData) -> + case do_list_logs(TracerData) of + {ok,no_log} -> + {{complete,no_log},void}; + {ok,Logs} -> % Ok, some trace_log and ti_log. + FetcherPid=do_fetch_log_listoffiles(CollectPid,Logs), + {{ok,FetcherPid},FetcherPid}; + {error,Reason} -> % Problem with tracerdata! + {{error,Reason},void} + end. + +do_fetch_log_listoffiles(CollectPid,FileSpec) -> + ExpandedFileSpec=do_fetch_log_expand_filespec(FileSpec), +%% !!! try out different ChunkSizes +% ChunkSize = 60, +% ChunkSize = 7*1024, + ChunkSize=1024, + _Fetcher=spawn_link(?MODULE, + fetch_init, + [self(),ExpandedFileSpec,CollectPid,ChunkSize]). + +%% Help function which expands the list of logs to have tags in front of every +%% file, as required by the fetch_loop. +do_fetch_log_expand_filespec(Logs) -> + TraceLogs= + case lists:keysearch(trace_log,1,Logs) of + {value,{_,Dir1,Logs1}} -> % There is a list of trace-logs. + lists:map(fun(File)->{trace_log,Dir1,File} end,Logs1); + false -> % No trace-logs! + [] + end, + TiLogs= + case lists:keysearch(ti_log,1,Logs) of + {value,{_,Dir2,Logs2}} -> + lists:map(fun(File)->{ti_log,Dir2,File} end,Logs2); + false -> + [] + end, + TiLogs++TraceLogs. + +%% ------------------------------------------------------------------------------ + +%% Function that removes all logfiles associated with a certain tracerdata. +do_delete_logs(TracerDataOrLogList) -> + case is_list_of_files_or_tracerdata(TracerDataOrLogList) of + tracerdata -> + case translate_td(TracerDataOrLogList) of + {ok,LogTD,TiTD} -> + case {list_logs_tracelog(LogTD),list_logs_tilog(TiTD)} of + {{_,no_log},{_,no_log}} -> % No logs nowhere! + {ok,no_log}; + {{LogDir,LogFiles},{_,no_log}} -> % No ti. + {ok,[{trace_log,delete_files(LogDir,LogFiles)}]}; + {{_,no_log},{TiDir,TiFiles}} -> + {ok,[{ti_log,delete_files(TiDir,TiFiles)}]}; + {{LogDir,LogFiles},{TiDir,TiFiles}} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}, + {ti_log,delete_files(TiDir,TiFiles)}]} + end; + {error,Reason} -> + {error,Reason} + end; + files -> % It is [{trace_log,Dir,Files},.. + if + is_list(hd(TracerDataOrLogList)) -> % Just a list of files. + {ok,delete_files(".",TracerDataOrLogList)}; + is_tuple(hd(TracerDataOrLogList)) -> % A "modern" logspec. + case {lists:keysearch(trace_log,1,TracerDataOrLogList), + lists:keysearch(ti_log,1,TracerDataOrLogList)} of + {false,false} -> % Hmm, no logs specified! + {ok,[]}; % Easy response! + {{value,{_,LogDir,LogFiles}},false} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}]}; + {false,{value,{_,TiDir,TiFiles}}} -> + {ok,[{ti_log,delete_files(TiDir,TiFiles)}]}; + {{value,{_,LogDir,LogFiles}},{value,{_,TiDir,TiFiles}}} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}, + {ti_log,delete_files(TiDir,TiFiles)}]} + end + end; + false -> % Can't tell which! + {ok,[]}; + error -> + {error,{badarg,TracerDataOrLogList}} + end. +%% ----------------------------------------------------------------------------- + +%% Function handling the request when a control component wishing to take +%% control over this already existing control component. It does not matter +%% what state it is in. It can very well already be tracing. +%% Returns {Reply,NewLoopData}. +%% Where the Reply tells the control component wether it took control of it +%% or not. {node_info,node(),self(),Vsn,State,Status,{tag,Tag}} means that we +%% can be adopted (and more precisely considers ourselves being adopted now). +do_try_to_adopt(Tag,if_ref,LoopData=#rt{tag=Tag},_Ctrl) -> + {{error,{wrong_reference,LoopData#rt.tag}},LoopData}; +do_try_to_adopt(NewTag,_Condition,LoopData,CtrlPid) -> + case LoopData#rt.timer_ref of % Do we have a running-alone timer? + undefined -> % No we don't. + true; + TimerRef -> + timer:cancel(TimerRef) + end, + CtrlRef=erlang:monitor(process,CtrlPid), % Lets monitor our new "master"! + {DepVal,_}=LoopData#rt.dependency, + {node_info,Node,Pid,VSN,State,Status,Tag}=collect_node_info(LoopData), + NewLoopData= + LoopData#rt{dependency={DepVal,node(CtrlPid)}, + ctrl=CtrlPid, + ctrl_ref=CtrlRef, % Monitoring our new master. + tag=NewTag, % Use this tag from now on. + timer_ref=undefined}, + {{node_info,Node,Pid,VSN,State,Status,{tag,Tag}},NewLoopData}. +%% ----------------------------------------------------------------------------- + +%% Function changing parameters accoring to a new options list. Note that we +%% can not change control component if the one we have is still working. +%% We can however of course change how this runtime component will react to +%% a running alone scenario. +%% Returns 'stop' or NewLoopData. +do_change_options(Options,LoopData) -> + NewLoopData=read_option_list(Options,LoopData), + if + NewLoopData/=LoopData -> % Some options changed. + case do_change_options_ctrl(LoopData,NewLoopData) of + stop -> + stop; + {ok,NewLoopData2} -> + NewLoopData3=do_change_options_overload(LoopData,NewLoopData2), + NewLoopData3#rt{next_loadcheck=now()} % Force a load check next. + end; + true -> + LoopData + end. + +%% Help function which sets up the new dependencies. Note that we only do that +%% if do not have a working control component. +%% Returns {ok,NewLoopData} or 'stop'. +do_change_options_ctrl(OldLD,NewLD) -> + if + OldLD#rt.timer_ref/=undefined -> % No control and waiting to terminate. + timer:cancel(OldLD#rt.timer_ref), + do_down_message(NewLD#rt{timer_ref=undefined}); + OldLD#rt.ctrl==undefiend -> % No control component. + do_down_message(NewLD); + true -> % We have a working control component! + {ok,NewLD} + end. + +do_change_options_overload(OldLD,NewLD) -> + if + OldLD#rt.overload/=NewLD#rt.overload -> + terminate_overload(OldLD), + NewOverloadData=initialize_overload(NewLD), + NewLD#rt{overload_data=NewOverloadData}; + true -> % No changes done. + NewLD + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling an incoming DOWN message from our control component. +%% If the runtime component is not allowed to run without a control component, it +%% simply terminates which closes the trace-port and process trace flags are +%% therefore automatically removed. +%% Returns 'stop' or a {ok,NewLoopData} structure. +do_down_message(LoopData) -> + case LoopData#rt.dependency of + {0,_} -> % Not allowed to run without controller. + stop; + {infinity,_} -> % Don't care. Just remove the controller. + {ok,LoopData#rt{ctrl=undefined,ctrl_ref=undefined}}; + {TimeOut,_} -> % Allowed to run TimeOut ms alone. + {ok,TimerRef}=timer:exit_after(TimeOut,self(),running_alone), + {ok,LoopData#rt{timer_ref=TimerRef,ctrl=undefined,ctrl_ref=undefined}} + end. +%% ----------------------------------------------------------------------------- + +%% Function handling incomming exit signals. We can expect exit signals from the +%% following: Our parent supervisor (runtime_tools_sup), a meta-tracer process, +%% a logfile fetcher process, or the auto_starter. +%% A trace-port may also generate an exit signal. +%% In addition it is possible that an overload mechanism generates exit-signals. +%% We can also get the running_alone exit signal from our self. This is the +%% situation if our control component has terminated and this runtime component +%% is not allowed to exist on its own for ever. +%% Also note that after we have stopped tracing, for any reason, it is not +%% impossible that we receive the EXIT signals from still working parts that +%% we are now shuting down. This is no problem, the code will mearly update +%% the loopdata structure once again. +%% Returns 'exit' indicating that the runtime component shall terminate now, +%% {NewLoopData,NewTimeOut} if the exit-signal resulted in an overload check, or +%% a new loopdata structure shall we ignore the exit, or it simply resulted in +%% a state-change. +act_on_exit(Parent,_Reason,#rt{parent=Parent}) -> + exit; +act_on_exit(_Pid,running_alone,_LoopData) -> + exit; +act_on_exit(MetaTracer,_Reason,LoopData=#rt{meta_tracer=MetaTracer}) -> + LoopData#rt{meta_tracer=undefined}; % It does not exit anylonger. +act_on_exit(Port,Reason,LoopData=#rt{tracer_port=Port}) -> + send_event({port_down,node(),Reason},LoopData), + _NewLoopData=do_stop_tracing(LoopData); +act_on_exit(AutoStarter,_Reason,LoopData=#rt{auto_starter=AutoStarter}) -> + LoopData#rt{auto_starter=undefined}; % The autostarter has terminated. +act_on_exit(Pid,Reason,LoopData) -> + case remove_fetcher_ld(Pid,LoopData) of + {true,NewLoopData} -> % Yes it really was a fetcher. + NewLoopData; + false -> % No it was not a fetcher. + act_on_exit_overload(Pid,Reason,LoopData) + end. + +%% Help function checking if this exit has anything to do with an overload +%% mechanism. Note that here we run the overload mechanism regardless of +%% if we are tracing or not. This because an exit signal from the overload +%% must most likely always be handled. +act_on_exit_overload(Pid,Reason,LoopData) -> + if + LoopData#rt.overload/=?NO_LOADCHECK -> + {_NewLD,_NewTimeOut}= + do_check_overload(LoopData, + {'EXIT',{Pid,Reason,LoopData#rt.overload_data}}); + true -> % Overload not in use. + LoopData + end. +%% ----------------------------------------------------------------------------- + + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%% ============================================================================== +%% Various help functions. +%% ============================================================================== + +%% Help function which calculates a new now-tuple by adding Interval milliseconds +%% to the first argument. Note that Interval may be 'infinity' too. +%% Returns a new now-tuple or "bigvalue" which is greater than any now-tuple. +add_to_now({MegSec,Sec,MicroSec},Interval) when is_integer(Interval) -> + NewSec=Sec+(Interval div 1000), + if + NewSec>=1000000 -> + {MegSec+1,NewSec-1000000,MicroSec}; + true -> + {MegSec,NewSec,MicroSec} + end; +add_to_now(_,infinity) -> + "bigvalue". +%% ------------------------------------------------------------------------------ + +%% Help function calculating the difference in milliseconds between its first +%% and second argument. This is useful when calculating an after timeout value +%% from current now() and next_loadcheck value. +calc_diff_to_now(T1={_,_,_},T2={_,_,_}) -> + TimeOut1=timer:now_diff(T2,T1), % The difference in microseconds. + if + TimeOut1<0 -> + 0; + true -> % Make milliseconds out of it. + TimeOut1 div 1000 + end; +calc_diff_to_now(_T1,_) -> % Next loadcheck is not activated. + infinity. % The the after timeout is infinity. +%% ------------------------------------------------------------------------------ + + +%% Help function returning information about this runtime component. +collect_node_info(#rt{vsn=VSN,state=State,status=Status,tag=Tag}) -> + {node_info,node(),self(),VSN,State,Status,Tag}. +%% ------------------------------------------------------------------------------ + +%% Help function sending information to the control component that state/status +%% change has occurred. Returns nothing significant. +send_event(state_change,LoopData=#rt{ctrl=CtrlPid}) when is_pid(CtrlPid) -> + Event={trace_event,{state_change,node(),{LoopData#rt.state,LoopData#rt.status}}}, + CtrlPid ! Event; +send_event(Event,#rt{ctrl=CtrlPid}) when is_pid(CtrlPid) -> + CtrlPid ! {event,Event}; +send_event(_,_) -> % We have no control to send to! + true. % Maybe tracing alone after autostart. +%% ------------------------------------------------------------------------------ + +%% Help function initializing the overload protection mechanism. This may be +%% necessary if it is a port program or similar. Returns {ok,Data} or 'void'. +%% The datastructure vill be given to LoadMF as argument whenever loadchecks +%% are done. +initialize_overload(#rt{overload={_MF,_Interval,{M,F,Args},_RemoveMFA}}) -> + case catch apply(M,F,Args) of + {ok,Data} -> + Data; + _ -> % 'EXIT' or other faulty returnvalue. + void + end; +initialize_overload(_) -> + void. +%% ------------------------------------------------------------------------------ + +%% Help function which terminates an overload protection mechanism. +%% Returns nothing significant. +terminate_overload(#rt{overload={_MF,_Interval,_InitMFA,{M,F,Args}}, + overload_data=Data}) -> + catch apply(M,F,[Data|Args]), % Interested in the side-effect. + true; +terminate_overload(_) -> + true. +%% ------------------------------------------------------------------------------ + + +%% Help function which checks that a process specified for trace flags is correct. +%% Either the built-in "aliases" for groups of processes, a pid, a locally registered +%% name. This function also works for globally registered names. It must then +%% first be established that the process is local for this node before setting any +%% process flags. +%% Returns {ok,PidSpec}, 'false' or {error,Reason}. +check_traceflag_pidspec(all) -> {ok,all}; +check_traceflag_pidspec(new) -> {ok,new}; +check_traceflag_pidspec(existing) -> {ok,existing}; +check_traceflag_pidspec(Name) when is_atom(Name) -> + check_traceflag_pidspec({local,Name}); +check_traceflag_pidspec({local,A}) when is_atom(A) -> + case whereis(A) of + undefined -> % Then it is considered faulty. + {error,{nonexistent_name,A}}; + Pid when is_pid(Pid) -> + {ok,Pid} + end; +check_traceflag_pidspec({global,Name}) when is_atom(Name) -> + case global:whereis_name(Name) of + undefined -> % Then the name does not exist at all. + {error,{nonexistent_name,{global,Name}}}; + Pid when is_pid(Pid) -> % Ok, but must check that it is here. + if + node()==node(Pid) -> + {ok,Pid}; + true -> % Pid is not at this node. + false % Not an error but cant be used. + end + end; +check_traceflag_pidspec(Pid) when is_pid(Pid) -> + {ok,Pid}; +check_traceflag_pidspec(Proc) -> + {error,{faulty,Proc}}. +%% ------------------------------------------------------------------------------ + +%% Help function removing all trace flags from all processes. Useful in connection +%% with suspend. Returns nothing significant. +do_clear_trace_flags() -> + erlang:trace(all, false, [all]). +%% ------------------------------------------------------------------------------ + +%% Help function which checks that only valid process trace flags are mentioned. +%% In order to create better fault reports. +%% Returns a list of the approved flags, or {error,Reason}. +check_flags(Flags) -> + check_flags_2(Flags,Flags). + +check_flags_2([send|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2(['receive'|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([call|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([return_to|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([procs|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([garbage_collection|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([running|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_spawn|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_first_spawn|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_link|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([timestamp|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([arity|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([silent|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([],Flags) -> Flags; +check_flags_2([Faulty|_],_Flags) -> {error,{bad_flag,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Help function which checks parameters to erlang:trace_pattern. The purpose of +%% the function is to avoid to get multiple error return values in the return +%% list for a pattern used together with a regexp expanded module name. +check_pattern_parameters(Mod,Func,Arity,MS) -> + if + (Mod=='_') and (Func=='_') and (Arity=='_') and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + (is_atom(Mod) and (Mod/='_')) and (Func=='_') and (Arity=='_') and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + (is_atom(Mod) and (Mod/='_')) and + (is_atom(Func) and (Func/='_')) and + ((Arity=='_') or is_integer(Arity)) and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + true -> + error + end. +%% ----------------------------------------------------------------------------- + +%% Help function finding out if Mod is loaded, and if not, if it can successfully +%% be loaded. The Opts list can prevent modules from being loaded. +%% Returns 'true' or 'false'. +load_module_on_option(Mod,Opts) when is_list(Opts) -> + case lists:member(no_loadcheck,Opts) of + true -> % Then just skip this, return true. + true; + false -> + case erlang:module_loaded(Mod) of + true -> + true; % It is loaded, do no more. + false -> + case lists:member(only_loaded,Opts) of + true -> % Then, make no attempts to load. + false; + false -> % Try to load! + case code:ensure_loaded(Mod) of + {module,_Mod} -> % Successfully loaded! + true; + {error,_Reason} -> + false + end + end + end + end; +load_module_on_option(Mod,_Opts) -> % Most likely Opts not a list! + load_module_on_option(Mod,[]). % Call without options. +%% ----------------------------------------------------------------------------- + +%% Help function taking a tuplelist of options turning them into a loopdata +%% structure. Returns the loopdata structure with the new values changed. +read_option_list([],LD) -> % Done, return loopdata. + LD; +read_option_list([{dependency,{Value,Node}}|Rest],LD) -> + read_option_list(Rest,LD#rt{dependency={Value,Node}}); +read_option_list([{dependency,Value}|Rest],LD) when is_integer(Value);Value==infinity -> + read_option_list(Rest,LD#rt{dependency={Value,node()}}); +read_option_list([overload|Rest],LD) -> % So that we can remove loadcheck. + read_option_list(Rest,LD#rt{overload=?NO_LOADCHECK}); +read_option_list([{overload,{MF,Interval}}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={MF,Interval,void,void}}); +read_option_list([{overload,{MF,Interval,InitMFA,RemoveMFA}}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={MF,Interval,InitMFA,RemoveMFA}}); +read_option_list([{overload,Interval}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={fun ?DEFAULT_OVERLOAD_FUNC/1, + Interval, + void, + void}}); +read_option_list([_|Rest],LD) -> % Unknown option. + read_option_list(Rest,LD). +%% ----------------------------------------------------------------------------- + +%% Help function which returns the version number for the runtime_tools +%% application. Since it is called from within the runtime_tools application +%% we can be "sure" that it really exists. +get_application_vsn() -> + {value,{_,_,VSN}}=lists:keysearch(runtime_tools,1,application:loaded_applications()), + VSN. +%% ----------------------------------------------------------------------------- + +%% Help function that examines an argument to determine if it is a list of files +%% or tracerdata. This since they are both complex structures, looking alike. +%% Returns 'tracerdata', 'files', 'false' or 'error'. Error is returned if it +%% can not be decided which it is. +is_list_of_files_or_tracerdata(What) -> + case inviso_rt_lib:is_tracerdata(What) of + true -> + tracerdata; + false -> + if + What==[] -> + false; + is_list(What),is_list(hd(What)) -> + files; + is_list(What) -> + case lists:keysearch(trace_log,1,What) of + {value,_} -> + files; + false -> + case lists:keysearch(ti_log,1,What) of + {value,_} -> + files; + false -> + error % Neither tracerdata nor list of files. + end + end; + true -> + error + end + end. +%% ------------------------------------------------------------------------------ + +%% Help function which removes all files in the ListOfFiles, assuming they +%% are located in Dir. +%% Returns a list of [{ok,FileName},...{error,Reason},...] +delete_files(Dir,ListOfFiles) -> + delete_files_2(Dir,ListOfFiles, []). + +delete_files_2(Dir,[File|Tail],Reply) when is_list(Dir),is_list(File) -> + case catch file:delete(filename:join(Dir,File)) of + ok -> + delete_files_2(Dir,Tail,[{ok,File}|Reply]); + {error,Posix} -> + delete_files_2(Dir,Tail,[{error,{Posix,File}}|Reply]); + {'EXIT',_Reason} -> % Probably not proper string. + delete_files_2(Dir,Tail,[{error,{badarg,[Dir,File]}}|Reply]) + end; +delete_files_2(Dir,[Faulty|Tail],Reply) -> + delete_files_2(Dir,Tail,[{error,{badarg,[Dir,Faulty]}}|Reply]); +delete_files_2(_,[],Reply) -> + Reply. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all trace logs belonging to this tracerdata. +%% Note that this function operates on internal LogTD structures. +list_logs_tracelog({file,FileName}) when is_list(FileName) -> + case file:read_file_info(FileName) of + {ok,_} -> % The file exists. + {filename:dirname(FileName),[filename:basename(FileName)]}; + _ -> % The file does not exist + {filename:dirname(FileName),[]} + end; +list_logs_tracelog({file,Wrap}) when is_tuple(Wrap),element(2,Wrap)==wrap -> + case {element(1,Wrap),element(3,Wrap)} of + {FileName,Tail} when is_list(FileName),is_list(Tail) -> + case catch {filename:dirname(FileName),list_wrapset(FileName,Tail)} of + {'EXIT',_Reason} -> % Garbage in either lists. + {"",no_log}; % Interpret as no log for tracerdata. + Tuple -> + Tuple + end; + _ -> + {"",no_log} + end; +list_logs_tracelog(void) -> % Trace log not used. + {"",no_log}; +list_logs_tracelog(_) -> % Some fun or similar. + {"",no_log}. % Then there are no files to report. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all ti-files belonging to this tracerdata. +%% Note that this function operates on the internal TiTD structure. +list_logs_tilog(TiTD) + when tuple_size(TiTD)>=2,element(1,TiTD)==file,is_list(element(2,TiTD)) -> + FileName=element(2,TiTD), + case file:read_file_info(FileName) of + {ok,_} -> % Yes the file exists. + {filename:dirname(FileName),[filename:basename(FileName)]}; + _ -> + {filename:dirname(FileName),[]} + end; +list_logs_tilog(void) -> % Internal representation for + {"",no_log}; % ti-file not in use. +list_logs_tilog(_) -> + {"",no_log}. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all files belonging to the wrap-set specified by +%% Prefix and Suffix. Note that there can be a directory in Prefix as well. +%% Will fail if either of Prefix or Suffix are not proper strings. +%% Returns a list of files, without dirname. +list_wrapset(Prefix,Suffix) -> + Name=filename:basename(Prefix), + Dirname=filename:dirname(Prefix), + case file:list_dir(Dirname) of + {ok,Files} -> + RegExp="^"++list_wrapset_escapes(Name)++"[0-9]+"++ + list_wrapset_escapes(Suffix)++"$", + list_wrapset_2(Files,RegExp); + {error,_Reason} -> % Translate this to no files! + [] + end. + +list_wrapset_2([File|Rest],RegExp) -> + Length=length(File), + case regexp:first_match(File,RegExp) of + {match,1,Length} -> % This is a member of the set. + [File|list_wrapset_2(Rest,RegExp)]; + _ -> + list_wrapset_2(Rest,RegExp) + end; +list_wrapset_2([],_) -> + []. + +%% Help function which inserts escape characters infront of characters which +%% will otherwise be missinterpreted by the regexp function as meta rather than +%% just the character itself. +list_wrapset_escapes([$.|Rest]) -> + [$\\,$.|list_wrapset_escapes(Rest)]; +list_wrapset_escapes([Char|Rest]) -> + [Char|list_wrapset_escapes(Rest)]; +list_wrapset_escapes([]) -> + []. +%% ----------------------------------------------------------------------------- + + + + + + +%% ============================================================================== +%% Handler functions for implementing simple trace-message handlers. +%% ============================================================================== +%% +%% A handler must be a function taking two arguments. The first is the trace- +%% message. The second is datastructure used by the handler. The handler shall +%% returns (possibly) new datastructure. + +%% ------------------------------------------------------------------------------ +%% Function implementing a relayer. This function is used to creat a fun handler +%% if the relay option is used in tracer-data. +%% ------------------------------------------------------------------------------ +relay_handler(Msg,Tracer) -> + Tracer ! Msg, + Tracer. + +%% ------------------------------------------------------------------------------ +%% Function implementing a default terminal io handler. +%% ------------------------------------------------------------------------------ + +dhandler(end_of_trace, Out) -> + Out; +dhandler(Trace, Out) when element(1, Trace) == trace, + tuple_size(Trace) >= 3 -> + dhandler1(Trace, tuple_size(Trace), Out); +dhandler(Trace, Out) when element(1, Trace) == trace_ts, + tuple_size(Trace) >= 4 -> + dhandler1(Trace, tuple_size(Trace)-1, Out); +dhandler(Trace, Out) when element(1, Trace) == drop, + tuple_size(Trace) == 2 -> + io:format(Out, "*** Dropped ~p messages.~n", [element(2,Trace)]), + Out; +dhandler(Trace, Out) when element(1, Trace) == seq_trace, + tuple_size(Trace) >= 3 -> + SeqTraceInfo = case Trace of + {seq_trace, Lbl, STI, TS} -> + io:format(Out, "SeqTrace ~p [~p]: ", + [TS, Lbl]), + STI; + {seq_trace, Lbl, STI} -> + io:format(Out, "SeqTrace [~p]: ", + [Lbl]), + STI + end, + case SeqTraceInfo of + {send, Ser, Fr, To, Mes} -> + io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n", + [Fr, To, Mes, Ser]); + {'receive', Ser, Fr, To, Mes} -> + io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n", + [To, Mes, Ser, Fr]); + {print, Ser, Fr, _, Info} -> + io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n", + [Info, Ser, Fr]); + Else -> + io:format(Out, "~p~n", [Else]) + end, + Out; +dhandler(_Trace, Out) -> + Out. + +dhandler1(Trace, Size, Out) -> +%%%! Self = self(), + From = element(2, Trace), + case element(3, Trace) of + 'receive' -> + case element(4, Trace) of + {dbg,ok} -> ok; + Message -> io:format(Out, "(~p) << ~p~n", [From,Message]) + end; + 'send' -> + Message = element(4, Trace), + case element(5, Trace) of +%%%! This causes messages to disappear when used by ttb (observer). Tests +%%%! so far show that there is no difference in results with dbg even if I +%%%! comment it out, so I hope this is only some old code which isn't +%%%! needed anymore... /siri +%%%! Self -> ok; + To -> io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message]) + end; + call -> + case element(4, Trace) of + MFA when Size == 5 -> + Message = element(5, Trace), + io:format(Out, "(~p) call ~s (~p)~n", + [From,ffunc(MFA),Message]); + MFA -> + io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)]) + end; + return -> %% To be deleted... + case element(4, Trace) of + MFA when Size == 5 -> + Ret = element(5, Trace), + io:format(Out, "(~p) old_ret ~s -> ~p~n", + [From,ffunc(MFA),Ret]); + MFA -> + io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)]) + end; + return_from -> + MFA = element(4, Trace), + Ret = element(5, Trace), + io:format(Out, "(~p) returned from ~s -> ~p~n", + [From,ffunc(MFA),Ret]); + return_to -> + MFA = element(4, Trace), + io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]); + spawn when Size == 5 -> + Pid = element(4, Trace), + MFA = element(5, Trace), + io:format(Out, "(~p) spawn ~p as ~s~n", [From,Pid,ffunc(MFA)]); + Op -> + io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)]) + end, + Out. + + +%%% These f* functions returns non-flat strings + +%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" +%% {M,F,A} -> "M:F/A" +ffunc({M,F,Argl}) when is_list(Argl) -> + io_lib:format("~p:~p(~s)", [M, F, fargs(Argl)]); +ffunc({M,F,Arity}) -> + io_lib:format("~p:~p/~p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~p", [X]). + +%% Integer -> "Integer" +%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" +fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); +fargs([]) -> []; +fargs([A]) -> io_lib:format("~p", [A]); %% last arg +fargs([A|Args]) -> [io_lib:format("~p,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~p", [A]). % last or only arg + +%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" +ftup(Trace, Index, Index) -> + io_lib:format("~p", [element(Index, Trace)]); +ftup(Trace, Index, Size) -> + [io_lib:format("~p ", [element(Index, Trace)]) + | ftup(Trace, Index+1, Size)]. +%% ------------------------------------------------------------------------------ + +%% ============================================================================== +%% Functions handling the trace-port. Copied from dbg.erl +%% ============================================================================== + +trace_port_control(Port, flush) -> + case trace_port_control(Port, $f, "") of + {ok, [0]} -> ok; + {ok, _} -> {error, not_supported_by_trace_driver}; + Other -> Other + end. + +trace_port_control(Port, Command, Arg) when is_port(Port)-> + case catch port_control(Port, Command, Arg) of + {'EXIT', _} -> {error, {no_trace_driver, node()}}; + Result -> Result + end. + + +trace_port(file, {Filename, wrap, Tail}) -> + trace_port(file, {Filename, wrap, Tail, 128*1024}); +trace_port(file, {Filename, wrap, Tail, WrapSize}) -> + trace_port(file, {Filename, wrap, Tail, WrapSize, 8}); +trace_port(file, {Filename, wrap, Tail, WrapSize, WrapCnt}) + when is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, WrapSize, WrapCnt, 0}); +trace_port(file, {Filename, wrap, Tail, {time, WrapTime}, WrapCnt}) + when is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, 0, WrapCnt, WrapTime}); +trace_port(file, Filename) when is_list(Filename) -> + trace_port1(file, Filename, nowrap); + +trace_port(ip, Portno) when is_integer(Portno) -> + trace_port(ip,{Portno,50}); + +trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) -> + fun() -> + Driver = "trace_ip_drv", + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + L = lists:flatten( + io_lib:format("~s ~p ~p 2", + [Driver, Portno, Qsiz])), + open_port({spawn, L}, [eof]) + end. + +trace_port1(file, Filename, Options) -> + Driver = "trace_file_drv", + fun() -> + Name = filename:absname(Filename), + %% Absname is needed since the driver uses + %% the supplied name without further investigations, + %% and if the name is relative the resulting path + %% might be too long which can cause a bus error + %% on vxworks instead of a nice error code return. + %% Also, the absname must be found inside the fun, + %% in case the actual node where the port shall be + %% started is on another node (or even another host) + {Wrap, Tail} = + case Options of + {wrap, T, WrapSize, WrapCnt, WrapTime} -> + {lists:flatten( + io_lib:format("w ~p ~p ~p ~p ", + [WrapSize, WrapCnt, WrapTime, + length(Name)])), + T}; + nowrap -> + {"", ""} + end, + Command = Driver ++ " " ++ Wrap ++ "n " ++ Name ++ Tail, + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + if element(1, Options) == wrap -> + %% Delete all files from any previous wrap log + Files = wrap_postsort(wrap_presort(Name, Tail)), + lists:foreach( + fun(N) -> file:delete(N) end, + Files); + true -> ok + end, + open_port({spawn, Command}, [eof]) + end. + +%% Find all possible wrap log files. +%% Returns: a list of sort converted filenames. +%% +%% The sort conversion is done by extracting the wrap sequence counter +%% from the filename, and calling wrap_encode/2. +wrap_presort(Filename, Tail) -> + Name = filename:basename(Filename), + Dirname = filename:dirname(Filename), + case file:list_dir(Dirname) of + {ok, Files} -> + lists:zf( + fun(N) -> + case match_front(N, Name) of + false -> + false; + X -> + case match_rear(X, Tail) of + false -> + false; + C -> % Counter + case match_0_9(C) of + true -> + {true, +% filename:join(Dirname, N)} + wrap_encode( + filename:join(Dirname, N), + C)}; + false -> + false + end + end + end + end, + Files); + _ -> + [] + end. + +%% Extract the filenames from a list of sort converted ones. +wrap_postsort(Files) -> + lists:map(fun wrap_name/1, Files). + +wrap_encode(N, C) -> + {list_to_integer(C), N}. + +wrap_name({_C, N}) -> + N. + +%% Returns what is left of ListA when removing all matching +%% elements from ListB, or false if some element did not match, +%% or if ListA runs out of elements before ListB. +match_front(ListA, []) when is_list(ListA) -> + ListA; +match_front([], ListB) when is_list(ListB) -> + false; +match_front([Hd|TlA], [Hd|TlB]) -> + match_front(TlA,TlB); +match_front([_HdA|_], [_HdB|_]) -> + false. + +%% Reversed version of match_front/2 +match_rear(ListA, ListB) when is_list(ListA), is_list(ListB) -> + case match_front(lists:reverse(ListA), lists:reverse(ListB)) of + false -> + false; + List -> + lists:reverse(List) + end. + +%% Returns true if the non-empty list arguments contains all +%% characters $0 .. $9. +match_0_9([]) -> + false; +match_0_9([H]) when is_integer(H), $0 =< H, H =< $9 -> + true; +match_0_9([H|T]) when is_integer(H), $0 =< H, H =< $9 -> + match_0_9(T); +match_0_9(L) when is_list(L) -> + false. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Functions working on the tracerdata structure. +%% ----------------------------------------------------------------------------- + +%% Tracerdata is the structure which specifies to where tracing is logged at this +%% runtime component. It may now (and in the future specify) several things. +%% Currently it can consist of: +%% LogTD: specifying how trace-log data shall be handled. +%% TiTD : trace information, specifying how trace information shall be handled. +%% +%% Tracerdata may also contain quick or standard forms of LogTD and/or TiTD. +%% For instance if a standard handler-fun shall be used. The handler fun is not +%% part of the tracerdata but rather specified by a constant. + + +%% Help function that translates an input-tracerdata to useful internal formats. +%% This since the tracerdata may consist of specifications which shall be +%% translated into funs or similar. +%% Returns {ok,LogTD,TiTD} or {error,Reason}. +%% Note that TiTD may be 'void' since TiTD is not mandatory. +translate_td(TracerData) when is_list(TracerData) -> % Both log and ti. + case translate_td_logtd(get_trace_log_tracerdata(TracerData)) of + {ok,LogTD} -> + case translate_td_titd(get_ti_log_tracerdata(TracerData)) of + {ok,TiTD} -> + {ok,LogTD,TiTD}; + {error,Reason} -> + {error,Reason} + end; + {error,Reason} -> + {error,Reason} + end; +translate_td(TracerData) -> % The it is just LogTD!? + case translate_td_logtd(TracerData) of + {ok,LogTD} -> + {ok,LogTD,void}; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +%% Help function translating trace-log tracerdata. +translate_td_logtd(collector) -> % This rt will act as receiver. + {ok,{fun dhandler/2,user}}; % Simple terminal io. +translate_td_logtd({relayer,Tracer}) when is_pid(Tracer) -> + {ok,{fun relay_handler/2,Tracer}}; % Relay trace-msg to Tracer-pid. +translate_td_logtd({HandlerFun,Data}) when is_function(HandlerFun) -> + {ok,{HandlerFun,Data}}; % Own invented fun. +translate_td_logtd({Type,Parameters}) when Type==ip;Type==file -> + {ok,{Type,Parameters}}; % Built in trace-port +translate_td_logtd(false) -> % Unusual but no trace log. + {ok,void}; +translate_td_logtd(Arg) -> + {error,{bad_log_td,Arg}}. +%% ----------------------------------------------------------------------------- + +%% Help function translating ti-log tracerdata. +translate_td_titd(TiTD={file,FileName}) when is_list(FileName) -> + {ok,TiTD}; +translate_td_titd({file,FileName, + {InitPublLDmfa={M1,F1,L1}, + RemovePublLDmf={M2,F2}, + CleanPublLDmf={M3,F3}}}) + when is_list(FileName),is_atom(M1),is_atom(F1),is_atom(M2),is_atom(F2),is_list(L1),is_atom(M3),is_atom(F3) -> + {ok,{file,FileName,{InitPublLDmfa,RemovePublLDmf,CleanPublLDmf}}}; +translate_td_titd({file,FileName, + {InitPublLDmfa={M1,F1,L1}, + void, + CleanPublLDmf={M3,F3}}}) + when is_list(FileName),is_atom(M1),is_atom(F1),is_list(L1),is_atom(M3),is_atom(F3) -> + {ok,{file,FileName,{InitPublLDmfa,void,CleanPublLDmf}}}; +translate_td_titd(false) -> % Means no ti-tracerdata. + {ok,void}; +translate_td_titd(TiTD) -> + {error,{bad_ti_td,TiTD}}. +%% ----------------------------------------------------------------------------- + +%% This function retrieves the trace-log part of a TracerData list structure. +%% Returns TraceLogTD or 'false'. +get_trace_log_tracerdata(TracerData) -> + case lists:keysearch(trace,1,TracerData) of + {value,{_,LogTD}} -> + LogTD; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% This function retrieves the ti-log part of a TracerData list structure. +%% Returns TiLogTD or 'false'. +get_ti_log_tracerdata(TracerData) -> + case lists:keysearch(ti,1,TracerData) of + {value,{_,TiTD}} -> + TiTD; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Help function which checks that parameters to the built in trace-port are +%% sane. +check_traceport_parameters(Type,Args) -> + case {Type,Args} of + {file,{FileName,wrap,Tail}} when is_list(FileName),is_list(Tail) -> + ok; + {file,{FileName,wrap,Tail,WrapSize}} + when is_list(FileName), + is_list(Tail), + is_integer(WrapSize),WrapSize>=0,WrapSize< (1 bsl 32) -> + ok; + {file,{FileName,wrap,Tail,WrapSize,WrapCnt}} + when is_list(FileName),is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + ok; + {file,{FileName,wrap,Tail,{time,WrapTime},WrapCnt}} + when is_list(FileName),is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + ok; + {file,FileName} when is_list(FileName) -> + ok; + {ip,Portno} when is_integer(Portno),Portno=<16#FFFF -> + ok; + {ip,{Portno,Qsiz}} when is_integer(Portno),Portno=<16#FFFF,is_integer(Qsiz) -> + ok; + _ -> + {error,{trace_port_args,[Type,Args]}} + end. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Default overload functionality. +%% ----------------------------------------------------------------------------- + +%% A default overload protection function. An overload function must take +%% one argument and return 'ok' or {suspend,SuspendReason}. +default_overload_func(_) -> + case process_info(self(),message_queue_len) of + {message_queue_len,N} when N > 1000 -> + {suspend,rt_max_queue_len}; + _ -> + ok + end. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Functions working on the internal loopdata structure. +%% ============================================================================= + +%% Help function simply adding Fetcher as a fetcher process to the loopdata. +%% Returns a new loopdata structure. +add_fetcher_ld(Fetcher,LD) -> + LD#rt{fetchers=[Fetcher|LD#rt.fetchers]}. +%% ----------------------------------------------------------------------------- + +%% Help function investigating if the first argument is a known fetcher process +%% or not. If it is, it also removed it from the fetchers list in the loopdata +%% structure. +%% Returns {true,NewLoopData} or 'false'. +remove_fetcher_ld(Fetcher,LD) -> + NewFetchers=lists:delete(Fetcher,LD#rt.fetchers), + if + NewFetchers/=LD#rt.fetchers -> + {true,LD#rt{fetchers=NewFetchers}}; + true -> % No it was not a fetcher process. + false + end. +%% ----------------------------------------------------------------------------- + +%%% end of file + diff --git a/lib/runtime_tools/src/inviso_rt_lib.erl b/lib/runtime_tools/src/inviso_rt_lib.erl new file mode 100644 index 0000000000..2c6964e53e --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt_lib.erl @@ -0,0 +1,474 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% +%% ------------------------------------------------------------------------------ +%% File : inviso_rt_lib.erl +%% Author : Lennart Öhman +%% Description : +%% +%% Created : 27 Sep 2005 by Lennart Öhman +%% ------------------------------------------------------------------------------ +-module(inviso_rt_lib). + +-export([expand_regexp/2,expand_regexp/3,expand_regexp/4]). +-export([is_tracerdata/1]). +-export([transform/2]). + +-export([rpc/4,rpc/5,match_modules/2,match_modules/3]). +-export([debug/3]). + +%% ------------------------------------------------------------------------------ + +%% ============================================================================== +%% Exported API functions. +%% ============================================================================== + +%% ------------------------------------------------------------------------------ +%% expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) = [{Node,Answer},...] | {error,Reason} +%% expand_regexp(Nodes,RegExpMod,Opts) = [{Node,Answer},...] | {error,Reason} +%% expand_regexp(RegExpDir,RegExpMod,Opts) = ListOfModules | {error,Reason} +%% expand_regexp(RegExpMod,Opts) = ListOfModules | {error,Reason} +%% Nodes=List of all nodes (atoms) where to expand. +%% RegExpDir=Reg.exp (string) specifying directories. +%% RegExpMod=Reg.exp (string) specifying module names. +%% Node=node name (atom). +%% Opts=[Opt,...] +%% Opt=only_loaded +%% Answer=List of modules (atoms) | 'badrpc' +%% +%% Expands, concurrently, the regular expression on Nodes and returns a list +%% of what modules it expanded to on the different nodes. Note that it may +%% differ between Erlang nodes depending on whether the modules are the same +%% or not. Also note that all modules becomes loaded as a result. +%% RegExpDir can further limit the modules. It introduces the requirement that +%% a module must be loaded from a directory with a path satisfying the RegExpDir. +%% All regular expression are according to the standard lib regexp module. +expand_regexp(RegExpMod,Opts) when is_list(RegExpMod),is_list(Opts) -> + match_modules(RegExpMod,Opts); +expand_regexp(RegExpMod,Opts) -> + {error,{badarg,[RegExpMod,Opts]}}. +expand_regexp(NodesOrRegExpDir,RegExpMod,Opts) + when is_list(NodesOrRegExpDir),is_list(RegExpMod),is_list(Opts) -> + case is_list_of_atoms(NodesOrRegExpDir) of + true -> % Interpret as list of nodes. + lists:foreach(fun(N)->spawn(?MODULE,rpc,[self(),N,RegExpMod,Opts]) end, + NodesOrRegExpDir), + expand_regexp_answers(NodesOrRegExpDir,[]); + false -> % Interpret as a string. + match_modules(NodesOrRegExpDir,RegExpMod,Opts) + end; +expand_regexp(NodesOrRegExpDir,RegExpMod,Opts) -> + {error,{badarg,[NodesOrRegExpDir,RegExpMod,Opts]}}. +expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) + when is_list(Nodes),is_list(RegExpDir),is_list(RegExpMod),is_list(Opts) -> + lists:foreach(fun(N)-> + spawn(?MODULE,rpc,[self(),N,RegExpDir,RegExpMod,Opts]) + end, + Nodes), + expand_regexp_answers(Nodes,[]); +expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) -> + {error,{badarg,[Nodes,RegExpDir,RegExpMod,Opts]}}. + +expand_regexp_answers([],Answers) -> Answers; % List of [{Node,Answer},...]. +expand_regexp_answers(Nodes,Answers) -> + receive + {?MODULE,Node,Answer} -> + expand_regexp_answers(lists:delete(Node,Nodes),[{Node,Answer}|Answers]) + end. +%% ------------------------------------------------------------------------------ + +%% is_tracerdata(TracerData)=true|false +%% Answers the question if TracerData is proper tracerdata. Note that true can be +%% returned if it resembles tracerdata very closely. +is_tracerdata({Fun,_Data}) when is_function(Fun) -> true; +is_tracerdata({relayer,To}) when is_pid(To);is_atom(To) -> true; +is_tracerdata(collector) -> true; +is_tracerdata({file,Param}) when is_tuple(Param);is_list(Param) -> true; +is_tracerdata({ip,_Param}) -> true; +is_tracerdata([{trace,LogTD}|Rest]) -> + case is_tracerdata(LogTD) of + true -> + is_tracerdata(Rest); + false -> + false + end; +is_tracerdata([{ti,TiData}|Rest]) -> + case is_tidata(TiData) of + true -> + is_tracerdata(Rest); + false -> + false + end; +is_tracerdata([]) -> + true; +is_tracerdata(_) -> + false. + +is_tidata({file,FileName}) when is_list(FileName) -> true; +is_tidata({file,FileName,{M,F,Args}}) when is_list(FileName),is_atom(M),is_atom(F),is_list(Args) -> + true; +is_tidata(_) -> false. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Help functions. +%% ============================================================================== + +%% Help function intended to be run in its own process. Will report with +%% a message when done. +%% This function will be spawned on. +rpc(Parent,Node,RegExpMod,Opts) -> + case rpc:call(Node,?MODULE,match_modules,[RegExpMod,Opts]) of + {badrpc,_Reason} -> % The node is probably not healthy. + Parent ! {?MODULE,Node,badrpc}; + Modules -> + Parent ! {?MODULE,Node,Modules} + end. + +rpc(Parent,Node,RegExpDir,RegExpMod,Opts) -> + case rpc:call(Node,?MODULE,match_modules,[RegExpDir,RegExpMod,Opts]) of + {badrpc,_Reason} -> % The node is probably not healthy. + Parent ! {?MODULE,Node,badrpc}; + Modules -> + Parent ! {?MODULE,Node,Modules} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Exported function which actually shall be in code.erl. +%% ============================================================================== + +%% match_modules(RegExpMod,Actions) = [Module,...] | {error,Reason} +%% match_modules(RegExpDir,RegExpMod,Actions)=[Module,...] | {error,Reason} +%% RegExpMod=Erlang regular expression describing module names (string). +%% RegExpDir=Erlang regular expression describing directory paths(string) | +%% void +%% Actions=List of;'only_loaded'. +%% +%% Function which matches a regular expresion against module names. The function +%% can also match the directory from where the module is loaded or will be loaded +%% against a regular expresion for directory paths. +%% The function uses the same strategy as code-loading if the same module is +%% discovered in several places. +%% (1) An already loaded module shadows all other occurancies. +%% (2) .beams found in by a path shadows .beams found by paths later in the +%% code paths. +%% +%% Description of actions: +%% only_loaded: Only consider modules which are loaded. +match_modules(RegExpMod,Actions) -> + match_modules(void,RegExpMod,Actions). +match_modules(RegExpDir,RegExpMod,Actions) -> + AllLoaded=code:all_loaded(), + Mods1=handle_expand_regexp_2(AllLoaded,RegExpDir,RegExpMod,[]), + case lists:member(only_loaded,Actions) of % Shall we do not loaded too? + false -> % Ok, search all paths too then. + Paths=code:get_path(), + handle_expand_regexp_3(Paths,RegExpDir,RegExpMod,AllLoaded,Mods1); + true -> % Only loaded modules then. + Mods1 + end. + + +%% Help function which traverses all loaded modules and determines +%% which shall be returned. First we check that the module satisfies the +%% module-regexp. Then we, if a dir reg-exp is given, checks that the +%% module is loaded from an approved path. Note that if it can not be +%% determined from where it was loaded (like preloaded or cover-compiled +%% etc), but dir reg-exps are used. That module will be excluded. +%% Returns a list of modules. +handle_expand_regexp_2([{Mod,Path}|Rest],RegExpDir,RegExpMod,Result) -> + ModStr=atom_to_list(Mod), + ModLen=length(ModStr), + case regexp:first_match(ModStr,RegExpMod) of + {match,1,ModLen} -> % Ok, The regexp matches the module. + if + is_list(RegExpDir),is_atom(Path) -> % Preloaded or covercompiled... + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result); + is_list(RegExpDir),is_list(Path) -> % Dir reg-exp is used! + PathOnly=filename:dirname(Path), % Must remove beam-file name. + case regexp:first_match(PathOnly,RegExpDir) of + {match,_,_} -> % Did find a match, that is enough! + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,[Mod|Result]); + _ -> % Either error or nomatch. + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result) + end; + true -> % Otherwise already done! + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,[Mod|Result]) + end; + _ -> % Then Mod is not part of the set. + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result) + end; +handle_expand_regexp_2([],_,_,Result) -> Result. + +%% Help function which traverses all paths and looks for modules satisfying +%% the module reg.exp. +%% Returns a list of unique module names. +handle_expand_regexp_3([Path|Rest],RegExpDir,RegExpMod,AllLoaded,Result) -> + if + is_list(RegExpDir) -> % We must consider the directory name. + AbsPath= + case filename:pathtype(Path) of + absolute -> % Is already abs. + Path; + relative -> % Then it must be made absolute. + filename:absname(Path); + volumerelative -> % Only on Windows!? + filename:absname(Path) + end, + case regexp:first_match(AbsPath,RegExpDir) of + {match,_,_} -> % Ok, the directory is allowed. + NewResult=handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result), + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,NewResult); + _ -> % This directory does not qualify. + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,Result) + end; + true -> % RegExpDir is not used! + NewResult=handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result), + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,NewResult) + end; +handle_expand_regexp_3([],_,_,_,Result) -> Result. + +handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result) -> + case file:list_dir(Path) of + {ok,FileNames} -> + handle_expand_regexp_3_2(FileNames,RegExpMod,AllLoaded,Result); + {error,_Reason} -> % Bad path!? Skip it. + Result + end. + +handle_expand_regexp_3_2([File|Rest],RegExpMod,AllLoaded,Result) -> + case filename:extension(File) of + ".beam" -> % It is a beam-file. Consider it! + ModStr=filename:basename(File,".beam"), + Mod=list_to_atom(ModStr), + case {lists:keysearch(Mod,1,AllLoaded),lists:member(Mod,Result)} of + {false,false} -> % This module is not tried before. + ModLen=length(ModStr), + case regexp:first_match(ModStr,RegExpMod) of + {match,1,ModLen} -> % This module satisfies the regexp. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,[Mod|Result]); + _ -> % Error or not perfect match. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; + {_,_} -> % This module is already tested. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; + _ -> % Not a beam-file, skip it. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; +handle_expand_regexp_3_2([],_,_,Result) -> Result. +%% ------------------------------------------------------------------------------ + +%% Help function which finds out if its argument is a list of zero or more +%% atoms. +%% Returns 'true' or 'false'. +is_list_of_atoms([A|Rest]) when is_atom(A) -> + is_list_of_atoms(Rest); +is_list_of_atoms([_|_]) -> + false; +is_list_of_atoms([]) -> + true. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================= +%% Functions transforming function calls in trace-case file. +%% ============================================================================= + +%% transform(Exprs,Translations)=NewExprs +%% Exprs=list(); List of abstract format erlang terms, as returned by +%% io:parse_erl_exprs/2. +%% Translations=list(); List of translations from function calls to other +%% function calls. [{Mod,Func,Arity,{NewMod,NewFunc,ParamTransformMF}},...] +%% Mod can actually be omitted, ParamTransformMF shall be {M,F} where F is +%% a function taking one argument (the parameter list), and returning the +%% new parameter list. It can also be anything else should no transformation +%% of the parameters be the case. +%% +%% Function that transforms function calls in a trace-case file. The transform/2 +%% can only transform shallow function calls. I.e where both module and function +%% name are specified as atoms. Any binding-environment is not examined. +transform([Expr|Rest],Translations) -> + [transform_2(Expr,Translations)|transform(Rest,Translations)]; +transform([],_) -> + []. + +%% Help function handling a single expr. +transform_2({call,L1,{remote,L2,ModExpr,FuncExpr},Params},Translations) -> + case transform_2(ModExpr,Translations) of + {atom,L3,M} -> + case transform_2(FuncExpr,Translations) of + {atom,L4,F} -> % Now we have a M:F/Arity! + case do_call_translation(M,F,Params,Translations) of + {ok,NewM,NewF,NewP} -> + NewParams=transform(NewP,Translations), + {call,L1,{remote,L2,{atom,L3,NewM},{atom,L4,NewF}},NewParams}; + false -> % No translation or faulty. + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,ModExpr,FuncExpr},NewParams} + end; + NewFuncExpr -> % Not translated to a shallow term. + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,ModExpr,NewFuncExpr},NewParams} + end; + NewModExpr -> % Not translated to a shallow term. + NewFuncExpr=transform_2(FuncExpr,Translations), + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,NewModExpr,NewFuncExpr},NewParams} + end; +transform_2({call,L1,FuncExpr,Params},Translations) -> + case transform_2(FuncExpr,Translations) of + {atom,L3,F} -> % Now we have a M:F/Arity! + case do_call_translation(F,Params,Translations) of + {ok,NewM,NewF,NewP} -> % It is turned into a global call. + NewParams=transform(NewP,Translations), + {call,L1,{remote,L1,{atom,L3,NewM},{atom,L3,NewF}},NewParams}; + false -> % No translation or faulty. + NewParams=transform(Params,Translations), + {call,L1,FuncExpr,NewParams} + end; + NewFuncExpr -> % Not translated to a shallow term. + NewParams=transform(Params,Translations), + {call,L1,NewFuncExpr,NewParams} + end; +transform_2({match,L,P,E},Translations) -> + NewPattern=transform_2(P,Translations), + NewExpr=transform_2(E,Translations), + {match,L,NewPattern,NewExpr}; +transform_2({op,L,Op,Arg1,Arg2},Translations) -> + NewArg1=transform_2(Arg1,Translations), + NewArg2=transform_2(Arg2,Translations), + {op,L,Op,NewArg1,NewArg2}; +transform_2({op,L,Op,Arg},Translations) -> + NewArg=transform_2(Arg,Translations), + {op,L,Op,NewArg}; +transform_2({block,L,Body},Translations) -> + NewBody=transform(Body,Translations), + {block,L,NewBody}; +transform_2({'if',L,Clauses},Translations) -> + NewClauses=transform_clauses(Clauses,Translations), + {'if',L,NewClauses}; +transform_2({'case',L,Func,Clauses},Translations) -> + NewFunc=transform_2(Func,Translations), + NewClauses=transform_clauses(Clauses,Translations), + {'case',L,NewFunc,NewClauses}; +transform_2({'fun',L,{clauses,Clauses}},Translations) -> + NewClauses=transform_clauses(Clauses,Translations), + {'fun',L,NewClauses}; +transform_2({lc,L,Items,GeneratorsFilters},Translations) -> + NewItem=transform_2(Items,Translations), + NewGensAndFilters=transform_gensandfilters(GeneratorsFilters,Translations), + {lc,L,NewItem,NewGensAndFilters}; +transform_2({'catch',L,Expr},Translations) -> + NewExpr=transform_2(Expr,Translations), + {'catch',L,NewExpr}; +transform_2({tuple,L,Elements},Translations) -> + NewElements=transform(Elements,Translations), + {tuple,L,NewElements}; +transform_2({cons,L,Element,Tail},Translations) -> + NewElement=transform_2(Element,Translations), + NewTail=transform_2(Tail,Translations), + {cons,L,NewElement,NewTail}; +transform_2({nil,L},_) -> + {nil,L}; +transform_2({bin,L,Elements},Translations) -> + NewElements=transform_binary(Elements,Translations), + {bin,L,NewElements}; +transform_2(Expr,_) -> % Can be a var for instance. + Expr. + +transform_binary([{bin_element,L,Val,Size,TSL}|Rest],Translations) -> + NewVal=transform_2(Val,Translations), + NewSize=transform_2(Size,Translations), + [{bin_element,L,NewVal,NewSize,TSL}|transform_binary(Rest,Translations)]; +transform_binary([],_) -> + []. + +transform_clauses([{clause,L,Pattern,Guards,Body}|Rest],Translations) -> + NewPattern=transform(Pattern,Translations), + NewBody=transform(Body,Translations), + [{clause,L,NewPattern,Guards,NewBody}|transform_clauses(Rest,Translations)]; +transform_clauses([],_Translations) -> + []. + +transform_gensandfilters([{generator,L,Pattern,Exprs}|Rest],Translations) -> + NewExprs=transform(Exprs,Translations), + [{generator,L,Pattern,NewExprs}|transform_gensandfilters(Rest,Translations)]; +transform_gensandfilters([Expr|Rest],Translations) -> + [transform_2(Expr,Translations)|transform_gensandfilters(Rest,Translations)]; +transform_gensandfilters([],_) -> + []. +%% ------------------------------------------------------------------------------ + +%% This is the heart of the translation functionality. Here we actually try to +%% replace calls to certain functions with other calls. This can include removing +%% arguments. +do_call_translation(M,F,Params,Translations) -> + case lists:keysearch({M,F,length(Params)},1,Translations) of + {value,{_,{NewM,NewF,ArgFun}}} -> % Lets transform the function. + do_call_translation_2(Params,NewM,NewF,ArgFun); + _ -> + false % No translations at all. + end. +do_call_translation(F,Params,Translations) -> + case lists:keysearch({F,length(Params)},1,Translations) of + {value,{_,{NewM,NewF,ArgFun}}} -> % Lets transform the function. + do_call_translation_2(Params,NewM,NewF,ArgFun); + _ -> + false % No translations at all. + end. + +do_call_translation_2(Params,NewM,NewF,ArgFun) -> + case ArgFun of + {M,F} when is_atom(M),is_atom(F) -> + case catch M:F(Params) of + {'EXIT',_Reason} -> + false; % If it does not work, skipp it. + MungedParams when is_list(MungedParams) -> + {ok,NewM,NewF,MungedParams}; + _ -> + false + end; + _ -> % No munging of parameters. + {ok,NewM,NewF,Params} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================= +%% Functions for the runtime component internal debugging system. +%% ============================================================================= + +%% The debug system is meant to provide tracing of ttb at different levels. +%% +%% debug(What,Level,Description) -> nothing significant. +%% What : controls what kind of event. This can both be certain parts of ttb +%% as well as certain levels (info to catastrophy). +%% Level: Determines if What shall be printed or not. +%% Description: this is what happend. +debug(off,_What,_Description) -> + true; % Debug is off, no action. +debug(On,What,Description) -> + debug_2(On,What,Description). + +debug_2(_,What,Description) -> + io:format("INVISO DEBUG:~w, ~p~n",[What,Description]). +%% ----------------------------------------------------------------------------- diff --git a/lib/runtime_tools/src/inviso_rt_meta.erl b/lib/runtime_tools/src/inviso_rt_meta.erl new file mode 100644 index 0000000000..6865dc2242 --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt_meta.erl @@ -0,0 +1,1207 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% +%% Author: Lennart Öhman, lennart.ohman@st.se +%% +%% This module implements the meta tracer process belonging to the +%% runtime component. Its main purpose is to write the ti-file (traceinformation). +%% The ti-file contains translations between process id:s and what ever "you" +%% want to read in the merged and formatted logfile. +%% This process interacts with the runtime component process. +%% +%% Currently it handles the following types of ti-files: +%% Plain raw, binary log. +%% Relay to other inviso_rt_meta process on another node. +%% +%% The TI file will be on binary format and each entry is: +%% <> +%% Pid=pid(), or if OP==unalias pid()|any_other_than_pid() +%% Op=alias|unalias +%% ----------------------------------------------------------------------------- +-module(inviso_rt_meta). + +%% ----------------------------------------------------------------------------- +%% API exports. +%% ----------------------------------------------------------------------------- + +-export([start/2,start/5]). +-export([stop/1,suspend/1]). +-export([init_tpm/5,init_tpm/8]). +-export([tpm/5,tpm/6,tpm/9,tpm_tracer/5,tpm_tracer/6,tpm_tracer/9]). +-export([tpm_ms/6,tpm_ms_tracer/6,ctpm_ms/5,ctpm/4]). +-export([local_register/1,global_register/1]). +-export([remove_local_register/1,remove_global_register/1]). + +-export([write_ti/1]). + +-export([get_tracer/0,tpm_ms/5,tpm_ms_tracer/5,list_tpm_ms/3,ctpm_ms/4]). + +-export([metacast_call/5,metacast_return_from/6]). +-export([get_state/1]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Internal exports. +%% ----------------------------------------------------------------------------- + +-export([init/6]). +-export([init_std_publld/2,clean_std_publld/1]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Constants. +%% ----------------------------------------------------------------------------- + +-define(NAMED_MS_TAB,inviso_rt_meta_named_ms). + +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Exported API (Meant to be used by a runtime component). +%% ============================================================================= + +%% start(TiData,Tracer)={ok,Pid} | {error,Reason} +%% start(TiData,Tracer,InitPublLDmfa,RemovePublLDmfa,CleanPublLDmf)= +%% {ok,Pid} | {error,Reason} +%% TiData={file,FileName}|{relay,Node} +%% Tracer=pid()|port() +%% FileName=string() +%% InitPublLDmfa={Mod,Func,ArgList} +%% RemovePublLDmf={Mod,Func} | void +%% RemovePublLDmf(PublLD)->nothing significant. +%% These functions are called to create and destroy the public loopdata +%% structure available to the meta-trace CallFunc and ReturnFunc. +%% CleanPublLDmf={Mod,Func} +%% This function will periodically be called to clean the public LD from +%% pending meta-trace messages waiting for a corresponding return_from +%% message. +%% +%% Starts a meta-tracer process, opening the ti-file specified in TiData. PublLD +%% is used to communicate data, typically between a call and return_from. +%% If no special initialization function is specified a standard one is used. +%% Note that the meta tracer function must know "who" is the regular tracer +%% (process or port). This because it must be possible to append {tracer,Tracer} +%% in meta match specs. +start(TiData,Tracer) -> + Pid=spawn_link(?MODULE, + init, + [self(), + TiData, + Tracer, + {?MODULE,init_std_publld,[2,[]]}, + void, + {?MODULE,clean_std_publld}]), + wait_for_reply(Pid). +start(TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) -> + Pid=spawn_link(?MODULE, + init, + [self(),TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf]), + wait_for_reply(Pid). + +wait_for_reply(Pid) -> + receive + {Pid,ok} -> + {ok,Pid}; + {Pid,{error,Reason}} -> + {error,Reason} + after + 10000 -> % After very long time. + exit(Pid,kill), % It must be hanging. + {error,time_out} + end. +%% ----------------------------------------------------------------------------- + +%% stop(Pid)=ok +%% Pid=Adders to the meta tracer, pid(). +%% Shutsdown the metatracer. +stop(Pid) -> + Pid ! {stop,self()}, + ok. +%% ----------------------------------------------------------------------------- + +%% suspend(Pid)=ok +%% Pid=Adders to the meta tracer, pid(). +%% Suspends the meta tracer by removing all meta trace patterns. +suspend(Pid) -> + Pid ! {suspend,self()}, + ok. +%% ----------------------------------------------------------------------------- + +%% init_tpm(Pid,Mod,Func,Arity,CallFunc)= +%% init_tpm(Pid,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=ok|{error,Reason}. +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function which shall be meta traced, atom(). +%% Arity=As above, integer(). +%% InitFunc,RemoveFunc={Module,Function}|fun(), functions being called when +%% to initialize the public loopdata structure, and to reset it. +%% InitFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD,Output} +%% Supposed to initialize whatever needs to be done before +%% handling any incoming meta-trace message for the Mod:Func/Arity. +%% RemoveFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD} +%% Called when meta tracing of Mod:Func/Arity is stopped. It is supposed +%% to clear datastructures away from the PublLD. +%% Initializes the public loopdata for this function. Note that we can not use wildcards +%% here (even if it is perfectly legal in Erlang). It also sets the CallFunc and +%% ReturnFunc for the meta traced function. The function is hence ready to be +%% meta traced with either tpm/5 or tpm_ms/5. +%% This function is synchronous, waiting for a reply from the meta server. +init_tpm(Pid,Mod,Func,Arity,CallFunc) -> + init_tpm(Pid,Mod,Func,Arity,void,CallFunc,void,void). +init_tpm(Pid,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + send_wait(Pid, + {init_tpm,{Mod,Func,Arity},InitFunc,CallFunc,ReturnFunc,RemoveFunc}). +%% ----------------------------------------------------------------------------- + +%% tpm(Pid,Mod,Func,Arity,MatchSpec)={ok,N}|{error,Reason} +%% tpm(Pid,Mod,Func,Arity,MatchSpec,CallFunc)={ok,N}|{error,Reason} +%% tpm(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc)= +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function which shall be meta traced, atom(). +%% Arity=As above, integer(). +%% MatchSpec=List of match specification, possibly empty. Remember {return_trace} +%% if expecting return_from messages. +%% InitFunc,CallFunc,ReturnFunc,RemoveFunc={Module,Function}|fun(), +%% functions being called when these functions are called by the meta trace +%% server at certain events. +%% CallFunc(CallingPid,ActualArgList,PublLD)->{ok,NewPrivLD,Output} +%% ReturnFunc(CallingPid,ReturnValue,PublLD)->{ok,NewPrivLD,Output} +%% When a call respectively return_from trace message arrives for the meta +%% traced function, the corresponding function is called. +%% The ReturnFunc must handle the fact that a return_from message arrives +%% for a call which was never noticed. This because the message queue of the +%% meta tracer may have been emptied. +%% Reason=badarg | +%% Output=Characters to be written to the ti-file, bin() | 'void' +%% The tpm/5 function simply starts meta tracing for the function. It must +%% previously have been initialized. +%% tpm/6 & /9 initializes the function and starts meta tracing. +tpm(Pid,Mod,Func,Arity,MatchSpec) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_'-> + send_wait(Pid,{tpm,{Mod,Func,Arity,MatchSpec}}); +tpm(_,_,_,_,_) -> + {error,badarg}. + +tpm(Pid,Mod,Func,Arity,MatchSpec,CallFunc) -> + tpm(Pid,Mod,Func,Arity,MatchSpec,void,CallFunc,void,void). + +tpm(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_' -> + send_wait(Pid,{tpm,{Mod,Func,Arity,MatchSpec},InitFunc,CallFunc,ReturnFunc,RemoveFunc}); +tpm(_,_,_,_,_,_,_,_,_) -> + {error,badarg}. +%% ----------------------------------------------------------------------------- + +%% Same as tpm/X but the meta tracer will automatically append {tracer,Tracer} +%% to the enable list in a {trace,Disable,Enable} match spec action term. +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_'-> + send_wait(Pid,{tpm_tracer,{Mod,Func,Arity,MatchSpec}}); +tpm_tracer(_,_,_,_,_) -> + {error,badarg}. + +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,CallFunc) -> + tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,void,CallFunc,void,void). + +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_' -> + send_wait(Pid,{tpm_tracer, + {Mod,Func,Arity,MatchSpec}, + InitFunc,CallFunc,ReturnFunc,RemoveFunc}); +tpm_tracer(_,_,_,_,_,_,_,_,_) -> + {error,badarg}. +%% ----------------------------------------------------------------------------- + +%% tpm_ms(Pid,Mod,Func,Arity,MSname,MS)={ok,N}|{error,Reason} +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function to which we shall add a match-spec., atom(). +%% Arity=As above, integer(). +%% MSname=A name to be used if this MS shall be removed later. term(). +%% MatchSpec=List of match specification, Remember {return_trace} +%% if expecting return_from messages. +%% This function adds a list of match-specs to the already existing ones. It +%% uses an internal database to keep track of existing match-specs. If the +%% match-spec does not result in any meta traced functions (for whatever reason), +%% the MS is not saved in the database. The previously known match-specs are +%% not removed. +tpm_ms(Pid,Mod,Func,Arity,MSname,MS) -> + send_wait(Pid,{tpm_ms,{Mod,Func,Arity},MSname,MS}). +%% ----------------------------------------------------------------------------- + +%% Same as tpm_ms/6 but the meta tracer will automatically append {tracer,Tracer} +%% to the enable list in a {trace,Disable,Enable} match spec action term. +tpm_ms_tracer(Pid,Mod,Func,Arity,MSname,MS) -> + send_wait(Pid,{tpm_ms_tracer,{Mod,Func,Arity},MSname,MS}). +%% ----------------------------------------------------------------------------- + +%% ctpm_ms(Pid,Mod,Func,Arity)=ok +%% +%% Removes a names match-spec from the meta traced function. Note that is never +%% a fault to remove an MS. Not even from a function which is non existant. +ctpm_ms(Pid,Mod,Func,Arity,MSname) -> + send_wait(Pid,{ctpm_ms,{Mod,Func,Arity},MSname}). +%% ----------------------------------------------------------------------------- + +%% Quick versions for erlang:register/2 which also uses a default CallFunc +%% and a default ReturnFunc. +local_register(Pid) -> + Res1=tpm(Pid, + erlang,register,2,[{'_',[],[{exception_trace}]}], + fun metafunc_init/4,fun local_register_call/3, + fun local_register_return/3,void), + Res2=tpm(Pid, + erlang,unregister,1,[], + void,fun local_unregister_call/3,void,void), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% Quick version for global:register_name/2, /3. +global_register(Pid) -> + Res1=tpm(Pid,global,handle_call,3,[{[{register,'_','_','_'},'_','_'],[],[]}], + void,fun global_register_call/3,void,void), + Res2=tpm(Pid,global,delete_global_name,2,[], + void,fun global_unregister_call/3,void,void), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% ctpm(Pid,Mod,Func,Arity)=ok|{error,bad_mfa} +%% +%% Removes the meta trace pattern for the function, means stops generating output +%% for this function. The public LD may be cleared by the previously entered +%% RemoveFunc. +ctpm(Pid,Mod,Func,Arity) -> + send_wait(Pid,{ctpm,{Mod,Func,Arity}}). +%% ----------------------------------------------------------------------------- + +%% remove_local_register(Pid)={Res1,Res2} +%% Res1,Res2=ok|{error,Reason} +remove_local_register(Pid) -> + Res1=ctpm(Pid,erlang,register,2), + Res2=ctpm(Pid,erlang,unregister,1), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% remove_global_register(Pid)={Res1,Res2} +%% Res1,Res2=ok|{error,Reason} +remove_global_register(Pid) -> + Res1=ctpm(Pid,global,handle_call,3), + Res2=ctpm(Pid,global,delete_global_name,2), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% Exported help functions which may be used in programming CallFunc and/or +%% ReturnFunc. Useful if the call is done on one node but must trigger the +%% start of something at other nodes. +metacast_call(Nodes,OrigPid,M,F,Args) -> + multicast(Nodes,{trace_ts,OrigPid,call,{M,F,Args},void}), + ok. + +metacast_return_from(Nodes,OrigPid,M,F,Arity,Value) -> + multicast(Nodes,{trace_ts,OrigPid,return_from,{M,F,Arity},Value,void}), + ok. + +multicast([Node|Rest],Msg) -> + {?MODULE,Node} ! Msg, + multicast(Rest,Msg); +multicast([],_) -> + true. +%% ----------------------------------------------------------------------------- + +%% get_states(Pid)={ok,LD,PubLD}. +get_state(Pid) -> + send_wait(Pid,get_state). +%% ----------------------------------------------------------------------------- + + +send_wait(To,Msg) -> + Ref=make_ref(), + MRef=erlang:monitor(process,To), + To ! {Msg,Ref,self()}, + receive + {inviso_rt_meta_reply,Ref,Reply} -> + erlang:demonitor(MRef), + Reply; + {'DOWN',MRef,_,_To,_Reason} -> + {error,no_metatracer} + end. + +reply(To,Ref,Reply) -> + To ! {inviso_rt_meta_reply,Ref,Reply}. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Special API. +%% ============================================================================= + +%% write_ti(OutPut)= +%% OutPut=binary() +%% Makes an extra entry into the trace information file (ti-file). This is useful +%% if a pid-alias association is learned in another way than through a meta traced +%% function call. Note that this API can only be used locally at the node in +%% question. +write_ti(OutPut) -> + catch ?MODULE ! {write_ti,OutPut}. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% API intended to be used on CallFuncs and RemoveFuncs. +%% ============================================================================= + +%% The reason there must be a special API for CallFuncs and RemoveFuncs are is +%% that those functions are executed inside *this* process context. Hence they +%% can not make function calls requiering this process to receive messages. + +%% Returns the tracer used for regular tracing. The reason this is implemented +%% in this way is that this function is intended to be used in meta trace call- +%% back functions. And there we can not have message passing API:s to the meta +%% trace(!). +get_tracer() -> + get(tracer). +%% ----------------------------------------------------------------------------- + +%% Function equivalent to inviso_rt:tpm_ms/6. This function can *only* be used +%% inside a CallFunc or a RemoveFunc. +tpm_ms(Mod,Func,Arity,MSname,MS) -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + {ok,h_tpm_ms(Mod,Func,Arity,MSname,MS)}; + no -> + {error,not_initiated} + end. +%% ----------------------------------------------------------------------------- + +tpm_ms_tracer(Mod,Func,Arity,MSname,MS) -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,get_tracer()), + {ok,h_tpm_ms(Mod,Func,Arity,MSname,NewMS)}; + no -> + {error,not_initiated} + end. +%% ----------------------------------------------------------------------------- + +%% Function that returns all MSname in use for Mod:Func/Arity +list_tpm_ms(Mod,Func,Arity) -> + {ok,h_list_tpm_ms(Mod,Func,Arity)}. +%% ----------------------------------------------------------------------------- + +%% Function equivalent to inviso_rt:ctpm_ms/5. This function can *only* be used +%% inside a CallFunc or a RemoveFunc. +ctpm_ms(Mod,Func,Arity,MSname) -> + h_ctpm_ms(Mod,Func,Arity,MSname), + ok. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% The server implemenation. +%% ============================================================================= + +init(Parent,TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) -> + process_flag(priority,high), % Since we may receive from many procs. + register(?MODULE,self()), % So we can act as relay receiver. + case open_traceinfo_file(TiData) of + {ok,TI} -> % The ti.-file. + TId=ets:new(?NAMED_MS_TAB,[named_table,set,protected]), + PublLD=do_init_publ_ld(InitPublLDmfa), + Parent ! {self(),ok}, + put(tracer,Tracer), % Uggly quick fix! + loop(Parent, + Tracer, + TI, + mk_new_ld(InitPublLDmfa,RemovePublLDmf,CleanPublLDmf,TId), + PublLD, + now()); + {error,Reason} -> + Parent ! {self(),{error,Reason}} + end. +%% ----------------------------------------------------------------------------- + +loop(Parent,Tracer,TI,LD,PrevPublLD,PrevCleanTime) -> + {PublLD,CleanTime}=throw_old_failed(get_cleanpublldmf_ld(LD),PrevPublLD,PrevCleanTime), + receive + {{init_tpm,{Mod,Func,Arity},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + {NewLD,NewPublLD}= + h_init_tpm(Mod,Func,Arity, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,ok), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> % Faulty arguments, + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> % If it already exists, cant init again. + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm,{Mod,Func,Arity,MS},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + {NewLD,NewPublLD,N}= + h_tpm(Mod,Func,Arity,MS, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm,{Mod,Func,Arity,MS}},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + {NewLD,N}=h_tpm(Mod,Func,Arity,MS,LD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,PublLD,CleanTime); + no -> % Must be initiated before. + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_tracer,{Mod,Func,Arity,MS},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + NewMS=add_tracer(MS,Tracer), + {NewLD,NewPublLD,N}= + h_tpm(Mod,Func,Arity,NewMS, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_tracer,{Mod,Func,Arity,MS}},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,Tracer), + {NewLD,N}=h_tpm(Mod,Func,Arity,NewMS,LD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,PublLD,CleanTime); + no -> % Must be initiated before. + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_ms,{Mod,Func,Arity},MSname,MS},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + reply(Parent,Ref,{ok,h_tpm_ms(Mod,Func,Arity,MSname,MS)}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + no -> + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_ms_tracer,{Mod,Func,Arity},MSname,MS},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,Tracer), + reply(Parent,Ref,{ok,h_tpm_ms(Mod,Func,Arity,MSname,NewMS)}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + no -> + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{ctpm_ms,{Mod,Func,Arity},MSname},Ref,Parent} -> + reply(Parent,Ref,ok), + h_ctpm_ms(Mod,Func,Arity,MSname), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {{ctpm,{Mod,Func,Arity}},Ref,Parent} -> + case get_remove_func_ld(Mod,Func,Arity,LD) of + false -> % Incorrect Mod:Func/Arity! + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); % Do nothing! + MF -> % {M,F}, Func or 'void'. + catch erlang:trace_pattern({Mod,Func,Arity},false,[meta]), + NewPublLD=do_removefunc(MF,Mod,Func,Arity,PublLD), + NewLD=ctpm_ld(Mod,Func,Arity,LD), + reply(Parent,Ref,ok), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime) + end; + {suspend,Parent} -> % Removes all meta trace patterns. + stop_all_meta_tracing(get_all_meta_funcs_ld(LD),PublLD,LD), + do_remove_publ_ld(get_removepublldmf_ld(LD),PublLD), + NewPublLD=do_init_publ_ld(get_initpublldmfa_ld(LD)), + loop(Parent,Tracer,TI,reset_ld(LD),NewPublLD,CleanTime); + {stop,Parent} -> % Make a controlled shutdown. + stop_all_meta_tracing(get_all_meta_funcs_ld(LD),PublLD,LD), + do_remove_publ_ld(get_removepublldmf_ld(LD),PublLD), + close_traceinfo_file(TI); % And then simply terminate. + {trace_ts,Pid,call,{M,F,Args},TS} -> + case handle_meta(get_call_func_ld(M,F,length(Args),LD),Pid,{call,Args,TS},PublLD) of + {ok,NewPublLD,Output} when is_binary(Output);is_list(Output) -> + write_output(TI,Output), + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + {ok,NewPublLD,_} -> % No output to the ti-file this time. + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + _ -> % Not handled correct, not much to do. + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {trace_ts,Pid,TypeTag,{M,F,Arity},Value,TS} + when TypeTag==return_from;TypeTag==exception_from -> + case handle_meta(get_return_func_ld(M,F,Arity,LD),Pid,{TypeTag,Value,TS},PublLD) of + {ok,NewPublLD,Output} when is_binary(Output);is_list(Output) -> + write_output(TI,Output), + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + {ok,NewPublLD,_} -> % No output to the ti-file this time. + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + _ -> % Not handled correct, not much to do. + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {relayed_meta,Bin} -> + write_output(TI,Bin), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {write_ti,OutPut} -> + write_output(TI,OutPut), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {get_state,Ref,From} -> % Debug function. + reply(From,Ref,{ok,LD,PublLD}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + _Other -> + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end. + + +%% ============================================================================= +%% First level help functions. +%% ============================================================================= + +%% Function which opens the trace-information file(s). It must understand +%% the tidata specification which is part of the tracerdata given to the +%% runtime component during init_tracing. +%% It must return an internal notation of the time of file open and a +%% useful descriptor the write_output function can use. +%% Returns {ok,TiDescriptor} or {error,Reason}. +open_traceinfo_file({file,FileName}) -> % A plain raw binary file. + case file:open(FileName,[write,raw,binary]) of + {ok,FD} -> + {ok,{file,FD}}; + {error,Reason} -> + {error,{open,[FileName,Reason]}} + end; +open_traceinfo_file({relay,ToNode}) -> % Use distributed Erlang. + {ok,{relay,ToNode}}; +open_traceinfo_file(IncorrectTI) -> + {error,{badarg,IncorrectTI}}. +%% ----------------------------------------------------------------------------- + +close_traceinfo_file({file,FD}) -> + file:close(FD); +close_traceinfo_file(_) -> + ok. +%% ----------------------------------------------------------------------------- + +%% Help function handling initializing meta tracing of a function. +%% Returns {NewLD,NewPublLD}. +h_init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD) -> + case do_initfunc(InitFunc,Mod,Func,Arity,PublLD) of + {NewPublLD,Output} -> + write_output(TI,Output), + NewLD=init_tpm_ld(Mod,Func,Arity,CallFunc,ReturnFunc,RemoveFunc,LD), + {NewLD,NewPublLD}; + false -> % The initfunc did not do anything. + NewLD=init_tpm_ld(Mod,Func,Arity,CallFunc,ReturnFunc,RemoveFunc,LD), + {NewLD,PublLD} + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling initializing meta tracing of a function and also +%% set the meta trace pattern as specified. +%% Returns {NewLD,NewPublLD,N}. +h_tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD) -> + {NewLD,NewPublLD}= + h_init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD), + case set_meta_tracing(Mod,Func,Arity,MS) of + true -> % Ok, set one pattern. + {NewLD,NewPublLD,1}; + false -> + {NewLD,NewPublLD,0} + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling setting meta trace patter for a function which has +%% already been intialized. Note that we must remove all potentially stored +%% match-specs, if this function has been given match-specs before with +%% tpm_ms. +%% Returns a {NewLD,N}. +h_tpm(Mod,Func,Arity,MS,LD) -> + case set_meta_tracing(Mod,Func,Arity,MS) of + true -> + {remove_ms_ld(Mod,Func,Arity,LD),1}; + false -> + {LD,0} + end. +%% ----------------------------------------------------------------------------- + +%% Help function that adds a match-spec to Mod:Func/Arity. It is not defined +%% in which order the match-specs will be given to the BIF. +%% Note that if an MS with the same name as an exiting is inserted, the previous +%% match-spec will be removed. +%% Very important to realise is that the empty meta match spec [] imposes no +%% restrictions what so ever on the generating of meta trace call messages. +%% Uncontrolled sending of such messages may quickly drain power from the system. +%% Since an empty match-spec will "disappear" when added to other match specs, +%% the empty match is transformed to what it actually is: [{'_',[],[]}]. +%% Returns 0 or 1 indicating failure or success. +h_tpm_ms(Mod,Func,Arity,MSname,MS) -> + MSsNames=get_ms_ld(Mod,Func,Arity), % Fetch all previous match-specs. + TransformedMS=h_tpm_ms_convert_null_ms(MS), + MSsNames1=lists:keydelete(MSname,1,MSsNames), % If it already existed, it is gone! + NewMSs=lists:flatten([TransformedMS,lists:map(fun({_Name,MSx})->MSx end,MSsNames1)]), + case set_meta_tracing(Mod,Func,Arity,NewMSs) of + true -> % We only save the MS if it was good. + put_ms_ld(Mod,Func,Arity,MSname,TransformedMS,MSsNames1), + 1; + false -> + 0 + end. + +%% Help function converting the null match spec into, still a null match spec, +%% on a proper match spec format. This because it will otherwise be difficult +%% to see the difference between no active tpm_ms and all a set of null ms. +h_tpm_ms_convert_null_ms([]) -> + [{'_',[],[]}]; +h_tpm_ms_convert_null_ms(MS) -> + MS. +%% ----------------------------------------------------------------------------- + +%% Help function returning a list of all names used for match-functions for +%% the Mod:Func/Arity in question. +h_list_tpm_ms(Mod,Func,Arity) -> + MSsNames=get_ms_ld(Mod,Func,Arity), % A list of {MSname,MS}. + lists:map(fun({MSname,_})->MSname end,MSsNames). +%% ----------------------------------------------------------------------------- + +%% Function that removes a named match-spec. Returns nothing significant. +%% Note that if we end up with no match-specs, we must remove the meta trace +%% patten all together. That is bringing the function back to just initiated. +h_ctpm_ms(Mod,Func,Arity,MSname) -> + case get_ms_ld(Mod,Func,Arity) of + [] -> % The name does certainly not exist! + true; % We don't have to do anything. + MSsNames -> + case lists:keysearch(MSname,1,MSsNames) of + {value,{_,_MS}} -> % Ok, we must do something! + NewMSsNames=lists:keydelete(MSname,1,MSsNames), + case lists:flatten(lists:map(fun({_Name,MS})->MS end,NewMSsNames)) of + [] -> % This means stop meta tracing. + set_meta_tracing(Mod,Func,Arity,false); + NewMSs -> + set_meta_tracing(Mod,Func,Arity,NewMSs) + end, + set_ms_ld(Mod,Func,Arity,NewMSsNames); + false -> % But this name does not exist. + true % So we do not have to do anything. + end + end. +%% ----------------------------------------------------------------------------- + +%% Function that checks the arguments to the meta trace pattern. The reason we +%% must do this is that we can only allow meta tracing on specific functions and +%% not using wildpatterns. Otherwise the meta trace server will not understand +%% which callfunc for instance to call when a meta-trace message is generated +%% for a function. +%% Returns 'true' or 'false'. +check_tpm_args(Mod,Func,Arity) + when is_atom(Mod),is_atom(Func),is_integer(Arity),Mod/='_',Func/='_' -> + true; +check_tpm_args(_,_,_) -> + false. +%% ----------------------------------------------------------------------------- + +%% Help function which calls the actual BIF setting meta-trace-patterns. +%% Returns 'true' or 'false'. +set_meta_tracing(Mod,Func,Arity,MS) when is_atom(Mod) -> + case erlang:module_loaded(Mod) of + true -> + set_meta_tracing_2(Mod,Func,Arity,MS); + false -> % The module is not loaded. + case code:ensure_loaded(Mod) of + {module,_Mod} -> + set_meta_tracing_2(Mod,Func,Arity,MS); + {error,_Reason} -> % Could not load the module. + false % No use try to trace. + end + end; +set_meta_tracing(_,_,_,_) -> + false. + +set_meta_tracing_2(Mod,Func,Arity,MS) -> + case catch erlang:trace_pattern({Mod,Func,Arity},MS,[meta]) of + 0 -> % Hmm, nothing happend :-) + false; + N when is_integer(N) -> % The normal case, some functions were hit. + true; + {'EXIT',_Reason} -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Help function which removes all meta trace pattern for the functions mentioned +%% in the list being first argument. It also executes the remove funcs for each +%% and every no longer meta traced function. This done since some of the remove +%% functions may do side-effects (like deleteing ETS tables). +%% Returns nothing significant. +stop_all_meta_tracing([{M,F,Arity}|Rest],PublLD,LD) -> + catch erlang:trace_pattern({M,F,Arity},false,[meta]), + NewPublLD=do_removefunc(get_remove_func_ld(M,F,Arity,LD),M,F,Arity,PublLD), + stop_all_meta_tracing(Rest,NewPublLD,LD); +stop_all_meta_tracing([],_,_) -> + true. +%% ----------------------------------------------------------------------------- + +%% This function calls the function registered to be handler for a certain +%% meta-traced function. Such a function or fun must take three arguments +%% and return {ok,NewPrivLD,OutPutBinary} or 'false'. OutPutBinary may be +%% something else, and is then ignored. +handle_meta({M,F},Pid,Arg1,PrivLD) -> + (catch M:F(Pid,Arg1,PrivLD)); +handle_meta(Fun,Pid,Arg1,PrivLD) when is_function(Fun) -> + (catch Fun(Pid,Arg1,PrivLD)); +handle_meta(_,_,_,_) -> % Don't know how to do this. + false. +%% ----------------------------------------------------------------------------- + +%% Help function writing output from a callback function to the ti-file. +%% Output can be a binary or a list of binaries. +write_output(TI,[OutPut|Rest]) -> + write_output(TI,OutPut), + write_output(TI,Rest); +write_output({file,FD},Bin) when is_binary(Bin) -> % Plain direct-binary file + Size=byte_size(Bin), + file:write(FD,list_to_binary([<<0,Size:32>>,Bin])); +write_output({relay,ToNode},Bin) when is_atom(ToNode),is_binary(Bin) -> + {inviso_rt_meta,ToNode} ! {relayed_meta,Bin}; +write_output(_,_) -> % Don't understand, just skip. + true. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Various help functions. +%% ============================================================================= + +%% Help function initializing the public loopdata structure. Note that if the +%% supplied InitPublLDmfa is faulty we let the structure become the error. +%% The error will most likely turn up in an error report somewhere, eventually. +do_init_publ_ld({M,F,Args}) when is_atom(M),is_atom(F),is_list(Args) -> + case catch apply(M,F,Args) of + {'EXIT',_Reason} -> + {error,init_publ_ld_func}; % Let the struct be this error! + InitialPublLD -> + InitialPublLD + end; +do_init_publ_ld(_) -> + {error,init_publ_ld_func}. +%% ----------------------------------------------------------------------------- + +%% Help function which removes the public loopdata structure. The function does +%% not necessarily have to exist. Returns nothing significant. +do_remove_publ_ld({M,F},PublLD) when is_atom(M),is_atom(F) -> + catch M:F(PublLD); +do_remove_publ_ld(_,_) -> + true. +%% ----------------------------------------------------------------------------- + +%% Hlp function initializing a particular meta traced function into the public +%% loopdata. Note that the function is not mandatory. +%% Returns {NewPublLD,Output} or 'false'. +do_initfunc({M,F},Mod,Func,Arity,PublLD) when is_atom(M),is_atom(F) -> + case catch M:F(Mod,Func,Arity,PublLD) of + {ok,NewPublLD,Output} -> + {NewPublLD,Output}; + _ -> % Everything else is an error. + false % Act as no initialization function. + end; +do_initfunc(Fun,Mod,Func,Arity,PublLD) when is_function(Fun) -> + case catch Fun(Mod,Func,Arity,PublLD) of + {ok,NewPublLD,Output} -> + {NewPublLD,Output}; + _ -> % Everything else is an error. + false % Act as no initialization function. + end; +do_initfunc(_,_,_,_,_) -> % Perhaps too generous, should be 'void' only. + false. +%% ----------------------------------------------------------------------------- + +%% Help function removing a particular meta traced function from the public +%% loopdata. Note that we do not make much noice should the call back function +%% be faulty. +do_removefunc({M,F},Mod,Func,Arity,PublLD) when is_atom(M),is_atom(F) -> + case catch M:F(Mod,Func,Arity,PublLD) of + {ok,NewPublLD} -> + NewPublLD; + _ -> % Everything else is an error. + PublLD % Act as no initialization function. + end; +do_removefunc(Fun,Mod,Func,Arity,PublLD) when is_function(Fun) -> + case catch Fun(Mod,Func,Arity,PublLD) of + {ok,NewPublLD} -> + NewPublLD; + _ -> % Everything else is an error. + PublLD % Act as no initialization function. + end; +do_removefunc(_,_,_,_,PublLD) -> + PublLD. +%% ----------------------------------------------------------------------------- + +%% Function that, if the time has come, goes through the priv-ld structure and +%% cleans away entryn left behind. The usual cause is that the function call +%% caused an exception and there were therefore no matching return_from. +%% Returns {NewPrivLD,now()}. +throw_old_failed({M,F},PrivLD,PrevClean) -> + case difference_in_now(PrevClean,now(),60) of % We clean once every minute. + true -> + case catch apply(M,F,[PrivLD]) of + {'EXIT',_Reason} -> % Something went wrong, ignore it. + {PrivLD,now()}; % Just keep the old priv-ld. + NewPrivLD -> % The function must return a priv-ld. + {NewPrivLD,now()} + end; + false -> % Not time yet! + {PrivLD,PrevClean} + end. +%% ----------------------------------------------------------------------------- + +%% Help function comparing two now timestamps. Returns true or false depending +%% on if S2 is more than DiffS seconds after S1. Only works for differences +%% less than 1 million seconds. +difference_in_now({MegaS1,S1,_},{MegaS2,S2,_},DiffS) -> + if + MegaS1+1 % More than 1 Mega sec. difference. + true; + MegaS1==MegaS2,S1+DiffS + true; + MegaS1+1==MegaS2,S1+DiffS + true; + true -> + false + end. +%% ----------------------------------------------------------------------------- + +%% This help function adds a {tracer,Tracer} to the enable-list in a 'trace' +%% match spec action. The reason for this is that the author of the a meta +%% match spec meant to turn tracing on for the process executing the match spec +%% can not know the tracer. This since the match spec is most likely authored +%% at the control component's node, and not here. +%% Note the double tuple necessary to make it just precise a tuple! +%% Returns a new match spec. +add_tracer([MS1|Rest],Tracer) -> + [add_tracer_2(MS1,Tracer)|add_tracer(Rest,Tracer)]; +add_tracer([],_) -> + []; +add_tracer(NotList,_Tracer) -> % Can be 'false', but also an error. + NotList. + +add_tracer_2({Head,Cond,Body},Tracer) -> + {Head,Cond,add_tracer_3(Body,Tracer)}; +add_tracer_2(Faulty,_Tracer) -> + Faulty. + +add_tracer_3([{trace,Disable,Enable}|Rest],Tracer) when is_list(Enable) -> + [{trace,Disable,Enable++[{{tracer,Tracer}}]}|Rest]; +add_tracer_3([ActionTerm|Rest],Tracer) -> + [ActionTerm|add_tracer_3(Rest,Tracer)]; +add_tracer_3([],_Tracer) -> + []; +add_tracer_3(FaultyBody,_Tracer) -> + FaultyBody. +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Help functions handling internal loopdata. +%% ----------------------------------------------------------------------------- + +-record(ld,{init_publ_ld_mfa, % {M,F,Args} + remove_publ_ld_mf, % {M,F} | void + clean_publ_ld_mf, % {Mod,Func} + ms_mfarities=notable, % ETS holding names match functions. + call_mfarities=[], % [{{M,F,Arity},2-TupleOrFun},...] + return_mfarities=[], % [{{M,F,Arity},2-TupleOrFun},...] + remove_mfarities=[] + }). + +mk_new_ld(InitPublLDmfa,RemovePublLDmf,CleanPublLDmf,TId) -> + #ld{ + init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId + }. +%% ----------------------------------------------------------------------------- + +%% Function which restores the internal loop data to somekind of initial state. +%% This is useful when tracing has been suspended. +reset_ld(#ld{init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId}) -> + ets:match_delete(TId,{'_','_'}), % Empty the table. + #ld{init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId}. +%% ----------------------------------------------------------------------------- + +get_initpublldmfa_ld(#ld{init_publ_ld_mfa=InitPublLDmfa}) -> + InitPublLDmfa. +%% ----------------------------------------------------------------------------- + +get_removepublldmf_ld(#ld{remove_publ_ld_mf=RemovePublLDmf}) -> + RemovePublLDmf. +%% ----------------------------------------------------------------------------- + +get_cleanpublldmf_ld(#ld{clean_publ_ld_mf=CleanPublLDmf}) -> + CleanPublLDmf. +%% ----------------------------------------------------------------------------- + +%% Help function adding data associated with a meta traced function to the +%% internal loopdata. Called when meta tracing is activated for M:F/Arity. +init_tpm_ld(M,F,Arity,CallFunc,ReturnFunc,RemoveFunc,LD) -> + ets:insert(LD#ld.ms_mfarities,{{M,F,Arity},[]}), + CallFuncs=LD#ld.call_mfarities, + ReturnFuncs=LD#ld.return_mfarities, + RemoveFuncs=LD#ld.remove_mfarities, + LD#ld{call_mfarities=[{{M,F,Arity},CallFunc}|CallFuncs], + return_mfarities=[{{M,F,Arity},ReturnFunc}|ReturnFuncs], + remove_mfarities=[{{M,F,Arity},RemoveFunc}|RemoveFuncs]}. +%% ----------------------------------------------------------------------------- + +%% Help function which answers the question if we have already initiated the +%% function. It is done by looking in the ETS-table with named match-functions. +%% If there is an entry in the set-type table for M:F/Arity, the function is +%% initiated. +%% Returns 'yes' or 'no'. +check_mfarity_exists(M,F,Arity) -> + case ets:lookup(?NAMED_MS_TAB,{M,F,Arity}) of + [] -> + no; + [_] -> + yes + end. +%% ----------------------------------------------------------------------------- + +%% Help function adding an entry with [{MSname,MSlist}|MSsNames] for M:F/Arity. +%% Note that any already existing entry is removed. +%% Returns nothing significant. +put_ms_ld(M,F,Arity,MSname,MS,MSsNames) -> + ets:insert(?NAMED_MS_TAB,{{M,F,Arity},[{MSname,MS}|MSsNames]}). +%% ----------------------------------------------------------------------------- + +%% Help function taking a list of {MSname,MSs} and storing them in the +%% internal loop data structure. The storage is actually implemented as an ETS +%% table. Any previous list of {MSname,MSs} associated with this {M,F,Arity} will +%% be lost. Returns nothing significant. +set_ms_ld(M,F,Arity,MSsNames) -> + ets:insert(?NAMED_MS_TAB,{{M,F,Arity},MSsNames}). +%% ----------------------------------------------------------------------------- + +%% Help function fetching a list of {MSname,MatchSpecs} for a M:F/Arity. The +%% match-functions are stored in an ETS table searchable on {M,F,Arity}. +get_ms_ld(M,F,Arity) -> + case ets:lookup(?NAMED_MS_TAB,{M,F,Arity}) of + [{_MFArity,MSsNames}] -> + MSsNames; + [] -> + [] + end. +%% ----------------------------------------------------------------------------- + +%% Help function removing all saved match-specs for a certain M:F/Arity. +%% Returns a new loopdata structure. +remove_ms_ld(M,F,Arity,LD) -> + ets:delete(LD#ld.ms_mfarities,{M,F,Arity}), + LD. +%% ----------------------------------------------------------------------------- + +%% Help function which removes all information about a meta traced function from +%% the internal loopdata. Returns a new loopdata structure. +ctpm_ld(M,F,Arity,LD) -> + ets:delete(LD#ld.ms_mfarities,{M,F,Arity}), + NewCallFuncs=lists:keydelete({M,F,Arity},1,LD#ld.call_mfarities), + NewReturnFuncs=lists:keydelete({M,F,Arity},1,LD#ld.return_mfarities), + NewRemoveFuncs=lists:keydelete({M,F,Arity},1,LD#ld.remove_mfarities), + LD#ld{call_mfarities=NewCallFuncs, + return_mfarities=NewReturnFuncs, + remove_mfarities=NewRemoveFuncs}. +%% ----------------------------------------------------------------------------- + +get_call_func_ld(M,F,Arity,#ld{call_mfarities=CallFuncs}) -> + case lists:keysearch({M,F,Arity},1,CallFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_return_func_ld(M,F,Arity,#ld{return_mfarities=CallFuncs}) -> + case lists:keysearch({M,F,Arity},1,CallFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_remove_func_ld(M,F,Arity,#ld{remove_mfarities=RemoveFuncs}) -> + case lists:keysearch({M,F,Arity},1,RemoveFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Function returning a list of all {Mod,Func,Arity} which are currently meta +%% traced. It does do by listifying the call_mfarities field in the internal +%% loopdata. +get_all_meta_funcs_ld(#ld{call_mfarities=CallFuncs}) -> + lists:map(fun({MFArity,_})->MFArity end,CallFuncs). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Functions for the standard PublLD structure. +%% +%% It is tuple {Part1,GlobalData} where Part1 is of length at least 2. +%% Where each field is a list of tuples. The last item in each tuple shall be +%% a now tuple, making it possible to clean it away should it be too old to be +%% relevant (there was no return_from message due to a failure). +%% Other fields can be used for other functions. +%% The GlobalData is not cleaned but instead meant to store data must be passed +%% to each CallFunc when a meta trace message arrives. +%% ============================================================================= + +%% Function returning our standard priv-loopdata structure. +init_std_publld(Size,GlobalData) -> + {list_to_tuple(lists:duplicate(Size,[])),GlobalData}. +%% ----------------------------------------------------------------------------- + +%% Function capable of cleaning out a standard publ-ld. The last element of each +%% tuple must be the now item. +%% Returns a new publ-ld structure. +clean_std_publld({Part1,GlobalData}) -> + {clean_std_publld_2(Part1,now(),tuple_size(Part1),[]),GlobalData}. + +clean_std_publld_2(_,_,0,Accum) -> + list_to_tuple(Accum); +clean_std_publld_2(PublLD,Now,Index,Accum) -> + NewTupleList=clean_std_publld_3(element(Index,PublLD),Now), + clean_std_publld_2(PublLD,Now,Index-1,[NewTupleList|Accum]). + +clean_std_publld_3([Tuple|Rest],Now) -> + PrevNow=element(tuple_size(Tuple),Tuple), % Last item shall be the now item. + case difference_in_now(PrevNow,Now,30) of + true -> % Remove it then! + clean_std_publld_3(Rest,Now); + false -> % Keep it! + [Tuple|clean_std_publld_3(Rest,Now)] + end; +clean_std_publld_3([],_) -> + []. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Functions used as handling functions (as funs) for registered process names. +%% (Given that we use the standard priv-ld, otherwise you must do your own!). +%% ============================================================================= + +%% Call-back for initializing the meta traced functions there are quick functions +%% for. Returns a new public loop data structure. +metafunc_init(erlang,register,2,{Part1,GlobalData}) -> + {setelement(1,Part1,[]),GlobalData}. +%% ----------------------------------------------------------------------------- + +%% Call-function for erlang:register/2. +%% This function adds the call to register/2 to a standard priv-ld structure. +%% Note that we *must* search for previous entries from the same process. If such +%% still in structure it means a failed register/2 call. It must first be removed +%% so it can not be mixed up with this one. Since meta-trace message will arrive +%% in order, there was no return_from message for that call if we are here now. +local_register_call(CallingPid,{call,[Alias,Pid],TS},{Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + NewTupleList=lists:keydelete(CallingPid,1,TupleList), % If present, remove previous call. + {ok, + {setelement(1,Part1,[{CallingPid,{Alias,Pid},TS}|NewTupleList]),GlobalData}, + void}. + +%% Return-function for the erlang:register/2 BIF. +%% This function formulates the output and removes the corresponding call entry +%% from the standard priv-ld structure. +local_register_return(CallingPid,{return_from,_Val,_TS},PublLD={Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + case lists:keysearch(CallingPid,1,TupleList) of + {value,{_,{Alias,Pid},NowTS}} -> + NewTupleList=lists:keydelete(CallingPid,1,TupleList), + {ok, + {setelement(1,Part1,NewTupleList),GlobalData}, + term_to_binary({Pid,Alias,alias,NowTS})}; + false -> % Strange, then don't know what to do. + {ok,PublLD,void} % Do nothing seems safe. + end; +local_register_return(CallingPid,{exception_from,_Val,_TS},{Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + NewTupleList=lists:keydelete(CallingPid,1,TupleList), + {ok,{setelement(1,Part1,NewTupleList),GlobalData},void}; % No association then. +local_register_return(_,_,PublLD) -> % Don't understand this. + {ok,PublLD,void}. + +%% When unregister/1 us called we simply want a unalias entry in the ti-file. +%% We can unfortunately not connect it with a certain pid. +local_unregister_call(_CallingPid,{_TypeTag,[Alias],TS},PublLD) -> + {ok,PublLD,term_to_binary({undefined,Alias,unalias,TS})}. +%% ----------------------------------------------------------------------------- + +%% Call-function for global:register_name/2,/3. +%% This function is actually the call function for the handle_call/3 in the +%% global server. Note that we must check that we only do this on the node +%% where Pid actually resides. +global_register_call(_CallingPid,{call,[{register,Alias,P,_},_,_],TS},PublLD) + when node(P)==node()-> + {ok,PublLD,term_to_binary({P,{global,Alias},alias,TS})}; +global_register_call(_CallingPid,_,PublLD) -> + {ok,PublLD,void}. + +%% Call-function for global:unregister_name. It acutally checks on the use of +%% global:delete_global_name/2 which is called when ever a global name is removed. +global_unregister_call(_CallingPid,{call,[Alias,P],TS},PublLD) when node(P)==node()-> + {ok,PublLD,term_to_binary({P,{global,Alias},unalias,TS})}; +global_unregister_call(_CallingPid,_,PublLD) -> + {ok,PublLD,void}. +%% ----------------------------------------------------------------------------- + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl new file mode 100644 index 0000000000..0f428de07a --- /dev/null +++ b/lib/runtime_tools/src/observer_backend.erl @@ -0,0 +1,320 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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(observer_backend). + +%% General +-export([vsn/0]). + +%% etop stuff +-export([etop_collect/1]). +-include("observer_backend.hrl"). + +%% ttb stuff +-export([ttb_init_node/3, + ttb_write_trace_info/3, + ttb_write_binary/2, + ttb_stop/1, + ttb_fetch/2, + ttb_get_filenames/1]). +-define(CHUNKSIZE,8191). % 8 kbytes - 1 byte + +vsn() -> + case application:load(runtime_tools) of + R when R=:=ok; R=:={error,{already_loaded,runtime_tools}} -> + application:get_key(runtime_tools,vsn); + Error -> Error + end. + + + +%% +%% etop backend +%% +etop_collect(Collector) -> + ProcInfo = etop_collect(processes(), []), + Collector ! {self(),#etop_info{now = now(), + n_procs = length(ProcInfo), + run_queue = erlang:statistics(run_queue), + wall_clock = erlang:statistics(wall_clock), + runtime = erlang:statistics(runtime), + memi = etop_memi(), + procinfo = ProcInfo + }}. + +etop_memi() -> + try + [{total, c:memory(total)}, + {processes, c:memory(processes)}, + {ets, c:memory(ets)}, + {atom, c:memory(atom)}, + {code, c:memory(code)}, + {binary, c:memory(binary)}] + catch + error:notsup -> + undefined + end. + +etop_collect([P|Ps], Acc) when P =:= self() -> + etop_collect(Ps, Acc); +etop_collect([P|Ps], Acc) -> + Fs = [registered_name,initial_call,memory,reductions,current_function,message_queue_len], + case process_info(P, Fs) of + undefined -> + etop_collect(Ps, Acc); + [{registered_name,Reg},{initial_call,Initial},{memory,Mem}, + {reductions,Reds},{current_function,Current},{message_queue_len,Qlen}] -> + Name = case Reg of + [] -> Initial; + _ -> Reg + end, + Info = #etop_proc_info{pid=P,mem=Mem,reds=Reds,name=Name, + cf=Current,mq=Qlen}, + etop_collect(Ps, [Info|Acc]) + end; +etop_collect([], Acc) -> Acc. + +%% +%% ttb backend +%% +ttb_init_node(MetaFile,PI,Traci) -> + if + is_list(MetaFile); + is_atom(MetaFile) -> + file:delete(MetaFile); + true -> % {local,_,_} + ok + end, + Self = self(), + MetaPid = spawn(fun() -> ttb_meta_tracer(MetaFile,PI,Self) end), + receive {MetaPid,started} -> ok end, + MetaPid ! {metadata,Traci}, + case PI of + true -> + Proci = pnames(), + MetaPid ! {metadata,Proci}; + false -> + ok + end, + {ok,MetaPid}. + +ttb_write_trace_info(MetaPid,Key,What) -> + MetaPid ! {metadata,Key,What}, + ok. + +ttb_meta_tracer(MetaFile,PI,Parent) -> + case PI of + true -> + ReturnMS = [{'_',[],[{return_trace}]}], + erlang:trace_pattern({erlang,spawn,3},ReturnMS,[meta]), + erlang:trace_pattern({erlang,spawn_link,3},ReturnMS,[meta]), + erlang:trace_pattern({erlang,spawn_opt,1},ReturnMS,[meta]), + erlang:trace_pattern({erlang,register,2},[],[meta]), + erlang:trace_pattern({global,register_name,2},[],[meta]); + false -> + ok + end, + Parent ! {self(),started}, + ttb_meta_tracer_loop(MetaFile,PI,dict:new()). + +ttb_meta_tracer_loop(MetaFile,PI,Acc) -> + receive + {trace_ts,_,call,{erlang,register,[Name,Pid]},_} -> + ttb_store_meta({pid,{Pid,Name}},MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + {trace_ts,_,call,{global,register_name,[Name,Pid]},_} -> + ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + {trace_ts,CallingPid,call,{erlang,spawn_opt,[{M,F,Args,_}]},_} -> + MFA = {M,F,length(Args)}, + NewAcc = dict:update(CallingPid, + fun(Old) -> [MFA|Old] end, [MFA], + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + {trace_ts,CallingPid,return_from,{erlang,spawn_opt,_Arity},Ret,_} -> + case Ret of + {NewPid,_Mref} when is_pid(NewPid) -> ok; + NewPid when is_pid(NewPid) -> ok + end, + NewAcc = + dict:update(CallingPid, + fun([H|T]) -> + ttb_store_meta({pid,{NewPid,H}},MetaFile), + T + end, + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + {trace_ts,CallingPid,call,{erlang,Spawn,[M,F,Args]},_} + when Spawn==spawn;Spawn==spawn_link -> + MFA = {M,F,length(Args)}, + NewAcc = dict:update(CallingPid, + fun(Old) -> [MFA|Old] end, [MFA], + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + + {trace_ts,CallingPid,return_from,{erlang,Spawn,_Arity},NewPid,_} + when Spawn==spawn;Spawn==spawn_link -> + NewAcc = + dict:update(CallingPid, + fun([H|T]) -> + ttb_store_meta({pid,{NewPid,H}},MetaFile), + T + end, + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + + {metadata,Data} when is_list(Data) -> + ttb_store_meta(Data,MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + {metadata,Key,Fun} when is_function(Fun) -> + ttb_store_meta([{Key,Fun()}],MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + {metadata,Key,What} -> + ttb_store_meta([{Key,What}],MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + stop when PI=:=true -> + erlang:trace_pattern({erlang,spawn,3},false,[meta]), + erlang:trace_pattern({erlang,spawn_link,3},false,[meta]), + erlang:trace_pattern({erlang,spawn_opt,1},false,[meta]), + erlang:trace_pattern({erlang,register,2},false,[meta]), + erlang:trace_pattern({global,register_name,2},false,[meta]); + stop -> + ok + end. + +pnames() -> + Processes = processes(), + Globals = lists:map(fun(G) -> {global:whereis_name(G),G} end, + global:registered_names()), + lists:flatten(lists:foldl(fun(Pid,Acc) -> [pinfo(Pid,Globals)|Acc] end, + [], Processes)). + +pinfo(P,Globals) -> + case process_info(P,registered_name) of + [] -> + case lists:keysearch(P,1,Globals) of + {value,{P,G}} -> {pid,{P,{global,G}}}; + false -> + case process_info(P,initial_call) of + {_,I} -> {pid,{P,I}}; + undefined -> [] % the process has terminated + end + end; + {_,R} -> {pid,{P,R}}; + undefined -> [] % the process has terminated + end. + + +ttb_store_meta(Data,{local,MetaFile,Port}) when is_list(Data) -> + ttb_send_to_port(Port,MetaFile,Data); +ttb_store_meta(Data,MetaFile) when is_list(Data) -> + {ok,Fd} = file:open(MetaFile,[raw,append]), + ttb_write_binary(Fd,Data), + file:close(Fd); +ttb_store_meta(Data,MetaFile) -> + ttb_store_meta([Data],MetaFile). + +ttb_write_binary(Fd,[H|T]) -> + file:write(Fd,ttb_make_binary(H)), + ttb_write_binary(Fd,T); +ttb_write_binary(_Fd,[]) -> + ok. + +ttb_send_to_port(Port,MetaFile,[H|T]) -> + B1 = ttb_make_binary(H), + B2 = term_to_binary({metadata,MetaFile,B1}), + erlang:port_command(Port,B2), + ttb_send_to_port(Port,MetaFile,T); +ttb_send_to_port(_Port,_MetaFile,[]) -> + ok. + +ttb_make_binary(Term) -> + B = term_to_binary(Term), + SizeB = byte_size(B), + if SizeB > 255 -> + %% size is bigger than 8 bits, must therefore add an extra + %% size field + SB = term_to_binary({'$size',SizeB}), + <<(byte_size(SB)):8, SB/binary, B/binary>>; + true -> + <> + end. + + +%% Stop ttb +ttb_stop(MetaPid) -> + Delivered = erlang:trace_delivered(all), + receive + {trace_delivered,all,Delivered} -> ok + end, + Ref = erlang:monitor(process,MetaPid), + MetaPid ! stop, + + %% Must wait for the process to terminate there + %% because dbg will be stopped when this function + %% returns, and then the Port (in {local,MetaFile,Port}) + %% cannot be accessed any more. + receive {'DOWN', Ref, process, MetaPid, _Info} -> ok end, + seq_trace:reset_trace(), + seq_trace:set_system_tracer(false). + +%% Fetch ttb logs from remote node +ttb_fetch(MetaFile,{Port,Host}) -> + erlang:process_flag(priority,low), + Files = ttb_get_filenames(MetaFile), + {ok, Sock} = gen_tcp:connect(Host, Port, [binary, {packet, 2}]), + send_files({Sock,Host},Files), + ok = gen_tcp:close(Sock). + + +send_files({Sock,Host},[File|Files]) -> + {ok,Fd} = file:open(File,[raw,read,binary]), + gen_tcp:send(Sock,<<1,(list_to_binary(File))/binary>>), + send_chunks(Sock,Fd), + file:delete(File), + send_files({Sock,Host},Files); +send_files({_Sock,_Host},[]) -> + done. + +send_chunks(Sock,Fd) -> + case file:read(Fd,?CHUNKSIZE) of + {ok,Bin} -> + ok = gen_tcp:send(Sock, <<0,Bin/binary>>), + send_chunks(Sock,Fd); + eof -> + ok; + {error,Reason} -> + ok = gen_tcp:send(Sock, <<2,(term_to_binary(Reason))/binary>>) + end. + +ttb_get_filenames(MetaFile) -> + Dir = filename:dirname(MetaFile), + Root = filename:rootname(filename:basename(MetaFile)), + {ok,List} = file:list_dir(Dir), + match_filenames(Dir,Root,List,[]). + +match_filenames(Dir,MetaFile,[H|T],Files) -> + case lists:prefix(MetaFile,H) of + true -> match_filenames(Dir,MetaFile,T,[filename:join(Dir,H)|Files]); + false -> match_filenames(Dir,MetaFile,T,Files) + end; +match_filenames(_Dir,_MetaFile,[],Files) -> + Files. diff --git a/lib/runtime_tools/src/percept_profile.erl b/lib/runtime_tools/src/percept_profile.erl new file mode 100644 index 0000000000..b333dee0cf --- /dev/null +++ b/lib/runtime_tools/src/percept_profile.erl @@ -0,0 +1,196 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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% +%% + +%% +%% @doc Percept Collector +%% +%% This module provides the user interface for the percept data +% collection (profiling). +%% + +-module(percept_profile). +-export([ + start/1, + start/2, + start/3, + stop/0 + ]). + + +%%========================================================================== +%% +%% Type definitions +%% +%%========================================================================== + +%% @type percept_option() = procs | ports | exclusive + +-type(percept_option() :: 'procs' | 'ports' | 'exclusive' | 'scheduler'). + +%%========================================================================== +%% +%% Interface functions +%% +%%========================================================================== + +%% @spec start(Filename::string()) -> {ok, Port} | {already_started, Port} +%% @equiv start(Filename, [procs]) + +-spec(start/1 :: (Filename :: string()) -> + {'ok', port()} | {'already_started', port()}). + +start(Filename) -> + profile_to_file(Filename, [procs]). + +%% @spec start(Filename::string(), [percept_option()]) -> {ok, Port} | {already_started, Port} +%% Port = port() +%% @doc Starts profiling with supplied options. +%% All events are stored in the file given by Filename. +%% An explicit call to stop/0 is needed to stop profiling. + +-spec(start/2 :: ( + Filename :: string(), + Options :: [percept_option()]) -> + {'ok', port()} | {'already_started', port()}). + +start(Filename, Options) -> + profile_to_file(Filename, Options). + +%% @spec start(string(), MFA::mfa(), [percept_option()]) -> ok | {already_started, Port} | {error, not_started} +%% Port = port() +%% @doc Starts profiling at the entrypoint specified by the MFA. All events are collected, +%% this means that processes outside the scope of the entry-point are also profiled. +%% No explicit call to stop/0 is needed, the profiling stops when +%% the entry function returns. + +-spec(start/3 :: ( + Filename :: string(), + Entry :: {atom(), atom(), list()}, + Options :: [percept_option()]) -> + 'ok' | {'already_started', port()} | {'error', 'not_started'}). + +start(Filename, {Module, Function, Args}, Options) -> + case whereis(percept_port) of + undefined -> + profile_to_file(Filename, Options), + erlang:apply(Module, Function, Args), + stop(); + Port -> + {already_started, Port} + end. + +deliver_all_trace() -> + Tracee = self(), + Tracer = spawn(fun() -> + receive {Tracee, start} -> ok end, + Ref = erlang:trace_delivered(Tracee), + receive {trace_delivered, Tracee, Ref} -> Tracee ! {self(), ok} end + end), + erlang:trace(Tracee, true, [procs, {tracer, Tracer}]), + Tracer ! {Tracee, start}, + receive {Tracer, ok} -> ok end, + erlang:trace(Tracee, false, [procs]), + ok. +-spec(stop/0 :: () -> 'ok' | {'error', 'not_started'}). + +%% @spec stop() -> ok | {'error', 'not_started'} +%% @doc Stops profiling. + + +stop() -> + erlang:system_profile(undefined, [runnable_ports, runnable_procs]), + erlang:trace(all, false, [procs, ports, timestamp]), + deliver_all_trace(), + case whereis(percept_port) of + undefined -> + {error, not_started}; + Port -> + erlang:port_command(Port, erlang:term_to_binary({profile_stop, erlang:now()})), + %% trace delivered? + erlang:port_close(Port), + ok + end. + +%%========================================================================== +%% +%% Auxiliary functions +%% +%%========================================================================== + +profile_to_file(Filename, Opts) -> + case whereis(percept_port) of + undefined -> + io:format("Starting profiling.~n", []), + + erlang:system_flag(multi_scheduling, block), + Port = (dbg:trace_port(file, Filename))(), + % Send start time + erlang:port_command(Port, erlang:term_to_binary({profile_start, erlang:now()})), + erlang:system_flag(multi_scheduling, unblock), + + %% Register Port + erlang:register(percept_port, Port), + set_tracer(Port, Opts), + {ok, Port}; + Port -> + io:format("Profiling already started at port ~p.~n", [Port]), + {already_started, Port} + end. + +%% set_tracer + +set_tracer(Port, Opts) -> + {TOpts, POpts} = parse_profile_options(Opts), + % Setup profiling and tracing + erlang:trace(all, true, [{tracer, Port}, timestamp | TOpts]), + erlang:system_profile(Port, POpts). + +%% parse_profile_options + +parse_profile_options(Opts) -> + parse_profile_options(Opts, {[],[]}). + +parse_profile_options([], Out) -> + Out; +parse_profile_options([Opt|Opts], {TOpts, POpts}) -> + case Opt of + procs -> + parse_profile_options(Opts, { + [procs | TOpts], + [runnable_procs | POpts] + }); + ports -> + parse_profile_options(Opts, { + [ports | TOpts], + [runnable_ports | POpts] + }); + scheduler -> + parse_profile_options(Opts, { + TOpts, + [scheduler | POpts] + }); + exclusive -> + parse_profile_options(Opts, { + TOpts, + [exclusive | POpts] + }); + _ -> + parse_profile_options(Opts, {TOpts, POpts}) + + end. diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src new file mode 100644 index 0000000000..e6dc7a21d4 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.app.src @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-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, runtime_tools, + [{description, "RUNTIME_TOOLS version 1"}, + {vsn, "%VSN%"}, + {modules, [dbg,observer_backend,percept_profile, + inviso_rt,inviso_rt_lib,inviso_rt_meta, + inviso_as_lib,inviso_autostart,inviso_autostart_server, + runtime_tools,runtime_tools_sup,erts_alloc_config]}, + {registered, [runtime_tools_sup,inviso_rt,inviso_rt_meta]}, + {applications, [kernel, stdlib]}, +% {env, [{inviso_autostart_mod,your_own_autostart_module}]}, + {env, []}, + {mod, {runtime_tools, []}}]}. + + diff --git a/lib/runtime_tools/src/runtime_tools.appup.src b/lib/runtime_tools/src/runtime_tools.appup.src new file mode 100644 index 0000000000..7a435e9b22 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.appup.src @@ -0,0 +1,19 @@ +%% +%% %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%",[],[]}. diff --git a/lib/runtime_tools/src/runtime_tools.erl b/lib/runtime_tools/src/runtime_tools.erl new file mode 100644 index 0000000000..2181244610 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.erl @@ -0,0 +1,49 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% +%% Description: Callback module for the runtime_tools application. +%% ---------------------------------------------------------------------------- + +-module(runtime_tools). +-behaviour(application). + +-export([start/2,stop/1]). + + +%% ----------------------------------------------------------------------------- +%% Callback functions for the runtime_tools application +%% ----------------------------------------------------------------------------- +start(_,AutoModArgs) -> + case supervisor:start_link({local,runtime_tools_sup}, + runtime_tools_sup, + AutoModArgs) of + {ok, Pid} -> + {ok, Pid, []}; + Error -> + Error + end. + +stop(_) -> + ok. +%% ----------------------------------------------------------------------------- + + + + + + diff --git a/lib/runtime_tools/src/runtime_tools_sup.erl b/lib/runtime_tools/src/runtime_tools_sup.erl new file mode 100644 index 0000000000..1a872c355d --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools_sup.erl @@ -0,0 +1,43 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% +%% ------------------------------------------------------------------------------ +%% File : runtime_tools_sup.erl +%% Author : Lennart Öhman + +-module(runtime_tools_sup). +-behaviour(supervisor). + +-export([init/1]). + + +%% ============================================================================= +%% Callback functions for the runtime_tools_sup supervisor +%% ============================================================================= + +%% The runtime tools top most supervisor starts: +%% -The inviso runtime component. This is the only way to get the runtime component +%% started automatically (if for instance autostart is wanted). +%% Note that it is not impossible that the runtime component terminates it self +%% should it discover that no autostart is configured. +init(AutoModArgs) -> + Flags = {one_for_one, 0, 3600}, + Children = [{inviso_rt, {inviso_rt, start_link_auto, [AutoModArgs]}, + temporary, 3000, worker, [inviso_rt]}], + {ok, {Flags, Children}}. +%% ----------------------------------------------------------------------------- diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk new file mode 100644 index 0000000000..591f3e1ac4 --- /dev/null +++ b/lib/runtime_tools/vsn.mk @@ -0,0 +1 @@ +RUNTIME_TOOLS_VSN = 1.8.2 -- cgit v1.2.3