/*
* Copyright 2011 Future Systems
*
* 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.logstorage.engine;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.krakenapps.confdb.ConfigService;
import org.krakenapps.cron.PeriodicJob;
import org.krakenapps.logstorage.DiskLackAction;
import org.krakenapps.logstorage.DiskLackCallback;
import org.krakenapps.logstorage.DiskSpaceType;
import org.krakenapps.logstorage.LogIndexer;
import org.krakenapps.logstorage.LogRetentionPolicy;
import org.krakenapps.logstorage.LogStorage;
import org.krakenapps.logstorage.LogStorageMonitor;
import org.krakenapps.logstorage.LogTableRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@PeriodicJob("* * * * *")
@Component(name = "logstorage-monitor")
@Provides
public class LogStorageMonitorEngine implements LogStorageMonitor {
private static final String DEFAULT_MIN_FREE_SPACE_TYPE = DiskSpaceType.Percentage.toString();
private static final int DEFAULT_MIN_FREE_SPACE_VALUE = 10;
private static final String DEFAULT_DISK_LACK_ACTION = DiskLackAction.StopLogging.toString();
private final Logger logger = LoggerFactory.getLogger(LogStorageMonitorEngine.class.getName());
@Requires
private LogTableRegistry tableRegistry;
@Requires
private LogStorage storage;
@Requires
private LogIndexer indexer;
@Requires
private ConfigService conf;
// last check time, check retention policy and purge files for every hour
private long lastPurgeCheck;
private DiskSpaceType minFreeSpaceType;
private int minFreeSpaceValue;
private DiskLackAction diskLackAction;
private Set<DiskLackCallback> diskLackCallbacks = new HashSet<DiskLackCallback>();
public LogStorageMonitorEngine() {
minFreeSpaceType = DiskSpaceType.valueOf(getStringParameter(Constants.MinFreeDiskSpaceType, DEFAULT_MIN_FREE_SPACE_TYPE));
minFreeSpaceValue = getIntParameter(Constants.MinFreeDiskSpaceValue, DEFAULT_MIN_FREE_SPACE_VALUE);
diskLackAction = DiskLackAction.valueOf(getStringParameter(Constants.DiskLackAction, DEFAULT_DISK_LACK_ACTION));
}
@Override
public int getMinFreeSpaceValue() {
return minFreeSpaceValue;
}
@Override
public DiskSpaceType getMinFreeSpaceType() {
return minFreeSpaceType;
}
@Override
public void setMinFreeSpace(int value, DiskSpaceType type) {
if (type == DiskSpaceType.Percentage) {
if (value <= 0 || value >= 100)
throw new IllegalArgumentException("invalid value");
} else if (type == DiskSpaceType.Megabyte) {
if (value <= 0)
throw new IllegalArgumentException("invalid value");
} else if (type == null)
throw new IllegalArgumentException("type cannot be null");
this.minFreeSpaceType = type;
this.minFreeSpaceValue = value;
ConfigUtil.set(conf, Constants.MinFreeDiskSpaceType, type.toString());
ConfigUtil.set(conf, Constants.MinFreeDiskSpaceValue, Integer.toString(value));
}
@Override
public DiskLackAction getDiskLackAction() {
return diskLackAction;
}
@Override
public void setDiskLackAction(DiskLackAction action) {
if (action == null)
throw new IllegalArgumentException("action cannot be null");
this.diskLackAction = action;
ConfigUtil.set(conf, Constants.DiskLackAction, action.toString());
}
@Override
public void registerDiskLackCallback(DiskLackCallback callback) {
diskLackCallbacks.add(callback);
}
@Override
public void unregisterDiskLackCallback(DiskLackCallback callback) {
diskLackCallbacks.remove(callback);
}
@Override
public void forceRetentionCheck() {
checkRetentions(true);
}
private boolean isDiskLack() {
File dir = storage.getDirectory();
long usable = dir.getUsableSpace();
long total = dir.getTotalSpace();
logger.trace("kraken logstorage: check {} {} free space", minFreeSpaceValue, minFreeSpaceType.toString().toLowerCase());
if (minFreeSpaceType == DiskSpaceType.Percentage) {
int percent = (int) (usable * 100 / total);
if (percent < minFreeSpaceValue) {
return true;
}
} else if (minFreeSpaceType == DiskSpaceType.Megabyte) {
int mega = (int) (usable / 1048576);
if (mega < minFreeSpaceValue) {
return true;
}
}
return false;
}
@Override
public void run() {
try {
runOnce();
} catch (Exception e) {
logger.error("kraken logstorage: storage monitor error", e);
}
}
private void runOnce() {
checkRetentions(false);
checkDiskLack();
}
private void checkRetentions(boolean force) {
long now = System.currentTimeMillis();
if (!force && now - lastPurgeCheck < 3600 * 1000)
return;
for (String tableName : tableRegistry.getTableNames()) {
checkAndPurgeFiles(tableName);
}
lastPurgeCheck = now;
}
private void checkAndPurgeFiles(String tableName) {
LogRetentionPolicy p = storage.getRetentionPolicy(tableName);
if (p == null || p.getRetentionDays() == 0) {
logger.debug("kraken logstorage: no retention policy for table [{}]", tableName);
return;
}
int retentionDays = p.getRetentionDays();
// purge tables
Date logBaseline = storage.getPurgeBaseline(tableName);
if (logBaseline != null)
storage.purge(tableName, null, logBaseline);
// purge index files
for (String indexName : indexer.getIndexNames(tableName)) {
Date indexBaseline = indexer.getPurgeBaseline(tableName, indexName);
if (indexBaseline == null)
continue;
if (logger.isTraceEnabled()) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
logger.trace("kraken logstorage: table [{}] retention days [{}] purging index [{}] baseline [{}]", new Object[] {
tableName, retentionDays, indexName, dateFormat.format(indexBaseline) });
}
indexer.purge(tableName, indexName, null, indexBaseline);
}
}
private void checkDiskLack() {
if (isDiskLack()) {
logger.warn("kraken logstorage: not enough disk space, current minimum free space config [{}] {}", minFreeSpaceValue,
(minFreeSpaceType == DiskSpaceType.Percentage ? "%" : "MB"));
if (diskLackAction == DiskLackAction.StopLogging) {
logger.info("kraken logstorage: stop logging");
storage.stop();
} else if (diskLackAction == DiskLackAction.RemoveOldLog) {
List<LogFile> files = new ArrayList<LogFile>();
for (String tableName : tableRegistry.getTableNames()) {
for (Date date : storage.getLogDates(tableName))
files.add(new LogFile(tableName, date));
}
Collections.sort(files, new LogFileComparator());
int index = 0;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
do {
if (index >= files.size()) {
logger.info("kraken logstorage: stop logging");
storage.stop();
break;
}
LogFile lf = files.get(index++);
logger.info("kraken logstorage: remove old log, table {}, {}", lf.tableName, sdf.format(lf.date));
lf.remove();
} while (isDiskLack());
for (DiskLackCallback callback : diskLackCallbacks)
callback.callback();
}
}
}
private String getStringParameter(Constants key, String defaultValue) {
String value = ConfigUtil.get(conf, key);
if (value != null)
return value;
return defaultValue;
}
private int getIntParameter(Constants key, int defaultValue) {
String value = ConfigUtil.get(conf, key);
if (value != null)
return Integer.valueOf(value);
return defaultValue;
}
private class LogFile {
private String tableName;
private Date date;
private File index;
private File data;
private LogFile(String tableName, Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
File tableDir = storage.getTableDirectory(tableName);
this.tableName = tableName;
this.date = date;
this.index = new File(tableDir, sdf.format(date) + ".idx");
this.data = new File(tableDir, sdf.format(date) + ".dat");
}
public void remove() {
index.delete();
data.delete();
}
}
private class LogFileComparator implements Comparator<LogFile> {
@Override
public int compare(LogFile o1, LogFile o2) {
return o1.date.compareTo(o2.date);
}
}
}