View Javadoc
1   /*
2    * Copyright (C) 2011, 2013 Google Inc., and others.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.internal.storage.dfs;
45  
46  import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
47  import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
48  
49  import java.util.Arrays;
50  import java.util.Comparator;
51  
52  import org.eclipse.jgit.annotations.NonNull;
53  import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
54  import org.eclipse.jgit.internal.storage.pack.PackExt;
55  import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
56  import org.eclipse.jgit.storage.pack.PackStatistics;
57  
58  /**
59   * Description of a DFS stored pack/index file.
60   * <p>
61   * Implementors may extend this class and add additional data members.
62   * <p>
63   * Instances of this class are cached with the DfsPackFile, and should not be
64   * modified once initialized and presented to the JGit DFS library.
65   */
66  public class DfsPackDescription {
67  	/**
68  	 * Comparator for packs when looking up objects in indexes.
69  	 * <p>
70  	 * This comparator tries to position packs in the order readers should examine
71  	 * them when looking for objects by SHA-1. The default tries to sort packs
72  	 * with more recent modification dates before older packs, and packs with
73  	 * fewer objects before packs with more objects.
74  	 * <p>
75  	 * Uses {@link PackSource#DEFAULT_COMPARATOR} for the portion of comparison
76  	 * where packs are sorted by source.
77  	 *
78  	 * @return comparator.
79  	 */
80  	public static Comparator<DfsPackDescription> objectLookupComparator() {
81  		return objectLookupComparator(PackSource.DEFAULT_COMPARATOR);
82  	}
83  
84  	/**
85  	 * Comparator for packs when looking up objects in indexes.
86  	 * <p>
87  	 * This comparator tries to position packs in the order readers should examine
88  	 * them when looking for objects by SHA-1. The default tries to sort packs
89  	 * with more recent modification dates before older packs, and packs with
90  	 * fewer objects before packs with more objects.
91  	 *
92  	 * @param packSourceComparator
93  	 *            comparator for the {@link PackSource}, used as the first step in
94  	 *            comparison.
95  	 * @return comparator.
96  	 */
97  	public static Comparator<DfsPackDescription> objectLookupComparator(
98  			Comparator<PackSource> packSourceComparator) {
99  		return Comparator.comparing(
100 					DfsPackDescription::getPackSource, packSourceComparator)
101 			.thenComparing((a, b) -> {
102 				PackSource as = a.getPackSource();
103 				PackSource bs = b.getPackSource();
104 
105 				// Tie break GC type packs by smallest first. There should be at most
106 				// one of each source, but when multiple exist concurrent GCs may have
107 				// run. Preferring the smaller file selects higher quality delta
108 				// compression, placing less demand on the DfsBlockCache.
109 				if (as == bs && isGC(as)) {
110 					int cmp = Long.signum(a.getFileSize(PACK) - b.getFileSize(PACK));
111 					if (cmp != 0) {
112 						return cmp;
113 					}
114 				}
115 
116 				// Newer packs should sort first.
117 				int cmp = Long.signum(b.getLastModified() - a.getLastModified());
118 				if (cmp != 0) {
119 					return cmp;
120 				}
121 
122 				// Break ties on smaller index. Readers may get lucky and find
123 				// the object they care about in the smaller index. This also pushes
124 				// big historical packs to the end of the list, due to more objects.
125 				return Long.signum(a.getObjectCount() - b.getObjectCount());
126 			});
127 	}
128 
129 	static Comparator<DfsPackDescription> reftableComparator() {
130 		return (a, b) -> {
131 				// GC, COMPACT reftables first by reversing default order.
132 				int c = PackSource.DEFAULT_COMPARATOR.reversed()
133 						.compare(a.getPackSource(), b.getPackSource());
134 				if (c != 0) {
135 					return c;
136 				}
137 
138 				// Lower maxUpdateIndex first.
139 				c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex());
140 				if (c != 0) {
141 					return c;
142 				}
143 
144 				// Older reftable first.
145 				return Long.signum(a.getLastModified() - b.getLastModified());
146 			};
147 	}
148 
149 	static Comparator<DfsPackDescription> reuseComparator() {
150 		return (a, b) -> {
151 			PackSource as = a.getPackSource();
152 			PackSource bs = b.getPackSource();
153 
154 			if (as == bs && DfsPackDescription.isGC(as)) {
155 				// Push smaller GC files last; these likely have higher quality
156 				// delta compression and the contained representation should be
157 				// favored over other files.
158 				return Long.signum(b.getFileSize(PACK) - a.getFileSize(PACK));
159 			}
160 
161 			// DfsPackDescription.compareTo already did a reasonable sort.
162 			// Rely on Arrays.sort being stable, leaving equal elements.
163 			return 0;
164 		};
165 	}
166 
167 	private final DfsRepositoryDescription repoDesc;
168 	private final String packName;
169 	private PackSource packSource;
170 	private long lastModified;
171 	private long[] sizeMap;
172 	private int[] blockSizeMap;
173 	private long objectCount;
174 	private long deltaCount;
175 	private long minUpdateIndex;
176 	private long maxUpdateIndex;
177 
178 	private PackStatistics packStats;
179 	private ReftableWriter.Stats refStats;
180 	private int extensions;
181 	private int indexVersion;
182 	private long estimatedPackSize;
183 
184 	/**
185 	 * Initialize a description by pack name and repository.
186 	 * <p>
187 	 * The corresponding index file is assumed to exist. If this is not true
188 	 * implementors must extend the class and override
189 	 * {@link #getFileName(PackExt)}.
190 	 * <p>
191 	 * Callers should also try to fill in other fields if they are reasonably
192 	 * free to access at the time this instance is being initialized.
193 	 *
194 	 * @param name
195 	 *            name of the pack file. Must end with ".pack".
196 	 * @param repoDesc
197 	 *            description of the repo containing the pack file.
198 	 * @param packSource
199 	 *            the source of the pack.
200 	 */
201 	public DfsPackDescription(DfsRepositoryDescription repoDesc, String name,
202 			@NonNull PackSource packSource) {
203 		this.repoDesc = repoDesc;
204 		int dot = name.lastIndexOf('.');
205 		this.packName = (dot < 0) ? name : name.substring(0, dot);
206 		this.packSource = packSource;
207 
208 		int extCnt = PackExt.values().length;
209 		sizeMap = new long[extCnt];
210 		blockSizeMap = new int[extCnt];
211 	}
212 
213 	/**
214 	 * Get description of the repository.
215 	 *
216 	 * @return description of the repository.
217 	 */
218 	public DfsRepositoryDescription getRepositoryDescription() {
219 		return repoDesc;
220 	}
221 
222 	/**
223 	 * Adds the pack file extension to the known list.
224 	 *
225 	 * @param ext
226 	 *            the file extension
227 	 */
228 	public void addFileExt(PackExt ext) {
229 		extensions |= ext.getBit();
230 	}
231 
232 	/**
233 	 * Whether the pack file extension is known to exist.
234 	 *
235 	 * @param ext
236 	 *            the file extension
237 	 * @return whether the pack file extension is known to exist.
238 	 */
239 	public boolean hasFileExt(PackExt ext) {
240 		return (extensions & ext.getBit()) != 0;
241 	}
242 
243 	/**
244 	 * Get file name
245 	 *
246 	 * @param ext
247 	 *            the file extension
248 	 * @return name of the file.
249 	 */
250 	public String getFileName(PackExt ext) {
251 		return packName + '.' + ext.getExtension();
252 	}
253 
254 	/**
255 	 * Get cache key for use by the block cache.
256 	 *
257 	 * @param ext
258 	 *            the file extension.
259 	 * @return cache key for use by the block cache.
260 	 */
261 	public DfsStreamKey getStreamKey(PackExt ext) {
262 		return DfsStreamKey.of(getRepositoryDescription(), getFileName(ext),
263 				ext);
264 	}
265 
266 	/**
267 	 * Get the source of the pack.
268 	 *
269 	 * @return the source of the pack.
270 	 */
271 	@NonNull
272 	public PackSource getPackSource() {
273 		return packSource;
274 	}
275 
276 	/**
277 	 * Set the source of the pack.
278 	 *
279 	 * @param source
280 	 *            the source of the pack.
281 	 * @return {@code this}
282 	 */
283 	public DfsPackDescription setPackSource(@NonNull PackSource source) {
284 		packSource = source;
285 		return this;
286 	}
287 
288 	/**
289 	 * Get time the pack was created, in milliseconds.
290 	 *
291 	 * @return time the pack was created, in milliseconds.
292 	 */
293 	public long getLastModified() {
294 		return lastModified;
295 	}
296 
297 	/**
298 	 * Set time the pack was created, in milliseconds.
299 	 *
300 	 * @param timeMillis
301 	 *            time the pack was created, in milliseconds. 0 if not known.
302 	 * @return {@code this}
303 	 */
304 	public DfsPackDescription setLastModified(long timeMillis) {
305 		lastModified = timeMillis;
306 		return this;
307 	}
308 
309 	/**
310 	 * Get minUpdateIndex for the reftable, if present.
311 	 *
312 	 * @return minUpdateIndex for the reftable, if present.
313 	 */
314 	public long getMinUpdateIndex() {
315 		return minUpdateIndex;
316 	}
317 
318 	/**
319 	 * Set minUpdateIndex for the reftable.
320 	 *
321 	 * @param min
322 	 *            minUpdateIndex for the reftable.
323 	 * @return {@code this}
324 	 */
325 	public DfsPackDescription setMinUpdateIndex(long min) {
326 		minUpdateIndex = min;
327 		return this;
328 	}
329 
330 	/**
331 	 * Get maxUpdateIndex for the reftable, if present.
332 	 *
333 	 * @return maxUpdateIndex for the reftable, if present.
334 	 */
335 	public long getMaxUpdateIndex() {
336 		return maxUpdateIndex;
337 	}
338 
339 	/**
340 	 * Set maxUpdateIndex for the reftable.
341 	 *
342 	 * @param max
343 	 *            maxUpdateIndex for the reftable.
344 	 * @return {@code this}
345 	 */
346 	public DfsPackDescription setMaxUpdateIndex(long max) {
347 		maxUpdateIndex = max;
348 		return this;
349 	}
350 
351 	/**
352 	 * Set size of the file in bytes.
353 	 *
354 	 * @param ext
355 	 *            the file extension.
356 	 * @param bytes
357 	 *            size of the file in bytes. If 0 the file is not known and will
358 	 *            be determined on first read.
359 	 * @return {@code this}
360 	 */
361 	public DfsPackDescription setFileSize(PackExt ext, long bytes) {
362 		int i = ext.getPosition();
363 		if (i >= sizeMap.length) {
364 			sizeMap = Arrays.copyOf(sizeMap, i + 1);
365 		}
366 		sizeMap[i] = Math.max(0, bytes);
367 		return this;
368 	}
369 
370 	/**
371 	 * Get size of the file, in bytes.
372 	 *
373 	 * @param ext
374 	 *            the file extension.
375 	 * @return size of the file, in bytes. If 0 the file size is not yet known.
376 	 */
377 	public long getFileSize(PackExt ext) {
378 		int i = ext.getPosition();
379 		return i < sizeMap.length ? sizeMap[i] : 0;
380 	}
381 
382 	/**
383 	 * Get blockSize of the file, in bytes.
384 	 *
385 	 * @param ext
386 	 *            the file extension.
387 	 * @return blockSize of the file, in bytes. If 0 the blockSize size is not
388 	 *         yet known and may be discovered when opening the file.
389 	 */
390 	public int getBlockSize(PackExt ext) {
391 		int i = ext.getPosition();
392 		return i < blockSizeMap.length ? blockSizeMap[i] : 0;
393 	}
394 
395 	/**
396 	 * Set blockSize of the file, in bytes.
397 	 *
398 	 * @param ext
399 	 *            the file extension.
400 	 * @param blockSize
401 	 *            blockSize of the file, in bytes. If 0 the blockSize is not
402 	 *            known and will be determined on first read.
403 	 * @return {@code this}
404 	 */
405 	public DfsPackDescription setBlockSize(PackExt ext, int blockSize) {
406 		int i = ext.getPosition();
407 		if (i >= blockSizeMap.length) {
408 			blockSizeMap = Arrays.copyOf(blockSizeMap, i + 1);
409 		}
410 		blockSizeMap[i] = Math.max(0, blockSize);
411 		return this;
412 	}
413 
414 	/**
415 	 * Set estimated size of the .pack file in bytes.
416 	 *
417 	 * @param estimatedPackSize
418 	 *            estimated size of the .pack file in bytes. If 0 the pack file
419 	 *            size is unknown.
420 	 * @return {@code this}
421 	 */
422 	public DfsPackDescription setEstimatedPackSize(long estimatedPackSize) {
423 		this.estimatedPackSize = Math.max(0, estimatedPackSize);
424 		return this;
425 	}
426 
427 	/**
428 	 * Get estimated size of the .pack file in bytes.
429 	 *
430 	 * @return estimated size of the .pack file in bytes. If 0 the pack file
431 	 *         size is unknown.
432 	 */
433 	public long getEstimatedPackSize() {
434 		return estimatedPackSize;
435 	}
436 
437 	/**
438 	 * Get number of objects in the pack.
439 	 *
440 	 * @return number of objects in the pack.
441 	 */
442 	public long getObjectCount() {
443 		return objectCount;
444 	}
445 
446 	/**
447 	 * Set number of objects in the pack.
448 	 *
449 	 * @param cnt
450 	 *            number of objects in the pack.
451 	 * @return {@code this}
452 	 */
453 	public DfsPackDescription setObjectCount(long cnt) {
454 		objectCount = Math.max(0, cnt);
455 		return this;
456 	}
457 
458 	/**
459 	 * Get number of delta compressed objects in the pack.
460 	 *
461 	 * @return number of delta compressed objects in the pack.
462 	 */
463 	public long getDeltaCount() {
464 		return deltaCount;
465 	}
466 
467 	/**
468 	 * Set number of delta compressed objects in the pack.
469 	 *
470 	 * @param cnt
471 	 *            number of delta compressed objects in the pack.
472 	 * @return {@code this}
473 	 */
474 	public DfsPackDescription setDeltaCount(long cnt) {
475 		deltaCount = Math.max(0, cnt);
476 		return this;
477 	}
478 
479 	/**
480 	 * Get statistics from PackWriter, if the pack was built with it.
481 	 *
482 	 * @return statistics from PackWriter, if the pack was built with it.
483 	 *         Generally this is only available for packs created by
484 	 *         DfsGarbageCollector or DfsPackCompactor, and only when the pack
485 	 *         is being committed to the repository.
486 	 */
487 	public PackStatistics getPackStats() {
488 		return packStats;
489 	}
490 
491 	DfsPackDescription setPackStats(PackStatistics stats) {
492 		this.packStats = stats;
493 		setFileSize(PACK, stats.getTotalBytes());
494 		setObjectCount(stats.getTotalObjects());
495 		setDeltaCount(stats.getTotalDeltas());
496 		return this;
497 	}
498 
499 	/**
500 	 * Get stats from the sibling reftable, if created.
501 	 *
502 	 * @return stats from the sibling reftable, if created.
503 	 */
504 	public ReftableWriter.Stats getReftableStats() {
505 		return refStats;
506 	}
507 
508 	void setReftableStats(ReftableWriter.Stats stats) {
509 		this.refStats = stats;
510 		setMinUpdateIndex(stats.minUpdateIndex());
511 		setMaxUpdateIndex(stats.maxUpdateIndex());
512 		setFileSize(REFTABLE, stats.totalBytes());
513 		setBlockSize(REFTABLE, stats.refBlockSize());
514 	}
515 
516 	/**
517 	 * Discard the pack statistics, if it was populated.
518 	 *
519 	 * @return {@code this}
520 	 */
521 	public DfsPackDescription clearPackStats() {
522 		packStats = null;
523 		refStats = null;
524 		return this;
525 	}
526 
527 	/**
528 	 * Get the version of the index file written.
529 	 *
530 	 * @return the version of the index file written.
531 	 */
532 	public int getIndexVersion() {
533 		return indexVersion;
534 	}
535 
536 	/**
537 	 * Set the version of the index file written.
538 	 *
539 	 * @param version
540 	 *            the version of the index file written.
541 	 * @return {@code this}
542 	 */
543 	public DfsPackDescription setIndexVersion(int version) {
544 		indexVersion = version;
545 		return this;
546 	}
547 
548 	/** {@inheritDoc} */
549 	@Override
550 	public int hashCode() {
551 		return packName.hashCode();
552 	}
553 
554 	/** {@inheritDoc} */
555 	@Override
556 	public boolean equals(Object b) {
557 		if (b instanceof DfsPackDescription) {
558 			DfsPackDescription desc = (DfsPackDescription) b;
559 			return packName.equals(desc.packName) &&
560 					getRepositoryDescription().equals(desc.getRepositoryDescription());
561 		}
562 		return false;
563 	}
564 
565 	static boolean isGC(PackSource s) {
566 		switch (s) {
567 		case GC:
568 		case GC_REST:
569 		case GC_TXN:
570 			return true;
571 		default:
572 			return false;
573 		}
574 	}
575 
576 	/** {@inheritDoc} */
577 	@Override
578 	public String toString() {
579 		return getFileName(PackExt.PACK);
580 	}
581 }