aboutsummaryrefslogtreecommitdiffstats
path: root/lib/runtime_tools
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/runtime_tools
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/runtime_tools')
-rw-r--r--lib/runtime_tools/AUTHORS6
-rw-r--r--lib/runtime_tools/Makefile38
-rw-r--r--lib/runtime_tools/c_src/Makefile5
-rw-r--r--lib/runtime_tools/c_src/Makefile.in151
-rw-r--r--lib/runtime_tools/c_src/trace_file_drv.c638
-rw-r--r--lib/runtime_tools/c_src/trace_ip_drv.c938
-rw-r--r--lib/runtime_tools/doc/html/.gitignore0
-rw-r--r--lib/runtime_tools/doc/man3/.gitignore0
-rw-r--r--lib/runtime_tools/doc/man6/.gitignore0
-rw-r--r--lib/runtime_tools/doc/pdf/.gitignore0
-rw-r--r--lib/runtime_tools/doc/src/Makefile123
-rw-r--r--lib/runtime_tools/doc/src/book.xml46
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml1187
-rw-r--r--lib/runtime_tools/doc/src/erts_alloc_config.xml211
-rw-r--r--lib/runtime_tools/doc/src/fascicules.xml15
-rw-r--r--lib/runtime_tools/doc/src/make.dep20
-rw-r--r--lib/runtime_tools/doc/src/note.gifbin0 -> 1539 bytes
-rw-r--r--lib/runtime_tools/doc/src/notes.xml503
-rw-r--r--lib/runtime_tools/doc/src/notes_history.xml48
-rw-r--r--lib/runtime_tools/doc/src/part_notes.xml38
-rw-r--r--lib/runtime_tools/doc/src/part_notes_history.xml38
-rw-r--r--lib/runtime_tools/doc/src/ref_man.xml38
-rw-r--r--lib/runtime_tools/doc/src/runtime_tools_app.xml56
-rw-r--r--lib/runtime_tools/doc/src/warning.gifbin0 -> 1498 bytes
-rw-r--r--lib/runtime_tools/ebin/.gitignore0
-rw-r--r--lib/runtime_tools/include/observer_backend.hrl42
-rw-r--r--lib/runtime_tools/info2
-rw-r--r--lib/runtime_tools/priv/.gitignore0
-rw-r--r--lib/runtime_tools/src/Makefile109
-rw-r--r--lib/runtime_tools/src/dbg.erl1749
-rw-r--r--lib/runtime_tools/src/erts_alloc_config.erl670
-rw-r--r--lib/runtime_tools/src/inviso_as_lib.erl155
-rw-r--r--lib/runtime_tools/src/inviso_autostart.erl208
-rw-r--r--lib/runtime_tools/src/inviso_autostart_server.erl311
-rw-r--r--lib/runtime_tools/src/inviso_rt.erl2895
-rw-r--r--lib/runtime_tools/src/inviso_rt_lib.erl474
-rw-r--r--lib/runtime_tools/src/inviso_rt_meta.erl1207
-rw-r--r--lib/runtime_tools/src/observer_backend.erl320
-rw-r--r--lib/runtime_tools/src/percept_profile.erl196
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src32
-rw-r--r--lib/runtime_tools/src/runtime_tools.appup.src19
-rw-r--r--lib/runtime_tools/src/runtime_tools.erl49
-rw-r--r--lib/runtime_tools/src/runtime_tools_sup.erl43
-rw-r--r--lib/runtime_tools/vsn.mk1
44 files changed, 12581 insertions, 0 deletions
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 <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef __WIN32__
+# include <io.h>
+# define write _write
+# define open _open
+# define close _close
+# define unlink _unlink
+#else
+# include <unistd.h>
+#endif
+#include <errno.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#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 <assert.h>
+#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 <WrapSize> <WrapCnt> <TailIndex> "] "n <Filename>"
+** Where...
+** <Filename>, a string ('\0' terminated):
+** The filename where the trace output is to be written.
+** "w ...", if present orders a size limited wrapping log.
+** <WrapSize>, an unsigned integer:
+** The size limit of each log file.
+** <WrapCnt>, an unsigned integer:
+** The number of log files.
+** <TailIndex>, 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 <Filename>" 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 <winsock2.h>
+# include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __WIN32__
+# ifdef VXWORKS
+# include <sockLib.h>
+# include <sys/times.h>
+# include <iosLib.h>
+# include <taskLib.h>
+# include <selectLib.h>
+# include <ioLib.h>
+# include "reclaim.h"
+# endif
+# include <unistd.h>
+# include <errno.h>
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <fcntl.h>
+#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 <assert.h>
+# 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):
+** <Portno> <Quesize> <Fl>
+** 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
--- /dev/null
+++ b/lib/runtime_tools/doc/html/.gitignore
diff --git a/lib/runtime_tools/doc/man3/.gitignore b/lib/runtime_tools/doc/man3/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/runtime_tools/doc/man3/.gitignore
diff --git a/lib/runtime_tools/doc/man6/.gitignore b/lib/runtime_tools/doc/man6/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/runtime_tools/doc/man6/.gitignore
diff --git a/lib/runtime_tools/doc/pdf/.gitignore b/lib/runtime_tools/doc/pdf/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/runtime_tools/doc/pdf/.gitignore
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE book SYSTEM "book.dtd">
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header titlestyle="normal">
+ <copyright>
+ <year>1999</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Runtime_Tools</title>
+ <prepared>Patrik Nyblom</prepared>
+ <docno></docno>
+ <date>1999-05-31</date>
+ <rev>A</rev>
+ <file>book.sgml</file>
+ </header>
+ <insidecover>
+ </insidecover>
+ <pagetext>Runtime_Tools</pagetext>
+ <preamble>
+ <contents level="2"></contents>
+ </preamble>
+ <applications>
+ <xi:include href="ref_man.xml"/>
+ </applications>
+ <releasenotes>
+ <xi:include href="notes.xml"/>
+ </releasenotes>
+ <listofterms></listofterms>
+ <index></index>
+</book>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>dbg</title>
+ <prepared>Patrik Nyblom (Claes Wikstrom)</prepared>
+ <responsible></responsible>
+ <docno>1</docno>
+ <approved>ETX/B/SFP (Kenneth Lundin)</approved>
+ <checked></checked>
+ <date>96-11-07</date>
+ <rev>A</rev>
+ <file>dbg.sgml</file>
+ </header>
+ <module>dbg</module>
+ <modulesummary>The Text Based Trace Facility</modulesummary>
+ <description>
+ <p>This module implements a text based interface to the
+ <c>trace/3</c> and the <c>trace_pattern/2</c> 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
+ <c>pman</c> module.
+ </p>
+ <p>For some examples of how to use <c>dbg</c> from the Erlang
+ shell, see the <seealso marker="#simple_example">simple example</seealso> section.
+ </p>
+ <p>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 <seealso marker="#advanced">advanced topics</seealso> section.
+ </p>
+ </description>
+ <funcs>
+ <func>
+ <name>fun2ms(LiteralFun) -> MatchSpec</name>
+ <fsummary>Pseudo function that transforms fun syntax to match_spec.</fsummary>
+ <type>
+ <v>LiteralFun = fun() literal</v>
+ <v>MatchSpec = term()</v>
+ </type>
+ <desc>
+ <p>Pseudo function that by means of a <c>parse_transform</c>
+ translates the <em>literal</em><c>fun()</c> typed as parameter in
+ the function call to a match specification as described in
+ the <c>match_spec</c> manual of <c>ERTS</c> users guide.
+ (with literal I mean that the <c>fun()</c> 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). </p>
+ <p>The parse transform is implemented in the module
+ <c>ms_transform</c> and the source <em>must</em> include the
+ file <c>ms_transform.hrl</c> 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
+ <c>-include_lib("stdlib/include/ms_transform.hrl").</c> to
+ the source file.</p>
+ <p>The <c>fun()</c> 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 <c>is_</c>XXX guard tests and one
+ cannot use language constructs that have no representation
+ in a match_spec (like <c>if</c>, <c>case</c>,
+ <c>receive</c> etc). The return value from the fun will be
+ the return value of the resulting match_spec.</p>
+ <p>Example:</p>
+ <pre>
+1> <input>dbg:fun2ms(fun([M,N]) when N > 3 -> return_trace() end).</input>
+[{['$1','$2'],[{'>','$2',3}],[{return_trace}]}]</pre>
+ <p>Variables from the environment can be imported, so that this
+ works:</p>
+ <pre>
+2> <input>X=3.</input>
+3
+3> <input>dbg:fun2ms(fun([M,N]) when N > X -> return_trace() end).</input>
+[{['$1','$2'],[{'>','$2',{const,3}}],[{return_trace}]}]</pre>
+ <p>The imported variables will be replaced by match_spec
+ <c>const</c> expressions, which is consistent with the
+ static scoping for Erlang <c>fun()</c>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:</p>
+ <pre>
+4> <input>dbg:fun2ms(fun([M,N]) when N > X, is_atomm(M) -> return_trace() end).</input>
+Error: fun containing local erlang function calls ('is_atomm' called in guard) cannot be translated into match_spec
+{error,transform_error}
+5> <input>dbg:fun2ms(fun([M,N]) when N > X, is_atom(M) -> return_trace() end).</input>
+[{['$1','$2'],[{'>','$2',{const,3}},{is_atom,'$1'}],[{return_trace}]}]</pre>
+ <p>As you can see by the example, the function can be called from
+ the shell too. The <c>fun()</c> 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).</p>
+ <warning>
+ <p>If the parse_transform is not applied to a module which calls this
+ pseudo function, the call will fail in runtime (with a
+ <c>badarg</c>). The module <c>dbg</c> 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
+ <c>parse_transform</c> is properly applied by including
+ the <c>ms_transform.hrl</c> header file, compiled code
+ will never call the function, but the function call is
+ replaced by a literal match_spec.</p>
+ </warning>
+ <p>More information is provided by the <c>ms_transform</c>
+ manual page in STDLIB.</p>
+ </desc>
+ </func>
+ <func>
+ <name>h() -> ok </name>
+ <fsummary>Give a list of available help items on standard output.</fsummary>
+ <desc>
+ <p>Gives a list of items for brief online help.</p>
+ </desc>
+ </func>
+ <func>
+ <name>h(Item) -> ok </name>
+ <fsummary>Give brief help for an item.</fsummary>
+ <type>
+ <v>Item = atom()</v>
+ </type>
+ <desc>
+ <p>Gives a brief help text for functions in the dbg module. The
+ available items can be listed with <c>dbg:h/0</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>p(Item) -> {ok, MatchDesc} | {error, term()} </name>
+ <fsummary>Trace messages to and from Item.</fsummary>
+ <desc>
+ <p>Equivalent to <c>p(Item, [m])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Trace Item according to Flags.</fsummary>
+ <type>
+ <v>MatchDesc = [MatchNum]</v>
+ <v>MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError}</v>
+ <v>RPCError = term()</v>
+ </type>
+ <desc>
+ <p>Traces <c>Item</c> in accordance to the value specified
+ by <c>Flags</c>. The variation of <c>Item</c> is listed below:</p>
+ <list type="bulleted">
+ <item>If the <c>Item</c> is a <c>pid()</c>, 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 (<seealso marker="#n">see</seealso><c>n/1</c> and <c>tracer/0/2/3</c>).</item>
+ <item>If the <c>Item</c> is the atom <c>all</c>, all processes in the
+ system as well as all processes created hereafter are
+ to be traced. This also affects all nodes added with the
+ <c>n/1</c> or <c>tracer/0/2/3</c> function.</item>
+ <item>If the <c>Item</c> is the atom <c>new</c>, no currently existing
+ processes are affected, but every process created after the
+ call is.This also affects all nodes added with the
+ <c>n/1</c> or <c>tracer/0/2/3</c> function.</item>
+ <item>If the <c>Item</c> is the atom <c>existing</c>, all
+ existing processes are traced, but new processes will not
+ be affected.This also affects all nodes added with the
+ <c>n/1</c> or <c>tracer/0/2/3</c> function.</item>
+ <item>If the <c>Item</c> is an atom other than <c>all</c>,
+ <c>new</c> or <c>existing</c>, 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 <c>n/1</c> or <c>tracer/0/2/3</c> function.</item>
+ <item>If the <c>Item</c> is an integer, the process <c><![CDATA[<0.Item.0>]]></c> is
+ traced.</item>
+ <item>If the <c>Item</c> is a tuple <c>{X, Y, Z}</c>, the
+ process <c><![CDATA[<X.Y.Z>]]></c> is
+ traced. </item>
+ <item>If the <c>Item</c> is a string <![CDATA["<X.Y.Z>"]]>
+ as returned from <c>pid_to_list/1</c>, the process
+ <c><![CDATA[<X.Y.Z>]]></c> is traced. </item>
+ </list>
+ <p><c>Flags</c> can be a single atom,
+ or a list of flags. The available flags are:
+ </p>
+ <taglist>
+ <tag><c>s (send)</c></tag>
+ <item>
+ <p>Traces the messages the process sends.</p>
+ </item>
+ <tag><c>r (receive)</c></tag>
+ <item>
+ <p>Traces the messages the process receives.</p>
+ </item>
+ <tag><c>m (messages)</c></tag>
+ <item>
+ <p>Traces the messages the process receives and sends.</p>
+ </item>
+ <tag><c>c (call)</c></tag>
+ <item>
+ <p>Traces global function calls for the process
+ according to the trace patterns set in the system (see tp/2).</p>
+ </item>
+ <tag><c>p (procs)</c></tag>
+ <item>
+ <p>Traces process related events to the process.</p>
+ </item>
+ <tag><c>sos (set on spawn)</c></tag>
+ <item>
+ <p>Lets all processes created by the traced
+ process inherit the trace flags
+ of the traced process.</p>
+ </item>
+ <tag><c>sol (set on link)</c></tag>
+ <item>
+ <p>Lets another process, <c>P2</c>, inherit the
+ trace flags of the traced
+ process whenever the traced process links to <c>P2</c>.</p>
+ </item>
+ <tag><c>sofs (set on first spawn)</c></tag>
+ <item>
+ <p>This is the same as <c>sos</c>, but only
+ for the first process spawned by the traced process.</p>
+ </item>
+ <tag><c>sofl (set on first link)</c></tag>
+ <item>
+ <p>This is the same as <c>sol</c>, but only for
+ the first call to
+ <c>link/1</c> by the traced process.</p>
+ </item>
+ <tag><c>all</c></tag>
+ <item>
+ <p>Sets all flags.</p>
+ </item>
+ <tag><c>clear</c></tag>
+ <item>
+ <p>Clears all flags.
+ </p>
+ </item>
+ </taglist>
+ <p>The list can also include any of the flags allowed in
+ <c>erlang:trace/3</c></p>
+ <p>The function returns either an error tuple or a tuple
+ <c>{ok, List}</c>. The <c>List</c> consists of
+ specifications of how many processes that matched (in the
+ case of a pure pid() exactly 1). The specification of
+ matched processes is <c>{matched, Node, N}</c>. If the
+ remote processor call,<c>rpc</c>, to a remote node fails,
+ the <c>rpc</c> 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
+ <c>rpc</c> calls to one, several or even all nodes failed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>c(Mod, Fun, Args)</name>
+ <fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>all</c>trace flags set.</fsummary>
+ <desc>
+ <p>Equivalent to <c>c(Mod, Fun, Args, all)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>c(Mod, Fun, Args, Flags)</name>
+ <fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>Flags</c>trace flags set.</fsummary>
+ <desc>
+ <p>Evaluates the expression <c>apply(Mod, Fun, Args)</c> with the trace
+ flags in <c>Flags</c> set. This is a convenient way to trace processes
+ from the Erlang shell.</p>
+ </desc>
+ </func>
+ <func>
+ <name>i() -> ok</name>
+ <fsummary>Display information about all traced processes.</fsummary>
+ <desc>
+ <p>Displays information about all traced processes.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tp(Module,MatchSpec)</name>
+ <fsummary>Set pattern for traced global function calls</fsummary>
+ <desc>
+ <p>Same as tp({Module, '_', '_'}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tp(Module,Function,MatchSpec)</name>
+ <fsummary>Set pattern for traced global function calls</fsummary>
+ <desc>
+ <p>Same as tp({Module, Function, '_'}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tp(Module, Function, Arity, MatchSpec)</name>
+ <fsummary>Set pattern for traced global function calls</fsummary>
+ <desc>
+ <p>Same as tp({Module, Function, Arity}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Set pattern for traced global function calls</fsummary>
+ <type>
+ <v>Module = atom() | '_'</v>
+ <v>Function = atom() | '_'</v>
+ <v>Arity = integer() |'_'</v>
+ <v>MatchSpec = integer() | atom() | [] | match_spec()</v>
+ <v>MatchDesc = [MatchInfo]</v>
+ <v>MatchInfo = {saved, integer()} | MatchNum</v>
+ <v>MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError}</v>
+ </type>
+ <desc>
+ <p>This function enables call trace for one or more
+ functions. All exported functions matching the <c>{Module, Function, Arity}</c> argument will be concerned, but the
+ <c>match_spec()</c> may further narrow down the set of function
+ calls generating trace messages.</p>
+ <p>For a description of the <c>match_spec()</c> syntax,
+ please turn to the
+ <em>User's guide</em> part of the online
+ documentation for the runtime system (<em>erts</em>). The
+ chapter <em>Match Specification in Erlang</em> explains the
+ general match specification "language".</p>
+ <p>The Module, Function and/or Arity parts of the tuple may
+ be specified as the atom <c>'_'</c> which is a "wild-card"
+ matching all modules/functions/arities. Note, if the
+ Module is specified as <c>'_'</c>, the Function and Arity
+ parts have to be specified as '_' too. The same holds for the
+ Functions relation to the Arity.</p>
+ <p>All nodes added with <c>n/1</c> or <c>tracer/0/2/3</c> will
+ be affected by this call, and if Module is not <c>'_'</c>
+ the module will be loaded on all nodes.</p>
+ <p>The function returns either an error tuple or a tuple
+ <c>{ok, List}</c>. The <c>List</c> consists of specifications of how
+ many functions that matched, in the same way as the processes
+ are presented in the return value of <c>p/2</c>. </p>
+ <p>There may be a tuple <c>{saved, N}</c> in the return value,
+ if the MatchSpec is other
+ than []. The integer <c>N</c> 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 <c>ltp/0</c> below).</p>
+ <p>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 <c>{error, string()}</c> where
+ the string is a textual explanation of the compilation
+ error. An example:</p>
+ <pre>
+(x@y)4> <input>dbg:tp({dbg,ltp,0},[{[],[],[{message, two, arguments}, {noexist}]}]).</input>
+{error,
+ [{error,"Special form 'message' called with wrong number of
+ arguments in {message,two,arguments}."},
+ {error,"Function noexist/1 does_not_exist."}]}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>tpl(Module,MatchSpec)</name>
+ <fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
+ <desc>
+ <p>Same as tpl({Module, '_', '_'}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tpl(Module,Function,MatchSpec)</name>
+ <fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
+ <desc>
+ <p>Same as tpl({Module, Function, '_'}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tpl(Module, Function, Arity, MatchSpec)</name>
+ <fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
+ <desc>
+ <p>Same as tpl({Module, Function, Arity}, MatchSpec)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
+ <desc>
+ <p>This function works as <c>tp/2</c>, but enables
+ tracing for local calls (and local functions) as well as for
+ global calls (and functions).</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctp()</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctp({'_', '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctp(Module)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctp({Module, '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctp(Module, Function)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctp({Module, Function, '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctp(Module, Function, Arity)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctp({Module, Function, Arity})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <type>
+ <v>Module = atom() | '_'</v>
+ <v>Function = atom() | '_'</v>
+ <v>Arity = integer() | '_'</v>
+ <v>MatchDesc = [MatchNum]</v>
+ <v>MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError}</v>
+ </type>
+ <desc>
+ <p>This function disables call tracing on the specified
+ functions. The semantics of the parameter is the same
+ as for the corresponding function specification in
+ <c>tp/2</c> or <c>tpl/2</c>. Both local and global call trace
+ is disabled. </p>
+ <p>The return value reflects how many functions that matched,
+ and is constructed as described in <c>tp/2</c>. No tuple
+ <c>{saved, N}</c> is however ever returned (for obvious reasons).</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpl()</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpl({'_', '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpl(Module)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpl({Module, '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpl(Module, Function)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpl({Module, Function, '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpl(Module, Function, Arity)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpl({Module, Function, Arity})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>This function works as <c>ctp/1</c>, but only disables
+ tracing set up with <c>tpl/2</c> (not with <c>tp/2</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpg()</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpg({'_', '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpg(Module)</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpg({Module, '_', '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpg(Module, Function)</name>
+ <fsummary>>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpg({Module, Function, '_'})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpg(Module, Function, Arity)</name>
+ <fsummary>>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>Same as ctpg({Module, Function, Arity})</p>
+ </desc>
+ </func>
+ <func>
+ <name>ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <fsummary>Clear call trace pattern for the specified functions</fsummary>
+ <desc>
+ <p>This function works as <c>ctp/1</c>, but only disables
+ tracing set up with <c>tp/2</c> (not with <c>tpl/2</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>ltp() -> ok</name>
+ <fsummary>List saved and built-in match specifications on the console.</fsummary>
+ <desc>
+ <p>Use this function to recall all match specifications previously
+ used in the session (i. e. previously saved during calls
+ to <c>tp/2</c>, 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 <c>stop/0</c> is called.</p>
+ <p>Match specifications used can be saved in a file (if a
+ read-write file system is present) for use in later
+ debugging sessions, see <c>wtp/1</c> and <c>rtp/1</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>dtp() -> ok</name>
+ <fsummary>Delete all saved match specifications.</fsummary>
+ <desc>
+ <p>Use this function to "forget" all match specifications
+ saved during calls to <c>tp/2</c>.
+ This is useful when one wants to restore other match
+ specifications from a file with <c>rtp/1</c>. Use
+ <c>dtp/1</c> to delete specific saved match specifications. </p>
+ </desc>
+ </func>
+ <func>
+ <name>dtp(N) -> ok</name>
+ <fsummary>Delete a specific saved match_spec.</fsummary>
+ <type>
+ <v>N = integer()</v>
+ </type>
+ <desc>
+ <p>Use this function to "forget" a specific match specification
+ saved during calls to <c>tp/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>wtp(Name) -> ok | {error, IOError}</name>
+ <fsummary>Write all saved and built-in match specifications to a file</fsummary>
+ <type>
+ <v>Name = string()</v>
+ <v>IOError = term()</v>
+ </type>
+ <desc>
+ <p>This function will save all match specifications saved
+ during the session (during calls to <c>tp/2</c>)
+ and built-in match specifications in a text
+ file with the name designated by <c>Name</c>. The format
+ of the file is textual, why it can be edited with an
+ ordinary text editor, and then restored with
+ <c>rtp/1</c>. </p>
+ <p>Each match spec in the file ends with a full stop
+ (<c>.</c>) and new (syntactically correct) match
+ specifications can be added to the file manually.</p>
+ <p>The function returns <c>ok</c> or an error tuple where the
+ second element contains the I/O error that made the
+ writing impossible.</p>
+ </desc>
+ </func>
+ <func>
+ <name>rtp(Name) -> ok | {error, Error}</name>
+ <fsummary>Read saved match specifications from file.</fsummary>
+ <type>
+ <v>Name = string()</v>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p>This function reads match specifications from a file
+ (possibly) generated by the <c>wtp/1</c> 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. </p>
+ <p>The match specifications in the file are <em>merged</em>
+ with the current match specifications, so that no duplicates
+ are generated. Use <c>ltp/0</c> to see what numbers were
+ assigned to the specifications from the file.</p>
+ <p>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. <marker id="n"></marker>
+</p>
+ </desc>
+ </func>
+ <func>
+ <name>n(Nodename) -> {ok, Nodename} | {error, Reason}</name>
+ <fsummary>Add a remote node to the list of traced nodes</fsummary>
+ <type>
+ <v>Nodename = atom()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <c>dbg</c> server keeps a list of nodes where tracing
+ should be performed. Whenever a <c>tp/2</c> call or a
+ <c>p/2</c> call is made, it is executed for all nodes in this
+ list including the local node (except for <c>p/2</c> with a
+ specific <c>pid()</c> as first argument, in which case the
+ command is executed only on the node where the designated
+ process resides).
+ </p>
+ <p>This function adds a remote node (<c>Nodename</c>) 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 <c>no_local_tracer</c> is returned. The
+ tracer process on the local node must be started with the
+ <c>tracer/0/2</c> function.
+ </p>
+ <p>If <c>Nodename</c> is the local node, the error reason
+ <c>cant_add_local_node</c> is returned.
+ </p>
+ <p>If a trace port (<seealso marker="#trace_port">see</seealso><c>trace_port/2</c>) is
+ running on the local node, remote nodes can not be traced with
+ a tracer process. The error reason
+ <c>cant_trace_remote_pid_to_local_port</c> is returned. A
+ trace port can however be started on the remote node with the
+ <c>tracer/3</c> function.
+ </p>
+ <p>The function will also return an error if the node
+ <c>Nodename</c> is not reachable.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cn(Nodename) -> ok</name>
+ <fsummary>Clear a node from the list of traced nodes.</fsummary>
+ <type>
+ <v>Nodename = atom()</v>
+ </type>
+ <desc>
+ <p>Clears a node from the list of traced nodes. Subsequent
+ calls to <c>tp/2</c> and <c>p/2</c> will not consider that
+ node, but tracing already activated on the node will continue
+ to be in effect.</p>
+ <p>Returns <c>ok</c>, cannot fail.</p>
+ </desc>
+ </func>
+ <func>
+ <name>ln() -> ok</name>
+ <fsummary>Show the list of traced nodes on the console.</fsummary>
+ <desc>
+ <p>Shows the list of traced nodes on the console.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tracer() -> {ok, pid()} | {error, already_started}</name>
+ <fsummary>Start a tracer server that handles trace messages.</fsummary>
+ <desc>
+ <p>This function starts a server on the local node that will
+ be the recipient of all trace messages. All subsequent calls
+ to <c>p/2</c> will result in messages sent to the newly
+ started trace server.</p>
+ <p>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 <c>tracer/2</c> for a description
+ of how the trace message handler can be customized. <marker id="tracer2"></marker>
+</p>
+ <p>To start a similar tracer on a remote node, use <c>n/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tracer(Type, Data) -> {ok, pid()} | {error, Error}</name>
+ <fsummary>Start a tracer server with additional parameters</fsummary>
+ <type>
+ <v>Type = port | process</v>
+ <v>Data = PortGenerator | HandlerSpec</v>
+ <v>HandlerSpec = {HandlerFun, InitialData}</v>
+ <v>HandlerFun = fun() (two arguments)</v>
+ <v>InitialData = term()</v>
+ <v>PortGenerator = fun() (no arguments)</v>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p>This function starts a tracer server with additional
+ parameters on the local node. The first parameter, the
+ <c>Type</c>, indicates if trace messages should be handled
+ by a receiving process (<c>process</c>) or by a tracer port
+ (<c>port</c>). For a description about tracer ports see
+ <c>trace_port/2</c>.
+ </p>
+ <p>If <c>Type</c> is a process, a message handler function can
+ be specified (<c>HandlerSpec</c>). The handler function, which
+ should be a <c>fun</c> 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 <c>InitialData</c>
+ part of the <c>HandlerSpec</c>. The <c>HandlerFun</c> may
+ chose any appropriate action to take when invoked, and can
+ save a state for the next invocation by returning it.
+ </p>
+ <p>If <c>Type</c> is a port, then the second parameter should
+ be a <em>fun</em> which takes no arguments and returns a
+ newly opened trace port when called. Such a <em>fun</em> is
+ preferably generated by calling <c>trace_port/2</c>.
+ </p>
+ <p>If an error is returned, it can either be due to a tracer
+ server already running (<c>{error,already_started}</c>) or
+ due to the <c>HandlerFun</c> throwing an exception.
+ </p>
+ <p>To start a similar tracer on a remote node, use
+ <c>tracer/3</c>. <marker id="trace_port"></marker>
+</p>
+ </desc>
+ </func>
+ <func>
+ <name>tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name>
+ <fsummary>Start a tracer server on given node with additional parameters</fsummary>
+ <type>
+ <v>Nodename = atom()</v>
+ </type>
+ <desc>
+ <p>This function is equivalent to <c>tracer/2</c>, but acts on
+ the given node. A tracer is started on the node
+ (<c>Nodename</c>) and the node is added to the list of traced
+ nodes.
+ </p>
+ <note>
+ <p>This function is not equivalent to <c>n/1</c>. While
+ <c>n/1</c> starts a process tracer which redirects all trace
+ information to a process tracer on the local node (i.e. the
+ trace control node), <c>tracer/3</c> starts a tracer of any
+ type which is independent of the tracer on the trace control
+ node.</p>
+ </note>
+ <p>For details, <seealso marker="#tracer2">see</seealso><c>tracer/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>trace_port(Type, Parameters) -> fun()</name>
+ <fsummary>Create and returns a trace port generating<em>fun</em></fsummary>
+ <type>
+ <v>Type = ip | file</v>
+ <v>Parameters = Filename | WrapFilesSpec | IPPortSpec</v>
+ <v>Filename = string() | [string()] | atom()</v>
+ <v>WrapFilesSpec = {Filename, wrap, Suffix} | {Filename, wrap, Suffix, WrapSize} | {Filename, wrap, Suffix, WrapSize, WrapCnt}</v>
+ <v>Suffix = string()</v>
+ <v>WrapSize = integer() >= 0 | {time, WrapTime}</v>
+ <v>WrapTime = integer() >= 1</v>
+ <v>WrapCnt = integer() >= 1</v>
+ <v>IpPortSpec = PortNumber | {PortNumber, QueSize}</v>
+ <v>PortNumber = integer()</v>
+ <v>QueSize = integer()</v>
+ </type>
+ <desc>
+ <p>This function creates a trace port generating <em>fun</em>.
+ The <em>fun</em> 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. <c>dbg:tracer(port, dbg:trace_port(ip, 4711))</c>. </p>
+ <p>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.</p>
+ <p>Two trace drivers are currently implemented, the
+ <c>file</c> and the <c>ip</c> 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
+ <c>trace_client/2</c> function. The ip driver opens a TCP/IP
+ port where it listens for connections. When a client
+ (preferably started by calling <c>trace_client/2</c> on
+ another Erlang node) connects, all trace messages are sent
+ over the IP network for further processing by the remote
+ client. </p>
+ <p>Using a trace port significantly lowers the overhead
+ imposed by using tracing.</p>
+ <p>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 <em>not</em>
+ guaranteed to be saved in the file in case of a system
+ crash. That is the price to pay for low tracing overhead.</p>
+ <p>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 <c>Filename ++ SeqCnt ++ Suffix</c>, where <c>SeqCnt</c> counts as a decimal string
+ from <c>0</c> to <c>WrapCnt</c> and then around again from
+ <c>0</c>. When a trace term written to
+ the current file makes it longer than <c>WrapSize</c>,
+ that file is closed, if the number of files in this
+ wrap trace is as many as <c>WrapCnt</c> 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
+ <c>WrapCnt</c> trace files saved with a size of at least
+ <c>WrapSize</c> (but not much bigger), except for
+ the last file that might even be empty. The default values
+ are <c>WrapSize = 128*1024</c> and <c>WrapCnt = 8</c>.</p>
+ <p>The <c>SeqCnt</c> values in the filenames are all in the
+ range <c>0</c> through <c>WrapCnt</c> with a gap in the
+ circular sequence. The gap is needed to find the end of the
+ trace.</p>
+ <p>If the <c>WrapSize</c> is specified as
+ <c>{time, WrapTime}</c>, the current file is closed when it
+ has been open more than <c>WrapTime</c> milliseconds,
+ regardless of it being empty or not.</p>
+ <p>The ip trace driver has a queue of <c>QueSize</c> 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 <c>trace_client/3</c> as the tuple <c>{drop, N}</c> where <c>N</c> 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.</p>
+ </desc>
+ </func>
+ <func>
+ <name>flush_trace_port()</name>
+ <fsummary>Equivalent to flush_trace_port(node()).</fsummary>
+ <desc>
+ <p>Equivalent to <c>flush_trace_port(node())</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>flush_trace_port(Nodename) -> ok | {error, Reason}</name>
+ <fsummary>Flush internal data buffers in a trace driver on the given node.</fsummary>
+ <desc>
+ <p>Equivalent to <c>trace_port_control(Nodename,flush)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>trace_port_control(Operation)</name>
+ <fsummary>Equivalent to trace_port_control(node(),Operation).</fsummary>
+ <desc>
+ <p>Equivalent to <c>trace_port_control(node(),Operation)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name>
+ <fsummary>Perform a control operation on the active trace port driver on the given node.</fsummary>
+ <type>
+ <v>Nodename = atom()</v>
+ </type>
+ <desc>
+ <p>This function is used to do a control operation on the
+ active trace port driver on the given node
+ (<c>Nodename</c>). Which operations that are allowed as well
+ as their return values are depending on which trace driver
+ that is used.</p>
+ <p>Returns either <c>ok</c> or <c>{ok, Result}</c>
+ if the operation was successful, or <c>{error, Reason}</c>
+ if the current tracer is a process
+ or if it is a port not supporting the operation.</p>
+ <p>The allowed values for <c>Operation</c> are:</p>
+ <taglist>
+ <tag><c>flush</c></tag>
+ <item>
+ <p>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 <c>ok</c>.</p>
+ </item>
+ <tag><c>get_listen_port</c></tag>
+ <item>
+ <p>Returns <c>{ok, IpPort}</c> where <c>IpPort</c>is
+ the IP port number used by the driver listen socket.
+ Only the ip trace driver supports this operation.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>trace_client(Type, Parameters) -> pid()</name>
+ <fsummary>Start a trace client that reads messages created by a trace port driver</fsummary>
+ <type>
+ <v>Type = ip | file | follow_file</v>
+ <v>Parameters = Filename | WrapFilesSpec | IPClientPortSpec</v>
+ <v>Filename = string() | [string()] | atom()</v>
+ <v>WrapFilesSpec = see trace_port/2</v>
+ <v>Suffix = string()</v>
+ <v>IpClientPortSpec = PortNumber | {Hostname, PortNumber}</v>
+ <v>PortNumber = integer()</v>
+ <v>Hostname = string()</v>
+ </type>
+ <desc>
+ <p>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 <c>tracer/0</c>
+ function.</p>
+ <p>If <c>Type</c> is <c>file</c>, the client reads all trace
+ messages stored in the file named <c>Filename</c> or
+ specified by <c>WrapFilesSpec</c> (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.</p>
+ <p>If <c>Type</c> is <c>follow_file</c>, the client behaves as
+ in the <c>file</c> case, but keeps trying to read (and
+ process) more data
+ from the file until stopped by <c>stop_trace_client/1</c>.
+ <c>WrapFilesSpec</c> is not allowed as second argument
+ for this <c>Type</c>.</p>
+ <p>If <c>Type</c> is <c>ip</c>, the client connects to the
+ TCP/IP port <c>PortNumber</c> on the host <c>Hostname</c>,
+ from where it reads trace messages until the TCP/IP connection
+ is closed. If no <c>Hostname</c> is specified, the local host
+ is assumed.</p>
+ <p>As an example, one can let trace messages be sent over the
+ network to another Erlang node (preferably <em>not</em>
+ distributed), where the formatting occurs:</p>
+ <p>On the node <c>stack</c> there's an Erlang node
+ <c>ant@stack</c>, in the shell, type the following:</p>
+ <pre>
+ant@stack> <input>dbg:tracer(port, dbg:trace_port(ip,4711)).</input>
+&lt;0.17.0>
+ant@stack> <input>dbg:p(self(), send).</input>
+{ok,1}</pre>
+ <p>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:</p>
+ <pre>
+-> <input>dbg:trace_client(ip, {"stack", 4711}).</input>
+&lt;0.42.0></pre>
+ <p>If we now send a message from the shell on the node
+ <c>ant@stack</c>, where all sends from the shell are traced:</p>
+ <pre>
+ant@stack> <input>self() ! hello.</input>
+hello</pre>
+ <p>The following will appear at the console on the node that
+ started the trace client:</p>
+ <pre>
+(&lt;0.23.0>) &lt;0.23.0> ! hello
+(&lt;0.23.0>) &lt;0.22.0> ! {shell_rep,&lt;0.23.0>,{value,hello,[],[]}}</pre>
+ <p>The last line is generated due to internal message passing
+ in the Erlang shell. The process id's will vary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>trace_client(Type, Parameters, HandlerSpec) -> pid()</name>
+ <fsummary>Start a trace client that reads messages created by a trace port driver, with a user defined handler</fsummary>
+ <type>
+ <v>Type = ip | file | follow_file</v>
+ <v>Parameters = Filename | WrapFilesSpec | IPClientPortSpec</v>
+ <v>Filename = string() | [string()] | atom()</v>
+ <v>WrapFilesSpec = see trace_port/2</v>
+ <v>Suffix = string()</v>
+ <v>IpClientPortSpec = PortNumber | {Hostname, PortNumber}</v>
+ <v>PortNumber = integer()</v>
+ <v>Hostname = string()</v>
+ <v>HandlerSpec = {HandlerFun, InitialData}</v>
+ <v>HandlerFun = fun() (two arguments)</v>
+ <v>InitialData = term()</v>
+ </type>
+ <desc>
+ <p>This function works exactly as <c>trace_client/2</c>, but
+ allows you to write your own handler function. The handler
+ function works mostly as the one described in
+ <c>tracer/2</c>, but will also have to be prepared to handle
+ trace messages of the form <c>{drop, N}</c>, where <c>N</c> is
+ the number of dropped messages. This pseudo trace message will
+ only occur if the ip trace driver is used.</p>
+ <p>For trace type <c>file</c>, the pseudo trace message
+ <c>end_of_trace</c> will appear at the end of the trace. The
+ return value from the handler function is in this case
+ ignored.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop_trace_client(Pid) -> ok</name>
+ <fsummary>Stop a trace client gracefully.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>This function shuts down a previously started trace
+ client. The <c>Pid</c> argument is the process id returned
+ from the <c>trace_client/2</c> or <c>trace_client/3</c> call.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_tracer()</name>
+ <fsummary>Equivalent to get_tracer(node()).</fsummary>
+ <desc>
+ <p>Equivalent to <c>get_tracer(node())</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_tracer(Nodename) -> {ok, Tracer}</name>
+ <fsummary>Return the process or port to which all trace messages are sent.</fsummary>
+ <type>
+ <v>Nodename = atom()</v>
+ <v>Tracer = port() | pid()</v>
+ </type>
+ <desc>
+ <p>Returns the process or port to which all trace
+ messages are sent. </p>
+ </desc>
+ </func>
+ <func>
+ <name>stop() -> stopped</name>
+ <fsummary>Stop the <c>dbg</c>server and the tracing of all processes.</fsummary>
+ <desc>
+ <p>Stops the <c>dbg</c> 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.</p>
+ <p>Note that no trace patterns are affected by this
+ function.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop_clear() -> stopped</name>
+ <fsummary>Stop the <c>dbg</c>server and the tracing of all processes, and clears trace patterns.</fsummary>
+ <desc>
+ <p>Same as stop/0, but also clears all trace patterns on local
+ and global functions calls.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <marker id="simple_example"></marker>
+ <title>Simple examples - tracing from the shell</title>
+ <p>The simplest way of tracing from the Erlang shell is to use
+ <c>dbg:c/3</c> or <c>dbg:c/4</c>, e.g. tracing the function
+ <c>dbg:get_tracer/0</c>:</p>
+ <pre>
+(tiger@durin)84> <input>dbg:c(dbg,get_tracer,[]).</input>
+(&lt;0.154.0>) &lt;0.152.0> ! {&lt;0.154.0>,{get_tracer,tiger@durin}}
+(&lt;0.154.0>) out {dbg,req,1}
+(&lt;0.154.0>) &lt;&lt; {dbg,{ok,&lt;0.153.0>}}
+(&lt;0.154.0>) in {dbg,req,1}
+(&lt;0.154.0>) &lt;&lt; timeout
+{ok,&lt;0.153.0>}
+(tiger@durin)85></pre>
+ <p>Another way of tracing from the shell is to explicitly start a
+ <em>tracer</em> and then set the <em>trace flags</em> of your
+ choice on the processes you want to trace, e.g. trace messages and
+ process events:</p>
+ <pre>
+(tiger@durin)66> <input>Pid = spawn(fun() -> receive {From,Msg} -> From ! Msg end end).</input>
+&lt;0.126.0>
+(tiger@durin)67> <input>dbg:tracer().</input>
+{ok,&lt;0.128.0>}
+(tiger@durin)68> <input>dbg:p(Pid,[m,procs]).</input>
+{ok,[{matched,tiger@durin,1}]}
+(tiger@durin)69> <input>Pid ! {self(),hello}.</input>
+(&lt;0.126.0>) &lt;&lt; {&lt;0.116.0>,hello}
+{&lt;0.116.0>,hello}
+(&lt;0.126.0>) &lt;&lt; timeout
+(&lt;0.126.0>) &lt;0.116.0> ! hello
+(&lt;0.126.0>) exit normal
+(tiger@durin)70> <input>flush().</input>
+Shell got hello
+ok
+(tiger@durin)71></pre>
+ <p>If you set the <c>call</c> trace flag, you also have to set a
+ <em>trace pattern</em> for the functions you want to trace:</p>
+ <pre>
+(tiger@durin)77> <input>dbg:tracer().</input>
+{ok,&lt;0.142.0>}
+(tiger@durin)78> <input>dbg:p(all,call).</input>
+{ok,[{matched,tiger@durin,3}]}
+(tiger@durin)79> <input>dbg:tp(dbg,get_tracer,0,[]).</input>
+{ok,[{matched,tiger@durin,1}]}
+(tiger@durin)80> <input>dbg:get_tracer().</input>
+(&lt;0.116.0>) call dbg:get_tracer()
+{ok,&lt;0.143.0>}
+(tiger@durin)81> <input>dbg:tp(dbg,get_tracer,0,[{'_',[],[{return_trace}]}]).</input>
+{ok,[{matched,tiger@durin,1},{saved,1}]}
+(tiger@durin)82> <input>dbg:get_tracer().</input>
+(&lt;0.116.0>) call dbg:get_tracer()
+(&lt;0.116.0>) returned from dbg:get_tracer/0 -> {ok,&lt;0.143.0>}
+{ok,&lt;0.143.0>}
+(tiger@durin)83></pre>
+ </section>
+
+ <section>
+ <marker id="advanced"></marker>
+ <title>Advanced topics - combining with seq_trace</title>
+ <p>The <c>dbg</c> module is primarily targeted towards
+ tracing through the <c>erlang:trace/3</c> function. It is
+ sometimes desired to trace messages in a more delicate way, which
+ can be done with the help of the <c>seq_trace</c> module.
+ </p>
+ <p><c>seq_trace</c> implements sequential tracing (known in the
+ AXE10 world, and sometimes called "forlopp tracing"). <c>dbg</c>
+ can interpret messages generated from <c>seq_trace</c> and the
+ same tracer function for both types of tracing can be used. The
+ <c>seq_trace</c> messages can even be sent to a trace port for
+ further analysis.
+ </p>
+ <p>As a match specification can turn on sequential tracing, the
+ combination of <c>dbg</c> and <c>seq_trace</c> can be quite
+ powerful. This brief example shows a session where sequential
+ tracing is used:</p>
+ <pre>
+1> <input>dbg:tracer().</input>
+{ok,&lt;0.30.0>}
+2> <input>{ok, Tracer} = dbg:get_tracer().</input>
+{ok,&lt;0.31.0>}
+3> <input>seq_trace:set_system_tracer(Tracer).</input>
+false
+4> <input>dbg:tp(dbg, get_tracer, 0, [{[],[],[{set_seq_token, send, true}]}]).</input>
+{ok,[{matched,nonode@nohost,1},{saved,1}]}
+5> <input>dbg:p(all,call).</input>
+{ok,[{matched,nonode@nohost,22}]}
+6> <input>dbg:get_tracer(), seq_trace:set_token([]).</input>
+(&lt;0.25.0>) call dbg:get_tracer()
+SeqTrace [0]: (&lt;0.25.0>) &lt;0.30.0> ! {&lt;0.25.0>,get_tracer} [Serial: {2,4}]
+SeqTrace [0]: (&lt;0.30.0>) &lt;0.25.0> ! {dbg,{ok,&lt;0.31.0>}} [Serial: {4,5}]
+{1,0,5,&lt;0.30.0>,4}</pre>
+ <p>This session sets the system_tracer to the same process as
+ the ordinary tracer process (i. e. &lt;0.31.0&gt;) and sets the
+ trace pattern for the function <c>dbg:get_tracer</c> 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
+ <c>seq_trace</c> messages are sent both for the server request
+ and the response. The <c>seq_trace:set_token([])</c> after the
+ call clears the <c>seq_trace</c> 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.</p>
+ </section>
+
+ <section>
+ <title>Note of caution</title>
+ <p>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 <c>io</c> function such as <c>format/2</c>. Note that when
+ <c>dbg:p(all,call)</c> is called, IO processes are also traced.
+ Here's an example:</p>
+ <pre>
+%% Using a default line editing shell
+1> <input>dbg:tracer(process, {fun(Msg,_) -> io:format("~p~n", [Msg]), 0 end, 0}).</input>
+{ok,&lt;0.37.0>}
+2> <input>dbg:p(all, [call]).</input>
+{ok,[{matched,nonode@nohost,25}]}
+3> <input>dbg:tp(mymod,[{'_',[],[]}]).</input>
+{ok,[{matched,nonode@nohost,0},{saved,1}]}
+4> <input>mymod:</input> % TAB pressed here
+%% -- Deadlock --</pre>
+ <p>Here's another example:</p>
+ <pre>
+%% Using a shell without line editing (oldshell)
+1> <input>dbg:tracer(process).</input>
+{ok,&lt;0.31.0>}
+2> <input>dbg:p(all, [call]).</input>
+{ok,[{matched,nonode@nohost,25}]}
+3> <input>dbg:tp(lists,[{'_',[],[]}]).</input>
+{ok,[{matched,nonode@nohost,0},{saved,1}]}
+% -- Deadlock --</pre>
+ <p>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
+ <c>mymod:module_info()</c>. This generates a trace message which, in turn, causes the
+ tracer process to send an IO request to the group leader (by calling <c>io:format/2</c>).
+ We end up in a deadlock.</p>
+ <p>In the second example we use the default trace handler function. This handler
+ prints to tty by sending IO requests to the <c>user</c> process. When Erlang is
+ started in oldshell mode, the shell process will have <c>user</c> as its
+ group leader and so will the tracer process in this example. Since <c>user</c> calls
+ functions in <c>lists</c> we end up in a deadlock as soon as the first IO request is sent.</p>
+ <p>Here are a few suggestions for how to avoid deadlock:</p>
+ <list type="bulleted">
+ <item>Don't trace the group leader of the tracer process. If tracing has been switched on
+ for all processes, call <c>dbg:p(TracerGLPid,clear)</c> to stop tracing the group leader
+ (<c>TracerGLPid</c>). <c>process_info(TracerPid,group_leader)</c> tells you
+ which process this is (<c>TracerPid</c> is returned from <c>dbg:get_tracer/0</c>).</item>
+ <item>Don't trace the <c>user</c> process if using the default trace handler function.</item>
+ <item>In your own trace handler function, call <c>erlang:display/1</c> instead of an
+ <c>io</c> function or, if <c>user</c> is not used as group leader, print to
+ <c>user</c> instead of the default group leader. Example:
+ <c>io:format(user,Str,Args)</c>.</item>
+ </list>
+ </section>
+</erlref>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2007</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>erts_alloc_config</title>
+ <prepared>Rickard Green</prepared>
+ <docno>1</docno>
+ <date>07-05-30</date>
+ <rev>1</rev>
+ <file>erts_alloc_config.sgml</file>
+ </header>
+ <module>erts_alloc_config</module>
+ <modulesummary>Configuration tool for erts_alloc</modulesummary>
+ <description>
+ <note>
+ <p><c>erts_alloc_config</c> is currently an experimental
+ tool and might be subject to backward incompatible
+ changes.</p>
+ </note>
+ <p><seealso marker="erts:erts_alloc">erts_alloc(3)</seealso> is an
+ Erlang Run-Time System internal memory allocator library.
+ <c>erts_alloc_config</c> is intended to be used to aid creation
+ of an <seealso marker="erts:erts_alloc">erts_alloc(3)</seealso>
+ configuration that is suitable for a limited number of runtime
+ scenarios. The configuration that <c>erts_alloc_config</c>
+ produce is intended as a suggestion, and may need to be
+ adjusted manually.</p>
+ <p>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.</p>
+ <p>The current implementation of <c>erts_alloc_config</c> 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 <c>erts_alloc_config</c> 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 <c>erts_alloc_config</c>.</p>
+ <p>A configuration is created in the following way:</p>
+ <list type="bulleted">
+ <item>
+ <p>Pass the <seealso marker="erts:erts_alloc#Mea">+Mea config</seealso>
+ 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 <c>erts_alloc_config</c> from
+ doing it's job. Note, you should <em>not</em> use this flag
+ when using the created configuration. Also note that it is
+ important that you use the same
+ <seealso marker="erts:erl#+S">amount of schedulers</seealso>
+ when creating the configuration as you are going the use on
+ the system using the configuration.</p>
+ </item>
+ <item>
+ <p>Run your applications with different scenarios (the more
+ the better) and save information about each scenario by calling
+ <seealso marker="#save_scenario/0">save_scenario/0</seealso>.
+ It may be hard to know when the applications are at an (for
+ <c>erts_alloc_config</c>) important runtime scenario. A good
+ approach may therefore be to call
+ <seealso marker="#save_scenario/0">save_scenario/0</seealso>
+ repeatedly, e.g. once every tenth second. Note that it is
+ important that your applications reach the runtime scenarios
+ that are important for <c>erts_alloc_config</c> when you are
+ saving scenarios; otherwise, the configuration may perform
+ bad.</p>
+ </item>
+ <item>
+ <p>When you have covered all scenarios, call
+ <seealso marker="#make_config/1">make_config/1</seealso>
+ 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
+ <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
+ to the <seealso marker="erts:erl">erl(1)</seealso> command.</p>
+ </item>
+ <item>
+ <p>The configuration produced by <c>erts_alloc_config</c> may
+ need to be manually adjusted as already stated. Do not modify the
+ file produced by <c>erts_alloc_config</c>; instead, put your
+ modifications in another file and load this file after the
+ file produced by <c>erts_alloc_config</c>. That is, put the
+ <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
+ argument that reads your modification file later on the
+ command-line than the
+ <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
+ argument that reads the configuration file produced by
+ <c>erts_alloc_config</c>. 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 <c>erts_alloc_config</c>. Doing it this way simplifies
+ things when you want to rerun <c>erts_alloc_config</c>.</p>
+ </item>
+ </list>
+ <note>
+ <p>The configuration created by <c>erts_alloc_config</c> 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
+ <c>erts_alloc_config</c> 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 <c>erts_alloc_config</c> if the Erlang
+ runtime system used is changed.</p>
+ </note>
+ <p><c>erts_alloc_config</c> saves information about runtime scenarios
+ and performs computations in a server that is automatically
+ started. The server register itself under the name
+ <c>'__erts_alloc_config__'</c>.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>save_scenario() -> ok | {error, Error}</name>
+ <fsummary>Saves information about current runtime scenario</fsummary>
+ <type>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p><c>save_scenario/0</c> saves information about the current
+ runtime scenario. This information will later be used when
+ <seealso marker="#make_config/0">make_config/0</seealso>,
+ or <seealso marker="#make_config/1">make_config/1</seealso>
+ is called.</p>
+ <p>The first time <c>save_scenario/0</c> is called a server
+ will be started. This server will save runtime scenarios. All
+ saved scenarios can be removed by calling
+ <seealso marker="#make_config/0">stop/0</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>make_config() -> ok | {error, Error}</name>
+ <fsummary>Creates an erts_alloc configuration</fsummary>
+ <type>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p>This is the same as calling
+ <seealso marker="#make_config/1">make_config(group_leader())</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>make_config(FileNameOrIODev) -> ok | {error, Error}</name>
+ <fsummary>Creates an erts_alloc configuration</fsummary>
+ <type>
+ <v>FileNameOrIODev = string() | io_device()</v>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p><c>make_config/1</c> uses the information previously saved by
+ <seealso marker="#save_scenario/0">save_scenario/0</seealso>
+ in order to produce an <c>erts_alloc</c> configuration. At
+ least one scenario have had to be saved. All scenarios
+ previously saved will be used when creating the
+ configuration. </p>
+ <p>If <c>FileNameOrIODev</c> is a <c>string()</c>,
+ <c>make_config/1</c> will use <c>FileNameOrIODev</c> as a
+ filename. A file named <c>FileNameOrIODev</c> is created and
+ the configuration will be written to that file. If
+ <c>FileNameOrIODev</c> is an
+ <seealso marker="stdlib:io">io_device()</seealso> (see the
+ documentation of the module
+ <seealso marker="stdlib:io">io</seealso>), the configuration
+ will be written to the io device.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop() -> ok | {error, Error}</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Error = term()</v>
+ </type>
+ <desc>
+ <p>Stops the server that saves runtime scenarios.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="erts:erts_alloc">erts_alloc(3)</seealso>,
+ <seealso marker="erts:erl">erl(1)</seealso>,
+ <seealso marker="stdlib:io">io(3)</seealso></p>
+ </section>
+</erlref>
+
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
+
+<fascicules>
+ <fascicule file="refman" href="refman_frame.html" entry="yes">
+ Reference Manual
+ </fascicule>
+ <fascicule file="part_notes" href="part_notes_frame.html" entry="no">
+ Release Notes
+ </fascicule>
+ <fascicule file="" href="../../../../doc/print.html" entry="no">
+ Off-Print
+ </fascicule>
+</fascicules>
+
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
--- /dev/null
+++ b/lib/runtime_tools/doc/src/note.gif
Binary files 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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2004</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Runtime_Tools Release Notes</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>notes.xml</file>
+ </header>
+ <p>This document describes the changes made to the Runtime_Tools
+ application.</p>
+
+<section><title>Runtime_Tools 1.8.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ 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.</p>
+ <p>
+ Own Id: OTP-8201</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.8.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <c>Makefile.in</c> has been updated to use the LDFLAGS
+ environment variable (if set). (Thanks to Davide
+ Pesavento.)</p>
+ <p>
+ Own Id: OTP-8157</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.8</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ <c>etop</c> would crash if the emulator's custom
+ allocators had been turned off (e.g. using the
+ <c>+Meamin</c> option).</p>
+ <p>
+ Own Id: OTP-7519</p>
+ </item>
+ <item>
+ <p>The copyright notices have been updated.</p>
+ <p>
+ Own Id: OTP-7851</p>
+ </item>
+ <item>
+ <p>Now, dbg:p/2 accepts {X,Y,Z} process specification as
+ stated in the documentation. It also now accepts
+ "&lt;X.Y.Z&gt;" like from erlang:pid_to_list/1.</p>
+ <p>There is now a pre-saved match spec in dbg that saves
+ a lot of typing. Use dbg:ltp/0 to find out more...</p>
+ <p>
+ Own Id: OTP-7867</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a timestamp problem where some events could be
+ sent out of order. Minor fixes to presentation of
+ data.</p>
+ <p>
+ Own Id: OTP-7544 Aux Id: otp-7442 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>etop</c> now collects process information faster
+ and more reliably than before (a race condition reported
+ by Igor Goryachev has been eliminated).</p>
+ <p>Trace messages could be lost when <c>ttb:stop/0</c>
+ was called.</p>
+ <p>
+ Own Id: OTP-7164</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.7.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>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 <c>p</c> of the
+ <c>io_lib</c> module.</p> <p>Superfluous empty lines have
+ been removed from code examples and from Erlang shell
+ examples.</p>
+ <p>
+ Own Id: OTP-6944 Aux Id: OTP-6554, OTP-6911 </p>
+ </item>
+ <item>
+ <p>Memory management improvements especially for the
+ runtime system with SMP support:</p> <list> <item> 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. </item> <item> <c>driver_alloc()</c>,
+ <c>driver_realloc()</c>, and <c>driver_free()</c> now use
+ their own erts specific memory allocator instead of the
+ default <c>malloc()</c> implementation on the system.
+ </item> <item> The default configuration of some
+ allocators have been changed to fit applications that use
+ much memory better. </item> <item> Some new
+ <c>erts_alloc</c> configuration parameters have been
+ added. </item> <item> <c>erts_alloc_config</c> has been
+ modified to be able to create configurations suitable for
+ multiple instances of allocators. </item> <item> The
+ returned value from <c>erlang:system_info({allocator,
+ Alloc})</c> has been changed. This since an allocator may
+ now run in multiple instances. </item> </list> <p>If you
+ for some reason want the memory allocators to be
+ configured as before, you can pass the <c>+Mea r11b</c>
+ command-line argument to <c>erl</c>.</p> <p>For more
+ information see the <c>erts_alloc(3)</c>, the
+ <c>erts_alloc_config(3)</c>, and the <c>erlang(3)</c>
+ documentation.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7100</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <c>dbg</c> could leave traced processes in a suspended
+ state if the tracer process was killed with exit reason
+ <c>kill</c>.</p>
+ <p>
+ <c>erlang:suspend_process/2</c> has been introduced which
+ accepts an option list as second argument. For more
+ information see the <c>erlang(3)</c> documentation.</p>
+ <p>
+ Processes suspended via
+ <c>erlang:suspend_process/[1,2]</c> will now be
+ automatically resumed if the process that called
+ <c>erlang:suspend_process/[1,2]</c> terminates.</p>
+ <p>
+ Processes could previously be suspended by one process
+ and resumed by another unless someone was tracing the
+ suspendee. This is <em>not</em> possible anymore. The
+ process resuming a process <em>has</em> to be the one
+ that suspended it.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-6946</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The undocumented and unsupported function
+ <c>dbg:tracer/1</c> has been removed. The undocumented,
+ unsupported, and broken function <c>dbg:i/1</c> has been
+ removed.</p>
+ <p>
+ Own Id: OTP-6939</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.6.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ 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.</p>
+ <p>
+ Own Id: OTP-6918</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+<section><title>Runtime_Tools 1.6.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ 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.</p>
+ <p>
+ *** INCOMPATIBILITY with Meta trace call-backs are called
+ with different arguments now. ***</p>
+ <p>
+ Own Id: OTP-6881</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section>
+ <title>Runtime_Tools 1.6.6</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>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.</p>
+ <p>Own Id: OTP-6703</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Minor Makefile changes.</p>
+ <p>Own Id: OTP-6689 Aux Id: OTP-6742 </p>
+ </item>
+ <item>
+ <p>An experimental tool called <c>erts_alloc_config</c>
+ has been added. <c>erts_alloc_config</c> is intended to
+ be used to aid creation of an <c>erts_alloc</c>
+ configuration that is suitable for a limited number of
+ runtime scenarios. For more information see the
+ <c>erts_alloc_config(3)</c> documentation.</p>
+ <p>Own Id: OTP-6700</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6.5</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Misc improvements.</p>
+ <p>Own Id: OTP-6576</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6.4</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>This application has been updated to eliminate warnings
+ by Dialyzer.</p>
+ <p>Own Id: OTP-6551</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6.3</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>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.</p>
+ <p>Own Id: OTP-6426</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6.2</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Several minor bugs and race conditions eliminated in the
+ runtime_tools and observer applications.</p>
+ <p>Own Id: OTP-6265</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6.1</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>There are new BIFs <c>erlang:spawn_monitor/1,3</c>,
+ and the new option <c>monitor</c> for
+ <c>spawn_opt/2,3,4,5</c>.</p>
+ <p>The <c>observer_backend</c> module has been updated to
+ handle the new BIFs.</p>
+ <p>Own Id: OTP-6281</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.6</title>
+ <p>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.</p>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.5.1.1</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>dbg</c> manual page has been updated with information
+ about how to avoid deadlock when tracing.</p>
+ <p>Own Id: OTP-5373 Aux Id: seq9729</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>Runtime_Tools 1.5.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Linked in drivers in the Crypto, and Asn1 applications
+ are now compiled with the <c>-D_THREAD_SAFE</c> and
+ <c>-D_REENTRANT</c> switches on unix when the emulator has
+ thread support enabled.</p>
+ <p>Linked in drivers on MacOSX are not compiled with the
+ undocumented <c>-lbundle1.o</c> switch anymore. Thanks to
+ Sean Hinde who sent us a patch.</p>
+ <p>Linked in driver in Crypto, and port programs in SSL, now
+ compiles on OSF1.</p>
+ <p>Minor <c>Makefile</c> improvements in Runtime_Tools.</p>
+ <p>Own Id: OTP-5346</p>
+ </item>
+ </list>
+ </section>
+ </section>
+</chapter>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2006</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+ </legalnotice>
+
+ <title>Runtime_Tools Release Notes History</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+
+ <section>
+ <title>Runtime_Tools 1.5</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Trace IP port with port number 0 can now be opened on
+ 64-bit.</p>
+ <p>Own Id: OTP-5023</p>
+ </item>
+ </list>
+ </section>
+ </section>
+</chapter>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2004</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Runtime_Tools Release Notes</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <description>
+ <p><em>Runtime_Tools</em> provides low footprint tracing/debugging
+ tools suitable for inclusion in a production system.</p>
+ <p>For information about older versions, see
+ <url href="part_notes_history_frame.html">Release Notes History</url>.</p>
+ </description>
+ <xi:include href="notes.xml"/>
+</part>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part>
+ <header>
+ <copyright>
+ <year>2006</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+ </legalnotice>
+
+ <title>Runtime_Tools Release Notes History</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <description>
+ <p><em>Runtime_Tools</em> provides low footprint tracing/debugging
+ tools suitable for inclusion in a production system.</p>
+ </description>
+ <include file="notes_history"></include>
+</part>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE application SYSTEM "application.dtd">
+
+<application xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>1999</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Runtime_Tools Reference Manual</title>
+ <prepared>Patrik Nyblom</prepared>
+ <docno></docno>
+ <date>1999-05-27</date>
+ <rev>A</rev>
+ </header>
+ <description>
+ <p><em>Runtime_Tools</em> provides low footprint tracing/debugging
+ tools suitable for inclusion in a production system.</p>
+ </description>
+ <xi:include href="runtime_tools_app.xml"/>
+ <xi:include href="dbg.xml"/>
+ <xi:include href="erts_alloc_config.xml"/>
+</application>
+
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 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE appref SYSTEM "appref.dtd">
+
+<appref>
+ <header>
+ <copyright>
+ <year>1999</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+ </legalnotice>
+
+ <title>runtime_tools</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible>Patrik Nyblom</responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>1999-05-27</date>
+ <rev>PA1</rev>
+ <file>runtime_tools_app.sgml</file>
+ </header>
+ <app>runtime_tools</app>
+ <appsummary>The Runtime tools Application</appsummary>
+ <description>
+ <p>This chapter describes the Runtime_Tools application in OTP,
+ which provides low footprint tracing/debugging tools suitable
+ for inclusion in a production system.</p>
+ </description>
+
+ <section>
+ <title>Configuration</title>
+ <p>There are currently no configuration parameters available for
+ this application.
+ </p>
+ </section>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p>application(3)</p>
+ </section>
+</appref>
+
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
--- /dev/null
+++ b/lib/runtime_tools/doc/src/warning.gif
Binary files differ
diff --git a/lib/runtime_tools/ebin/.gitignore b/lib/runtime_tools/ebin/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/runtime_tools/ebin/.gitignore
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
--- /dev/null
+++ b/lib/runtime_tools/priv/.gitignore
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} | "<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, <<Op, Size:32>> = 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 <[email protected]>
+%%% 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, [email protected]
+-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, [email protected]
+%%
+-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, [email protected]
+%% Lennart �hman, [email protected]
+%% -----------------------------------------------------------------------------
+
+-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 <[email protected]>
+%% Description :
+%%
+%% Created : 27 Sep 2005 by Lennart �hman <[email protected]>
+%% ------------------------------------------------------------------------------
+-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, [email protected]
+%%
+%% 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:
+%% <<LengthIndicator:32, {Pid,Alias,Op,NowStamp} >>
+%% 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<MegaS2 -> % More than 1 Mega sec. difference.
+ true;
+ MegaS1==MegaS2,S1+DiffS<S2 ->
+ true;
+ MegaS1+1==MegaS2,S1+DiffS<S2+1000000 ->
+ 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 ->
+ <<SizeB:8, B/binary>>
+ 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 <[email protected]>
+
+-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