mirror of https://github.com/micromata/borgbackup-butler.git

Kai Reinhard
07.50.2019 219ec32448572da39d629dfcd9c37ec362378ffd
Stores recent gz files in MemoryCache (doesn't improve performance on ssd drives).
2 files added
1 files modified
202 ■■■■■ changed files
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveFilelistCache.java 112 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/memory/MemoryCache.java 60 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/memory/MemoryCacheObject.java 30 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveFilelistCache.java
@@ -1,5 +1,7 @@
package de.micromata.borgbutler.cache;
import de.micromata.borgbutler.cache.memory.MemoryCache;
import de.micromata.borgbutler.cache.memory.MemoryCacheObject;
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.FileSystemFilter;
@@ -9,6 +11,8 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.nustaq.serialization.FSTConfiguration;
import org.nustaq.serialization.FSTObjectInput;
@@ -33,19 +37,19 @@
    private static Logger log = LoggerFactory.getLogger(ArchiveFilelistCache.class);
    private static final String CACHE_ARCHIVE_LISTS_BASENAME = "archive-content-";
    private static final String CACHE_FILE_GZIP_EXTENSION = ".gz";
    private static final int MAX_NUMBER_OF_RECENT_ENTRIES = 2;
    private static final int MAX_SIZE_MEMORY_CACHE = 30 * 1024 * 1024; // 50 MB
    private File cacheDir;
    private int cacheArchiveContentMaxDiscSizeMB;
    private long FILES_EXPIRE_TIME = 7 * 24 * 3660 * 1000; // Expires after 7 days.
    // For avoiding concurrent writing of same files (e. g. after the user has pressed a button twice).
    private Set<File> savingFiles = new HashSet<>();
    private Recents recents = new Recents(MAX_NUMBER_OF_RECENT_ENTRIES);
    private MemoryCache<Archive, RecentEntry> recents = new MemoryCache<>(MAX_SIZE_MEMORY_CACHE);
    final FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();
    ArchiveFilelistCache(File cacheDir, int cacheArchiveContentMaxDiscSizeMB) {
        this.cacheDir = cacheDir;
        this.cacheArchiveContentMaxDiscSizeMB = cacheArchiveContentMaxDiscSizeMB;
        conf.registerClass(BorgFilesystemItem.class);
        conf.registerClass(Integer.class, BorgFilesystemItem.class);
        conf.setShareReferences(false);
    }
@@ -65,18 +69,33 @@
                Collections.sort(filesystemItems); // Sort by path.
            }
            log.info("Saving archive content as file list: " + file.getAbsolutePath());
            boolean ok = false;
            int fileNumber = -1;
            try (FSTObjectOutput outputStream
                         = new FSTObjectOutput(new BufferedOutputStream(new GzipCompressorOutputStream(new FileOutputStream(file))), conf)) {
                outputStream.writeObject(filesystemItems.size(), Integer.class);
                Iterator<BorgFilesystemItem> it = filesystemItems.iterator();
                while (it.hasNext()) {
                    BorgFilesystemItem item = it.next();
                    item.setFileNumber(++fileNumber);
                    outputStream.writeObject(item, BorgFilesystemItem.class);
                }
                outputStream.writeObject("EOF");
                ok = true;
            } catch (IOException ex) {
                log.error("Error while writing file list '" + file.getAbsolutePath() + "': " + ex.getMessage(), ex);
            }
            if (ok) {
                // Storing current read gz archive in memory cache (recents):
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    FileUtils.copyFile(file, baos);
                    recents.add(new RecentEntry(archive, baos.toByteArray()));
                } catch (IOException ex) {
                    log.error("Error while writing gz archive to memory cache: " + ex.getMessage(), ex);
                }
            }
        } finally {
            synchronized (savingFiles) {
                savingFiles.remove(file);
@@ -151,22 +170,26 @@
        } catch (IOException ex) {
            log.error("Can't set lastModifiedTime on file '" + file.getAbsolutePath() + "'. Pruning old cache files may not work.");
        }
        RecentEntry recentEntry = recents.getRecent(archive);
        byte[] bytes = null;
        if (recentEntry != null) {
            bytes = recentEntry.serializedGz;
        } else {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                FileUtils.copyFile(file, baos);
                bytes = baos.toByteArray();
                recents.add(new RecentEntry(archive, bytes));
            } catch (IOException ex) {
                log.error("Error while restoring file: " + file.getAbsolutePath() + ": " + ex.getMessage(), ex);
                return null;
            }
        }
        List<BorgFilesystemItem> list = new ArrayList<>();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
/*        try {
            IOUtils.copy(new BufferedInputStream(new GzipCompressorInputStream(new FileInputStream(file))), out);
        } catch (IOException ex) {
        }*/
        byte[] bytes = out.toByteArray();
        out = null;
        try (FSTObjectInput inputStream = new FSTObjectInput(new BufferedInputStream(new GzipCompressorInputStream(new FileInputStream(file))), conf)) {
        try (FSTObjectInput inputStream = new FSTObjectInput(new BufferedInputStream(new GzipCompressorInputStream(new ByteArrayInputStream(bytes))), conf)) {
            int size = (Integer) inputStream.readObject(Integer.class);
            int fileNumber = -1;
            for (int i = 0; i < size; i++) {
                ++fileNumber;
                BorgFilesystemItem item = (BorgFilesystemItem) inputStream.readObject(BorgFilesystemItem.class);
                item.setFileNumber(fileNumber);
                if (filter == null || filter.matches(item)) {
                    list.add(item);
                    if (filter != null && filter.isFinished()) break;
@@ -175,7 +198,7 @@
        } catch (Exception ex) {
            log.error("Error while reading file list '" + file.getAbsolutePath() + "': " + ex.getMessage(), ex);
        }
        Collections.sort(list); // Sort by path (if archive list order wasn't correct).
        log.info("Loading done.");
        return filter(list, filter);
    }
@@ -283,57 +306,22 @@
        return file.getName().startsWith(CACHE_ARCHIVE_LISTS_BASENAME);
    }
    private class Recents {
        private RecentEntry[] recents;
        private int pos = 0;
        private int size;
    private class RecentEntry extends MemoryCacheObject<Archive> {
        private byte[] serializedGz;
        private Recents(int size) {
            this.size = size;
            recents = new RecentEntry[size];
        @Override
        protected boolean matches(Archive identifier) {
            return StringUtils.equals(this.getIdentifier().getId(), identifier.getId());
        }
        private void add(RecentEntry entry) {
            synchronized (this) {
                log.info("Add recent at position #" + pos + ": " + entry.archive.getName());
                recents[pos++] = entry;
                if (pos >= size) pos = 0;
            }
        @Override
        protected int getSize() {
            return serializedGz != null ? serializedGz.length : 0;
        }
        private RecentEntry getRecent(Archive archive) {
            synchronized (this) {
                for (RecentEntry entry : recents) {
                    if (entry != null && entry.matches(archive)) {
                        return entry;
                    }
                }
            }
            log.info("No recent entry found for archive: " + archive.getName());
            return null;
        }
        private void removeOldestEntry() {
            int oldestEntry = pos + 1 >= size ? 0 : pos + 1;
            recents[oldestEntry] = null;
            log.info("Remove oldest entry #" + oldestEntry + ": " + recents[oldestEntry]);
        }
    }
    private class RecentEntry {
        private Archive archive;
        private List<BorgFilesystemItem> filesystemItems;
        private boolean matches(Archive archive) {
            if (this.archive == null || archive == null) {
                return false;
            }
            return StringUtils.equals(this.archive.getId(), archive.getId());
        }
        private RecentEntry(Archive archive, List<BorgFilesystemItem> filesystemItems) {
            this.archive = archive;
            this.filesystemItems = filesystemItems;
        private RecentEntry(Archive archive, byte[] serializedGz) {
            super(archive);
            this.serializedGz = serializedGz;
        }
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/memory/MemoryCache.java
New file
@@ -0,0 +1,60 @@
package de.micromata.borgbutler.cache.memory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class MemoryCache<I, M extends MemoryCacheObject<I>> {
    private static Logger log = LoggerFactory.getLogger(MemoryCache.class);
    private long maxMemorySize;
    private List<M> recents = new ArrayList<>();
    public MemoryCache(long maxMemorySize) {
        this.maxMemorySize = maxMemorySize;
    }
    public M getRecent(I identifier) {
        synchronized (recents) {
            for (M entry : recents) {
                if (entry._matches(identifier)) {
                    log.debug("Getting recent entry: " + entry.getIdentifier());
                    return entry;
                }
            }
        }
        return null;
    }
    public void add(M newEntry) {
        if (newEntry.getSize() > maxMemorySize) {
            // Object to large for storing in memory.
            return;
        }
        synchronized (recents) {
            recents.add(newEntry);
            log.debug("Add new recent entry: " + newEntry.getIdentifier());
            int size;
            while (true) {
                size = 0;
                MemoryCacheObject<I> oldest = null;
                for (MemoryCacheObject<I> entry : recents) {
                    if (oldest == null || entry.lastAcess < oldest.lastAcess) {
                        oldest = entry;
                    }
                    size += entry.getSize();
                }
                if (oldest == null) {
                    break;
                }
                if (size >= maxMemorySize) {
                    log.debug("Removing oldest recent entry: " + oldest.getIdentifier());
                    recents.remove(oldest);
                } else {
                    break;
                }
            }
        }
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/memory/MemoryCacheObject.java
New file
@@ -0,0 +1,30 @@
package de.micromata.borgbutler.cache.memory;
import lombok.Getter;
public abstract class MemoryCacheObject<I> {
    @Getter
    private I identifier;
    long lastAcess;
    abstract protected boolean matches(I identifier);
    abstract protected int getSize();
    boolean _matches(I identifier) {
        if (this.identifier == null || identifier == null) {
            return false;
        }
        if (matches(identifier)) {
            lastAcess = System.currentTimeMillis();
            return true;
        }
        return false;
    }
    public MemoryCacheObject(I identifier) {
        this.identifier = identifier;
        lastAcess = System.currentTimeMillis();
    }
}