Re: [PATCH 4/4] perf tools: add JVMTI agent library

Adrian Hunter
2015-02-16T07:03:29Z

On 11/02/15 01:42, Stephane Eranian wrote:
> This is a standalone JVMTI library to help profile Java jitted
> code with perf record/perf report. The library is not installed
> or compiled automatically by perf Makefile. It is not used
> directly by perf. It is arch agnostic and has been tested on
> X86 and ARM. It needs to be used with a Java runtime, such
> as OpenJDK, as follows:
>
> $ java -agentpath:libjvmti.so .......
>
> When used this way, java will generate a jitdump binary file in
> $HOME/.debug/java/jit/java-jit-*
>
> This binary dump file contains information to help symbolize and
> annotate jitted code.
>
> The next step is to inject the jitdump information into the
> perf.data file:
> $ perf inject -j $HOME/.debug/java/jit/java-jit-XXXX/jit-ZZZ.dump \
> -i perf.data -o perf.data.jitted
>
> This injects the MMAP records to cover the jitted code and also generates
> one ELF image for each jitted function. The ELF images are created in the
> same subdir as the jitdump file. The MMAP records point there too.
>
> Then to visualize the function or asm profile, simply use the regular
> perf commands:
> $ perf report -i perf.data.jitted
> or
> $ perf annotate -i perf.data.jitted
>
> JVMTI agent code adapted from OProfile's opagent code.
>
> Signed-off-by: Stephane Eranian <eranian@google.com>
> ---
> tools/perf/jvmti/Makefile | 70 +++++++++
> tools/perf/jvmti/jvmti_agent.c | 349 +++++++++++++++++++++++++++++++++++++++++
> tools/perf/jvmti/jvmti_agent.h | 23 +++
> tools/perf/jvmti/libjvmti.c | 149 ++++++++++++++++++
> 4 files changed, 591 insertions(+)
> create mode 100644 tools/perf/jvmti/Makefile
> create mode 100644 tools/perf/jvmti/jvmti_agent.c
> create mode 100644 tools/perf/jvmti/jvmti_agent.h
> create mode 100644 tools/perf/jvmti/libjvmti.c
>
> diff --git a/tools/perf/jvmti/Makefile b/tools/perf/jvmti/Makefile
> new file mode 100644
> index 0000000..9eda64b
> --- /dev/null
> +++ b/tools/perf/jvmti/Makefile
> @@ -0,0 +1,70 @@
> +ARCH=$(shell uname -m)
> +
> +ifeq ($(ARCH), x86_64)
> +JARCH=amd64
> +endif
> +ifeq ($(ARCH), armv7l)
> +JARCH=armhf
> +endif
> +ifeq ($(ARCH), armv6l)
> +JARCH=armhf
> +endif
> +ifeq ($(ARCH), ppc64)
> +JARCH=powerpc
> +endif
> +ifeq ($(ARCH), ppc64le)
> +JARCH=powerpc
> +endif
> +
> +DESTDIR=/usr/local
> +
> +VERSION=1
> +REVISION=0
> +AGE=0
> +
> +LN=ln -sf
> +RM=rm
> +
> +SJVMTI=libjvmti.so.$(VERSION).$(REVISION).$(AGE)
> +VJVMTI=libjvmti.so.$(VERSION)
> +SLDFLAGS=-shared -Wl,-soname -Wl,$(VLIBPFM)
> +SOLIBEXT=so
> +
> +JDIR=$(shell /usr/sbin/update-java-alternatives -l | head -1 | cut -d ' ' -f 3)
> +# -lrt required in 32-bit mode for clock_gettime()
> +LIBS=-lelf -lrt
> +INCDIR=-I $(JDIR)/include -I $(JDIR)/include/linux
> +
> +TARGETS=$(SJVMTI)
> +
> +SRCS=libjvmti.c jvmti_agent.c
> +OBJS=$(SRCS:.c=.o)
> +SOBJS=$(OBJS:.o=.lo)
> +OPT=-O2 -g -Werror -Wall
> +
> +CFLAGS=$(INCDIR) $(OPT)
> +
> +all: $(TARGETS)
> +
> +.c.o:
> + $(CC) $(CFLAGS) -c $*.c
> +.c.lo:
> + $(CC) -fPIC -DPIC $(CFLAGS) -c $*.c -o $*.lo
> +
> +$(OBJS) $(SOBJS): Makefile jvmti_agent.h ../util/jitdump.h
> +
> +$(SJVMTI): $(SOBJS)
> + $(CC) $(CFLAGS) $(SLDFLAGS) -o $@ $(SOBJS) $(LIBS)
> + $(LN) $@ libjvmti.$(SOLIBEXT)
> +
> +clean:
> + $(RM) -f *.o *.so.* *.so *.lo
> +
> +install:
> + -mkdir -p $(DESTDIR)/lib
> + install -m 755 $(SJVMTI) $(DESTDIR)/lib/
> + (cd $(DESTDIR)/lib; $(LN) $(SJVMTI) $(VJVMTI))
> + (cd $(DESTDIR)/lib; $(LN) $(SJVMTI) libjvmti.$(SOLIBEXT))
> + ldconfig
> +
> +.SUFFIXES: .c .S .o .lo
> diff --git a/tools/perf/jvmti/jvmti_agent.c b/tools/perf/jvmti/jvmti_agent.c
> new file mode 100644
> index 0000000..d2d5215
> --- /dev/null
> +++ b/tools/perf/jvmti/jvmti_agent.c
> @@ -0,0 +1,349 @@
> +/*
> + * jvmti_agent.c: JVMTI agent interface
> + *
> + * Adapted from the Oprofile code in opagent.c:
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + *
> + * Copyright 2007 OProfile authors
> + * Jens Wilke
> + * Daniel Hansel
> + * Copyright IBM Corporation 2007
> + */
> +#include <sys/types.h>
> +#include <sys/stat.h> /* for mkdir() */
> +#include <stdio.h>
> +#include <errno.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <stdint.h>
> +#include <limits.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <time.h>
> +#include <syscall.h> /* for gettid() */
> +#include <err.h>
> +
> +#include "jvmti_agent.h"
> +#include "../util/jitdump.h"
> +
> +#define JIT_LANG "java"
> +
> +static char jit_path[PATH_MAX];
> +
> +/*
> + * padding buffer
> + */
> +static const char pad_bytes[7];
> +
> +/*
> + * perf_events event fd
> + */
> +static int perf_fd;
> +
> +static inline pid_t gettid(void)
> +{
> + return (pid_t)syscall(__NR_gettid);
> +}
> +
> +static int get_e_machine(struct jitheader *hdr)
> +{
> + ssize_t sret;
> + char id[16];
> + int fd, ret = -1;
> + int m = -1;
> + struct {
> + uint16_t e_type;
> + uint16_t e_machine;
> + } info;
> +
> + fd = open("/proc/self/exe", O_RDONLY);
> + if (fd == -1)
> + return -1;
> +
> + sret = read(fd, id, sizeof(id));
> + if (sret != sizeof(id))
> + goto error;
> +
> + /* check ELF signature */
> + if (id[0] != 0x7f || id[1] != 'E' || id[2] != 'L' || id[3] != 'F')
> + goto error;
> +
> + sret = read(fd, &info, sizeof(info));
> + if (sret != sizeof(info))
> + goto error;
> +
> + m = info.e_machine;
> + if (m < 0)
> + m = 0; /* ELF EM_NONE */
> +
> + hdr->elf_mach = m;
> + ret = 0;
> +error:
> + close(fd);
> + return ret;
> +}
> +
> +#define CLOCK_DEVICE "/dev/trace_clock"
> +#define CLOCKFD 3
> +#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)
> +#define CLOCKID_TO_FD(id) ((~(int) (id) >> 3) & ~CLOCKFD)
> +
> +#define NSEC_PER_SEC 1000000000
> +
> +#ifndef CLOCK_INVALID
> +#define CLOCK_INVALID -1
> +#endif
> +
> +static inline clockid_t get_clockid(int fd)
> +{
> + return FD_TO_CLOCKID(fd);
> +}
> +
> +static int
> +perf_open_timestamp(void)
> +{
> + int fd, id;
> +
> + fd = open(CLOCK_DEVICE, O_RDONLY);
> + if (fd == -1) {
> + if (errno == ENOENT)
> + warnx("jvmti: %s not present, check your kernel for trace_clock module", CLOCK_DEVICE);
> + if (errno == EPERM)
> + warnx("jvmti: %s has wrong permissions, suggesting chmod 644 %s", CLOCK_DEVICE, CLOCK_DEVICE);
> + }
> +
> + id = get_clockid(fd);
> + if (CLOCK_INVALID == id)
> + return CLOCK_INVALID;
> +
> + return get_clockid(fd);
> +}
> +
> +static inline void
> +perf_close_timestamp(int id)
> +{
> + close(CLOCKID_TO_FD(id));
> +}
> +
> +
> +static inline uint64_t
> +timespec_to_ns(const struct timespec *ts)
> +{
> + return ((uint64_t) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
> +}
> +
> +static inline uint64_t
> +perf_get_timestamp(int id)
> +{
> + struct timespec ts;
> +
> + clock_gettime(id, &ts);
> + return timespec_to_ns(&ts);
> +}
> +
> +static int
> +debug_cache_init(void)
> +{
> + char str[32];
> + char *base, *p;
> + struct tm tm;
> + time_t t;
> + int ret;
> +
> + time(&t);
> + localtime_r(&t, &tm);
> +
> + base = getenv("JITDUMPDIR");
> + if (!base)
> + base = getenv("HOME");
> + if (!base)
> + base = ".";
> +
> + strftime(str, sizeof(str), JIT_LANG"-jit-%Y%m%d", &tm);
> +
> + snprintf(jit_path, PATH_MAX - 1, "%s/.debug/", base);
> +
> + ret = mkdir(jit_path, 0755);
> + if (ret == -1) {
> + if (errno != EEXIST) {
> + warn("jvmti: cannot create jit cache dir %s", jit_path);
> + return -1;
> + }
> + }
> +
> + snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit", base);
> + ret = mkdir(jit_path, 0755);
> + if (ret == -1) {
> + if (errno != EEXIST) {
> + warn("cannot create jit cache dir %s", jit_path);
> + return -1;
> + }
> + }
> +
> + snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit/%s.XXXXXXXX", base, str);
> +
> + p = mkdtemp(jit_path);
> + if (p != jit_path) {
> + warn("cannot create jit cache dir %s", jit_path);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +void *jvmti_open(void)
> +{
> + int pad_cnt;
> + char dump_path[PATH_MAX];
> + struct jitheader header;
> + FILE *fp;
> +
> + perf_fd = perf_open_timestamp();
> + if (perf_fd == -1)
> + warnx("jvmti: kernel does not support /dev/trace_clock or permissions are wrong on that device");
> +
> + memset(&header, 0, sizeof(header));
> +
> + debug_cache_init();
> +
> + snprintf(dump_path, PATH_MAX, "%s/jit-%i.dump", jit_path, getpid());
> +
> + fp = fopen(dump_path, "w");
> + if (!fp) {
> + warn("jvmti: cannot create %s", dump_path);
> + goto error;
> + }
> +
> + warnx("jvmti: jitdump in %s", dump_path);
> +
> + if (get_e_machine(&header)) {
> + warn("get_e_machine failed\n");
> + goto error;
> + }
> +
> + header.magic = JITHEADER_MAGIC;
> + header.version = JITHEADER_VERSION;
> + header.total_size = sizeof(header);
> + header.pid = getpid();
> +
> + /* calculate amount of padding '\0' */
> + pad_cnt = PADDING_8ALIGNED(header.total_size);
> + header.total_size += pad_cnt;
> +
> + header.timestamp = perf_get_timestamp(perf_fd);
> +
> + if (!fwrite(&header, sizeof(header), 1, fp)) {
> + warn("jvmti: cannot write dumpfile header");
> + goto error;
> + }
> +
> + /* write padding '\0' if necessary */
> + if (pad_cnt && !fwrite(pad_bytes, pad_cnt, 1, fp)) {
> + warn("jvmti: cannot write dumpfile header padding");
> + goto error;
> + }
> +
> + return fp;
> +error:
> + fclose(fp);
> + perf_close_timestamp(perf_fd);
> + return NULL;
> +}
> +
> +int
> +jvmti_close(void *agent)
> +{
> + struct jr_code_close rec;
> + FILE *fp = agent;
> +
> + if (!fp) {
> + warnx("jvmti: incalid fd in close_agent");
> + return -1;
> + }
> +
> + rec.p.id = JIT_CODE_CLOSE;
> + rec.p.total_size = sizeof(rec);
> +
> + rec.p.timestamp = perf_get_timestamp(perf_fd);
> +
> + if (!fwrite(&rec, sizeof(rec), 1, fp))
> + return -1;
> +
> + fclose(fp);
> +
> + perf_close_timestamp(perf_fd);
> +
> + fp = NULL;
> +
> + return 0;
> +}
> +
> +int jvmti_write_code(void *agent, char const *sym,
> + uint64_t vma, void const *code, unsigned int const size)
> +{
> + static int code_generation = 1;
> + struct jr_code_load rec;
> + size_t sym_len;
> + size_t padding_count;
> + FILE *fp = agent;
> + int ret = -1;
> +
> + /* don't care about 0 length function, no samples */
> + if (size == 0)
> + return 0;
> +
> + if (!fp) {
> + warnx("jvmti: invalid fd in write_native_code");
> + return -1;
> + }
> +
> + sym_len = strlen(sym) + 1;
> +
> + rec.p.id = JIT_CODE_LOAD;
> + rec.p.total_size = sizeof(rec) + sym_len;
> + padding_count = PADDING_8ALIGNED(rec.p.total_size);
> + rec.p. total_size += padding_count;
> + rec.p.timestamp = perf_get_timestamp(perf_fd);

Do you know whether the JVM is guaranteed not to start executing the
generated code before the return of compiled_method_load_cb(), otherwise the
timestamp will be too late?

> +
> + rec.code_size = size;
> + rec.vma = vma;
> + rec.code_addr = vma;
> + rec.pid = getpid();
> + rec.tid = gettid();
> + rec.code_index = code_generation++;
> +
> + if (code)
> + rec.p.total_size += size;
> +
> + /*
> + * If JVM is multi-threaded, nultiple concurrent calls to agent
> + * may be possible, so protect file writes
> + */
> + flockfile(fp);
> +
> + ret = fwrite_unlocked(&rec, sizeof(rec), 1, fp);
> + fwrite_unlocked(sym, sym_len, 1, fp);
> + if (code)
> + fwrite_unlocked(code, size, 1, fp);
> +
> + if (padding_count)
> + fwrite_unlocked(pad_bytes, padding_count, 1, fp);
> +
> + funlockfile(fp);
> +
> + ret = 0;
> +
> + return ret;
> +}
> diff --git a/tools/perf/jvmti/jvmti_agent.h b/tools/perf/jvmti/jvmti_agent.h
> new file mode 100644
> index 0000000..54e5c5e
> --- /dev/null
> +++ b/tools/perf/jvmti/jvmti_agent.h
> @@ -0,0 +1,23 @@
> +#ifndef __JVMTI_AGENT_H__
> +#define __JVMTI_AGENT_H__
> +
> +#include <sys/types.h>
> +#include <stdint.h>
> +
> +#define __unused __attribute__((unused))
> +
> +#if defined(__cplusplus)
> +extern "C" {
> +#endif
> +
> +void *jvmti_open(void);
> +int jvmti_close(void *agent);
> +int jvmti_write_code(void *agent, char const *symbol_name,
> + uint64_t vma, void const *code,
> + const unsigned int code_size);
> +
> +#if defined(__cplusplus)
> +}
> +
> +#endif
> +#endif /* __JVMTI_H__ */
> diff --git a/tools/perf/jvmti/libjvmti.c b/tools/perf/jvmti/libjvmti.c
> new file mode 100644
> index 0000000..8b8d782
> --- /dev/null
> +++ b/tools/perf/jvmti/libjvmti.c
> @@ -0,0 +1,149 @@
> +#include <sys/types.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <err.h>
> +#include <jvmti.h>
> +
> +#include "jvmti_agent.h"
> +
> +void *jvmti_agent;
> +
> +static void JNICALL
> +compiled_method_load_cb(jvmtiEnv *jvmti,
> + jmethodID method,
> + jint code_size,
> + void const *code_addr,
> + jint map_length,
> + jvmtiAddrLocationMap const *map,
> + void const *compile_info __unused)
> +{
> + jclass decl_class;
> + char *class_sign = NULL;
> + char *func_name = NULL;
> + char *func_sign = NULL;
> + jvmtiError ret;
> + size_t len;
> +
> + ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method,
> + &decl_class);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: getmethoddeclaringclass failed");
> + return;
> + }
> +
> + ret = (*jvmti)->GetClassSignature(jvmti, decl_class,
> + &class_sign, NULL);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: getclassignature failed");
> + goto error;
> + }
> +
> + ret = (*jvmti)->GetMethodName(jvmti, method, &func_name,
> + &func_sign, NULL);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: failed getmethodname");
> + goto error;
> + }
> +
> + len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2;
> +
> + {
> + char str[len];
> + uint64_t addr = (uint64_t)(unsigned long)code_addr;
> + snprintf(str, len, "%s%s%s", class_sign, func_name, func_sign);
> + ret = jvmti_write_code(jvmti_agent, str, addr, code_addr, code_size);
> + if (ret)
> + warnx("jvmti: write_code() failed");
> + }
> +error:
> + (*jvmti)->Deallocate(jvmti, (unsigned char *)func_name);
> + (*jvmti)->Deallocate(jvmti, (unsigned char *)func_sign);
> + (*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign);
> +}
> +
> +static void JNICALL
> +code_generated_cb(jvmtiEnv *jvmti,
> + char const *name,
> + void const *code_addr,
> + jint code_size)
> +{
> + uint64_t addr = (uint64_t)(unsigned long)code_addr;
> + int ret;
> +
> + ret = jvmti_write_code(jvmti_agent, name, addr, code_addr, code_size);
> + if (ret)
> + warnx("jvmti: write_code() failed for code_generated");
> +}
> +
> +JNIEXPORT jint JNICALL
> +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __unused)
> +{
> + jvmtiEventCallbacks cb;
> + jvmtiCapabilities caps1;
> + jvmtiEnv *jvmti = NULL;
> + jint ret;
> +
> + jvmti_agent = jvmti_open();
> + if (!jvmti_agent) {
> + warnx("jvmti: open_agent failed");
> + return -1;
> + }
> +
> + /*
> + * Request a JVMTI interface version 1 environment
> + */
> + ret = (*jvm)->GetEnv(jvm, (void *)&jvmti, JVMTI_VERSION_1);
> + if (ret != JNI_OK) {
> + warnx("jvmti: jvmti version 1 not supported");
> + return -1;
> + }
> +
> + /*
> + * acquire method_load capability, we require it
> + */
> + memset(&caps1, 0, sizeof(caps1));
> + caps1.can_generate_compiled_method_load_events = 1;
> +
> + ret = (*jvmti)->AddCapabilities(jvmti, &caps1);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: acquire compiled_method capability failed");
> + return -1;
> + }
> +
> + memset(&cb, 0, sizeof(cb));
> +
> + cb.CompiledMethodLoad = compiled_method_load_cb;
> + cb.DynamicCodeGenerated = code_generated_cb;
> +
> + ret = (*jvmti)->SetEventCallbacks(jvmti, &cb, sizeof(cb));
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: cannot set event callbacks");
> + return -1;
> + }
> +
> + ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
> + JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: setnotification failed for method_load");
> + return -1;
> + }
> +
> + ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
> + JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);
> + if (ret != JVMTI_ERROR_NONE) {
> + warnx("jvmti: setnotification failed on code_generated");
> + return -1;
> + }
> + return 0;
> +}
> +
> +JNIEXPORT void JNICALL
> +Agent_OnUnload(JavaVM *jvm __unused)
> +{
> + int ret;
> +
> + ret = jvmti_close(jvmti_agent);
> + if (ret)
> + errx(1, "Error: op_close_agent()");
> +}
>