Package org.openhab.persistence.rrd4j.internal

Source Code of org.openhab.persistence.rrd4j.internal.RRD4jService

/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.persistence.rrd4j.internal;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.RejectedExecutionException;

import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.QueryablePersistenceService;
import org.openhab.core.types.State;
import org.rrd4j.ConsolFun;
import org.rrd4j.DsType;
import org.rrd4j.core.FetchData;
import org.rrd4j.core.FetchRequest;
import org.rrd4j.core.RrdDb;
import org.rrd4j.core.RrdDef;
import org.rrd4j.core.Sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* This is the implementation of the RRD4j {@link PersistenceService}. To learn
* more about RRD4j please visit their <a href="http://code.google.com/p/rrd4j/">website</a>.
*
* @author Kai Kreuzer
* @since 1.0.0
*/
public class RRD4jService implements QueryablePersistenceService {

  private static final String DATASOURCE_STATE = "state";

  protected final static String DB_FOLDER = getUserDataFolder() + File.separator + "rrd4j";
 
  private static final Logger logger = LoggerFactory.getLogger(RRD4jService.class);

  private Map<String,Timer> timers = new HashMap<String,Timer>();
 
  protected ItemRegistry itemRegistry;
 
  public void setItemRegistry(ItemRegistry itemRegistry) {
    this.itemRegistry = itemRegistry;
  }

  public void unsetItemRegistry(ItemRegistry itemRegistry) {
    this.itemRegistry = null;
  }

  /**
   * @{inheritDoc}
   */
  public String getName() {
    return "rrd4j";
  }

  /**
   * @{inheritDoc}
   */
  public synchronized void store(final Item item, final String alias) {
    final String name = alias==null ? item.getName() : alias;
    ConsolFun function = getConsolidationFunction(item);
    RrdDb db = getDB(name, function);
    if(db!=null) {
      long now = System.currentTimeMillis()/1000;
      if(function!=ConsolFun.AVERAGE) {
        try {
          // we store the last value again, so that the value change in the database is not interpolated, but
          // happens right at this spot
          if(now - 1 > db.getLastUpdateTime()) {
            // only do it if there is not already a value
            double lastValue = db.getLastDatasourceValue(DATASOURCE_STATE);
            if(!Double.isNaN(lastValue)) {
              Sample sample = db.createSample();
                    sample.setTime(now - 1);
                    sample.setValue(DATASOURCE_STATE, lastValue);
                    sample.update();
                        logger.debug("Stored '{}' with state '{}' in rrd4j database", name, mapToState(lastValue, item.getName()));
            }
          }
        } catch (IOException e) {
          logger.debug("Error re-storing last value: {}", e.getMessage());
        }
      }
      try {
        Sample sample = db.createSample();
              sample.setTime(now);
             
              DecimalType state = (DecimalType) item.getStateAs(DecimalType.class);
              if (state!=null) {
                    double value = state.toBigDecimal().doubleValue();
                    sample.setValue(DATASOURCE_STATE, value);
                    sample.update();
                    logger.debug("Stored '{}' with state '{}' in rrd4j database", name, item.getState());
              }
      } catch (IllegalArgumentException e) {
        if(e.getMessage().contains("at least one second step is required")) {

          // we try to store the value one second later
          TimerTask task = new TimerTask() {
            public void run() {
              store(item, name);
            }
          };
          Timer timer = timers.get(name);
          if(timer!=null) {
            timer.cancel();
            timers.remove(name);
          }
          timer = new Timer();
          timers.put(name, timer);
          timer.schedule(task, 1000);
        } else {
          logger.warn("Could not persist '{}' to rrd4j database: {}", new String[] { name, e.getMessage() });
        }
      } catch (Exception e) {
        logger.warn("Could not persist '{}' to rrd4j database: {}", new String[] { name, e.getMessage() });
      }
            try {
        db.close();
      } catch (IOException e) {
        logger.debug("Error closing rrd4j database: {}", e.getMessage());
      }
    }
  }

  /**
   * @{inheritDoc}
   */
  public void store(Item item) {
    store(item, null);
  }
 
  @Override
  public Iterable<HistoricItem> query(FilterCriteria filter) {
    String itemName = filter.getItemName();
    ConsolFun consolidationFunction = getConsolidationFunction(itemName);
    RrdDb db = getDB(itemName, consolidationFunction);
    if(db!=null) {
      long start = 0L;
      long end = filter.getEndDate()==null ? System.currentTimeMillis()/1000 - 1 : filter.getEndDate().getTime()/1000;

      try {
        if(filter.getBeginDate()==null) {
          // as rrd goes back for years and gets more and more inaccurate, we only support descending order and a single return value
          // if there is no begin date is given - this case is required specifically for the historicState() query, which we
          // want to support
          if(filter.getOrdering()==Ordering.DESCENDING && filter.getPageSize()==1 && filter.getPageNumber()==0) {
            if(filter.getEndDate()==null) {
              // we are asked only for the most recent value!
              double lastValue = db.getLastDatasourceValue(DATASOURCE_STATE);
              if(!Double.isNaN(lastValue)) {
                HistoricItem rrd4jItem = new RRD4jItem(itemName, mapToState(lastValue, itemName), new Date(db.getLastArchiveUpdateTime() * 1000));
                return Collections.singletonList(rrd4jItem);
              } else {
                return Collections.emptyList();
              }
            } else {
              start = end;
            }
          } else {
            throw new UnsupportedOperationException("rrd4j does not allow querys without a begin date, " +
                "unless order is decending and a single value is requested");
          }
        } else {
          start = filter.getBeginDate().getTime()/1000;
        }
        FetchRequest request = db.createFetchRequest(consolidationFunction, start, end, 1);

        List<HistoricItem> items = new ArrayList<HistoricItem>();
        FetchData result = request.fetchData();
        long ts = result.getFirstTimestamp();
        long step = result.getRowCount() > 1 ? result.getStep() : 0;
        for(double value : result.getValues(DATASOURCE_STATE)) {
          if(!Double.isNaN(value)) {
            RRD4jItem rrd4jItem = new RRD4jItem(itemName, mapToState(value, itemName), new Date(ts * 1000));
            items.add(rrd4jItem);
          }
          ts += step;
        }
        return items;
      } catch (IOException e) {
        logger.warn("Could not query rrd4j database for item '{}': {}", new String[] { itemName, e.getMessage() });
     
    }
    return Collections.emptyList();
  }

  protected synchronized RrdDb getDB(String alias, ConsolFun function) {
    RrdDb db = null;
        File file = new File(DB_FOLDER + File.separator + alias + ".rrd");
      try {
            if (file.exists()) {
              // recreate the RrdDb instance from the file
              db = new RrdDb(file.getAbsolutePath());
            } else {
              File folder = new File(DB_FOLDER);
              if(!folder.exists()) {
                folder.mkdir();
              }
              // create a new database file
                db = new RrdDb(getRrdDef(function, file));
            }
    } catch (IOException e) {
      logger.error("Could not create rrd4j database file '{}': {}", new String[] { file.getAbsolutePath(), e.getMessage() });
    } catch(RejectedExecutionException e) {
      // this happens if the system is shut down
      logger.debug("Could not create rrd4j database file '{}': {}", new String[] { file.getAbsolutePath(), e.getMessage() });
    }
    return db;
  }

  private RrdDef getRrdDef(ConsolFun function, File file) {
      RrdDef rrdDef = new RrdDef(file.getAbsolutePath());
      if(function==ConsolFun.AVERAGE) {
        // for measurement values, we define archives that are suitable for charts
        rrdDef.setStep(60);
          rrdDef.setStartTime(System.currentTimeMillis()/1000-1);
          rrdDef.addDatasource(DATASOURCE_STATE, DsType.GAUGE, 60, Double.NaN, Double.NaN);
          rrdDef.addArchive(function, 0.5, 1, 480); // 8 hours (granularity 1 min)
          rrdDef.addArchive(function, 0.5, 4, 360); // one day (granularity 4 min)
          rrdDef.addArchive(function, 0.5, 15, 644); // one week (granularity 15 min)
          rrdDef.addArchive(function, 0.5, 60, 720); // one month (granularity 1 hour)
          rrdDef.addArchive(function, 0.5, 720, 730); // one year (granularity 12 hours)
          rrdDef.addArchive(function, 0.5, 10080, 520); // ten years (granularity 7 days)
      } else {
        // for other things, we mainly provide a high level of detail for the last hour
        rrdDef.setStep(1);
          rrdDef.setStartTime(System.currentTimeMillis()/1000-1);
          rrdDef.addDatasource(DATASOURCE_STATE, DsType.GAUGE, 3600, Double.NaN, Double.NaN);
          rrdDef.addArchive(function, .999, 1, 3600); // 1 hour (granularity 1 sec)
          rrdDef.addArchive(function, .999, 10, 1440); // 4 hours (granularity 10 sec)
          rrdDef.addArchive(function, .999, 60, 1440); // one day (granularity 1 min)
          rrdDef.addArchive(function, .999, 900, 2880); // one month (granularity 15 min)
          rrdDef.addArchive(function, .999, 21600, 1460); // one year (granularity 6 hours)
          rrdDef.addArchive(function, .999, 86400, 3650); // ten years (granularity 1 day)
      }
    return rrdDef;
  }

  static public ConsolFun getConsolidationFunction(Item item) {
    if(item instanceof NumberItem) {
      return ConsolFun.AVERAGE;
    } else {
      // for all other values (like ON/OFF etc.) use the maximum value for consolidation
      return ConsolFun.MAX;
    }
  }

  private ConsolFun getConsolidationFunction(String itemName) {
    if(itemRegistry!=null) {
      try {
        Item item = itemRegistry.getItem(itemName);
        return getConsolidationFunction(item);
      } catch (ItemNotFoundException e) {
        logger.debug("Could not find item '{}' in registry", itemName);
      }
    }
    // use MAX as the default
    return ConsolFun.MAX;
  }

  private State mapToState(double value, String itemName) {
    if(itemRegistry!=null) {
      try {
        Item item = itemRegistry.getItem(itemName);
        if(item instanceof SwitchItem && !(item instanceof DimmerItem)) {
          return value==0.0d ? OnOffType.OFF : OnOffType.ON;
        } else if(item instanceof ContactItem) {
          return value==0.0d ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
        }
      } catch (ItemNotFoundException e) {
        logger.debug("Could not find item '{}' in registry", itemName);
      }
    }
    // just return a DecimalType as a fallback
    return new DecimalType(value);
  }
 
  static private String getUserDataFolder() {
    String progArg = System.getProperty("smarthome.userdata");
    if (progArg != null) {
      return progArg;
    } else {
      return "etc";
    }
  }

}
TOP

Related Classes of org.openhab.persistence.rrd4j.internal.RRD4jService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.