/*
* Copyright 2010 NCHOVY
*
* 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 org.krakenapps.rrd.impl;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.krakenapps.rrd.ArchiveConfig;
import org.krakenapps.rrd.ConsolidateFunc;
import org.krakenapps.rrd.DataSourceConfig;
import org.krakenapps.rrd.FetchResult;
import org.krakenapps.rrd.RrdConfig;
import org.krakenapps.rrd.io.PersistentLayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RrdRaw {
private Logger logger = LoggerFactory.getLogger(RrdRaw.class);
private long step;
private long lastUpdate;
private int rowCapacity = 0;
private List<Archive> archives = new ArrayList<Archive>();
private List<DataSource> dataSources = new ArrayList<DataSource>();
public RrdRaw(RrdConfig config) {
this.step = config.getStep();
this.lastUpdate = config.getStartTime() / 1000L;
if (config.getArchives().isEmpty())
throw new IllegalArgumentException("no archives in config");
for (ArchiveConfig rraConfig : config.getArchives()) {
archives.add(new Archive(this, rraConfig));
rowCapacity += rraConfig.getRowCapacity();
}
if (config.getDataSources().isEmpty())
throw new IllegalArgumentException("no data sources in config");
for (DataSourceConfig dsConfig : config.getDataSources())
dataSources.add(DataSource.createInstance(this, dsConfig, lastUpdate));
}
public RrdRaw(PersistentLayer persLayer) {
try {
persLayer.open();
int version = persLayer.readByte();
if (version != 1)
throw new IllegalStateException();
step = persLayer.readLong();
lastUpdate = persLayer.readLong();
int archiveSize = persLayer.readInt();
if (archiveSize == 0)
throw new IllegalArgumentException("no archives in config");
for (int i = 0; i < archiveSize; i++) {
Archive archive = new Archive(this, persLayer);
archives.add(archive);
rowCapacity += archive.getRowCapacity();
}
int dataSourceSize = persLayer.readInt();
if (dataSourceSize == 0)
throw new IllegalArgumentException("no data sources in config");
for (int i = 0; i < dataSourceSize; i++)
dataSources.add(DataSource.createFromPersLayer(this, persLayer));
} catch (Exception e) {
logger.error("kraken rrd: rrdraw read failed", e);
throw new IllegalStateException(e);
} finally {
try {
persLayer.close();
} catch (IOException e) {
}
}
}
public List<ArchiveConfig> getArchiveConfigs() {
List<ArchiveConfig> result = new ArrayList<ArchiveConfig>();
for (Archive ar : archives)
result.add(RrdUtil.archiveToConfig(ar));
return result;
}
public List<DataSourceConfig> getDataSourceConfigs() {
List<DataSourceConfig> result = new ArrayList<DataSourceConfig>();
for (DataSource ds : dataSources)
result.add(RrdUtil.dataSourceToConfig(ds));
return result;
}
public void addDataSource(DataSourceConfig dsConfig) {
for (DataSource ds : dataSources) {
if (ds.getName().equals(dsConfig.getName()))
throw new IllegalArgumentException("duplicate name");
}
DataSource ds = DataSource.createInstance(this, dsConfig, lastUpdate);
dataSources.add(ds);
}
public void removeDataSource(String name) {
Iterator<DataSource> it = dataSources.iterator();
while (it.hasNext()) {
if (it.next().getName().equals(name)) {
it.remove();
return;
}
}
throw new IllegalArgumentException("datasource not found");
}
public void update(Date time, Map<String, Double> values) {
Double[] v = new Double[dataSources.size()];
int i = 0;
for (DataSource ds : dataSources) {
Double value = values.get(ds.getName());
v[i++] = (value != null) ? value : Double.NaN;
}
update(time, v);
}
public void update(Date time, Double[] values) {
long lTime = time.getTime() / 1000L;
if (values.length != dataSources.size())
throw new IndexOutOfBoundsException("the size of values is not match with the number of data sources");
if (lTime < lastUpdate)
throw new IllegalArgumentException(String.format("time %1$d is not after lastUpdate %2$d", lTime, lastUpdate));
processExpiredCdp(lTime);
int i = 0;
for (DataSource ds : dataSources)
ds.update(lTime, lastUpdate, values[i++]);
for (Archive ar : archives)
ar.onRrdUpdated(lTime);
this.lastUpdate = lTime;
}
public void processExpiredCdp(long lTime) {
for (Archive ar : archives)
ar.processExpiredCdp(lTime);
}
public FetchResult fetch(ConsolidateFunc f, Date start, Date end, long resolution) {
long lStart = start.getTime() / 1000L;
long lEnd = end.getTime() / 1000L;
Archive archive = selectArchive(f, resolution);
assert (archive != null);
return new FetchResult(this, archive, lStart, lEnd);
}
// find longest step in archives of which step is smaller than required resolution.
// TODO: this implementation is different from original 'rrdtool fetch'.
private Archive selectArchive(ConsolidateFunc f, long resolution) {
Archive ret = null;
long r = 0;
for (Archive ar : archives) {
long archiveResolution = step * ar.getPdpPerRow();
if (r < archiveResolution && archiveResolution <= resolution) {
ret = ar;
r = archiveResolution;
}
}
return (ret != null) ? ret : archives.get(0);
}
public long getStep() {
return step;
}
public void updateArchives(long time, long lastUpdate, DataSource dataSource, double checkpointValue, double newValue) {
for (Archive ar : archives)
ar.update(time, lastUpdate, dataSource, checkpointValue);
}
public Date getLastUpdateDate() {
return new Date(lastUpdate * 1000L);
}
public void dump(OutputStream os) {
PrintWriter writer = new PrintWriter(os);
writer.printf("step: %d\n", this.step);
writer.printf("lastUpdate: %d\n", this.lastUpdate);
writer.flush();
writer.printf("\n= datasources =\n");
for (DataSource ds : dataSources) {
ds.dump(writer);
writer.flush();
}
writer.printf("\n= archives =\n");
for (Archive ar : archives) {
ar.dump(writer);
writer.flush();
}
}
public int length() {
int len = 17;
len += 4;
for (DataSource ds : dataSources)
len += ds.length();
len += 4;
for (Archive ar : archives)
len += ar.length();
return len;
}
public void write(PersistentLayer persLayer) {
try {
persLayer.open(false);
persLayer.writeByte(1); // version
persLayer.writeLong(this.step);
persLayer.writeLong(this.lastUpdate);
persLayer.writeInt(archives.size());
for (Archive ar : archives)
ar.writeToPersLayer(persLayer);
persLayer.writeInt(dataSources.size());
for (DataSource ds : dataSources)
ds.writeToPersLayer(persLayer);
} catch (IOException e) {
logger.error("kraken rrd: rrdraw write failed", e);
} finally {
try {
persLayer.close();
} catch (IOException e) {
}
}
}
public int getRowCapacity() {
return rowCapacity;
}
public List<Archive> getArchives() {
return archives;
}
public List<DataSource> getDataSources() {
return dataSources;
}
@Override
public boolean equals(Object obj) {
RrdRaw rhs = (RrdRaw) obj;
if (this.step != rhs.step)
return false;
if (this.lastUpdate != rhs.lastUpdate)
return false;
if (this.dataSources.size() != rhs.dataSources.size())
return false;
if (this.archives.size() != rhs.archives.size())
return false;
int dataSourceSize = this.dataSources.size();
for (int i = 0; i < dataSourceSize; ++i) {
if (!this.dataSources.get(i).equals(rhs.dataSources.get(i)))
return false;
}
int archiveSize = this.archives.size();
for (int i = 0; i < archiveSize; ++i) {
if (!this.archives.get(i).equals(rhs.archives.get(i)))
return false;
}
return true;
}
}