summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.settings/.api_filters23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java258
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java176
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java82
5 files changed, 510 insertions, 35 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 56edd61d75..9ee9ff7b68 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -45,6 +45,12 @@
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
<filter id="1142947843">
<message_arguments>
+ <message_argument value="5.1.13"/>
+ <message_argument value="CONFIG_JMX_SECTION"/>
+ </message_arguments>
+ </filter>
+ <filter id="1142947843">
+ <message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="CONFIG_FILESYSTEM_SECTION"/>
</message_arguments>
@@ -78,6 +84,15 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/storage/file/WindowCacheStats.java" type="org.eclipse.jgit.storage.file.WindowCacheStats">
+ <filter id="337809484">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.storage.file.WindowCacheStats"/>
+ <message_argument value="4"/>
+ <message_argument value="8"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
<filter id="336658481">
<message_arguments>
@@ -232,6 +247,14 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/util/Monitoring.java" type="org.eclipse.jgit.util.Monitoring">
+ <filter id="1109393411">
+ <message_arguments>
+ <message_argument value="5.1.13"/>
+ <message_argument value="org.eclipse.jgit.util.Monitoring"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache">
<filter id="1109393411">
<message_arguments>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index 8cf1d4e219..6cf5bd948a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
@@ -48,13 +48,16 @@ import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Random;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.ReentrantLock;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.storage.file.WindowCacheStats;
+import org.eclipse.jgit.util.Monitoring;
/**
* Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in
@@ -124,6 +127,196 @@ import org.eclipse.jgit.storage.file.WindowCacheConfig;
* other threads.
*/
public class WindowCache {
+
+ /**
+ * Record statistics for a cache
+ */
+ static interface StatsRecorder {
+ /**
+ * Record cache hits. Called when cache returns a cached entry.
+ *
+ * @param count
+ * number of cache hits to record
+ */
+ void recordHits(int count);
+
+ /**
+ * Record cache misses. Called when the cache returns an entry which had
+ * to be loaded.
+ *
+ * @param count
+ * number of cache misses to record
+ */
+ void recordMisses(int count);
+
+ /**
+ * Record a successful load of a cache entry
+ *
+ * @param loadTimeNanos
+ * time to load a cache entry
+ */
+ void recordLoadSuccess(long loadTimeNanos);
+
+ /**
+ * Record a failed load of a cache entry
+ *
+ * @param loadTimeNanos
+ * time used trying to load a cache entry
+ */
+ void recordLoadFailure(long loadTimeNanos);
+
+ /**
+ * Record cache evictions due to the cache evictions strategy
+ *
+ * @param count
+ * number of evictions to record
+ */
+ void recordEvictions(int count);
+
+ /**
+ * Record files opened by cache
+ *
+ * @param count
+ * delta of number of files opened by cache
+ */
+ void recordOpenFiles(int count);
+
+ /**
+ * Record cached bytes
+ *
+ * @param count
+ * delta of cached bytes
+ */
+ void recordOpenBytes(int count);
+
+ /**
+ * Returns a snapshot of this recorder's stats. Note that this may be an
+ * inconsistent view, as it may be interleaved with update operations.
+ *
+ * @return a snapshot of this recorder's stats
+ */
+ @NonNull
+ WindowCacheStats getStats();
+ }
+
+ static class StatsRecorderImpl
+ implements StatsRecorder, WindowCacheStats {
+ private final LongAdder hitCount;
+ private final LongAdder missCount;
+ private final LongAdder loadSuccessCount;
+ private final LongAdder loadFailureCount;
+ private final LongAdder totalLoadTime;
+ private final LongAdder evictionCount;
+ private final LongAdder openFileCount;
+ private final LongAdder openByteCount;
+
+ /**
+ * Constructs an instance with all counts initialized to zero.
+ */
+ public StatsRecorderImpl() {
+ hitCount = new LongAdder();
+ missCount = new LongAdder();
+ loadSuccessCount = new LongAdder();
+ loadFailureCount = new LongAdder();
+ totalLoadTime = new LongAdder();
+ evictionCount = new LongAdder();
+ openFileCount = new LongAdder();
+ openByteCount = new LongAdder();
+ }
+
+ @Override
+ public void recordHits(int count) {
+ hitCount.add(count);
+ }
+
+ @Override
+ public void recordMisses(int count) {
+ missCount.add(count);
+ }
+
+ @Override
+ public void recordLoadSuccess(long loadTimeNanos) {
+ loadSuccessCount.increment();
+ totalLoadTime.add(loadTimeNanos);
+ }
+
+ @Override
+ public void recordLoadFailure(long loadTimeNanos) {
+ loadFailureCount.increment();
+ totalLoadTime.add(loadTimeNanos);
+ }
+
+ @Override
+ public void recordEvictions(int count) {
+ evictionCount.add(count);
+ }
+
+ @Override
+ public void recordOpenFiles(int count) {
+ openFileCount.add(count);
+ }
+
+ @Override
+ public void recordOpenBytes(int count) {
+ openByteCount.add(count);
+ }
+
+ @Override
+ public WindowCacheStats getStats() {
+ return this;
+ }
+
+ @Override
+ public long getHitCount() {
+ return hitCount.sum();
+ }
+
+ @Override
+ public long getMissCount() {
+ return missCount.sum();
+ }
+
+ @Override
+ public long getLoadSuccessCount() {
+ return loadSuccessCount.sum();
+ }
+
+ @Override
+ public long getLoadFailureCount() {
+ return loadFailureCount.sum();
+ }
+
+ @Override
+ public long getEvictionCount() {
+ return evictionCount.sum();
+ }
+
+ @Override
+ public long getTotalLoadTime() {
+ return totalLoadTime.sum();
+ }
+
+ @Override
+ public long getOpenFileCount() {
+ return openFileCount.sum();
+ }
+
+ @Override
+ public long getOpenByteCount() {
+ return openByteCount.sum();
+ }
+
+ @Override
+ public void resetCounters() {
+ hitCount.reset();
+ missCount.reset();
+ loadSuccessCount.reset();
+ loadFailureCount.reset();
+ totalLoadTime.reset();
+ evictionCount.reset();
+ }
+ }
+
private static final int bits(int newSize) {
if (newSize < 4096)
throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
@@ -228,9 +421,9 @@ public class WindowCache {
private final int windowSize;
- private final AtomicInteger openFiles;
+ private final StatsRecorder statsRecorder;
- private final AtomicLong openBytes;
+ private final StatsRecorderImpl mbean;
private WindowCache(WindowCacheConfig cfg) {
tableSize = tableSize(cfg);
@@ -263,8 +456,9 @@ public class WindowCache {
windowSizeShift = bits(cfg.getPackedGitWindowSize());
windowSize = 1 << windowSizeShift;
- openFiles = new AtomicInteger();
- openBytes = new AtomicLong();
+ mbean = new StatsRecorderImpl();
+ statsRecorder = mbean;
+ Monitoring.registerMBean(WindowCacheStats.class, "block_cache"); //$NON-NLS-1$
if (maxFiles < 1)
throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
@@ -273,61 +467,63 @@ public class WindowCache {
}
/**
- * @return the number of open files.
+ * @return cache statistics for the WindowCache
*/
- public int getOpenFiles() {
- return openFiles.get();
+ public WindowCacheStats getStats() {
+ return statsRecorder.getStats();
}
/**
- * @return the number of open bytes.
+ * Reset stats. Does not reset open bytes and open files stats.
*/
- public long getOpenBytes() {
- return openBytes.get();
+ public void resetStats() {
+ mbean.resetCounters();
}
private int hash(int packHash, long off) {
return packHash + (int) (off >>> windowSizeShift);
}
- private ByteWindow load(PackFile pack, long offset)
- throws IOException {
+ private ByteWindow load(PackFile pack, long offset) throws IOException {
+ long startTime = System.nanoTime();
if (pack.beginWindowCache())
- openFiles.incrementAndGet();
+ statsRecorder.recordOpenFiles(1);
try {
if (mmap)
return pack.mmap(offset, windowSize);
- return pack.read(offset, windowSize);
- } catch (IOException e) {
- close(pack);
- throw e;
- } catch (RuntimeException e) {
- close(pack);
- throw e;
- } catch (Error e) {
+ ByteArrayWindow w = pack.read(offset, windowSize);
+ statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
+ return w;
+ } catch (IOException | RuntimeException | Error e) {
close(pack);
+ statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
throw e;
+ } finally {
+ statsRecorder.recordMisses(1);
}
}
private Ref createRef(PackFile p, long o, ByteWindow v) {
final Ref ref = new Ref(p, o, v, queue);
- openBytes.addAndGet(ref.size);
+ statsRecorder.recordOpenBytes(ref.size);
return ref;
}
private void clear(Ref ref) {
- openBytes.addAndGet(-ref.size);
+ statsRecorder.recordOpenBytes(-ref.size);
+ statsRecorder.recordEvictions(1);
close(ref.pack);
}
private void close(PackFile pack) {
- if (pack.endWindowCache())
- openFiles.decrementAndGet();
+ if (pack.endWindowCache()) {
+ statsRecorder.recordOpenFiles(-1);
+ }
}
private boolean isFull() {
- return maxFiles < openFiles.get() || maxBytes < openBytes.get();
+ return maxFiles < mbean.getOpenFileCount()
+ || maxBytes < mbean.getOpenByteCount();
}
private long toStart(long offset) {
@@ -365,15 +561,19 @@ public class WindowCache {
final int slot = slot(pack, position);
final Entry e1 = table.get(slot);
ByteWindow v = scan(e1, pack, position);
- if (v != null)
+ if (v != null) {
+ statsRecorder.recordHits(1);
return v;
+ }
synchronized (lock(pack, position)) {
Entry e2 = table.get(slot);
if (e2 != e1) {
v = scan(e2, pack, position);
- if (v != null)
+ if (v != null) {
+ statsRecorder.recordHits(1);
return v;
+ }
}
v = load(pack, position);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 82ccd7b034..43776e0817 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -451,4 +451,10 @@ public final class ConfigConstants {
* @since 5.1.9
*/
public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold";
+
+ /**
+ * The "jmx" section
+ * @since 5.1.13
+ */
+ public static final String CONFIG_JMX_SECTION = "jmx";
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
index 3570733e41..b7f6394df6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
@@ -40,29 +40,193 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
package org.eclipse.jgit.storage.file;
+import javax.management.MXBean;
+
import org.eclipse.jgit.internal.storage.file.WindowCache;
/**
- * Accessor for stats about {@link WindowCache}.
+ * Cache statistics for {@link WindowCache}.
*
* @since 4.11
- *
*/
-public class WindowCacheStats {
+@MXBean
+public interface WindowCacheStats {
/**
* @return the number of open files.
+ * @deprecated use {@link #getOpenFileCount()} instead
*/
+ @Deprecated
public static int getOpenFiles() {
- return WindowCache.getInstance().getOpenFiles();
+ return (int) WindowCache.getInstance().getStats().getOpenFileCount();
}
/**
* @return the number of open bytes.
+ * @deprecated use {@link #getOpenByteCount()} instead
*/
+ @Deprecated
public static long getOpenBytes() {
- return WindowCache.getInstance().getOpenBytes();
+ return WindowCache.getInstance().getStats().getOpenByteCount();
+ }
+
+ /**
+ * @return cache statistics for the WindowCache
+ * @since 5.1.13
+ */
+ public static WindowCacheStats getStats() {
+ return WindowCache.getInstance().getStats();
+ }
+
+ /**
+ * Number of cache hits
+ *
+ * @return number of cache hits
+ */
+ long getHitCount();
+
+ /**
+ * Ratio of cache requests which were hits defined as
+ * {@code hitCount / requestCount}, or {@code 1.0} when
+ * {@code requestCount == 0}. Note that {@code hitRate + missRate =~ 1.0}.
+ *
+ * @return the ratio of cache requests which were hits
+ */
+ default double getHitRatio() {
+ long requestCount = getRequestCount();
+ return (requestCount == 0) ? 1.0
+ : (double) getHitCount() / requestCount;
+ }
+
+ /**
+ * Number of cache misses.
+ *
+ * @return number of cash misses
+ */
+ long getMissCount();
+
+ /**
+ * Ratio of cache requests which were misses defined as
+ * {@code missCount / requestCount}, or {@code 0.0} when
+ * {@code requestCount == 0}. Note that {@code hitRate + missRate =~ 1.0}.
+ * Cache misses include all requests which weren't cache hits, including
+ * requests which resulted in either successful or failed loading attempts.
+ *
+ * @return the ratio of cache requests which were misses
+ */
+ default double getMissRatio() {
+ long requestCount = getRequestCount();
+ return (requestCount == 0) ? 0.0
+ : (double) getMissCount() / requestCount;
+ }
+
+ /**
+ * Number of successful loads
+ *
+ * @return number of successful loads
+ */
+ long getLoadSuccessCount();
+
+ /**
+ * Number of failed loads
+ *
+ * @return number of failed loads
+ */
+ long getLoadFailureCount();
+
+ /**
+ * Ratio of cache load attempts which threw exceptions. This is defined as
+ * {@code loadFailureCount / (loadSuccessCount + loadFailureCount)}, or
+ * {@code 0.0} when {@code loadSuccessCount + loadFailureCount == 0}.
+ *
+ * @return the ratio of cache loading attempts which threw exceptions
+ */
+ default double getLoadFailureRatio() {
+ long loadFailureCount = getLoadFailureCount();
+ long totalLoadCount = getLoadSuccessCount() + loadFailureCount;
+ return (totalLoadCount == 0) ? 0.0
+ : (double) loadFailureCount / totalLoadCount;
}
+
+ /**
+ * Total number of times that the cache attempted to load new values. This
+ * includes both successful load operations, as well as failed loads. This
+ * is defined as {@code loadSuccessCount + loadFailureCount}.
+ *
+ * @return the {@code loadSuccessCount + loadFailureCount}
+ */
+ default long getLoadCount() {
+ return getLoadSuccessCount() + getLoadFailureCount();
+ }
+
+ /**
+ * Number of cache evictions
+ *
+ * @return number of evictions
+ */
+ long getEvictionCount();
+
+ /**
+ * Ratio of cache evictions. This is defined as
+ * {@code evictionCount / requestCount}, or {@code 0.0} when
+ * {@code requestCount == 0}.
+ *
+ * @return the ratio of cache loading attempts which threw exceptions
+ */
+ default double getEvictionRatio() {
+ long evictionCount = getEvictionCount();
+ long requestCount = getRequestCount();
+ return (requestCount == 0) ? 0.0
+ : (double) evictionCount / requestCount;
+ }
+
+ /**
+ * Number of times the cache returned either a cached or uncached value.
+ * This is defined as {@code hitCount + missCount}.
+ *
+ * @return the {@code hitCount + missCount}
+ */
+ default long getRequestCount() {
+ return getHitCount() + getMissCount();
+ }
+
+ /**
+ * Average time in nanoseconds for loading new values. This is
+ * {@code totalLoadTime / (loadSuccessCount + loadFailureCount)}.
+ *
+ * @return the average time spent loading new values
+ */
+ default double getAverageLoadTime() {
+ long totalLoadCount = getLoadSuccessCount() + getLoadFailureCount();
+ return (totalLoadCount == 0) ? 0.0
+ : (double) getTotalLoadTime() / totalLoadCount;
+ }
+
+ /**
+ * Total time in nanoseconds the cache spent loading new values.
+ *
+ * @return the total number of nanoseconds the cache has spent loading new
+ * values
+ */
+ long getTotalLoadTime();
+
+ /**
+ * Number of pack files kept open by the cache
+ *
+ * @return number of files kept open by cache
+ */
+ long getOpenFileCount();
+
+ /**
+ * Number of bytes cached
+ *
+ * @return number of bytes cached
+ */
+ long getOpenByteCount();
+
+ /**
+ * Reset counters. Does not reset open bytes and open files counters.
+ */
+ void resetCounters();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java
new file mode 100644
index 0000000000..a3c8f3e047
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2019 Matthias Sohn <matthias.sohn@sap.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.util;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Enables monitoring JGit via JMX
+ *
+ * @since 5.1.13
+ */
+public class Monitoring {
+ private final static Logger LOG = LoggerFactory.getLogger(Monitoring.class);
+
+ /**
+ * Register a MBean with the platform MBean server
+ *
+ * @param mbean
+ * the mbean interface to register
+ * @param metricName
+ * name of the JGit metric, will be prefixed with
+ * "org.eclipse.jgit/"
+ * @return the registered mbean's object instance
+ */
+ public static @Nullable ObjectInstance registerMBean(Class mbean,
+ String metricName) {
+ boolean register;
+ try {
+ register = SystemReader.getInstance().getUserConfig().getBoolean(
+ ConfigConstants.CONFIG_JMX_SECTION,
+ mbean.getSimpleName(), false);
+ } catch (IOException | ConfigInvalidException e) {
+ LOG.error(e.getMessage(), e);
+ return null;
+ }
+ if (!register) {
+ return null;
+ }
+ MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+ try {
+ ObjectName mbeanName = objectName(mbean, metricName);
+ if (server.isRegistered(mbeanName)) {
+ server.unregisterMBean(mbeanName);
+ }
+ return server.registerMBean(mbean, mbeanName);
+ } catch (MalformedObjectNameException | InstanceAlreadyExistsException
+ | MBeanRegistrationException | NotCompliantMBeanException
+ | InstanceNotFoundException e) {
+ LOG.error(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ private static ObjectName objectName(Class mbean, String metricName)
+ throws MalformedObjectNameException {
+ return new ObjectName(String.format("org.eclipse.jgit/%s:type=%s", //$NON-NLS-1$
+ metricName, mbean.getSimpleName()));
+ }
+}