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
51 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
52 import org.eclipse.jgit.internal.storage.pack.PackExt;
53 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
54 import org.eclipse.jgit.storage.pack.PackStatistics;
55
56 /**
57 * Description of a DFS stored pack/index file.
58 * <p>
59 * Implementors may extend this class and add additional data members.
60 * <p>
61 * Instances of this class are cached with the DfsPackFile, and should not be
62 * modified once initialized and presented to the JGit DFS library.
63 */
64 public class DfsPackDescription implements Comparable<DfsPackDescription> {
65 private final DfsRepositoryDescription repoDesc;
66 private final String packName;
67 private PackSource packSource;
68 private long lastModified;
69 private long[] sizeMap;
70 private int[] blockSizeMap;
71 private long objectCount;
72 private long deltaCount;
73 private long minUpdateIndex;
74 private long maxUpdateIndex;
75
76 private PackStatistics packStats;
77 private ReftableWriter.Stats refStats;
78 private int extensions;
79 private int indexVersion;
80 private long estimatedPackSize;
81
82 /**
83 * Initialize a description by pack name and repository.
84 * <p>
85 * The corresponding index file is assumed to exist. If this is not true
86 * implementors must extend the class and override
87 * {@link #getFileName(PackExt)}.
88 * <p>
89 * Callers should also try to fill in other fields if they are reasonably
90 * free to access at the time this instance is being initialized.
91 *
92 * @param name
93 * name of the pack file. Must end with ".pack".
94 * @param repoDesc
95 * description of the repo containing the pack file.
96 */
97 public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) {
98 this.repoDesc = repoDesc;
99 int dot = name.lastIndexOf('.');
100 this.packName = (dot < 0) ? name : name.substring(0, dot);
101
102 int extCnt = PackExt.values().length;
103 sizeMap = new long[extCnt];
104 blockSizeMap = new int[extCnt];
105 }
106
107 /**
108 * Get description of the repository.
109 *
110 * @return description of the repository.
111 */
112 public DfsRepositoryDescription getRepositoryDescription() {
113 return repoDesc;
114 }
115
116 /**
117 * Adds the pack file extension to the known list.
118 *
119 * @param ext
120 * the file extension
121 */
122 public void addFileExt(PackExt ext) {
123 extensions |= ext.getBit();
124 }
125
126 /**
127 * Whether the pack file extension is known to exist.
128 *
129 * @param ext
130 * the file extension
131 * @return whether the pack file extension is known to exist.
132 */
133 public boolean hasFileExt(PackExt ext) {
134 return (extensions & ext.getBit()) != 0;
135 }
136
137 /**
138 * Get file name
139 *
140 * @param ext
141 * the file extension
142 * @return name of the file.
143 */
144 public String getFileName(PackExt ext) {
145 return packName + '.' + ext.getExtension();
146 }
147
148 /**
149 * Get cache key for use by the block cache.
150 *
151 * @param ext
152 * the file extension.
153 * @return cache key for use by the block cache.
154 */
155 public DfsStreamKey getStreamKey(PackExt ext) {
156 return DfsStreamKey.of(getRepositoryDescription(), getFileName(ext),
157 ext);
158 }
159
160 /**
161 * Get the source of the pack.
162 *
163 * @return the source of the pack.
164 */
165 public PackSource getPackSource() {
166 return packSource;
167 }
168
169 /**
170 * Set the source of the pack.
171 *
172 * @param source
173 * the source of the pack.
174 * @return {@code this}
175 */
176 public DfsPackDescription setPackSource(PackSource source) {
177 packSource = source;
178 return this;
179 }
180
181 /**
182 * Get time the pack was created, in milliseconds.
183 *
184 * @return time the pack was created, in milliseconds.
185 */
186 public long getLastModified() {
187 return lastModified;
188 }
189
190 /**
191 * Set time the pack was created, in milliseconds.
192 *
193 * @param timeMillis
194 * time the pack was created, in milliseconds. 0 if not known.
195 * @return {@code this}
196 */
197 public DfsPackDescription setLastModified(long timeMillis) {
198 lastModified = timeMillis;
199 return this;
200 }
201
202 /**
203 * Get minUpdateIndex for the reftable, if present.
204 *
205 * @return minUpdateIndex for the reftable, if present.
206 */
207 public long getMinUpdateIndex() {
208 return minUpdateIndex;
209 }
210
211 /**
212 * Set minUpdateIndex for the reftable.
213 *
214 * @param min
215 * minUpdateIndex for the reftable.
216 * @return {@code this}
217 */
218 public DfsPackDescription setMinUpdateIndex(long min) {
219 minUpdateIndex = min;
220 return this;
221 }
222
223 /**
224 * Get maxUpdateIndex for the reftable, if present.
225 *
226 * @return maxUpdateIndex for the reftable, if present.
227 */
228 public long getMaxUpdateIndex() {
229 return maxUpdateIndex;
230 }
231
232 /**
233 * Set maxUpdateIndex for the reftable.
234 *
235 * @param max
236 * maxUpdateIndex for the reftable.
237 * @return {@code this}
238 */
239 public DfsPackDescription setMaxUpdateIndex(long max) {
240 maxUpdateIndex = max;
241 return this;
242 }
243
244 /**
245 * Set size of the file in bytes.
246 *
247 * @param ext
248 * the file extension.
249 * @param bytes
250 * size of the file in bytes. If 0 the file is not known and will
251 * be determined on first read.
252 * @return {@code this}
253 */
254 public DfsPackDescription setFileSize(PackExt ext, long bytes) {
255 int i = ext.getPosition();
256 if (i >= sizeMap.length) {
257 sizeMap = Arrays.copyOf(sizeMap, i + 1);
258 }
259 sizeMap[i] = Math.max(0, bytes);
260 return this;
261 }
262
263 /**
264 * Get size of the file, in bytes.
265 *
266 * @param ext
267 * the file extension.
268 * @return size of the file, in bytes. If 0 the file size is not yet known.
269 */
270 public long getFileSize(PackExt ext) {
271 int i = ext.getPosition();
272 return i < sizeMap.length ? sizeMap[i] : 0;
273 }
274
275 /**
276 * Get blockSize of the file, in bytes.
277 *
278 * @param ext
279 * the file extension.
280 * @return blockSize of the file, in bytes. If 0 the blockSize size is not
281 * yet known and may be discovered when opening the file.
282 */
283 public int getBlockSize(PackExt ext) {
284 int i = ext.getPosition();
285 return i < blockSizeMap.length ? blockSizeMap[i] : 0;
286 }
287
288 /**
289 * Set blockSize of the file, in bytes.
290 *
291 * @param ext
292 * the file extension.
293 * @param blockSize
294 * blockSize of the file, in bytes. If 0 the blockSize is not
295 * known and will be determined on first read.
296 * @return {@code this}
297 */
298 public DfsPackDescription setBlockSize(PackExt ext, int blockSize) {
299 int i = ext.getPosition();
300 if (i >= blockSizeMap.length) {
301 blockSizeMap = Arrays.copyOf(blockSizeMap, i + 1);
302 }
303 blockSizeMap[i] = Math.max(0, blockSize);
304 return this;
305 }
306
307 /**
308 * Set estimated size of the .pack file in bytes.
309 *
310 * @param estimatedPackSize
311 * estimated size of the .pack file in bytes. If 0 the pack file
312 * size is unknown.
313 * @return {@code this}
314 */
315 public DfsPackDescription setEstimatedPackSize(long estimatedPackSize) {
316 this.estimatedPackSize = Math.max(0, estimatedPackSize);
317 return this;
318 }
319
320 /**
321 * Get estimated size of the .pack file in bytes.
322 *
323 * @return estimated size of the .pack file in bytes. If 0 the pack file
324 * size is unknown.
325 */
326 public long getEstimatedPackSize() {
327 return estimatedPackSize;
328 }
329
330 /**
331 * Get number of objects in the pack.
332 *
333 * @return number of objects in the pack.
334 */
335 public long getObjectCount() {
336 return objectCount;
337 }
338
339 /**
340 * Set number of objects in the pack.
341 *
342 * @param cnt
343 * number of objects in the pack.
344 * @return {@code this}
345 */
346 public DfsPackDescription setObjectCount(long cnt) {
347 objectCount = Math.max(0, cnt);
348 return this;
349 }
350
351 /**
352 * Get number of delta compressed objects in the pack.
353 *
354 * @return number of delta compressed objects in the pack.
355 */
356 public long getDeltaCount() {
357 return deltaCount;
358 }
359
360 /**
361 * Set number of delta compressed objects in the pack.
362 *
363 * @param cnt
364 * number of delta compressed objects in the pack.
365 * @return {@code this}
366 */
367 public DfsPackDescription setDeltaCount(long cnt) {
368 deltaCount = Math.max(0, cnt);
369 return this;
370 }
371
372 /**
373 * Get statistics from PackWriter, if the pack was built with it.
374 *
375 * @return statistics from PackWriter, if the pack was built with it.
376 * Generally this is only available for packs created by
377 * DfsGarbageCollector or DfsPackCompactor, and only when the pack
378 * is being committed to the repository.
379 */
380 public PackStatistics getPackStats() {
381 return packStats;
382 }
383
384 DfsPackDescription setPackStats(PackStatistics stats) {
385 this.packStats = stats;
386 setFileSize(PACK, stats.getTotalBytes());
387 setObjectCount(stats.getTotalObjects());
388 setDeltaCount(stats.getTotalDeltas());
389 return this;
390 }
391
392 /**
393 * Get stats from the sibling reftable, if created.
394 *
395 * @return stats from the sibling reftable, if created.
396 */
397 public ReftableWriter.Stats getReftableStats() {
398 return refStats;
399 }
400
401 void setReftableStats(ReftableWriter.Stats stats) {
402 this.refStats = stats;
403 setMinUpdateIndex(stats.minUpdateIndex());
404 setMaxUpdateIndex(stats.maxUpdateIndex());
405 setFileSize(REFTABLE, stats.totalBytes());
406 setBlockSize(REFTABLE, stats.refBlockSize());
407 }
408
409 /**
410 * Discard the pack statistics, if it was populated.
411 *
412 * @return {@code this}
413 */
414 public DfsPackDescription clearPackStats() {
415 packStats = null;
416 refStats = null;
417 return this;
418 }
419
420 /**
421 * Get the version of the index file written.
422 *
423 * @return the version of the index file written.
424 */
425 public int getIndexVersion() {
426 return indexVersion;
427 }
428
429 /**
430 * Set the version of the index file written.
431 *
432 * @param version
433 * the version of the index file written.
434 * @return {@code this}
435 */
436 public DfsPackDescription setIndexVersion(int version) {
437 indexVersion = version;
438 return this;
439 }
440
441 /** {@inheritDoc} */
442 @Override
443 public int hashCode() {
444 return packName.hashCode();
445 }
446
447 /** {@inheritDoc} */
448 @Override
449 public boolean equals(Object b) {
450 if (b instanceof DfsPackDescription) {
451 DfsPackDescription desc = (DfsPackDescription) b;
452 return packName.equals(desc.packName) &&
453 getRepositoryDescription().equals(desc.getRepositoryDescription());
454 }
455 return false;
456 }
457
458 /**
459 * {@inheritDoc}
460 * <p>
461 * Sort packs according to the optimal lookup ordering.
462 * <p>
463 * This method tries to position packs in the order readers should examine
464 * them when looking for objects by SHA-1. The default tries to sort packs
465 * with more recent modification dates before older packs, and packs with
466 * fewer objects before packs with more objects.
467 */
468 @Override
469 public int compareTo(DfsPackDescription b) {
470 // Cluster by PackSource, pushing UNREACHABLE_GARBAGE to the end.
471 PackSource as = getPackSource();
472 PackSource bs = b.getPackSource();
473 if (as != null && bs != null) {
474 int cmp = as.category - bs.category;
475 if (cmp != 0)
476 return cmp;
477 }
478
479 // Tie break GC type packs by smallest first. There should be at most
480 // one of each source, but when multiple exist concurrent GCs may have
481 // run. Preferring the smaller file selects higher quality delta
482 // compression, placing less demand on the DfsBlockCache.
483 if (as != null && as == bs && isGC(as)) {
484 int cmp = Long.signum(getFileSize(PACK) - b.getFileSize(PACK));
485 if (cmp != 0) {
486 return cmp;
487 }
488 }
489
490 // Newer packs should sort first.
491 int cmp = Long.signum(b.getLastModified() - getLastModified());
492 if (cmp != 0)
493 return cmp;
494
495 // Break ties on smaller index. Readers may get lucky and find
496 // the object they care about in the smaller index. This also pushes
497 // big historical packs to the end of the list, due to more objects.
498 return Long.signum(getObjectCount() - b.getObjectCount());
499 }
500
501 static boolean isGC(PackSource s) {
502 switch (s) {
503 case GC:
504 case GC_REST:
505 case GC_TXN:
506 return true;
507 default:
508 return false;
509 }
510 }
511
512 /** {@inheritDoc} */
513 @Override
514 public String toString() {
515 return getFileName(PackExt.PACK);
516 }
517 }