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]

Reply via email to