github-advanced-security[bot] commented on code in PR #3174: URL: https://github.com/apache/jackrabbit-oak/pull/3174#discussion_r4218787427
########## oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/DocumentCacheBenchmark.java: ########## @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.benchmark; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.SplittableRandom; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.oak.cache.CacheValue; +import org.apache.jackrabbit.oak.cache.EmpiricalWeigher; +import org.apache.jackrabbit.oak.cache.api.Cache; +import org.apache.jackrabbit.oak.cache.api.CacheBuilder; +import org.apache.jackrabbit.oak.cache.api.CacheStatsSnapshot; +import org.apache.jackrabbit.oak.fixture.RepositoryFixture; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder; +import org.apache.jackrabbit.oak.plugins.document.Path; +import org.apache.jackrabbit.oak.plugins.document.PathRev; +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType; + +/** + * Measures the production node cache with a fixed-weight synthetic loader and persistence. + * Database latency is excluded; see the README for isolated-JVM commands. + */ +public final class DocumentCacheBenchmark extends Benchmark { + private static final int VALUE_MEMORY = 512; + static final long RANDOM_SEED = 42; + private static final double[] RATIOS = {0.5, 1, 2, 5, 10}; + private static final RevisionVector REVISION = new RevisionVector(new Revision(1, 0, 1)); + + enum Policy { CACHE_LIRS, CAFFEINE_SYNC, CAFFEINE_ASYNC } + enum Scenario { STEADY_STATE, CHURN, INVALIDATION, CONCURRENT } + enum State { WARM, COLD } + + record Configuration(int entries, int operations, int threads, int warmup, double multiplier, + boolean persistent, Set<Policy> policies, Set<Scenario> scenarios, Set<State> states) { + Configuration { + policies = Set.copyOf(policies); + scenarios = Set.copyOf(scenarios); + states = Set.copyOf(states); + if (policies.isEmpty() || scenarios.isEmpty() || states.isEmpty()) { + throw new IllegalArgumentException("Select at least one policy, scenario and cache state"); + } + if (entries <= 0 || entries > Integer.MAX_VALUE / 10) { + throw new IllegalArgumentException("document.cache.entries must be between 1 and " + Integer.MAX_VALUE / 10); + } + if (operations <= 0 || threads <= 0 || warmup < 0) { + throw new IllegalArgumentException("operations and threads must be positive; warmup must be non-negative"); + } + if (!Double.isFinite(multiplier) || multiplier <= 0) { + throw new IllegalArgumentException("document.cache.caffeine.maximumWeightMultiplier must be finite and positive"); + } + } + } + + record Result(long elapsedNanos, long operations, long backendLoads, long p95Nanos, + CacheStatsSnapshot stats) { } + + @Override + public void run(Iterable<RepositoryFixture> fixtures) { + Configuration config = configuration(); + System.out.printf(Locale.ROOT, "%nDocumentCacheBenchmark entries=%d operations=%d threads=%d" + + " persistent=%s persistentWrites=ASYNC seed=%d caffeineWeightMultiplier=%.2f%n", + config.entries, config.operations, config.threads, config.persistent, RANDOM_SEED, config.multiplier); + System.out.println("scenario state ratio policy ops/s ns/op sampledP95ns hit% misses evictions loaderCalls"); + for (Scenario scenario : Scenario.values()) { + if (!config.scenarios.contains(scenario)) { continue; } + for (State state : State.values()) { + if (!config.states.contains(state)) { continue; } + for (double ratio : RATIOS) { + for (Policy policy : Policy.values()) { + if (!config.policies.contains(policy)) { continue; } + Result result = runScenario(config, policy, scenario, state, + Math.max(1, (int) (config.entries * ratio))); + System.out.printf(Locale.ROOT, "%s %s %.1fx %s %.0f %.1f %d %.2f %d %d %d%n", + scenario, state, ratio, policy, result.operations * 1e9 / result.elapsedNanos, + (double) result.elapsedNanos / result.operations, result.p95Nanos, + result.stats.hitRate() * 100, result.stats.missCount(), result.stats.evictionCount(), + result.backendLoads); + } + } + } + } + } + + static Configuration configuration() { + int operations = integer("document.cache.operations", 2_000_000); + boolean caffeine = Boolean.getBoolean("oak.documentMK.caffeineCache"); + Set<Policy> defaults = caffeine + ? (Boolean.getBoolean("oak.documentMK.asyncCacheMaintenance") + ? Set.of(Policy.CAFFEINE_SYNC, Policy.CAFFEINE_ASYNC) : Set.of(Policy.CAFFEINE_SYNC)) + : Set.of(Policy.CACHE_LIRS); + Set<Policy> policies = selected("document.cache.policies", Policy.class, defaults); + if (policies.contains(Policy.CACHE_LIRS) && Boolean.getBoolean("oak.documentMK.guavaCache")) { + throw new IllegalArgumentException("CACHE_LIRS requires -Doak.documentMK.guavaCache=false"); + } + if (policies.contains(Policy.CACHE_LIRS) == caffeine + || (!caffeine && policies.stream().anyMatch(p -> p != Policy.CACHE_LIRS))) { + throw new IllegalArgumentException("Run CACHE_LIRS in a separate JVM with -Doak.documentMK.caffeineCache=false;" + + " Caffeine modes require -Doak.documentMK.caffeineCache=true"); + } + return new Configuration(integer("document.cache.entries", 10_000), operations, + integer("document.cache.threads", Math.max(2, Runtime.getRuntime().availableProcessors())), + integer("document.cache.warmup", Math.max(10_000, operations / 10)), + Double.parseDouble(System.getProperty("document.cache.caffeine.maximumWeightMultiplier", "1.0")), + Boolean.parseBoolean(System.getProperty("document.cache.persistent.enabled", "true")), policies, + selected("document.cache.scenarios", Scenario.class, Set.of(Scenario.values())), + selected("document.cache.states", State.class, Set.of(State.values()))); + } + + static Result runScenario(Configuration config, Policy policy, Scenario scenario, State state, int workingSet) { + try (Context context = createContext(config, policy, scenario)) { + List<PathRev> keys = new ArrayList<>(workingSet); + for (int i = 0; i < workingSet; i++) { + keys.add(new PathRev(Path.fromString( + String.format(Locale.ROOT, "/benchmark-%08d", i)), REVISION)); + } + runOperations(context, keys, scenario, config.warmup); + context.cache.cleanUp(); + if (state == State.COLD) { + context.cache.invalidateAll(); + context.cache.cleanUp(); + } + CacheStatsSnapshot initialStats = context.cache.stats(); + long start = System.nanoTime(); + List<TaskResult> tasks = runOperations(context, keys, scenario, config.operations); + context.cache.cleanUp(); + long elapsed = System.nanoTime() - start; + long completed = 0; + long loads = 0; + List<Long> samples = new ArrayList<>(); + for (TaskResult task : tasks) { + completed += task.operations; + loads += task.loads; + for (long sample : task.samples) { samples.add(sample); } + } + return new Result(elapsed, completed, loads, percentile95(samples), + context.cache.stats().minus(initialStats)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Document cache workload failed: " + policy + "/" + scenario + "/" + state, e); + } catch (IOException | RuntimeException e) { + throw new IllegalStateException("Document cache workload failed: " + policy + "/" + scenario + "/" + state, e); + } + } + + static Context createContext(Configuration config, Policy policy, Scenario scenario) throws IOException { + PathRev sampleKey = new PathRev(Path.fromString("/benchmark-00000000"), REVISION); + long weight = new EmpiricalWeigher().weigh(sampleKey, (CacheValue) () -> VALUE_MEMORY); + long maximum = Math.multiplyExact(config.entries, weight); + if (policy != Policy.CACHE_LIRS) { + double scaled = maximum * config.multiplier; + if (scaled > Long.MAX_VALUE / 2.0 || scaled < 1) { + throw new IllegalArgumentException("Caffeine maximum weight is outside the supported range"); + } + maximum = (long) scaled; + } + File directory = config.persistent ? Files.createTempDirectory("oak-document-cache-benchmark-").toFile() : null; Review Comment: ## SonarCloud / Temporary files should not be created in publicly writable directories <!--SONAR_ISSUE_KEY:AaEbceFQn4McE7C67_0Z-->Make sure publicly writable directories are used safely here. <p>See more on <a href="https://sonarcloud.io/project/issues?id=org.apache.jackrabbit%3Ajackrabbit-oak&issues=AaEbceFQn4McE7C67_0Z&open=AaEbceFQn4McE7C67_0Z&pullRequest=3174">SonarQube Cloud</a></p> [Show more details](https://github.com/apache/jackrabbit-oak/security/code-scanning/194) ########## oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/DocumentCacheRepositoryBenchmark.java: ########## @@ -0,0 +1,389 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.benchmark; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.SplittableRandom; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.benchmark.DocumentCacheBenchmark.Policy; +import org.apache.jackrabbit.oak.cache.AbstractCacheStats; +import org.apache.jackrabbit.oak.cache.api.CacheBuilder; +import org.apache.jackrabbit.oak.cache.api.CacheStatsSnapshot; +import org.apache.jackrabbit.oak.fixture.RepositoryFixture; +import org.apache.jackrabbit.oak.plugins.document.Collection; +import org.apache.jackrabbit.oak.plugins.document.Document; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder; +import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCacheStats; +import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.EmptyHook; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.DefaultNodeStateDiff; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** Measures real DocumentNodeStore reads, traversal, commits and cache reopen on a memory backend. */ +public final class DocumentCacheRepositoryBenchmark extends Benchmark { + private static final int CHILDREN_PER_GROUP = positiveProperty("document.repository.childrenPerGroup", 100); + private static final int GROUPS_PER_BUCKET = 100; + enum Scenario { POINT_READ, CHILDREN_SCAN, COMMIT_ONLY, COMMIT_DIFF, CONCURRENT_MIXED, CONCURRENT_WRITERS, REOPEN_READ } + + record Result(long operations, long reads, long writes, long documentFinds, long documentQueries, long elapsedNanos, + CacheStatsSnapshot nodeStats, long persistentHits, long localDiffHits, long localDiffMisses, + long mergeNanos, long diffNanos, long readbackNanos) { } + + /** + * Runs repository workloads separately from the direct-cache microbenchmark. + * @param fixtures runner selection; these workloads always use MemoryDocumentStore + */ + @Override + public void run(Iterable<RepositoryFixture> fixtures) { + Objects.requireNonNull(fixtures); + DocumentCacheBenchmark.Configuration cacheConfig = DocumentCacheBenchmark.configuration(); + int nodes = positiveProperty("document.repository.nodes", 10_000); + int operations = positiveProperty("document.repository.operations", 20_000); + int memoryMB = positiveProperty("document.repository.cacheMB", 256); + int durationSeconds = nonNegativeProperty("document.repository.durationSeconds", 60); + int warmupSeconds = nonNegativeProperty("document.repository.warmupSeconds", 20); + Set<Scenario> scenarios = selectedScenarios(); + System.out.printf(Locale.ROOT, "%nDocumentCacheRepositoryBenchmark nodes=%d operations=%d cacheMB=%d" + + " childrenPerGroup=%d backgroundMillis=%d threads=%d persistent=%s durationSeconds=%d warmupSeconds=%d backend=MemoryDocumentStore%n", + nodes, operations, memoryMB, CHILDREN_PER_GROUP, + nonNegativeProperty("document.repository.backgroundMillis", 1000), + cacheConfig.threads(), cacheConfig.persistent(), durationSeconds, warmupSeconds); + System.out.println("Every eligible Document cache, including LOCAL_DIFF, uses the named policy."); + System.out.println("repositoryScenario policy ops/s reads writes documentFinds documentQueries nodeHit% persistentHits localDiffHits localDiffMisses mergeMs diffMs readbackMs elapsedMs"); + for (Policy policy : Policy.values()) { + if (!cacheConfig.policies().contains(policy)) { continue; } + for (Scenario scenario : Scenario.values()) { + if (!scenarios.contains(scenario)) { continue; } + if (warmupSeconds > 0) { + runScenario(policy, scenario, nodes, operations, memoryMB, + cacheConfig.threads(), cacheConfig.persistent(), TimeUnit.SECONDS.toNanos(warmupSeconds)); + } + Result result = runScenario(policy, scenario, nodes, operations, memoryMB, + cacheConfig.threads(), cacheConfig.persistent(), TimeUnit.SECONDS.toNanos(durationSeconds)); + System.out.printf(Locale.ROOT, "%s %s %.0f %d %d %d %d %.2f %d %d %d %.3f %.3f %.3f %.3f%n", scenario, policy, + result.operations() * 1e9 / result.elapsedNanos(), result.reads(), result.writes(), + result.documentFinds(), result.documentQueries(), result.nodeStats().hitRate() * 100, result.persistentHits(), + result.localDiffHits(), result.localDiffMisses(), result.mergeNanos() / 1e6, + result.diffNanos() / 1e6, result.readbackNanos() / 1e6, result.elapsedNanos() / 1e6); + } + } + } + + static Result runScenario(Policy policy, Scenario scenario, int nodes, int operations, int memoryMB, + int threads, boolean persistent) { + return runScenario(policy, scenario, nodes, operations, memoryMB, threads, persistent, 0); + } + + static Result runScenario(Policy policy, Scenario scenario, int nodes, int operations, int memoryMB, + int threads, boolean persistent, long minimumDurationNanos) { + if (nodes <= 0 || operations <= 0 || memoryMB <= 0 || threads <= 0 || minimumDurationNanos < 0) { + throw new IllegalArgumentException("sizes must be positive and minimum duration must be non-negative"); + } + File directory = null; + DocumentNodeStore store = null; + ScheduledExecutorService statisticsExecutor = Executors.newSingleThreadScheduledExecutor(); + StatisticsProvider statistics = new DefaultStatisticsProvider(statisticsExecutor); + IllegalStateException failure = null; + try { + directory = persistent ? Files.createTempDirectory("oak-document-repository-benchmark-").toFile() : null; Review Comment: ## SonarCloud / Temporary files should not be created in publicly writable directories <!--SONAR_ISSUE_KEY:AaEbceCBn4McE7C67_0P-->Make sure publicly writable directories are used safely here. <p>See more on <a href="https://sonarcloud.io/project/issues?id=org.apache.jackrabbit%3Ajackrabbit-oak&issues=AaEbceCBn4McE7C67_0P&open=AaEbceCBn4McE7C67_0P&pullRequest=3174">SonarQube Cloud</a></p> [Show more details](https://github.com/apache/jackrabbit-oak/security/code-scanning/193) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
