[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