[Tarantool-patches] [PATCH luajit v1 03/11] profile: introduce profiler writing module

Sergey Kaplun skaplun at tarantool.org
Wed Dec 16 22:13:38 MSK 2020


This patch introduces module for writing profile data.
Its usage will be added at the next patches.

It can be used for memory profiler or for signal-based
cpu profiler.

Part of tarantool/tarantool#5442
---

Custom memcpy function (see below) makes sense if this module will be
used for cpu/sample profiler based on a signal-based timer. Else it can
be easily redefined.

 src/Makefile            |   5 +-
 src/Makefile.dep        |   2 +
 src/profile/ljp_write.c | 195 ++++++++++++++++++++++++++++++++++++++++
 src/profile/ljp_write.h |  84 +++++++++++++++++
 4 files changed, 284 insertions(+), 2 deletions(-)
 create mode 100644 src/profile/ljp_write.c
 create mode 100644 src/profile/ljp_write.h

diff --git a/src/Makefile b/src/Makefile
index be7ed95..4b1d937 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -469,6 +469,7 @@ DASM_FLAGS= $(DASM_XFLAGS) $(DASM_AFLAGS)
 DASM_DASC= vm_$(DASM_ARCH).dasc
 
 UTILS_O= utils/leb128.o
+PROFILE_O= profile/ljp_write.o
 BUILDVM_O= host/buildvm.o host/buildvm_asm.o host/buildvm_peobj.o \
 	   host/buildvm_lib.o host/buildvm_fold.o
 BUILDVM_T= host/buildvm
@@ -499,7 +500,7 @@ LJCORE_O= lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \
 	  lj_ctype.o lj_cdata.o lj_cconv.o lj_ccall.o lj_ccallback.o \
 	  lj_carith.o lj_clib.o lj_cparse.o \
 	  lj_lib.o lj_alloc.o lib_aux.o \
-	  $(LJLIB_O) lib_init.o $(UTILS_O)
+	  $(LJLIB_O) lib_init.o $(UTILS_O) $(PROFILE_O)
 
 LJVMCORE_O= $(LJVM_O) $(LJCORE_O)
 LJVMCORE_DYNO= $(LJVMCORE_O:.o=_dyn.o)
@@ -517,7 +518,7 @@ ALL_HDRGEN= lj_bcdef.h lj_ffdef.h lj_libdef.h lj_recdef.h lj_folddef.h \
 	    host/buildvm_arch.h
 ALL_GEN= $(LJVM_S) $(ALL_HDRGEN) $(LIB_VMDEFP)
 WIN_RM= *.obj *.lib *.exp *.dll *.exe *.manifest *.pdb *.ilk
-ALL_RM= $(ALL_T) $(ALL_GEN) *.o host/*.o utils/*.o $(WIN_RM)
+ALL_RM= $(ALL_T) $(ALL_GEN) *.o host/*.o utils/*.o profile/*.o $(WIN_RM)
 
 ##############################################################################
 # Build mode handling.
diff --git a/src/Makefile.dep b/src/Makefile.dep
index cc75d03..7fdbfbe 100644
--- a/src/Makefile.dep
+++ b/src/Makefile.dep
@@ -248,4 +248,6 @@ host/buildvm_lib.o: host/buildvm_lib.c host/buildvm.h lj_def.h lua.h luaconf.h \
 host/buildvm_peobj.o: host/buildvm_peobj.c host/buildvm.h lj_def.h lua.h \
  luaconf.h lj_arch.h lj_bc.h lj_def.h lj_arch.h
 host/minilua.o: host/minilua.c
+profile/ljp_write.o: profile/ljp_write.c profile/ljp_write.h utils/leb128.h \
+ lj_def.h lua.h luaconf.h
 utils/leb128.o: utils/leb128.c
diff --git a/src/profile/ljp_write.c b/src/profile/ljp_write.c
new file mode 100644
index 0000000..de7202d
--- /dev/null
+++ b/src/profile/ljp_write.c
@@ -0,0 +1,195 @@
+/*
+** Low-level writer for LuaJIT Profiler.
+**
+** Major portions taken verbatim or adapted from the LuaVela.
+** Copyright (C) 2015-2019 IPONWEB Ltd.
+*/
+
+#include <unistd.h>
+#include <errno.h>
+
+#include "profile/ljp_write.h"
+#include "utils/leb128.h"
+#include "lj_def.h"
+
+/*
+** memcpy from the standard C library is not guaranteed to be async-signal-safe.
+** Common sense might tell us that it should be async-signal-safe (at least
+** because it is unlikely that memcpy will allocate anything dynamically),
+** but the standard is always the standard. So LuaJIT offers its own
+** implementation of memcpy. Of course it will never be as fast as the system
+** memcpy, but it will be guaranteed to always stay async-signal-safe. Feel
+** free to remove the define below to fall-back to the system memcpy if the
+** custom implementation becomes a botlleneck, but this is at your own risk.
+** You are warned:)
+*/
+#define USE_CUSTOM_LJ_MEMCPY
+
+#ifdef USE_CUSTOM_LJ_MEMCPY
+/*
+** Behaves exactly as memcpy from the standard C library with following caveats:
+** - Guaranteed to be async-signal-safe.
+** - Does *not* handle unaligned byte stores.
+** - src cannot be declared as (const void *restrict) unless we start
+**   supporting C99 explicitly. Possible overlapping of dst in src is ignored.
+*/
+#define COPY_BUFFER_TYPE uint64_t
+#define COPY_BUFFER_SIZE sizeof(COPY_BUFFER_TYPE)
+
+static void *write_memcpy(void *dst, const void *src, size_t n)
+{
+  size_t loops;
+  size_t i;
+  uint8_t *dst_pos = (uint8_t *)dst;
+  const uint8_t *src_pos = (const uint8_t *)src;
+
+  loops = n / COPY_BUFFER_SIZE;
+  for (i = 0; i < loops; i++) {
+    *(COPY_BUFFER_TYPE *)dst_pos = *(const COPY_BUFFER_TYPE *)src_pos;
+    dst_pos += COPY_BUFFER_SIZE;
+    src_pos += COPY_BUFFER_SIZE;
+  }
+
+  loops = n % COPY_BUFFER_SIZE;
+  for (i = 0; i < loops; i++) {
+    *dst_pos = *src_pos;
+    dst_pos++;
+    src_pos++;
+  }
+
+  return dst;
+}
+#else /* !USE_CUSTOM_LJ_MEMCPY */
+#define write_memcpy memcpy
+#endif /* USE_CUSTOM_LJ_MEMCPY */
+
+static LJ_AINLINE void write_set_flag(struct ljp_buffer *buf, uint8_t flag)
+{
+  buf->flags |= flag;
+}
+
+static LJ_AINLINE void write_save_errno(struct ljp_buffer *buf)
+{
+  buf->saved_errno = errno;
+}
+
+/* Wraps a write syscall ensuring all data have been written. */
+static void write_buffer_sys(struct ljp_buffer *buffer, const void **data,
+			     size_t len)
+{
+  void *ctx = buffer->ctx;
+  size_t written;
+
+  lua_assert(!ljp_write_test_flag(buffer, STREAM_STOP));
+
+  written = buffer->writer(data, len, ctx);
+
+  if (LJ_UNLIKELY(written < len)) {
+    write_set_flag(buffer, STREAM_ERR_IO);
+    write_save_errno(buffer);
+  }
+  if (LJ_UNLIKELY(*data == NULL)) {
+    write_set_flag(buffer, STREAM_STOP);
+    write_save_errno(buffer);
+  }
+}
+
+static LJ_AINLINE size_t write_bytes_buffered(const struct ljp_buffer *buf)
+{
+  return (size_t)(buf->pos - buf->buf);
+}
+
+static LJ_AINLINE int write_buffer_has(const struct ljp_buffer *buf, size_t n)
+{
+  return (buf->size - write_bytes_buffered(buf)) >= n;
+}
+
+void ljp_write_flush_buffer(struct ljp_buffer *buf)
+{
+  lua_assert(!ljp_write_test_flag(buf, STREAM_STOP));
+
+  write_buffer_sys(buf, (const void **)&buf->buf, write_bytes_buffered(buf));
+  buf->pos = buf->buf;
+}
+
+void ljp_write_init(struct ljp_buffer *buf, ljp_writer writer, void *ctx,
+		    uint8_t *mem, size_t size)
+{
+  buf->ctx = ctx;
+  buf->writer = writer;
+  buf->buf = mem;
+  buf->pos = mem;
+  buf->size = size;
+  buf->flags = 0;
+  buf->saved_errno = 0;
+}
+
+void ljp_write_terminate(struct ljp_buffer *buf)
+{
+  ljp_write_init(buf, NULL, NULL, NULL, 0);
+}
+
+static LJ_AINLINE void write_reserve(struct ljp_buffer *buf, size_t n)
+{
+  if (LJ_UNLIKELY(!write_buffer_has(buf, n)))
+    ljp_write_flush_buffer(buf);
+}
+
+/* Writes a byte to the output buffer. */
+void ljp_write_byte(struct ljp_buffer *buf, uint8_t b)
+{
+  if (LJ_UNLIKELY(ljp_write_test_flag(buf, STREAM_STOP)))
+    return;
+  write_reserve(buf, sizeof(b));
+  *buf->pos++ = b;
+}
+
+/* Writes an unsigned integer which is at most 64 bits long to the output. */
+void ljp_write_u64(struct ljp_buffer *buf, uint64_t n)
+{
+  if (LJ_UNLIKELY(ljp_write_test_flag(buf, STREAM_STOP)))
+    return;
+  write_reserve(buf, LEB128_U64_MAXSIZE);
+  buf->pos += (ptrdiff_t)write_uleb128(buf->pos, n);
+}
+
+/* Writes n bytes from an arbitrary buffer src to the output. */
+static void write_buffer(struct ljp_buffer *buf, const void *src, size_t n)
+{
+  if (LJ_UNLIKELY(ljp_write_test_flag(buf, STREAM_STOP)))
+    return;
+  /*
+  ** Very unlikely: We are told to write a large buffer at once.
+  ** Buffer not belong to us so we must to pump data
+  ** through buffer.
+  */
+  while (LJ_UNLIKELY(n > buf->size)) {
+    ljp_write_flush_buffer(buf);
+    write_memcpy(buf->pos, src, buf->size);
+    buf->pos += (ptrdiff_t)buf->size;
+    n -= buf->size;
+  }
+
+  write_reserve(buf, n);
+  write_memcpy(buf->pos, src, n);
+  buf->pos += (ptrdiff_t)n;
+}
+
+/* Writes a \0-terminated C string to the output buffer. */
+void ljp_write_string(struct ljp_buffer *buf, const char *s)
+{
+  const size_t l = strlen(s);
+
+  ljp_write_u64(buf, (uint64_t)l);
+  write_buffer(buf, s, l);
+}
+
+int ljp_write_test_flag(const struct ljp_buffer *buf, uint8_t flag)
+{
+  return buf->flags & flag;
+}
+
+int ljp_write_errno(const struct ljp_buffer *buf)
+{
+  return buf->saved_errno;
+}
diff --git a/src/profile/ljp_write.h b/src/profile/ljp_write.h
new file mode 100644
index 0000000..29c1669
--- /dev/null
+++ b/src/profile/ljp_write.h
@@ -0,0 +1,84 @@
+/*
+** Low-level event streaming for LuaJIT Profiler.
+** NB! Please note that all events may be streamed inside a signal handler.
+** This means effectively that only async-signal-safe library functions and
+** syscalls MUST be used for streaming. Check with `man 7 signal` when in
+** doubt.
+** Major portions taken verbatim or adapted from the LuaVela.
+** Copyright (C) 2015-2019 IPONWEB Ltd.
+*/
+
+#ifndef _LJP_WRITE_H
+#define _LJP_WRITE_H
+
+#include <stdint.h>
+
+/*
+** Data format for strings:
+**
+** string         := string-len string-payload
+** string-len     := <ULEB128>
+** string-payload := <BYTE> {string-len}
+**
+** Note.
+** For strings shorter than 128 bytes (most likely scenario in our case)
+** we write the same amount of data (1-byte ULEB128 + actual payload) as we
+** would have written with straightforward serialization (actual payload + \0),
+** but make parsing easier.
+*/
+
+/* Stream errors. */
+#define STREAM_ERR_IO 0x1
+#define STREAM_STOP   0x2
+
+typedef size_t (*ljp_writer)(const void **data, size_t len, void *opt);
+
+/* Write buffer for profilers. */
+struct ljp_buffer {
+  /*
+  ** Buffer writer which will called at buffer write.
+  ** Should return amount of written bytes on success or zero in case of error.
+  ** *data should contain new buffer of size greater or equal to len.
+  ** If *data == NULL stream stops.
+  */
+  ljp_writer writer;
+  /* Context to writer function. */
+  void *ctx;
+  /* Buffer size. */
+  size_t size;
+  /* Saved errno in case of error. */
+  int saved_errno;
+  /* Start of buffer. */
+  uint8_t *buf;
+  /* Current position in buffer. */
+  uint8_t *pos;
+  /* Internal flags. */
+  volatile uint8_t flags;
+};
+
+/* Write string. */
+void ljp_write_string(struct ljp_buffer *buf, const char *s);
+
+/* Write single byte. */
+void ljp_write_byte(struct ljp_buffer *buf, uint8_t b);
+
+/* Write uint64_t in uleb128 format. */
+void ljp_write_u64(struct ljp_buffer *buf, uint64_t n);
+
+/* Immediatly flush buffer. */
+void ljp_write_flush_buffer(struct ljp_buffer *buf);
+
+/* Init buffer. */
+void ljp_write_init(struct ljp_buffer *buf, ljp_writer writer, void *ctx,
+		    uint8_t *mem, size_t size);
+
+/* Check flags. */
+int ljp_write_test_flag(const struct ljp_buffer *buf, uint8_t flag);
+
+/* Return saved errno. */
+int ljp_write_errno(const struct ljp_buffer *buf);
+
+/* Set pointers to NULL and reset flags. */
+void ljp_write_terminate(struct ljp_buffer *buf);
+
+#endif
-- 
2.28.0



More information about the Tarantool-patches mailing list