如何为 orientdb 嵌入式设置 jmh 测试?

How to setup jmh testing for orientdb embedded?

我正在尝试为嵌入的 orient db 设置一个 jmh 测试。测试套件如下:

@State(Scope.Benchmark)
public class OrientDbTest {
    private OObjectDatabaseTx db;
    private Person[] personList;

    @Setup
    public void setUp() throws IOException {
        deleteDir("/tmp/orientdb/");
        db = new OObjectDatabaseTx("plocal:/tmp/orientdb/person").create();
        ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
        loadData();
    }

    @TearDown
    public void cleanUp() {
        if (db != null) {
            ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
            db.commit();
            db.drop();
            db.close();
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void benchmarkInsertCompany() {
        ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
        db.getEntityManager().registerEntityClass(Person.class);

        for (Person person : personList) {
            db.save(person);
        }
    }

    void loadData() throws IOException {
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("data.json");
        ObjectMapper objectMapper = new ObjectMapper();
        personList = objectMapper.readValue(inputStream, Person[].class);
    }

    void deleteDir(String dirName) {
        File file = new File(dirName);
        if (file.exists()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    if (child.isDirectory()) {
                        deleteDir(child.getAbsolutePath());
                    } else {
                        child.delete();
                    }
                }
            } else {
                file.delete();
            }
        }
    }
}

这个项目是一个 gradle 项目,我 运行 使用 gradle-jmh 插件。这是 build.gradle 文件中的 jmh 设置:

jmh {
    jmhVersion = '1.14'
    iterations = 10 // Number of measurement iterations to do.
    fork = 2 // How many times to forks a single benchmark. Use 0 to disable forking altogether
    jvmArgs = '-server -XX:MaxDirectMemorySize=15986m'
    resultsFile = project.file("${project.buildDir}/reports/jmh/results.txt") // results file
    profilers = ['cl', 'gc', 'hs_thr'] // Use profilers to collect additional data. Supported profilers: [cl, comp, gc, stack, perf, perfnorm, perfasm, xperf, xperfasm, hs_cl, hs_comp, hs_gc, hs_rt, hs_thr]
    resultFormat = 'CSV' // Result format type (one of CSV, JSON, NONE, SCSV, TEXT)
    threads = 4 // Number of worker threads to run with.
    timeUnit = 'ms' // Output time unit. Available time units are: [m, s, ms, us, ns].
    warmupForks = 2 // How many warmup forks to make for a single benchmark. 0 to disable warmup forks.
    warmupIterations = 10 // Number of warmup iterations to do.
}

当我 运行 测试时,出现以下错误:

INFO: OrientDB auto-config DISKCACHE=10,384MB (heap=3,554MB direct=15,986MB os=15,986MB)
<failure>

com.orientechnologies.orient.core.exception.OStorageExistsException: Cannot create new storage 'plocal:/tmp/orientdb/person' because it is not closed
        DB name="person"
        at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.create(OAbstractPaginatedStorage.java:423)
        at com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage.create(OLocalPaginatedStorage.java:125)
        at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.create(ODatabaseDocumentTx.java:429)
        at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.create(ODatabaseDocumentTx.java:389)
        at com.orientechnologies.orient.core.db.ODatabaseWrapperAbstract.create(ODatabaseWrapperAbstract.java:75)
        at org.dizitart.no2.benchmark.OrientDbTest.setUp(OrientDbTest.java:24)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest._jmh_tryInit_f_orientdbtest0_G(OrientDbTest_benchmarkInsertCompany_jmhTest.java:400)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest.benchmarkInsertCompany_AverageTime(OrientDbTest_benchmarkInsertCompany_jmhTest.java:149)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

我这里的设置有什么问题吗?

编辑:

看完this,我修改了代码如下:

public class OrientDbTest {

    @State(Scope.Benchmark)
    public static class TestState {
        private OObjectDatabaseTx db;
        private Person[] personList;
        private BenchmarkTestHelper testHelper = new BenchmarkTestHelper();

        @Setup(Level.Trial)
        public void setUp() throws IOException {
            System.out.println("started setup code");
            testHelper.deleteDir("/tmp/orientdb/");

            try {
                db = new OObjectDatabaseTx("plocal:/tmp/orientdb/person").open(null, null);
                db.getEntityManager().registerEntityClass(Person.class);
                personList = testHelper.loadData();
            } finally {
                if (db != null) {
                    db.close();
                }
            }
        }

        @TearDown(Level.Trial)
        public void cleanUp() {
            System.out.println("started cleanup code");
            if (db != null) {
                ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
                db.commit();
                db.drop();
                db.close();
            }
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void benchmarkInsertCompany(TestState state, Blackhole blackhole) {
        OObjectDatabaseTx db = state.db;
        Person[] personList = state.personList;

        ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
        for (Person person : personList) {
            blackhole.consume(db.save(person));
        }
    }
}


class BenchmarkTestHelper {
    Person[] loadData() throws IOException {
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("data.json");
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(inputStream, Person[].class);
    }

    void deleteDir(String dirName) {
        File file = new File(dirName);
        if (file.exists()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    if (child.isDirectory()) {
                        deleteDir(child.getAbsolutePath());
                    } else {
                        child.delete();
                    }
                }
                file.delete();
            } else {                   
                file.delete();
            }
        }
    }
}

新设置后,出现以下错误:

# JMH 1.14 (released 19 days ago)
# VM version: JDK 1.8.0_77, VM 25.77-b03
# VM invoker: /home/anindya/app/jdk1.8.0_77/jre/bin/java
# VM options: -server -XX:MaxDirectMemorySize=15986m
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.dizitart.no2.benchmark.OrientDbTest.benchmarkInsertCompany

# Run progress: 50.00% complete, ETA 00:01:28
# Warmup Fork: 1 of 2
# Warmup Iteration   1: started setup code
Sep 26, 2016 11:15:57 AM com.orientechnologies.common.log.OLogManager log
INFO: OrientDB auto-config DISKCACHE=10,384MB (heap=3,554MB direct=15,986MB os=15,986MB)
started setup code
started setup code
started setup code
<failure>

com.orientechnologies.orient.core.exception.OStorageExistsException: Cannot create new storage 'plocal:/tmp/orientdb/person' because it is not closed
        DB name="person"
        at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.create(OAbstractPaginatedStorage.java:423)
        at com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage.create(OLocalPaginatedStorage.java:125)
        at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.create(ODatabaseDocumentTx.java:429)
        at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.create(ODatabaseDocumentTx.java:389)
        at com.orientechnologies.orient.core.db.ODatabaseWrapperAbstract.create(ODatabaseWrapperAbstract.java:75)
        at org.dizitart.no2.benchmark.OrientDbTest$TestState.setUp(OrientDbTest.java:28)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest._jmh_tryInit_f_teststate1_G(OrientDbTest_benchmarkInsertCompany_jmhTest.java:409)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest.benchmarkInsertCompany_AverageTime(OrientDbTest_benchmarkInsertCompany_jmhTest.java:153)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

...

# Run progress: 87.50% complete, ETA 00:00:18
# Fork: 2 of 2
# Warmup Iteration   1: started setup code
Sep 26, 2016 11:16:38 AM com.orientechnologies.common.log.OLogManager log
INFO: OrientDB auto-config DISKCACHE=10,384MB (heap=3,554MB direct=15,986MB os=15,986MB)
started setup code
started setup code
started setup code
<failure>

java.lang.ClassCastException: com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx cannot be cast to com.orientechnologies.orient.object.db.OObjectDatabaseTx
        at org.dizitart.no2.benchmark.OrientDbTest$TestState.setUp(OrientDbTest.java:28)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest._jmh_tryInit_f_teststate1_G(OrientDbTest_benchmarkInsertCompany_jmhTest.java:409)
        at org.dizitart.no2.benchmark.generated.OrientDbTest_benchmarkInsertCompany_jmhTest.benchmarkInsertCompany_AverageTime(OrientDbTest_benchmarkInsertCompany_jmhTest.java:153)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
        at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)


Sep 26, 2016 11:16:49 AM com.orientechnologies.common.log.OLogManager log
INFO: Orient Engine is shutting down...
Sep 26, 2016 11:16:49 AM com.orientechnologies.common.log.OLogManager log
INFO: - shutdown storage: person...

db.drop(); 未执行,因此您收到错误消息:Cannot create new storage because it is not closed

好的,所以有几个麻烦:

a) setUp() 方法本身有问题,它抛出:

java.lang.ClassCastException: com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx cannot be cast to com.orientechnologies.orient.object.db.OObjectDatabaseTx
    at jmh.demo.OrientDbTest$TestState.setUp(OrientDbTest.java:30)
    at jmh.demo.generated.OrientDbTest_benchmarkInsert_jmhTest._jmh_tryInit_f_teststate1_G(OrientDbTest_benchmarkInsert_jmhTest.java:409)
    at jmh.demo.generated.OrientDbTest_benchmarkInsert_jmhTest.benchmarkInsert_AverageTime(OrientDbTest_benchmarkInsert_jmhTest.java:153)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
...

b) @State(Benchmark)@Setup 方法应该只执行一次。但是,由于 bug in JMH,如果第一个线程因上述异常而失败,则其他线程将 尝试进入 @Setup 然后他们将尝试打开数据库,但是失败了,因为它已经打开了?

事实上,失败 (a) 即使在原始 post 中也是可见的,并且从多个线程进入 @Setup 会报告什么异常将取决于纯粹的运气(这在JMH 1.14.1)。如果您设置 threads=1,它肯定会失败并出现适当的异常。另外,setUp()中的清理路径看起来与tearDown()不一致,这或许可以解释为什么重新进入线程无法打开数据库。

底线:在进行多线程测试之前,尝试进行单线程测试。

Aleksey 的回答帮助我弄清楚在测试中使用 fork 是错误的。我在这里发布工作代码以避免问题更加混乱。

public class OrientDbTest {

    @State(Scope.Benchmark)
    public static class TestState {
        private OObjectDatabaseTx db;
        private Person[] personList;

        @Setup(Level.Trial)
        public void setUp() throws IOException {
            System.out.println("started setup code");
            try {
                personList = loadData();
                deleteDir("/tmp/orientdb/");
                db = new OObjectDatabaseTx("plocal:/tmp/orientdb/person");
                if (db.exists()) {
                    db.open("admin", "admin");
                    db.drop();
                }
                db.create();
                db.getEntityManager().registerEntityClass(Person.class);
                db.getEntityManager().registerEntityClass(Address.class);
                db.getEntityManager().registerEntityClass(PrivateData.class);
            } catch (Throwable e) {
                System.out.println("error in creating db ");
                e.printStackTrace();
            }
        }

        @TearDown(Level.Trial)
        public void cleanUp() {
            System.out.println("started cleanup code");
            if (db != null) {
                ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
                db.commit();
                db.close();
            }
        }

        private void deleteDir(String dirName) {
            File file = new File(dirName);
            if (file.exists()) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File child : files) {
                        if (child.isDirectory()) {
                            deleteDir(child.getAbsolutePath());
                        } else {
                            child.delete();
                        }
                    }
                    file.delete();
                } else {
                    file.delete();
                }
            }
        }

        private Person[] loadData() throws IOException {
            InputStream inputStream = Thread.currentThread()
                    .getContextClassLoader().getResourceAsStream("data.json");
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(inputStream, Person[].class);
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @Fork(0)
    public void benchmarkInsert(TestState state, Blackhole blackhole) {
        OObjectDatabaseTx db = state.db;
        Person[] personList = state.personList;

        if (db == null) {
            System.out.println("db null.. exiting");
            System.exit(0);
        }

        ODatabaseRecordThreadLocal.INSTANCE.set(db.getUnderlying());
        for (Person person : personList) {
            blackhole.consume(db.save(person));
        }
    }