1 /*
2 * Copyright (C) 2011, Google Inc. and others
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Distribution License v. 1.0 which is available at
6 * https://www.eclipse.org/org/documents/edl-v10.php.
7 *
8 * SPDX-License-Identifier: BSD-3-Clause
9 */
10
11 package org.eclipse.jgit.internal.storage.dfs;
12
13 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
14 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
15 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT;
16 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE;
17 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONCURRENCY_LEVEL;
18 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
19
20 import java.text.MessageFormat;
21 import java.time.Duration;
22 import java.util.Collections;
23 import java.util.Map;
24 import java.util.function.Consumer;
25
26 import org.eclipse.jgit.internal.JGitText;
27 import org.eclipse.jgit.internal.storage.pack.PackExt;
28 import org.eclipse.jgit.lib.Config;
29
30 /**
31 * Configuration parameters for
32 * {@link org.eclipse.jgit.internal.storage.dfs.DfsBlockCache}.
33 */
34 public class DfsBlockCacheConfig {
35 /** 1024 (number of bytes in one kibibyte/kilobyte) */
36 public static final int KB = 1024;
37
38 /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */
39 public static final int MB = 1024 * KB;
40
41 /** Default number of max cache hits. */
42 public static final int DEFAULT_CACHE_HOT_MAX = 1;
43
44 private long blockLimit;
45 private int blockSize;
46 private double streamRatio;
47 private int concurrencyLevel;
48
49 private Consumer<Long> refLock;
50 private Map<PackExt, Integer> cacheHotMap;
51
52 private IndexEventConsumer indexEventConsumer;
53
54 /**
55 * Create a default configuration.
56 */
57 public DfsBlockCacheConfig() {
58 setBlockLimit(32 * MB);
59 setBlockSize(64 * KB);
60 setStreamRatio(0.30);
61 setConcurrencyLevel(32);
62 cacheHotMap = Collections.emptyMap();
63 }
64
65 /**
66 * Get maximum number bytes of heap memory to dedicate to caching pack file
67 * data.
68 *
69 * @return maximum number bytes of heap memory to dedicate to caching pack
70 * file data. <b>Default is 32 MB.</b>
71 */
72 public long getBlockLimit() {
73 return blockLimit;
74 }
75
76 /**
77 * Set maximum number bytes of heap memory to dedicate to caching pack file
78 * data.
79 * <p>
80 * It is strongly recommended to set the block limit to be an integer multiple
81 * of the block size. This constraint is not enforced by this method (since
82 * it may be called before {@link #setBlockSize(int)}), but it is enforced by
83 * {@link #fromConfig(Config)}.
84 *
85 * @param newLimit
86 * maximum number bytes of heap memory to dedicate to caching
87 * pack file data; must be positive.
88 * @return {@code this}
89 */
90 public DfsBlockCacheConfig setBlockLimit(long newLimit) {
91 if (newLimit <= 0) {
92 throw new IllegalArgumentException(MessageFormat.format(
93 JGitText.get().blockLimitNotPositive,
94 Long.valueOf(newLimit)));
95 }
96 blockLimit = newLimit;
97 return this;
98 }
99
100 /**
101 * Get size in bytes of a single window mapped or read in from the pack
102 * file.
103 *
104 * @return size in bytes of a single window mapped or read in from the pack
105 * file. <b>Default is 64 KB.</b>
106 */
107 public int getBlockSize() {
108 return blockSize;
109 }
110
111 /**
112 * Set size in bytes of a single window read in from the pack file.
113 *
114 * @param newSize
115 * size in bytes of a single window read in from the pack file.
116 * The value must be a power of 2.
117 * @return {@code this}
118 */
119 public DfsBlockCacheConfig setBlockSize(int newSize) {
120 int size = Math.max(512, newSize);
121 if ((size & (size - 1)) != 0) {
122 throw new IllegalArgumentException(
123 JGitText.get().blockSizeNotPowerOf2);
124 }
125 blockSize = size;
126 return this;
127 }
128
129 /**
130 * Get the estimated number of threads concurrently accessing the cache.
131 *
132 * @return the estimated number of threads concurrently accessing the cache.
133 * <b>Default is 32.</b>
134 */
135 public int getConcurrencyLevel() {
136 return concurrencyLevel;
137 }
138
139 /**
140 * Set the estimated number of threads concurrently accessing the cache.
141 *
142 * @param newConcurrencyLevel
143 * the estimated number of threads concurrently accessing the
144 * cache.
145 * @return {@code this}
146 */
147 public DfsBlockCacheConfig setConcurrencyLevel(
148 final int newConcurrencyLevel) {
149 concurrencyLevel = newConcurrencyLevel;
150 return this;
151 }
152
153 /**
154 * Get highest percentage of {@link #getBlockLimit()} a single pack can
155 * occupy while being copied by the pack reuse strategy.
156 *
157 * @return highest percentage of {@link #getBlockLimit()} a single pack can
158 * occupy while being copied by the pack reuse strategy. <b>Default
159 * is 0.30, or 30%</b>.
160 */
161 public double getStreamRatio() {
162 return streamRatio;
163 }
164
165 /**
166 * Set percentage of cache to occupy with a copied pack.
167 *
168 * @param ratio
169 * percentage of cache to occupy with a copied pack.
170 * @return {@code this}
171 */
172 public DfsBlockCacheConfig setStreamRatio(double ratio) {
173 streamRatio = Math.max(0, Math.min(ratio, 1.0));
174 return this;
175 }
176
177 /**
178 * Get the consumer of the object reference lock wait time in milliseconds.
179 *
180 * @return consumer of wait time in milliseconds.
181 */
182 public Consumer<Long> getRefLockWaitTimeConsumer() {
183 return refLock;
184 }
185
186 /**
187 * Set the consumer for lock wait time.
188 *
189 * @param c
190 * consumer of wait time in milliseconds.
191 * @return {@code this}
192 */
193 public DfsBlockCacheConfig setRefLockWaitTimeConsumer(Consumer<Long> c) {
194 refLock = c;
195 return this;
196 }
197
198 /**
199 * Get the map of hot count per pack extension for {@code DfsBlockCache}.
200 *
201 * @return map of hot count per pack extension for {@code DfsBlockCache}.
202 */
203 public Map<PackExt, Integer> getCacheHotMap() {
204 return cacheHotMap;
205 }
206
207 /**
208 * Set the map of hot count per pack extension for {@code DfsBlockCache}.
209 *
210 * @param cacheHotMap
211 * map of hot count per pack extension for {@code DfsBlockCache}.
212 * @return {@code this}
213 */
214 public DfsBlockCacheConfig setCacheHotMap(
215 Map<PackExt, Integer> cacheHotMap) {
216 this.cacheHotMap = Collections.unmodifiableMap(cacheHotMap);
217 return this;
218 }
219
220 /**
221 * Get the consumer of cache index events.
222 *
223 * @return consumer of cache index events.
224 */
225 public IndexEventConsumer getIndexEventConsumer() {
226 return indexEventConsumer;
227 }
228
229 /**
230 * Set the consumer of cache index events.
231 *
232 * @param indexEventConsumer
233 * consumer of cache index events.
234 * @return {@code this}
235 */
236 public DfsBlockCacheConfig setIndexEventConsumer(
237 IndexEventConsumer indexEventConsumer) {
238 this.indexEventConsumer = indexEventConsumer;
239 return this;
240 }
241
242 /**
243 * Update properties by setting fields from the configuration.
244 * <p>
245 * If a property is not defined in the configuration, then it is left
246 * unmodified.
247 * <p>
248 * Enforces certain constraints on the combination of settings in the config,
249 * for example that the block limit is a multiple of the block size.
250 *
251 * @param rc
252 * configuration to read properties from.
253 * @return {@code this}
254 */
255 public DfsBlockCacheConfig fromConfig(Config rc) {
256 long cfgBlockLimit = rc.getLong(
257 CONFIG_CORE_SECTION,
258 CONFIG_DFS_SECTION,
259 CONFIG_KEY_BLOCK_LIMIT,
260 getBlockLimit());
261 int cfgBlockSize = rc.getInt(
262 CONFIG_CORE_SECTION,
263 CONFIG_DFS_SECTION,
264 CONFIG_KEY_BLOCK_SIZE,
265 getBlockSize());
266 if (cfgBlockLimit % cfgBlockSize != 0) {
267 throw new IllegalArgumentException(MessageFormat.format(
268 JGitText.get().blockLimitNotMultipleOfBlockSize,
269 Long.valueOf(cfgBlockLimit),
270 Long.valueOf(cfgBlockSize)));
271 }
272
273 setBlockLimit(cfgBlockLimit);
274 setBlockSize(cfgBlockSize);
275
276 setConcurrencyLevel(rc.getInt(
277 CONFIG_CORE_SECTION,
278 CONFIG_DFS_SECTION,
279 CONFIG_KEY_CONCURRENCY_LEVEL,
280 getConcurrencyLevel()));
281
282 String v = rc.getString(
283 CONFIG_CORE_SECTION,
284 CONFIG_DFS_SECTION,
285 CONFIG_KEY_STREAM_RATIO);
286 if (v != null) {
287 try {
288 setStreamRatio(Double.parseDouble(v));
289 } catch (NumberFormatException e) {
290 throw new IllegalArgumentException(MessageFormat.format(
291 JGitText.get().enumValueNotSupported3,
292 CONFIG_CORE_SECTION,
293 CONFIG_DFS_SECTION,
294 CONFIG_KEY_STREAM_RATIO, v), e);
295 }
296 }
297 return this;
298 }
299
300 /** Consumer of DfsBlockCache loading and eviction events for indexes. */
301 public interface IndexEventConsumer {
302 /**
303 * Accept an event of an index requested. It could be loaded from either
304 * cache or storage.
305 *
306 * @param packExtPos
307 * position in {@code PackExt} enum
308 * @param cacheHit
309 * true if an index was already in cache. Otherwise, the
310 * index was loaded from storage into the cache in the
311 * current request,
312 * @param loadMicros
313 * time to load an index from cache or storage in
314 * microseconds
315 * @param bytes
316 * number of bytes loaded
317 * @param lastEvictionDuration
318 * time since last eviction, 0 if was not evicted yet
319 */
320 void acceptRequestedEvent(int packExtPos, boolean cacheHit,
321 long loadMicros, long bytes, Duration lastEvictionDuration);
322
323 /**
324 * Accept an event of an index evicted from cache.
325 *
326 * @param packExtPos
327 * position in {@code PackExt} enum
328 * @param bytes
329 * number of bytes evicted
330 * @param totalCacheHitCount
331 * number of times an index was accessed while in cache
332 * @param lastEvictionDuration
333 * time since last eviction, 0 if was not evicted yet
334 */
335 default void acceptEvictedEvent(int packExtPos, long bytes,
336 int totalCacheHitCount, Duration lastEvictionDuration) {
337 // Off by default.
338 }
339
340 /**
341 * @return true if reporting evicted events is enabled.
342 */
343 default boolean shouldReportEvictedEvent() {
344 return false;
345 }
346 }
347 }