WindowCache: add metric for cached bytes per repository

Since ObjectDatabase and PackFile don't know their repository use the
packfile's grand-grand-parent directory as an identifier for the
repository the packfile resides in.

Remove metric for a repository if the number of cached bytes for the
repository drops to 0 in order to ensure the map of cached bytes per
repository doesn't contain repositories which have no data cached in the
WindowCache.

Change-Id: I969ab8029db0a292e7585cbb36ca0baa797da20b
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
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 e8ab735..3b4f662 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
@@ -47,12 +47,16 @@
 import java.io.IOException;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
+import java.util.Collections;
+import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 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 java.util.stream.Collectors;
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.internal.JGitText;
@@ -184,18 +188,21 @@ static interface StatsRecorder {
 		/**
 		 * Record files opened by cache
 		 *
-		 * @param count
+		 * @param delta
 		 *            delta of number of files opened by cache
 		 */
-		void recordOpenFiles(int count);
+		void recordOpenFiles(int delta);
 
 		/**
 		 * Record cached bytes
 		 *
-		 * @param count
+		 * @param pack
+		 *            pack file the bytes are read from
+		 *
+		 * @param delta
 		 *            delta of cached bytes
 		 */
-		void recordOpenBytes(int count);
+		void recordOpenBytes(PackFile pack, int delta);
 
 		/**
 		 * Returns a snapshot of this recorder's stats. Note that this may be an
@@ -217,6 +224,7 @@ static class StatsRecorderImpl
 		private final LongAdder evictionCount;
 		private final LongAdder openFileCount;
 		private final LongAdder openByteCount;
+		private final Map<String, LongAdder> openByteCountPerRepository;
 
 		/**
 		 * Constructs an instance with all counts initialized to zero.
@@ -230,6 +238,7 @@ public StatsRecorderImpl() {
 			evictionCount = new LongAdder();
 			openFileCount = new LongAdder();
 			openByteCount = new LongAdder();
+			openByteCountPerRepository = new ConcurrentHashMap<>();
 		}
 
 		@Override
@@ -260,13 +269,28 @@ public void recordEvictions(int count) {
 		}
 
 		@Override
-		public void recordOpenFiles(int count) {
-			openFileCount.add(count);
+		public void recordOpenFiles(int delta) {
+			openFileCount.add(delta);
 		}
 
 		@Override
-		public void recordOpenBytes(int count) {
-			openByteCount.add(count);
+		public void recordOpenBytes(PackFile pack, int delta) {
+			openByteCount.add(delta);
+			String repositoryId = repositoryId(pack);
+			LongAdder la = openByteCountPerRepository
+					.computeIfAbsent(repositoryId, k -> new LongAdder());
+			la.add(delta);
+			if (delta < 0) {
+				openByteCountPerRepository.computeIfPresent(repositoryId,
+						(k, v) -> v.longValue() == 0 ? null : v);
+			}
+		}
+
+		private static String repositoryId(PackFile pack) {
+			// use repository's gitdir since packfile doesn't know its
+			// repository
+			return pack.getPackFile().getParentFile().getParentFile()
+					.getParent();
 		}
 
 		@Override
@@ -323,6 +347,15 @@ public void resetCounters() {
 			totalLoadTime.reset();
 			evictionCount.reset();
 		}
+
+		@Override
+		public Map<String, Long> getOpenByteCountPerRepository() {
+			return Collections.unmodifiableMap(
+					openByteCountPerRepository.entrySet().stream()
+							.collect(Collectors.toMap(Map.Entry::getKey,
+									e -> Long.valueOf(e.getValue().sum()),
+									(u, v) -> v)));
+		}
 	}
 
 	private static final int bits(int newSize) {
@@ -519,12 +552,12 @@ private PageRef<ByteWindow> createRef(PackFile p, long o, ByteWindow v) {
 		final PageRef<ByteWindow> ref = useStrongRefs
 				? new StrongRef(p, o, v, queue)
 				: new SoftRef(p, o, v, (SoftCleanupQueue) queue);
-		statsRecorder.recordOpenBytes(ref.getSize());
+		statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
 		return ref;
 	}
 
 	private void clear(PageRef<ByteWindow> ref) {
-		statsRecorder.recordOpenBytes(-ref.getSize());
+		statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
 		statsRecorder.recordEvictions(1);
 		close(ref.getPack());
 	}
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 b7f6394..65f8dae 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
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.storage.file;
 
+import java.util.Map;
+
 import javax.management.MXBean;
 
 import org.eclipse.jgit.internal.storage.file.WindowCache;
@@ -226,6 +228,13 @@ default double getAverageLoadTime() {
 	long getOpenByteCount();
 
 	/**
+	 * Number of bytes cached per repository
+	 *
+	 * @return number of bytes cached per repository
+	 */
+	Map<String, Long> getOpenByteCountPerRepository();
+
+	/**
 	 * Reset counters. Does not reset open bytes and open files counters.
 	 */
 	void resetCounters();