/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.consolidate;
import com.github.geophile.erdo.map.SealedMap;
import com.github.geophile.erdo.map.emptymap.EmptyMap;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.github.geophile.erdo.consolidate.Consolidation.Element;
import static com.github.geophile.erdo.util.Math.log2;
// Tracks a set of Consolidation.Elements as they move from being available for consolidation,
// to being consolidated, and then to obsolete. All Elements managed by one tracker are alike
// in their durability status
abstract class ConsolidationElementTracker
{
// ConsolidationElementTracker interface
public abstract String type();
public List<Element> elements()
{
assert Thread.holdsLock(container);
List<Element> elements = new ArrayList<>(nonEmptyAvailableForConsolidation);
elements.addAll(beingConsolidated);
return elements;
}
public abstract void add(Element element);
public abstract void removeElementBeingConsolidated(Element element);
public abstract void beingConsolidated(List<Element> elements);
public abstract List<Element> availableForConsolidation();
public long totalBytes()
{
assert Thread.holdsLock(container);
long size = 0;
for (Element element : nonEmptyAvailableForConsolidation) {
size += element.sizeBytes();
}
for (Element element : beingConsolidated) {
size += element.sizeBytes();
}
return size;
}
public int totalRecords()
{
assert Thread.holdsLock(container);
int count = 0;
for (Element element : nonEmptyAvailableForConsolidation) {
count += element.count();
}
for (Element element : beingConsolidated) {
count += element.count();
}
return count;
}
public double complexity()
{
assert Thread.holdsLock(container);
double sumLog = 0;
for (Element element : nonEmptyAvailableForConsolidation) {
long n = element.count();
if (n > 0) {
sumLog += log2(n);
}
}
for (Element element : beingConsolidated) {
long n = element.count();
if (n > 0) {
sumLog += log2(n);
}
}
return sumLog;
}
public void consolidationFailed(List<Element> elements)
{
assert Thread.holdsLock(container);
for (Element element : elements) {
if (beingConsolidated.remove(element)) {
LOG.log(Level.WARNING, "Moving {0} back to durable due to failed consolidation", element);
add(element);
}
}
}
public void describe(Logger log, Level level, String label)
{
describe(log, level, label, nonEmptyAvailableForConsolidation, "non-empty available");
describe(log, level, label, beingConsolidated, "beingConsolidated");
}
public static ConsolidationElementTracker durable(Consolidation.Container container)
{
return new DurableConsolidationElementTracker(container);
}
public static ConsolidationElementTracker nonDurable(Consolidation.Container container)
{
return new NonDurableConsolidationElementTracker(container);
}
// For use by subclasses
protected ConsolidationElementTracker(Consolidation.Container container)
{
this.container = container;
}
protected void describe(Logger log,
Level level,
String label,
Set<Element> elements,
String subLabel)
{
long recordCount = 0;
for (Element element : elements) {
recordCount += element.count();
}
log.log(level,
"{0} {1} {2}: {3}/{4}",
new Object[]{
label,
type(),
subLabel,
elements.size(),
recordCount});
}
// For use by this class
private String describeDistribution(Collection<Element> elements)
{
StringBuilder buffer = new StringBuilder();
Map<Long, Integer> counts = new TreeMap<>();
for (Element element : elements) {
Integer frequency = counts.get(element.count());
if (frequency == null) {
frequency = 0;
}
frequency = frequency + 1;
counts.put(element.count(), frequency);
}
for (Map.Entry<Long, Integer> entry : counts.entrySet()) {
if (buffer.length() > 0) {
buffer.append(", ");
}
buffer.append(entry.getValue());
buffer.append(" x ");
buffer.append(entry.getKey());
}
return buffer.toString();
}
// Class state
private static final Logger LOG = Logger.getLogger(ConsolidationElementTracker.class.getName());
// Object state
protected final Consolidation.Container container;
protected final Set<Element> nonEmptyAvailableForConsolidation = new HashSet<>();
protected final Set<Element> beingConsolidated = new HashSet<>();
// Inner classes
public static class DurableConsolidationElementTracker extends ConsolidationElementTracker
{
@Override
public String type()
{
return "durable";
}
@Override
public void add(Element element)
{
// Empty element should not go to emptyAvailableForConsolidation, due to bug 4.
assert element.durable();
assert Thread.holdsLock(container);
nonEmptyAvailableForConsolidation.add(element);
}
@Override
public void removeElementBeingConsolidated(Element element)
{
assert Thread.holdsLock(container);
boolean removed = beingConsolidated.remove(element);
assert removed : element;
}
@Override
public void beingConsolidated(List<Element> elements)
{
assert Thread.holdsLock(container);
for (Element element : elements) {
boolean removed = nonEmptyAvailableForConsolidation.remove(element);
assert removed;
boolean added = beingConsolidated.add(element);
assert added;
}
}
@Override
public List<Element> availableForConsolidation()
{
assert Thread.holdsLock(container);
// Copy so that the caller has a static view
ArrayList<Element> available = new ArrayList<>(nonEmptyAvailableForConsolidation);
if (LOG.isLoggable(Level.FINER) && !available.isEmpty()) {
LOG.log(Level.FINER, "available for consolidation: {0}", available);
}
return available;
}
public DurableConsolidationElementTracker(Consolidation.Container container)
{
super(container);
}
}
public static class NonDurableConsolidationElementTracker extends ConsolidationElementTracker
{
@Override
public String type()
{
return "nonDurable";
}
@Override
public List<Element> elements()
{
List<Element> elements = super.elements();
if (!emptyAvailableForConsolidation.timestamps().empty()) {
// Copy so that the caller has a static view
elements.add(emptyAvailableForConsolidation.copy());
}
return elements;
}
@Override
public void add(Element element)
{
assert !element.durable();
assert Thread.holdsLock(container);
if (element.count() == 0) {
emptyAvailableForConsolidation.addEmpty((SealedMap) element);
} else {
nonEmptyAvailableForConsolidation.add(element);
}
}
@Override
public void removeElementBeingConsolidated(Element element)
{
assert Thread.holdsLock(container);
boolean removed = beingConsolidated.remove(element);
assert removed : element;
}
@Override
public void beingConsolidated(List<Element> elements)
{
assert Thread.holdsLock(container);
for (Element element : elements) {
if (element.count() == 0) {
emptyAvailableForConsolidation.removeEmpty((SealedMap) element);
} else {
boolean removed = nonEmptyAvailableForConsolidation.remove(element);
assert removed;
}
boolean added = beingConsolidated.add(element);
assert added;
}
}
@Override
public List<Element> availableForConsolidation()
{
assert Thread.holdsLock(container);
// Copy so that the caller has a static view
ArrayList<Element> available = new ArrayList<>(nonEmptyAvailableForConsolidation);
if (!emptyAvailableForConsolidation.timestamps().empty()) {
available.add(emptyAvailableForConsolidation.copy());
}
if (LOG.isLoggable(Level.FINER) && !available.isEmpty()) {
LOG.log(Level.FINER, "available for consolidation: {0}", available);
}
return available;
}
@Override
public void describe(Logger log, Level level, String label)
{
super.describe(log, level, label);
describe(log, level, label, Collections.<Element>singleton(emptyAvailableForConsolidation), "empty available");
}
public NonDurableConsolidationElementTracker(Consolidation.Container container)
{
super(container);
this.emptyAvailableForConsolidation = new EmptyMap(container.factory());
}
private final EmptyMap emptyAvailableForConsolidation;
}
}