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 }