JITWatchを試す2

前回はJITWatchのmakeDemoLogFile.shを使ってHotSpotログファイルを作成したので、今回はソースコードを書いて試してみる。
手順は、ログファイルの作成以外は前回と同じなので割愛。
https://hirakida29.hatenablog.com/entry/2020/06/06/235407

ソースコード

build.gradle

plugins {
    id 'java'
    id 'application'
}

group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11

mainClassName = 'com.example.Main'

run {
    jvmArgs = ['-XX:+UnlockDiagnosticVMOptions', '-XX:+TraceClassLoading', '-XX:+LogCompilation']
}

Main.java

デモアプリの一部を抜粋 https://github.com/AdoptOpenJDK/jitwatch/blob/master/core/src/main/java/org/adoptopenjdk/jitwatch/demo/MakeHotSpotLog.java

package com.example;

public class Main {

    public Main(int iterations) {
        addVariable(iterations);
    }

    private void addVariable(int iterations) {
        long count = 0;
        for (int i = 0; i < iterations; i++) {
            count = add(count, i);
        }
        System.out.println(count);
    }

    private long add(long a, long b) {
        return a + b;
    }

    public static void main(String[] args) throws Exception {
        new Main(1_000_000);
    }
}

HotSpotログファイルを作成

% ./gradlew run

JITWatchで解析

f:id:hirakida29:20201120222833p:plain

JUnit5とSpockを同じプロジェクトで使う

JUnit5の環境でSpockを使いたいときのメモ。

ソースコード

plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
    id 'groovy'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
    testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
    testImplementation 'org.spockframework:spock-spring:1.3-groovy-2.5'
    testImplementation 'org.codehaus.groovy:groovy-all:2.5.13'
}

test {
    useJUnitPlatform()
    testLogging {
        showStandardStreams = true
    }
}
@RestController
public class HelloController {
    @GetMapping("/hello")
    public Map<String, String> hello() {
        return Map.of("message", "Hello!");
    }
}

テストコード

@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest extends Specification {
    @Autowired
    MockMvc mvc

    def "hello"() {
        expect:
        mvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().json("""
                    {
                        "message":"Hello!"
                    }
               """))
    }
}

参考

unit testing - Can we run Spock Testcases and Junit 5 test cases together In one project? - Stack Overflow

Spring Data RedisのテストにTestcontainersを使う

Spring Data RedisのテストにTestcontainersを使いたいときのメモ。

ソースコード

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation platform('org.testcontainers:testcontainers-bom:1.14.3')
    testImplementation 'org.testcontainers:testcontainers'
    testImplementation 'org.testcontainers:junit-jupiter'
}
@Component
public class RedisClient {
    private final StringRedisTemplate redisTemplate;

    public RedisClient(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }
}

テストコード

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@Testcontainers
public class RedisClientTest {
    @Container
    private static final GenericContainer<?> CONTAINER =
        new GenericContainer<>("redis:5.0").withExposedPorts(6379);
    @Autowired
    private RedisClient client;

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.port", () -> CONTAINER.getMappedPort(6379));
    }

    @BeforeEach
    void init() {
        client.set("key1", "value1");
    }

    @Test
    public void get() {
        String result = client.get("key1");
        assertNotNull(result);
        assertEquals("value1", result);
    }
}

参考

@DynamicPropertySource in Spring Framework 5.2.5 and Spring Boot 2.2.6
Networking and communicating with containers - Testcontainers

Spring BootのWebClientのテストにSpring Cloud ContractのWireMockを使う

WebClientのテストをするときは、OkHttp MockWebServerなどのMock Web Serverを使う必要があるので、Spring Cloud ContractのWireMockを使ってみる。
(MockRestServiceServerはWebClientをサポートしていない)

テスト対象のソースコード

build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR8'
    }
}

dependencies {
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.projectreactor:reactor-test'
    testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
}

test {
    useJUnitPlatform()
}

GitHubApiClient.java

@Component
public class GitHubApiClient {
    private final WebClient webClient;

    public GitHubApiClient(WebClient.Builder builder,
                           @Value("${base-url:https://api.github.com}") String baseUrl) {
        webClient = builder.baseUrl(baseUrl).build();
    }

    public Mono<User> getUser(String username) {
        return webClient.get()
                        .uri("/users/{username}", username)
                        .retrieve()
                        .bodyToMono(User.class);
    }
}

User.java

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(SnakeCaseStrategy.class)
@Data
public class User {
    private long id;
    private String login;
    ...
}

テストコード

GitHubApiClientTest.java

package com.example;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@SpringBootTest(properties = "base-url=http://localhost:8081")
@AutoConfigureWireMock(port = 8081)
public class GitHubApiClientTest {
    @Autowired
    private GitHubApiClient client;

    @Test
    public void getUser() {
        ResponseDefinitionBuilder response =
                aResponse().withBody("{\"login\":\"hirakida\",\"id\":100}")
                           .withHeader(HttpHeaders.CONTENT_TYPE,
                                       MediaType.APPLICATION_JSON_VALUE)
                           .withStatus(HttpStatus.OK.value());
        stubFor(get(urlEqualTo("/users/hirakida")).willReturn(response));

        Mono<User> result = client.getUser("hirakida");

        StepVerifier.create(result)
                    .expectNextMatches(user -> user != null && "hirakida".equals(user.getLogin()) && user.getId() == 100)
                    .verifyComplete();
    }
}

参考

https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-testing
Support of MockRestServiceServer for WebClient [SPR-15286] · Issue #19852 · spring-projects/spring-framework · GitHub
Update `@RestClientTest` to work with `WebClient` · Issue #8404 · spring-projects/spring-boot · GitHub
https://youtu.be/wAYt4Z4SF7g?t=2310

GradleでJNIを試す

GradleでJNIを試したときのメモ。

hello.c

src/hello/c/配下

#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL Java_com_example_HelloJNI_hello(JNIEnv *env, jobject obj) {
  printf("Hello!\n");
}

HelloJNI.java

src/main/java/com/example/配下

package com.example;

public class HelloJNI {
    static {
        System.loadLibrary("hello");
    }

    public native void hello();
}

Main.java

src/main/java/com/example/配下

package com.example;

public final class Main {

    public static void main(String[] args) {
        HelloJNI jni = new HelloJNI();
        jni.hello();
    }
}

build.gradle

MacOSの設定しか入れていないが、Windowsなどの設定も入れる場合はこちらを参照。
Set up JNI development in Gradle project - Help | IntelliJ IDEA

import org.gradle.internal.jvm.Jvm

plugins {
    id 'java'
    id 'application'
    id 'c'
}

group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

model {
    components {
        hello(NativeLibrarySpec) {
            binaries.all {
                cCompiler.args '-I', "${Jvm.current().javaHome}/include"
                cCompiler.args '-I', "${Jvm.current().javaHome}/include/darwin"
            }
        }
    }
}

build.dependsOn 'helloSharedLibrary'

mainClassName = 'com.example.Main'

run {
    systemProperty 'java.library.path', file("${buildDir}/libs/hello/shared").absolutePath
}

実行結果

% ./gradlew run 

> Task :run
Hello!

参考

Set up JNI development in Gradle project - Help | IntelliJ IDEA

JITWatchを試す

JITWatchを試したときのメモ。
今回はhsdis (HotSpot disassembler) は使っていないので、Assemblyは見れない。

準備

Java 11をインストールする

SDKMANでAdoptOpenJDK 11 HosSpotをインストールする

% sdk install java 11.0.7.hs-adpt 
% sdk use java 11.0.7.hs-adpt  

JITWatchをダウンロードする

% git clone git@github.com:AdoptOpenJDK/jitwatch.git    

JITWatchをビルドする

% cd jitwatch  
% ./gradlew clean build

HotSpotログファイルを準備する

今回はJITWatchのデモアプリを使う
以下のシェルを実行すると、カレントディレクトリにhotspot_pidxxxxx.logというログファイルが出力される

% ./makeDemoLogFile.sh

JITWatchの設定

JITWatchを起動する

% ./gradlew run

JITWatch Configuration

ConfigからJITWatch Configurationの画面を開く
Source locationsとClass locationsを設定した後にSaveする

f:id:hirakida29:20201120195236p:plain

Source locations

Add JDK srcをクリックしてJDKのsrc.zipを追加する

$HOME/.sdkman/candidates/java/11.0.7.hs-adpt/lib/src.zip

Add Folderから解析するsourceを追加する

※デモアプリの場合
[working directory]/jitwatch/core/src/main/java

Class locations

Add Folderから解析するclassを追加する

※デモアプリの場合
[working directory]/jitwatch/core/build/classes/java/main

JITWatchで解析

ログファイルを解析する

Open LogからHotSpotログファイルを選択して、Startをクリックする
解析が終わると、Compilations TimelineやTriViewなどが見れる

f:id:hirakida29:20201120224213p:plain

f:id:hirakida29:20201120224226p:plain

参考

詳細はこちら
https://github.com/AdoptOpenJDK/jitwatch
https://www.chrisnewland.com/images/jitwatch/HotSpot_Profiling_Using_JITWatch.pdf

GradleでJava 14のプレビュー機能を試す

GradleでJava 14のプレビュー機能を試したときのメモ。

Gradle

Gradle 6.3以降を使う
Gradle 6.3 Release Notes

IntelliJ IDEA

IntelliJ IDEAでJava 14を使う場合は、2020.1以降を使う
Java 14 and IntelliJ IDEA – IntelliJ IDEA Blog | JetBrains

build.gradle

plugins {
    id 'java'
    id 'application'
}

group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_14

repositories {
    mavenCentral()
}

tasks.withType(JavaCompile) {
    options.compilerArgs += ['--enable-preview']
}

mainClassName = 'com.example.Main'

run {
    jvmArgs = ['--enable-preview']
}

test {
    jvmArgs = ['--enable-preview']
}

Main.java

今回はRecordsを試す
JEP 359: Records (Preview)

package com.example;

public class Main {

    public static void main(String... args) {
        Point point = new Point(1, 2);
        System.out.println(point);

        Range range = new Range(1, 2);
        System.out.println(range);
    }

    record Point(int x, int y) {}

    record Range(int lo, int hi) {
        public Range {
            if (lo > hi) {
                throw new IllegalArgumentException("(%d,%d)".formatted(lo, hi));
            }
        }
    }
}

実行結果

% ./gradlew run

> Task :run
Point[x=1, y=2]
Range[lo=1, hi=2]

BUILD SUCCESSFUL in 1s