From d81729b38dcec8dc7933b472ae4d6b7632115a29 Mon Sep 17 00:00:00 2001 From: Philipp Marek Date: Wed, 24 Jun 2020 19:37:59 +0200 Subject: [PATCH] SBCL perf interface (MAP and JIT files), by Luke Gorrie --- contrib/sb-perf/README | 0 contrib/sb-perf/export-syms.lisp | 159 +++++++++++++++++++++++++++++++ src/code/save.lisp | 9 ++ src/runtime/GNUmakefile | 3 + 4 files changed, 171 insertions(+) create mode 100644 contrib/sb-perf/README create mode 100644 contrib/sb-perf/export-syms.lisp diff --git a/contrib/sb-perf/README b/contrib/sb-perf/README new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/sb-perf/export-syms.lisp b/contrib/sb-perf/export-syms.lisp new file mode 100644 index 0000000000..e0e54126cb --- /dev/null +++ b/contrib/sb-perf/export-syms.lisp @@ -0,0 +1,159 @@ +;;; SBCL integration with Linux perf jitdump interface +;;; https://gist.github.com/lukego/e0192eab0091ccb6c149bd98f59ee7bf + +(defpackage :sb-perf + (:use :common-lisp) + (:export #:write-perfmap + #:write-jitdump)) +(in-package :sb-perf) + + +;;;; perf map + +(defun code-short-name (code) + (let* ((debug (sb-kernel:%code-debug-info code)) + (name (and (typep debug 'sb-c::compiled-debug-info) + (sb-c::compiled-debug-info-name (sb-kernel:%code-debug-info code))))) + (if name (remove #\newline (prin1-to-string name))))) + + +(defun write-perfmap () + (with-open-file (s (format nil "/tmp/perf-~d.map" (sb-posix:getpid)) + :direction :output :if-exists :supersede + :if-does-not-exist :create) + (sb-vm:map-code-objects (lambda (code) + (let ((name (code-short-name code))) + (when name + (format s "~x ~x ~a~%" + (sb-sys:sap-int (sb-kernel:code-instructions code)) + (sb-kernel:%code-code-size code) + (substitute #\_ #\Space name)))))))) + + +;;;; jitdump + +(defvar *jitdump* nil + "Output stream writing to jitdump log file.") + +(defvar *mmap* nil + "mmap() of the dump file for perf to see.") + +(defun timestamp () + "Return 64-bit monotonic timestamp." + (multiple-value-bind (sec ns) (sb-unix::clock-gettime 1) + (dpb ns (byte 32 32) sec))) + +(defun filename () + (format nil "/tmp/jit-~a.dump" (sb-posix:getpid))) + +(defun le-bytes (value bytes) + (loop for i below bytes + for v = value then (ash v -8) + collect (logand v #xff))) + +(defun u32 (value) + "Write one 32-bit unsigned little-endian VALUE." + (write-sequence (le-bytes value 4) + *jitdump*)) + +(defun u64 (value) + "Write one 64-bit unsigned VALUE." + (write-sequence (le-bytes value 8) + *jitdump*)) + +(defun bin (sap len) + "Write a sequence of LEN bytes from memory address SAP." + (loop for i from 0 below len + do (write-byte (sb-sys:sap-ref-8 sap i) *jitdump*))) + +(defun str (string) + "Write STRING with a null-terminator." + (multiple-value-bind (bytes len) + (sb-alien:make-alien-string string + :external-format :utf-8 + :null-terminate t) + (unwind-protect (bin (sb-alien:alien-sap bytes) + len) + (sb-alien:free-alien bytes)))) + +(defun write-file-header () + "Write the Jitdump file header." + (u32 #x4A695444 ) ; Magic + (u32 1) ; Version + (u32 (+ 4 4 4 4 4 4 8 8)) ; Total size + (u32 62) ; ELF Mach = x86-64 (XXX) + (u32 0) ; Pad + (u32 (sb-posix:getpid)) ; Pid + (u64 (timestamp)) ; Timestamp + (u64 0)) ; Flags + +(defun write-record-header (&key id content-size (timestamp (timestamp))) + "Write a Jitdump record header." + (u32 id) + (u32 (+ 4 4 8 content-size)) + (u64 timestamp)) + +(defun write-load (&key name id address size + (pid (sb-posix:getpid)) + (tid (sb-thread:thread-os-tid sb-thread:*current-thread*))) + "Write a Jitdump LOAD event." + (write-record-header :id 0 + :content-size (+ 4 4 8 8 8 8 (1+ (length name)) size) + :timestamp (get-universal-time)) + (u32 pid) ; PID + (u32 tid) ; Thread ID + (u64 (sb-sys:sap-int address)) ; VMA - address of function start + (u64 (sb-sys:sap-int address)) ; Code address + (u64 size) ; Code size + (u64 id) ; Unique ID + (str name) ; Function name + (bin address size)) ; Raw machine code + +(defun code-info (code) + (let* ((id (sb-kernel:%code-serialno code)) + (debug-info (sb-kernel:%code-debug-info code)) + (name (and (typep debug-info 'sb-c::compiled-debug-info) + (sb-c::compiled-debug-info-name debug-info)))) + (list :name (if (null name) + "?" + (let ((*package* (find-package :keyword))) + (prin1-to-string name))) + :id id + :address (sb-kernel:code-instructions code) + :size (sb-kernel:%code-code-size code)))) + +(defun write-jitdump () + (with-open-file (*jitdump* (filename) + :element-type '(unsigned-byte 8) + :direction :output :if-exists :supersede :if-does-not-exist :create) + (write-file-header) + (sb-vm:map-code-objects (lambda (code) + (apply #'write-load (code-info code))))) + (when *mmap* + (sb-posix:munmap (car *mmap*) (cdr *mmap*)) + (setf *mmap* nil)) + ;; Needs to be open for reading only + (with-open-file (*jitdump* (filename) + :element-type '(unsigned-byte 8)) + (let* ((size (file-length *jitdump*)) + (pagesize 4096) ;; TODO: (sysconf PAGESIZE) + (rounded-size (* pagesize (ceiling size pagesize))) + (addr (sb-posix:mmap (sb-sys:int-sap 0) + rounded-size + (logior sb-posix:prot-read sb-posix:prot-exec) + (logior sb-posix:map-private) + (sb-sys:fd-stream-fd *jitdump*) + 0))) + (setf *mmap* (cons addr rounded-size))))) + +#+(or) +(write-perfmap) +#+(or) +(write-jitdump) + +#+(or) +(defun foo (x) + (loop for i below x)) + +#+(or) +(foo 1000000000) diff --git a/src/code/save.lisp b/src/code/save.lisp index 4c8c8da176..88c73e63da 100644 --- a/src/code/save.lisp +++ b/src/code/save.lisp @@ -100,6 +100,8 @@ (purify t) (root-structures ()) (environment-name "auxiliary") + #+linux + (usdt-probes nil) (compression nil) #+win32 (application-type :console)) @@ -173,6 +175,12 @@ The following &KEY arguments are defined: The notable difference is that :GUI doesn't automatically create a console window. The default is :CONSOLE. + :USDT-PROBES + On Linux you can put ELF notes in the binary, telling perf(1) where + to find the symbols. If T, all symbols are documented; if a string, + only symbols in this package are. + Only useful if :EXECUTABLE is set. + The save/load process changes the values of some global variables: *STANDARD-OUTPUT*, *DEBUG-IO*, etc. @@ -203,6 +211,7 @@ This isn't because we like it this way, but just because there don't seem to be good quick fixes for either limitation and no one has been sufficiently motivated to do lengthy fixes." (declare (ignore environment-name)) + (declare (ignore usdt-probes)) #+gencgc (declare (ignorable root-structures)) (when (and callable-exports toplevel-supplied) diff --git a/src/runtime/GNUmakefile b/src/runtime/GNUmakefile index b70fbd6c7e..269162df81 100644 --- a/src/runtime/GNUmakefile +++ b/src/runtime/GNUmakefile @@ -37,6 +37,9 @@ CFLAGS += -g -Wall -Wundef -Wsign-compare -Wpointer-arith -O3 ASFLAGS += $(CFLAGS) CPPFLAGS += -I. -I../../ +# https://perf.wiki.kernel.org/index.php/Tutorial#Binary_identification_with_build-id +LDFLAGS += -Wl,--build-id + # Give make access to the target Lisp features. include genesis/Makefile.features