Add properties stats with durations to nodes and query stats Change-Id: I269864b429c3ba0a73afc52b72b496ced9d6caab
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/StopWatch.java b/src/main/java/com/googlesource/gerrit/plugins/task/StopWatch.java new file mode 100644 index 0000000..f7d36e7 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/task/StopWatch.java
@@ -0,0 +1,82 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.task; + +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; +import java.util.function.LongConsumer; + +public class StopWatch { + protected Stopwatch stopwatch; + protected LongConsumer consumer; + protected long nanoseconds; + + public StopWatch enableIfNonNull(Object statistics) { + if (statistics != null) { + enable(); + } + return this; + } + + public StopWatch enable() { + stopwatch = Stopwatch.createUnstarted(); + return this; + } + + public StopWatch run(Runnable runnable) { + start(); + runnable.run(); + stop(); + return this; + } + + public StopWatch start() { + if (stopwatch != null && !stopwatch.isRunning()) { + stopwatch.start(); + } + return this; + } + + public StopWatch stop() { + if (stopwatch != null && stopwatch.isRunning()) { + stopwatch.stop(); + if (consumer != null) { + consume(consumer); + } + } + return this; + } + + public StopWatch setConsumer(LongConsumer consumer) { + if (consumer != null) { + stopwatch = Stopwatch.createUnstarted(); + } + this.consumer = consumer; + return this; + } + + public StopWatch consume(LongConsumer consumer) { + if (stopwatch != null) { + consumer.accept(get()); + } + return this; + } + + public long get() { + nanoseconds += stopwatch.elapsed(TimeUnit.NANOSECONDS); + stopwatch.reset(); + return nanoseconds; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java index 65b53cb..414ca25 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
@@ -69,6 +69,7 @@ public boolean isTaskRefreshNeeded; public Boolean hasUnfilterableSubNodes; public Object nodesByBranchCache; + public Object properties; } public Boolean applicable; @@ -163,6 +164,7 @@ statistics.numberOfDuplicates++; } attribute.statistics = new TaskAttribute.Statistics(); + attribute.statistics.properties = node.propertiesStatistics; } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java index 2f543bb..17b1a1b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
@@ -72,6 +72,7 @@ public Object definitionsPerSubSectionCache; public Object definitionsByBranchBySubSectionCache; public Object changesByNamesFactoryQueryCache; + public Properties.Statistics properties; } protected static final String TASK_DIR = "task"; @@ -232,6 +233,7 @@ public Task task; public boolean isDuplicate; + protected Properties.Statistics propertiesStatistics; protected final Properties properties; protected final TaskKey taskKey; protected StatisticsMap<BranchNameKey, List<Node>> nodesByBranch; @@ -277,10 +279,7 @@ public List<Node> getApplicableSubNodes() throws ConfigInvalidException, IOException, StorageException { - if (hasUnfilterableSubNodes) { - return getSubNodes(); - } - return new ApplicableNodeFilter().getSubNodes(); + return hasUnfilterableSubNodes ? getSubNodes() : new ApplicableNodeFilter().getSubNodes(); } @Override @@ -304,6 +303,10 @@ isDuplicate = path.contains(key); path.add(key); + if (statistics != null) { + properties.setStatisticsConsumer( + s -> statistics.properties = (propertiesStatistics = s).sum(statistics.properties)); + } this.task = properties.getTask(getChangeData()); this.duplicateKeys = new LinkedList<>(parent.duplicateKeys);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java index 3813319..c970cec 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/AbstractExpander.java
@@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -39,6 +40,12 @@ * the name/value associations via the getValueForName() method. */ public abstract class AbstractExpander { + protected Consumer<Matcher.Statistics> statisticsConsumer; + + public void setStatisticsConsumer(Consumer<Matcher.Statistics> statisticsConsumer) { + this.statisticsConsumer = statisticsConsumer; + } + /** * Returns expanded object if property found in the Strings in the object's Fields (except the * excluded ones). Returns same object if no expansions occurred. @@ -134,6 +141,7 @@ return null; } Matcher m = new Matcher(text); + m.setStatisticsConsumer(statisticsConsumer); if (!m.find()) { return text; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/CopyOnWrite.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/CopyOnWrite.java index 3e55158..7d94438 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/properties/CopyOnWrite.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/CopyOnWrite.java
@@ -14,11 +14,13 @@ package com.googlesource.gerrit.plugins.task.properties; +import com.googlesource.gerrit.plugins.task.StopWatch; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Optional; import java.util.function.Function; +import java.util.function.LongConsumer; public class CopyOnWrite<T> { public static class CloneOnWrite<C extends Cloneable> extends CopyOnWrite<C> { @@ -67,6 +69,7 @@ } protected Function<T, T> copier; + protected StopWatch stopWatch = new StopWatch(); protected T original; protected T copy; @@ -75,6 +78,10 @@ this.copier = copier; } + protected void setNanosecondsConsumer(LongConsumer nanosConsumer) { + stopWatch.setConsumer(nanosConsumer); + } + public T getOriginal() { return original; } @@ -84,7 +91,10 @@ } public T getForWrite() { - return copy = isCopy() ? copy : copier.apply(original); + if (!isCopy()) { + stopWatch.run(() -> copy = copier.apply(original)); + } + return copy; } public boolean isCopy() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java index abe203d..dc7bc18 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Matcher.java
@@ -14,26 +14,61 @@ package com.googlesource.gerrit.plugins.task.properties; +import com.googlesource.gerrit.plugins.task.StopWatch; +import java.util.function.Consumer; + /** A handcrafted properties Matcher which has an API similar to an RE Matcher, but is faster. */ public class Matcher { - String text; - int start; - int nameStart; - int end; - int cursor; + public static class Statistics { + public long appendNanoseconds; + public long findNanoseconds; + + public Statistics sum(Statistics other) { + if (other == null) { + return this; + } + Statistics statistics = new Statistics(); + statistics.appendNanoseconds = appendNanoseconds + other.appendNanoseconds; + statistics.findNanoseconds = findNanoseconds + other.findNanoseconds; + return statistics; + } + } + + protected String text; + protected int start; + protected int nameStart; + protected int end; + protected int cursor; + + protected Statistics statistics; + protected StopWatch appendNanoseconds = new StopWatch(); + protected StopWatch findNanoseconds = new StopWatch(); public Matcher(String text) { this.text = text; } + protected void setStatisticsConsumer(Consumer<Statistics> statisticsConsumer) { + if (statisticsConsumer != null) { + statistics = new Statistics(); + statisticsConsumer.accept(statistics); + appendNanoseconds.setConsumer(ns -> statistics.appendNanoseconds = ns); + findNanoseconds.setConsumer(ns -> statistics.findNanoseconds = ns); + } + } + public boolean find() { + findNanoseconds.start(); start = text.indexOf("${", cursor); nameStart = start + 2; if (start < 0 || text.length() < nameStart + 1) { + findNanoseconds.stop(); return false; } end = text.indexOf('}', nameStart); - return end >= 0; + boolean found = end >= 0; + findNanoseconds.stop(); + return found; } public String getName() { @@ -41,17 +76,21 @@ } public void appendValue(StringBuffer buffer, String value) { + appendNanoseconds.start(); if (start > cursor) { buffer.append(text.substring(cursor, start)); } buffer.append(value); cursor = end + 1; + appendNanoseconds.stop(); } public void appendTail(StringBuffer buffer) { + appendNanoseconds.start(); if (cursor < text.length()) { buffer.append(text.substring(cursor)); cursor = text.length(); } + appendNanoseconds.stop(); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/properties/Properties.java b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Properties.java index e13594a..5fc6c12 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/task/properties/Properties.java +++ b/src/main/java/com/googlesource/gerrit/plugins/task/properties/Properties.java
@@ -17,15 +17,36 @@ import com.google.common.collect.ImmutableSet; import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.server.query.change.ChangeData; +import com.googlesource.gerrit.plugins.task.StopWatch; import com.googlesource.gerrit.plugins.task.TaskConfig; import com.googlesource.gerrit.plugins.task.TaskConfig.NamesFactory; import com.googlesource.gerrit.plugins.task.TaskConfig.Task; import com.googlesource.gerrit.plugins.task.TaskTree; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; /** Use to expand properties like ${_name} in the text of various definitions. */ public class Properties { + public static class Statistics { + public long getTaskNanoseconds; + public long copierNanoseconds; + public Matcher.Statistics matcher; + + public Statistics sum(Statistics other) { + if (other == null) { + return this; + } + Statistics statistics = new Statistics(); + statistics.getTaskNanoseconds = getTaskNanoseconds + other.getTaskNanoseconds; + statistics.copierNanoseconds = copierNanoseconds + other.copierNanoseconds; + statistics.matcher = matcher == null ? other.matcher : matcher.sum(other.matcher); + return statistics; + } + + protected StopWatch getTask; + } + public static final Properties EMPTY = new Properties() { @Override @@ -37,6 +58,9 @@ public final Task origTask; protected final TaskTree.Node node; protected final CopyOnWrite<Task> task; + protected Statistics statistics; + protected Consumer<Statistics> statisticsConsumer; + protected Consumer<Matcher.Statistics> matcherStatisticsConsumer; protected Expander expander; protected Loader loader; protected boolean init = true; @@ -57,8 +81,12 @@ /** Use to expand properties specifically for Tasks. */ public Task getTask(ChangeData changeData) throws StorageException { + if (statistics != null) { + statistics.getTask = new StopWatch().enable().start(); + } loader = new Loader(origTask, changeData, getParentMapper()); expander = new Expander(n -> loader.load(n)); + expander.setStatisticsConsumer(matcherStatisticsConsumer); if (isTaskRefreshRequired || init) { expander.expand(task, TaskConfig.KEY_APPLICABLE); isApplicableRefreshRequired = loader.isNonTaskDefinedPropertyLoaded(); @@ -75,9 +103,23 @@ isTaskRefreshRequired = loader.isNonTaskDefinedPropertyLoaded(); } } + if (statisticsConsumer != null) { + statistics.getTaskNanoseconds = statistics.getTask.stop().get(); + statistics.getTask = null; + statisticsConsumer.accept(statistics); + } return task.getForRead(); } + public void setStatisticsConsumer(Consumer<Statistics> statisticsConsumer) { + if (statisticsConsumer != null) { + this.statisticsConsumer = statisticsConsumer; + statistics = new Statistics(); + matcherStatisticsConsumer = s -> statistics.matcher = s; + task.setNanosecondsConsumer(ns -> statistics.copierNanoseconds = ns); + } + } + // To detect NamesFactories dependent on non task defined properties, the checking must be // done after subnodes are fully loaded, which unfortunately happens after getTask() is // called, therefore this must be called after all subnodes have been loaded.