Skip to content

Commit

Permalink
[influxdb] Add option for using metadata value as measurement name (o…
Browse files Browse the repository at this point in the history
…penhab#9943)

* Add option for using metadata value as measurement name

Also-by: Joan Pujol <joanpujol@gmail.com>
Signed-off-by: Johannes Ott <info@johannes-ott.net>
Signed-off-by: John Marshall <john.marshall.au@gmail.com>
  • Loading branch information
DerOetzi authored and themillhousegroup committed May 10, 2021
1 parent 6959684 commit faab129
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 74 deletions.
95 changes: 72 additions & 23 deletions bundles/org.openhab.persistence.influxdb/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# InfluxDB (0.9 and newer) Persistence

This service allows you to persist and query states using the [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) and [InfluxDB 2.0](https://v2.docs.influxdata.com/v2.0/) time series database. The persisted values can be queried from within openHAB.
This service allows you to persist and query states using the [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) and [InfluxDB 2.0](https://v2.docs.influxdata.com/v2.0/) time series database. The persisted values can be queried from within openHAB.
There also are nice tools on the web for visualizing InfluxDB time series, such as [Grafana](http://grafana.org/) and new Influx DB 2.0 version introduces [powerful data processing features.](https://docs.influxdata.com/influxdb/v2.0/process-data/get-started/)

## Database Structure


- This service allows you to persist and query states using the time series database.
- The states of an item are persisted in *measurements* points with names equal to the name of the item, or the alias, if one is provided. In both variants, a *tag* named "item" is added, containing the item name.
All values are stored in a *field* called "value" using the following types:
- **float** for DecimalType and QuantityType
- **integer** for `OnOffType` and `OpenClosedType` (values are stored using 0 or 1) and `DateTimeType` (milliseconds since 1970-01-01T00:00:00Z)
- **string** for the rest of types

- The states of an item are persisted in _measurements_ points with names equal to the name of the item, its alias, or from some metadata depending on the configuration. In all variants, a tag named "item" is added, containing the item name.
All values are stored in a _field_ called "value" using the following types:
- **float** for DecimalType and QuantityType
- **integer** for `OnOffType` and `OpenClosedType` (values are stored using 0 or 1) and `DateTimeType` (milliseconds since 1970-01-01T00:00:00Z)
- **string** for the rest of types
- If configured, extra tags for item category, label or type can be added fore each point.

Some example entries for an item with the name "speedtest" without any further configuration would look like this:
Expand All @@ -22,34 +20,85 @@ Some example entries for an item with the name "speedtest" without any further c
|> range(start: -30d)
|> filter(fn: (r) => r._measurement == "speedtest")
name: speedtest

_time _item _value
----- ----- ------
1558302027124000000 speedtest 123289369.0
1558332852716000000 speedtest 80423789.0


## Prerequisites

First of all you have to setup and run an InfluxDB 1.X or 2.X server.
This is very easy and you will find good documentation on it on the
First of all, you have to setup and run an InfluxDB 1.X or 2.X server.
This is very easy and you will find good documentation on it on the
[InfluxDB web site for 2.X version](https://v2.docs.influxdata.com/v2.0/get-started/) and [InfluxDB web site for 1.X version](https://docs.influxdata.com/influxdb/v1.7/).

## Configuration

This service can be configured in the file `services/influxdb.cfg`.

| Property | Default | Required | Description |
|------------------------------------|-------------------------|----------|------------------------------------------|
| version | V1 | No | InfluxDB database version V1 for 1.X and V2 for 2.x|
| url | http://127.0.0.1:8086 | No | database URL |
| user | openhab | No | name of the database user, e.g. `openhab`|
| password | | No(*) | password of the database user you choose |
| token | | No(*) | token to authenticate the database (only for V2) [Intructions about how to create one](https://v2.docs.influxdata.com/v2.0/security/tokens/create-token/) |
| db | openhab | No | name of the database for V1 and name of the organization for V2 |
| retentionPolicy | autogen | No | name of the retention policy for V1 and name of the bucket for V2 |
| Property | Default | Required | Description |
| --------------- | --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| version | V1 | No | InfluxDB database version V1 for 1.X and V2 for 2.x |
| url | http://127.0.0.1:8086 | No | database URL |
| user | openhab | No | name of the database user, e.g. `openhab` |
| password | | No(\*) | password of the database user you choose |
| token | | No(\*) | token to authenticate the database (only for V2) [Intructions about how to create one](https://v2.docs.influxdata.com/v2.0/security/tokens/create-token/) |
| db | openhab | No | name of the database for V1 and name of the organization for V2 |
| retentionPolicy | autogen | No | name of the retention policy for V1 and name of the bucket for V2 |

(*) For 1.X version you must provide user and password, for 2.X you can use user and password or a token. That means
that if you use all default values at minimum you must provide a password or a token.
(\*) For 1.X version you must provide user and password, for 2.X you can use user and password or a token. That means
that if you use all default values at minimum you must provide a password or a token.

All item- and event-related configuration is defined in the file `persistence/influxdb.persist`.

### Additional configuration for customized storage options in InfluxDB

By default, the plugin writes the data to a `measurement` name equals to the `item's name` and adds a tag with key item and value `item's name` as well.
You can customize that behavior and use a single measurement for several items using item metadata.

#### Measurement name by Item Metadata

By setting the `influxdb` metadata key you can change the name of the measurement by setting the desired name as metadata value.
You can also add additional tags for structuring your data. For example, you can add a floor tag to all sensors to filter all sensors from the first floor or combine all temperature sensors into one measurement.

The item configuration will look like this:

```
Group:Number:AVG gTempSensors
Number:Temperature tempLivingRoom (gTempSensors) { influxdb="temperature" [floor="groundfloor"] }
Number:Temperature tempKitchen (gTempSensors) { influxdb="temperature" [floor="groundfloor"] }
Number:Temperature tempBedRoom (gTempSensors) { influxdb="temperature" [floor="firstfloor"] }
Number:Temperature tempBath (gTempSensors) { influxdb="temperature" [floor="firstfloor"] }
```

You can also set the `influxdb` metadata using the UI. From each item configuration screen do:

`Metadata``Add Metadata``Enter Custom Namespace` → Enter `influxdb` as namespace name → And enter your desired item name in value field. i.e.:

value: temperature
config: {}

This will end up with one measurement named temperature and four different series inside:

```
temperature,item=tempLivingRoom,floor=groundfloor
temperature,item=tempKitchen,floor=groundfloor
temperature,item=tempBedRoom,floor=firstfloor
temperature,item=tempBath,floor=firstfloor
```

You can now easily select all temperatures of the firstfloor or the average temperature of the groundfloor.

#### Extended automatic tagging

Besides the metadata tags, there are additional configuration parameters to activate different automatic tags generation.

| Property | Default | Required | Description |
| -------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------- |
| addCategoryTag | false | no | Should the category of the item be included as tag "category"? If no category is set, "n/a" is used. |
| addTypeTag | false | no | Should the item type be included as tag "type"? |
| addLabelTag | false | no | Should the item label be included as tag "label"? If no label is set, "n/a" is used. |
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,23 @@
import org.slf4j.LoggerFactory;

/**
* This is the implementation of the InfluxDB {@link PersistenceService}. It persists item values
* using the <a href="http://influxdb.org">InfluxDB time series database. The states (
* {@link State}) of an {@link Item} are persisted by default in a time series with names equal to the name of
* the item.
* This is the implementation of the InfluxDB {@link PersistenceService}. It
* persists item values using the <a href="http://influxdb.org">InfluxDB time
* series database. The states ( {@link State}) of an {@link Item} are persisted
* by default in a time series with names equal to the name of the item.
*
* This addon supports 1.X and 2.X versions, as two versions are incompatible and use different drivers the
* specific code for each version is accessed by {@link InfluxDBRepository} and {@link FilterCriteriaQueryCreator}
* interfaces and specific implementation reside in {@link org.openhab.persistence.influxdb.internal.influx1} and
* This addon supports 1.X and 2.X versions, as two versions are incompatible
* and use different drivers the specific code for each version is accessed by
* {@link InfluxDBRepository} and {@link FilterCriteriaQueryCreator} interfaces
* and specific implementation reside in
* {@link org.openhab.persistence.influxdb.internal.influx1} and
* {@link org.openhab.persistence.influxdb.internal.influx2} packages
*
* @author Theo Weiss - Initial contribution, rewrite of org.openhab.persistence.influxdb
* @author Joan Pujol Espinar - Addon rewrite refactoring code and adding support for InfluxDB 2.0. Some tag code is
* based
* from not integrated branch from Dominik Vorreiter
* @author Theo Weiss - Initial contribution, rewrite of
* org.openhab.persistence.influxdb
* @author Joan Pujol Espinar - Addon rewrite refactoring code and adding
* support for InfluxDB 2.0. Some tag code is based from not integrated
* branch from Dominik Vorreiter
*/
@NonNullByDefault
@Component(service = { PersistenceService.class,
Expand Down Expand Up @@ -222,7 +225,7 @@ public Iterable<HistoricItem> query(FilterCriteria filter) {
filter.getItemName(), filter.getOrdering().toString(), filter.getState(), filter.getOperator(),
filter.getBeginDate(), filter.getEndDate(), filter.getPageSize(), filter.getPageNumber());

String query = RepositoryFactory.createQueryCreator(configuration).createQuery(filter,
String query = RepositoryFactory.createQueryCreator(configuration, metadataRegistry).createQuery(filter,
configuration.getRetentionPolicy());
logger.trace("Query {}", query);
List<InfluxRow> results = influxDBRepository.query(query);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.persistence.influxdb.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.Metadata;
import org.openhab.core.items.MetadataKey;
import org.openhab.core.items.MetadataRegistry;
import org.openhab.persistence.influxdb.InfluxDBPersistenceService;

/**
* Logic to use items metadata from an openHAB {@link Item}
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class InfluxDBMetadataUtils {

private InfluxDBMetadataUtils() {
}

public static String calculateMeasurementNameFromMetadataIfPresent(
final @Nullable MetadataRegistry currentMetadataRegistry, String name, @Nullable String itemName) {

if (itemName == null || currentMetadataRegistry == null) {
return name;
}

MetadataKey key = new MetadataKey(InfluxDBPersistenceService.SERVICE_NAME, itemName);
Metadata metadata = currentMetadataRegistry.get(key);
if (metadata != null) {
String metaName = metadata.getValue();
if (!metaName.isBlank()) {
name = metaName;
}
}

return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public ItemToStorePointCreator(InfluxDBConfiguration configuration, @Nullable Me
private String calculateMeasurementName(Item item, @Nullable String storeAlias) {
String name = storeAlias != null && !storeAlias.isBlank() ? storeAlias : item.getName();

name = InfluxDBMetadataUtils.calculateMeasurementNameFromMetadataIfPresent(metadataRegistry, name,
item.getName());

if (configuration.isReplaceUnderscore()) {
name = name.replace('_', '.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
package org.openhab.persistence.influxdb.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.items.MetadataRegistry;
import org.openhab.persistence.influxdb.internal.influx1.Influx1FilterCriteriaQueryCreatorImpl;
import org.openhab.persistence.influxdb.internal.influx1.InfluxDB1RepositoryImpl;
import org.openhab.persistence.influxdb.internal.influx2.Influx2FilterCriteriaQueryCreatorImpl;
import org.openhab.persistence.influxdb.internal.influx2.InfluxDB2RepositoryImpl;

/**
* Factory that returns {@link InfluxDBRepository} and {@link FilterCriteriaQueryCreator} implementations
* depending on InfluxDB version
* Factory that returns {@link InfluxDBRepository} and
* {@link FilterCriteriaQueryCreator} implementations depending on InfluxDB
* version
*
* @author Joan Pujol Espinar - Initial contribution
*/
Expand All @@ -38,12 +40,13 @@ public static InfluxDBRepository createRepository(InfluxDBConfiguration influxDB
}
}

public static FilterCriteriaQueryCreator createQueryCreator(InfluxDBConfiguration influxDBConfiguration) {
public static FilterCriteriaQueryCreator createQueryCreator(InfluxDBConfiguration influxDBConfiguration,
MetadataRegistry metadataRegistry) {
switch (influxDBConfiguration.getVersion()) {
case V1:
return new Influx1FilterCriteriaQueryCreatorImpl();
return new Influx1FilterCriteriaQueryCreatorImpl(influxDBConfiguration, metadataRegistry);
case V2:
return new Influx2FilterCriteriaQueryCreatorImpl();
return new Influx2FilterCriteriaQueryCreatorImpl(influxDBConfiguration, metadataRegistry);
default:
throw new UnnexpectedConditionException("Not expected version " + influxDBConfiguration.getVersion());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
import static org.openhab.persistence.influxdb.internal.InfluxDBStateConvertUtils.stateToObject;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.influxdb.dto.Query;
import org.influxdb.querybuilder.Appender;
import org.influxdb.querybuilder.BuiltQuery;
import org.influxdb.querybuilder.Select;
import org.influxdb.querybuilder.Where;
import org.influxdb.querybuilder.clauses.SimpleClause;
import org.openhab.core.items.MetadataRegistry;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.persistence.influxdb.internal.FilterCriteriaQueryCreator;
import org.openhab.persistence.influxdb.internal.InfluxDBConfiguration;
import org.openhab.persistence.influxdb.internal.InfluxDBMetadataUtils;
import org.openhab.persistence.influxdb.internal.InfluxDBVersion;

/**
Expand All @@ -35,20 +39,33 @@
@NonNullByDefault
public class Influx1FilterCriteriaQueryCreatorImpl implements FilterCriteriaQueryCreator {

private InfluxDBConfiguration configuration;
private MetadataRegistry metadataRegistry;

public Influx1FilterCriteriaQueryCreatorImpl(InfluxDBConfiguration configuration,
MetadataRegistry metadataRegistry) {
this.configuration = configuration;
this.metadataRegistry = metadataRegistry;
}

@Override
public String createQuery(FilterCriteria criteria, String retentionPolicy) {
final String tableName;
boolean hasCriteriaName = criteria.getItemName() != null;
if (hasCriteriaName) {
tableName = criteria.getItemName();
} else {
tableName = "/.*/";
}
final String itemName = criteria.getItemName();
boolean hasCriteriaName = itemName != null;

Select select = select(COLUMN_VALUE_NAME_V1).fromRaw(null,
fullQualifiedTableName(retentionPolicy, tableName, hasCriteriaName));
tableName = calculateTableName(itemName);

Select select = select().column("\"" + COLUMN_VALUE_NAME_V1 + "\"::field")
.column("\"" + TAG_ITEM_NAME + "\"::tag")
.fromRaw(null, fullQualifiedTableName(retentionPolicy, tableName, hasCriteriaName));

Where where = select.where();

if (itemName != null && !tableName.equals(itemName)) {
where = where.and(BuiltQuery.QueryBuilder.eq(TAG_ITEM_NAME, itemName));
}

if (criteria.getBeginDate() != null) {
where = where.and(
BuiltQuery.QueryBuilder.gte(COLUMN_TIME_NAME_V1, criteria.getBeginDate().toInstant().toString()));
Expand Down Expand Up @@ -82,6 +99,22 @@ public String createQuery(FilterCriteria criteria, String retentionPolicy) {
return query.getCommand();
}

private String calculateTableName(@Nullable String itemName) {
if (itemName == null) {
return "/.*/";
}

String name = itemName;

name = InfluxDBMetadataUtils.calculateMeasurementNameFromMetadataIfPresent(metadataRegistry, name, itemName);

if (configuration.isReplaceUnderscore()) {
name = name.replace('_', '.');
}

return name;
}

private String fullQualifiedTableName(String retentionPolicy, String tableName, boolean escapeTableName) {
StringBuilder sb = new StringBuilder();
Appender.appendName(retentionPolicy, sb);
Expand Down
Loading

0 comments on commit faab129

Please sign in to comment.