Skip to content

Commit

Permalink
Add jitdump for perf record (#21)
Browse files Browse the repository at this point in the history
We should be able to use RDTSC as a timestamp counter with `JITDUMP_FLAGS_ARCH_TIMESTAMP` flag, but it does not work with perf-6.10.6-200.fc40.x86_64  at least. libperf-jvmti.so provided by perf tool is same behavior. So I suspect it is a bug in perf tool, and I use monotonic clock in this feature.

See a237173 for RDTSC version.
  • Loading branch information
YaSuenag authored Sep 16, 2024
1 parent 046e240 commit 88a5b8f
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
target/
jit-*.dump
jitted-*.so
perf.*data*
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,46 @@ perf map file would be written at [shutdown hook](https://docs.oracle.com/en/jav

You need to enable perf map dumper via `CodeSegment::enablePerfMapDumper`. Call `CodeSegment::disablePerfMapDumper` if you want to cancel the dumper.

## Generate jitdump

perf tool on Linux supports JIT-generated code. ffmasm can dump generated code as a jitdump. See an [example](examples/perf) for details.

### Record assembled code as a JIT'ed code

Pass `JitDump` insntace to `build` method.

```java
jitdump = JitDump.getInstance(Path.of("."));

:

.build("GeneratedFunc", jitdump);
```

Then you can run `perf record`. Note that you have to set monotonic clock with `-k` option.

```
perf record -k 1 $JAVA_HOME/bin/java ...
```

As a result, you would get `jit-<PID>.dump` which includes JIT information. You should keep until run `perf inject`.

### Inject JIT'ed code into recording file

`perf.data` generated by `perf record` would not include JIT'ed code, so you need to inject them via `perf inject` as following.

```
perf inject --jit -i perf.data -o perf.jit.data
```

You will get some `.so` file and `perf.jit.data` as an injected file as a result.

### Check with `perf report`

```
perf report -i perf.jit.data
```

# License

The GNU Lesser General Public License, version 3.0
31 changes: 31 additions & 0 deletions examples/perf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Example of profiling with perf tool
===================

This example shows how to use `JitDump` to profile your assembly code generated by ffmasm. [PerfJit.java](src/main/java/com/yasuenag/ffmasm/examples/perf/PerfJit.java) issues `RDRAND` instruction in 10,000,000 times.

# How to build

```
cd /path/to/ffasm
mvn install
cd examples/perf
mvn package
```

# Run test

[perf.sh](perf.sh) runs the example with `perf` and injects jitted code (it means assembly code generated by ffmasm) with `perf inject`.

```
./perf.sh
```

You can see `jit-<PID>.dump`, `jitted-*.so`, and `perf.*data*` on working directory of `perf.sh`. Please keep them until you run `perf report`.

# Profiling with `perf report`

You need to specify `perf.jit.data` with `-i` option to load injected data.

```
perf report -i perf.jit.data
```
7 changes: 7 additions & 0 deletions examples/perf/perf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

BASEDIR=$(dirname $0)

perf record -k 1 $JAVA_HOME/bin/java -jar $BASEDIR/target/perf-example-0.1.0.jar && \
perf inject --jit -i perf.data -o perf.jit.data && \
echo 'Completed. run "perf report -i perf.jit.data"'
75 changes: 75 additions & 0 deletions examples/perf/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.yasuenag</groupId>
<artifactId>perf-example</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.yasuenag</groupId>
<artifactId>ffmasm</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<compilerArgs>
<arg>-Xlint:all</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifestEntries>
<Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.yasuenag.ffmasm.examples.perf.PerfJit</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.yasuenag.ffmasm.examples.perf;

import java.lang.foreign.*;
import java.lang.invoke.*;
import java.nio.file.*;
import java.util.*;

import com.yasuenag.ffmasm.*;
import com.yasuenag.ffmasm.amd64.*;


public class PerfJit{

private static final CodeSegment seg;

private static final MethodHandle rdtsc;

private static final JitDump jitdump;

static{
try{
seg = new CodeSegment();
jitdump = JitDump.getInstance(Path.of("."));
var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG);
rdtsc = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc)
/* .align 16 */ .alignTo16BytesWithNOP()
/* retry: */ .label("retry")
/* rdrand %rax */ .rdrand(Register.RAX)
/* jae retry */ .jae("retry")
/* ret */ .ret()
.build("ffm_rdtsc", jitdump, Linker.Option.critical(false));
}
catch(Throwable t){
throw new RuntimeException(t);
}
}

public static void main(String[] args) throws Throwable{
try(jitdump; seg){
for(int i = 0; i < 10_000_000; i++){
long _ = (long)rdtsc.invokeExact();
}
}
}

}
19 changes: 11 additions & 8 deletions src/main/java/com/yasuenag/ffmasm/CodeSegment.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,18 +188,21 @@ public MemorySegment getAddr(){
}

/**
* Add method info to the list which should be dumped as perf map.
*
* @param mh MethodHandle should be recorded
* @param name method name
* @param address top address of the method
* @param size method size
* Add method info. It will be dumped to perf map as related method of this CodeSegment.
* @param mh MethodHandle of the method
* @param name Method name
* @param address Address of the method
* @param size Size of the method (machine code)
* @return MethodInfo of the method info.
* @throws IllegalArgumentException if the address is out of range from this CodeSegment.
*/
public void addMethodInfo(MethodHandle mh, String name, long address, int size){
public MethodInfo addMethodInfo(MethodHandle mh, String name, long address, int size){
if((address < addr.address()) || ((addr.address() + this.size) < (address + size))){
throw new IllegalArgumentException("Address is out of range from CodeSegment.");
}
methods.add(new MethodInfo(mh, name, address, size));
var methodInfo = new MethodInfo(mh, name, address, size);
methods.add(methodInfo);
return methodInfo;
}

private void dumpPerfMap(Path path){
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/com/yasuenag/ffmasm/JitDump.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2024, Yasumasa Suenaga
*
* This file is part of ffmasm.
*
* ffmasm 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 3 of the License, or
* (at your option) any later version.
*
* ffmasm 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 ffmasm. If not, see <http://www.gnu.org/licenses/>.
*/
package com.yasuenag.ffmasm;

import java.io.IOException;
import java.nio.file.Path;

import com.yasuenag.ffmasm.internal.linux.PerfJitDump;


/**
* Interface of jitdump for perf command on Linux.
*
* @author Yasumasa Suenaga
*/
public interface JitDump extends AutoCloseable{

/**
* Get instance of JitDump.
*
* @param dir Base directory which jitdump is generated.
* @throws UnsupportedPlatformException if the call happens on unsupported platform.
*/
public static JitDump getInstance(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{
var osName = System.getProperty("os.name");
if(osName.equals("Linux")){
return new PerfJitDump(dir);
}

throw new UnsupportedPlatformException("This platform is not supported in JitDump");
}

/**
* Write method info to jitdump.
*
* @param method MethodInfo should be written.
*/
public void writeFunction(CodeSegment.MethodInfo method);

}
20 changes: 19 additions & 1 deletion src/main/java/com/yasuenag/ffmasm/amd64/AMD64AsmBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.Set;

import com.yasuenag.ffmasm.CodeSegment;
import com.yasuenag.ffmasm.JitDump;
import com.yasuenag.ffmasm.UnsupportedPlatformException;


Expand Down Expand Up @@ -976,17 +977,34 @@ public MethodHandle build(Linker.Option... options){
/**
* Build as a MethodHandle
*
* @param name Method name
* @param options Linker options to pass to downcallHandle().
* @return MethodHandle for this assembly
* @throws IllegalStateException when label(s) are not defined even if they are used
*/
public MethodHandle build(String name, Linker.Option... options){
return build(name, null, options);
}

/**
* Build as a MethodHandle
*
* @param name Method name
* @param jitdump JitDump instance which should be written.
* @param options Linker options to pass to downcallHandle().
* @return MethodHandle for this assembly
* @throws IllegalStateException when label(s) are not defined even if they are used
*/
public MethodHandle build(String name, JitDump jitdump, Linker.Option... options){
updateTail();
var top = mem.address();
var size = byteBuf.position();
var mh = Linker.nativeLinker().downcallHandle(mem, desc, options);

seg.addMethodInfo(mh, name, top, size);
var methodInfo = seg.addMethodInfo(mh, name, top, size);
if(jitdump != null){
jitdump.writeFunction(methodInfo);
}
return mh;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ public class LinuxExecMemory implements ExecMemory{

private static final MemorySegment errnoSeg;

private MethodHandle hndMmap = null;
private static MethodHandle hndMmap = null;

private MethodHandle hndMunmap = null;
private static MethodHandle hndMunmap = null;

private VarHandle hndErrno = null;
private static VarHandle hndErrno = null;

/**
* page can be read
Expand Down Expand Up @@ -90,7 +90,12 @@ public class LinuxExecMemory implements ExecMemory{
errnoSeg = Arena.global().allocate(Linker.Option.captureStateLayout());
}

private MemorySegment mmap(MemorySegment addr, long length, int prot, int flags, int fd, long offset) throws PlatformException{
/**
* Call mmap(2) via FFM. See manpage of mmap(2) for details.
*
* @throws PlatformException if mmap(2) or FFM call failed.
*/
public static MemorySegment mmap(MemorySegment addr, long length, int prot, int flags, int fd, long offset) throws PlatformException{
if(hndMmap == null){
var func = sym.find("mmap").get();
var desc = FunctionDescriptor.of(
Expand Down Expand Up @@ -120,7 +125,12 @@ private MemorySegment mmap(MemorySegment addr, long length, int prot, int flags,
}
}

private int munmap(MemorySegment addr, long length) throws PlatformException{
/**
* Call munmap(2) via FFM. See manpage of munmap(2) for details.
*
* @throws PlatformException if munmap(2) or FFM call failed.
*/
public static int munmap(MemorySegment addr, long length) throws PlatformException{
if(hndMunmap == null){
var func = sym.find("munmap").get();
var desc = FunctionDescriptor.of(
Expand Down
Loading

0 comments on commit 88a5b8f

Please sign in to comment.