/** * Copyright 2013 Benjamin Lerer * * Licensed 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 io.horizondb.db.series; import io.horizondb.db.Configuration; import io.horizondb.db.HorizonDBException; import io.horizondb.db.commitlog.ReplayPosition; import io.horizondb.model.core.DataBlock; import io.horizondb.model.core.Field; import io.horizondb.model.core.ResourceIterator; import io.horizondb.model.core.iterators.BlockIterators; import io.horizondb.model.schema.TimeSeriesDefinition; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.annotation.concurrent.Immutable; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.util.concurrent.ListenableFuture; /** * Container for the time series elements. * * @author Benjamin * */ @Immutable final class TimeSeriesElements { /** * The database configuration */ private final Configuration configuration; /** * The time series definition. */ private final TimeSeriesDefinition definition; /** * The elements. */ private final List<TimeSeriesElement> elements; /** * The amount of memory used by the elements. */ private final int memoryUsage; public TimeSeriesElements(Configuration configuration, TimeSeriesDefinition definition, TimeSeriesElement element) { this(configuration, definition, Collections.singletonList(element)); } private TimeSeriesElements(Configuration configuration, TimeSeriesDefinition definition, List<TimeSeriesElement> elements) { this.configuration = configuration; this.definition = definition; this.elements = elements; this.memoryUsage = computeMemoryUsage(configuration, elements); } /** * Returns the <code>TimeSeriesFile</code>. * * @return the <code>TimeSeriesFile</code>. */ public TimeSeriesFile getFile() { return (TimeSeriesFile) this.elements.get(0); } /** * Returns the memory usage. * * @return the memory usage. */ public int getMemoryUsage() { return this.memoryUsage; } /** * Returns the last of the elements. * * @return the last of the elements. */ public TimeSeriesElement getLast() { return this.elements.get(this.elements.size() - 1); } /** * Returns the last <code>MemTimeSeries</code> of the elements or null if there are no <code>MemTimeSeries</code>. * * @return the last <code>MemTimeSeries</code> of the elements or null if there are no <code>MemTimeSeries</code>. */ public MemTimeSeries getLastMemTimeSeries() { if (hasMemTimeSeries()) { return (MemTimeSeries) getLast(); } return null; } /** * Returns the first <code>MemTimeSeries</code> of the elements or null if there are no <code>MemTimeSeries</code>. * * @return the first <code>MemTimeSeries</code> of the elements or null if there are no <code>MemTimeSeries</code>. */ public MemTimeSeries getFirstMemTimeSeries() { if (hasMemTimeSeries()) { return (MemTimeSeries) this.elements.get(1); } return null; } /** * Returns a new iterator over all the blocks of those elements. * * @param rangeSet the time range for which the blocks must be returned * @return a new iterator that can be used to read all the data of those elements. * @throws IOException if an I/O problem occurs. */ public ResourceIterator<DataBlock> iterator() throws IOException { List<ResourceIterator<DataBlock>> iterators = new ArrayList<>(); for (int i = 0, m = this.elements.size(); i < m; i++) { TimeSeriesElement element = this.elements.get(i); iterators.add(element.iterator()); } return BlockIterators.concat(iterators); } /** * Returns a new iterator over the blocks of those element containing the specified time range. * * @param rangeSet the time range for which the blocks must be returned * @return a new iterator that can be used to read the data of those elements. * @throws IOException if an I/O problem occurs. */ public ResourceIterator<DataBlock> iterator(RangeSet<Field> rangeSet) throws IOException { List<ResourceIterator<DataBlock>> iterators = new ArrayList<>(); for (int i = 0, m = this.elements.size(); i < m; i++) { TimeSeriesElement element = this.elements.get(i); iterators.add(element.iterator(rangeSet)); } return BlockIterators.concat(iterators); } public TimeSeriesElements write(SlabAllocator allocator, DataBlock block, ListenableFuture<ReplayPosition> future) throws IOException, HorizonDBException { if (!hasMemTimeSeries()) { MemTimeSeries memSeries = newMemTimeSeries(); memSeries = memSeries.write(allocator, block, future); return newTimeSeriesElements(Arrays.asList(getLast(), memSeries)); } MemTimeSeries memSeries = getLastMemTimeSeries(); List<TimeSeriesElement> newElements = new ArrayList<>(this.elements.subList(0, this.elements.size() - 1)); if (memSeries.isFull()) { newElements.add(memSeries); memSeries = newMemTimeSeries(); } memSeries = memSeries.write(allocator, block, future); newElements.add(memSeries); return newTimeSeriesElements(newElements); } /** * Flushes to the disk all <code>MemTimeSeries</code>. * * @throws IOException if an I/O problem occurs while flushing the data to the disk. * @throws InterruptedException if the thread has been interrupted. */ public TimeSeriesElements forceFlush() throws IOException, InterruptedException { if (!hasMemTimeSeries()) { return this; } return flush(getMemTimeSeriesList()); } /** * Flushes to the disk the <code>MemTimeSeries</code> that are full. * * @throws IOException if an I/O problem occurs while flushing the data to the disk. * @throws InterruptedException if the thread has been interrupted. */ public TimeSeriesElements flush() throws IOException, InterruptedException { if (!hasMemTimeSeries()) { return this; } return flush(getFullMemTimeSeriesList()); } /** * Returns the ID of the first segment that contains non persisted data or <code>null</code> if all the data have been * flushed to disk. * * @return the ID of the first segment that contains non persisted data or <code>null</code> if all the data have been * flushed to disk. */ public Long getFirstSegmentContainingNonPersistedData() { MemTimeSeries first = getFirstMemTimeSeries(); if (first == null) { return null; } return Long.valueOf(first.getFirstSegmentId()); } /** * Flushes to the disk the specified <code>MemTimeSeries</code>. * * @param elementList the elements to flush * @throws IOException if an I/O problem occurs while flushing the data to the disk. * @throws InterruptedException if the thread has been interrupted. */ private TimeSeriesElements flush(List<TimeSeriesElement> elementList) throws IOException, InterruptedException { TimeSeriesFile newFile = getFile().append(elementList); List<TimeSeriesElement> newElements = new ArrayList<>(); newElements.add(newFile); if (elementList.size() != this.elements.size() - 1) { newElements.add(getLast()); } return newTimeSeriesElements(newElements); } /** * Returns all the <code>MemTimeSeries</code>. * * @return all the <code>MemTimeSeries</code>. */ private List<TimeSeriesElement> getMemTimeSeriesList() { return this.elements.subList(1, this.elements.size()); } /** * Returns all the full <code>MemTimeSeries</code>. * * @return all the full <code>MemTimeSeries</code>. * @throws IOException if an I/O problem occurs */ private List<TimeSeriesElement> getFullMemTimeSeriesList() throws IOException { if (getLastMemTimeSeries().isFull()) { return this.elements.subList(1, this.elements.size()); } return this.elements.subList(1, this.elements.size() - 1); } /** * Returns <code>true</code> if the elements contains some <code>MemTimeSeries</code>, <code>false</code> otherwise. * * @return <code>true</code> if the elements contains some <code>MemTimeSeries</code>, <code>false</code> otherwise. */ private boolean hasMemTimeSeries() { return this.elements.size() > 1; } /** * Creates a new <code>TimeSeriesElements</code> containing the specified elements. * * @param newElements the elements of the new <code>TimeSeriesElements</code>. * @return a new <code>TimeSeriesElements</code> containing the specified elements. */ private TimeSeriesElements newTimeSeriesElements(List<TimeSeriesElement> newElements) { return new TimeSeriesElements(this.configuration, this.definition, newElements); } /** * Computes the amount of memory used by the specified time series elements * * @param elements the time series elements * @return the amount of memory used by the specified time series elements */ private static int computeMemoryUsage(Configuration configuration, List<TimeSeriesElement> elements) { int numberOfRegions = getNumberOfRegionsUsed(elements); return numberOfRegions * configuration.getMemTimeSeriesSize(); } /** * @param elements * @return */ private static int getNumberOfRegionsUsed(List<TimeSeriesElement> elements) { if (elements.size() == 1) { return 0; } Range<Integer> regionUsage = getRegionUsage(elements.get(1)); for (int i = 2, m = elements.size(); i < m; i++) { regionUsage = getRegionUsage(elements.get(i)).span(regionUsage); } return (regionUsage.upperEndpoint().intValue() - regionUsage.lowerEndpoint().intValue()) + 1; } /** * * * @return */ private MemTimeSeries newMemTimeSeries() { return new MemTimeSeries(this.configuration, this.definition); } /** * @param elements * @return */ private static Range<Integer> getRegionUsage(TimeSeriesElement element) { return ((MemTimeSeries) element).getRegionUsage(); } }