org.apache.jackrabbit.oak.segment.file.GCJournal Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.jackrabbit.oak.segment.file;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.segment.file.tar.GCGeneration.newGCGeneration;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Joiner;
import org.apache.jackrabbit.oak.segment.RecordId;
import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
import org.apache.jackrabbit.oak.segment.spi.persistence.GCJournalFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Persists the repository size and the reclaimed size following a cleanup
* operation in the {@code gc.log} file with the format:
* 'repoSize, reclaimedSize, timestamp, gc generation, gc full generation (since Oak 1.8),
* number of nodes compacted, root id (since Oak 1.8)'.
*/
public class GCJournal {
private static final Logger LOG = LoggerFactory.getLogger(GCJournal.class);
private final GCJournalFile journalFile;
private GCJournalEntry latest;
public GCJournal(@NotNull GCJournalFile journalFile) {
this.journalFile = journalFile;
}
/**
* Persists the repository stats (current size, reclaimed size, gc
* generation, number of compacted nodes) following a cleanup operation for
* a successful compaction. NOOP if the gcGeneration is the same as the one
* persisted previously.
*
* @param reclaimedSize size reclaimed by cleanup
* @param repoSize current repo size
* @param gcGeneration gc generation
* @param nodes number of compacted nodes
* @param root record id of the compacted root node
*/
public synchronized void persist(long reclaimedSize, long repoSize,
@NotNull GCGeneration gcGeneration, long nodes, @NotNull String root
) {
GCJournalEntry current = read();
if (current.getGcGeneration().equals(gcGeneration)) {
// failed compaction, only update the journal if the generation
// increases
return;
}
latest = new GCJournalEntry(repoSize, reclaimedSize,
System.currentTimeMillis(), gcGeneration, nodes, checkNotNull(root));
try {
journalFile.writeLine(latest.toString());
} catch (IOException e) {
LOG.error("Error writing gc journal", e);
}
}
/**
* Returns the latest entry available
*/
public synchronized GCJournalEntry read() {
if (latest == null) {
List all = readLines();
if (all.isEmpty()) {
latest = GCJournalEntry.EMPTY;
} else {
String info = all.get(all.size() - 1);
latest = GCJournalEntry.fromString(info);
}
}
return latest;
}
/**
* Returns all available entries from the journal
*/
public synchronized Collection readAll() {
List all = new ArrayList();
for (String l : readLines()) {
all.add(GCJournalEntry.fromString(l));
}
return all;
}
private List readLines() {
try {
return journalFile.readLines();
} catch (IOException e) {
LOG.error("Error reading gc journal", e);
}
return new ArrayList();
}
public static class GCJournalEntry {
static final GCJournalEntry EMPTY = new GCJournalEntry(
-1, -1, -1, GCGeneration.NULL, -1, RecordId.NULL.toString10());
private final long repoSize;
private final long reclaimedSize;
private final long ts;
@NotNull
private final GCGeneration gcGeneration;
private final long nodes;
@NotNull
private final String root;
public GCJournalEntry(long repoSize, long reclaimedSize, long ts,
@NotNull GCGeneration gcGeneration, long nodes, @NotNull String root
) {
this.repoSize = repoSize;
this.reclaimedSize = reclaimedSize;
this.ts = ts;
this.gcGeneration = gcGeneration;
this.nodes = nodes;
this.root = root;
}
@Override
public String toString() {
return Joiner.on(",").join(
repoSize,
reclaimedSize,
ts,
gcGeneration.getGeneration(),
gcGeneration.getFullGeneration(),
nodes,
root
);
}
static GCJournalEntry fromString(String in) {
String[] items = in.split(",");
int index = 0;
long repoSize = parseLong(items, index++);
long reclaimedSize = parseLong(items, index++);
long ts = parseLong(items, index++);
int generation = parseInt(items, index++);
int fullGeneration;
if (items.length == 7) {
// gc.log from Oak 1.8 onward
fullGeneration = parseInt(items, index++);
} else {
// gc.log from Oak 1.6
fullGeneration = generation;
}
long nodes = parseLong(items, index++);
String root = parseString(items, index);
if (root == null) {
root = RecordId.NULL.toString10();
}
return new GCJournalEntry(repoSize, reclaimedSize, ts,
newGCGeneration(generation, fullGeneration, false), nodes, root);
}
@Nullable
private static String parseString(String[] items, int index) {
if (index >= items.length) {
return null;
}
return items[index];
}
private static long parseLong(String[] items, int index) {
String in = parseString(items, index);
if (in != null) {
try {
return Long.parseLong(in);
} catch (NumberFormatException ex) {
LOG.warn("Unable to parse {} as long value.", in, ex);
}
}
return -1;
}
private static int parseInt(String[] items, int index) {
String in = parseString(items, index);
if (in != null) {
try {
return Integer.parseInt(in);
} catch (NumberFormatException e) {
LOG.warn("Unable to parse {} as an integer value.", in, e);
}
}
return -1;
}
/**
* Returns the repository size
*/
public long getRepoSize() {
return repoSize;
}
/**
* Returns the reclaimed size
*/
public long getReclaimedSize() {
return reclaimedSize;
}
/**
* Returns the timestamp
*/
public long getTs() {
return ts;
}
/**
* Returns the gc generation
*/
@NotNull
public GCGeneration getGcGeneration() {
return gcGeneration;
}
/**
* Returns the number of compacted nodes
*/
public long getNodes() {
return nodes;
}
/**
* Returns the record id of the root created by the compactor
*/
@NotNull
public String getRoot() {
return root;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + gcGeneration.hashCode();
result = prime * result + root.hashCode();
result = prime * result + (int) (nodes ^ (nodes >>> 32));
result = prime * result + (int) (reclaimedSize ^ (reclaimedSize >>> 32));
result = prime * result + (int) (repoSize ^ (repoSize >>> 32));
result = prime * result + (int) (ts ^ (ts >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GCJournalEntry other = (GCJournalEntry) obj;
if (!gcGeneration.equals(other.gcGeneration)) {
return false;
}
if (nodes != other.nodes) {
return false;
}
if (reclaimedSize != other.reclaimedSize) {
return false;
}
if (repoSize != other.repoSize) {
return false;
}
if (ts != other.ts) {
return false;
}
if (!root.equals(other.root)) {
return false;
}
return true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy