Skip to content

Commit

Permalink
Merge branch 'master' into K-Jo-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
K-Jo committed May 14, 2019
2 parents 27d48ed + 7f57265 commit d935ec1
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 8 deletions.
8 changes: 5 additions & 3 deletions README.md
@@ -1,5 +1,6 @@
[![CircleCI](https://circleci.com/gh/RedisLabsModules/RedisTimeSeries/tree/master.svg?style=svg)](https://circleci.com/gh/RedisLabsModules/RedisTimeSeries/tree/master)
[![GitHub issues](https://img.shields.io/github/release/RedisLabsModules/redis-timeseries.svg?kill_cache=1)](https://github.com/RedisLabsModules/redis-timeseries/releases/latest)
[![GitHub issues](https://img.shields.io/github/release/RedisTimeSeries/RedisTimeSeries.svg?kill_cache=1)](https://github.com/RedisTimeSeries/RedisTimeSeries/releases/latest)
[![CircleCI](https://circleci.com/gh/RedisTimeSeries/RedisTimeSeries/tree/master.svg?style=svg)](https://circleci.com/gh/RedisTimeSeries/RedisTimeSeries/tree/master)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/redislabs/redistimeseries.svg)](https://hub.docker.com/r/redislabs/redistimeseries/builds/)

# RedisTimeSeries
RedisTimeSeries is a Redis Module adding a Time Series data structure to Redis.
Expand Down Expand Up @@ -43,7 +44,7 @@ You can also build and run RedisTimeSeries on your own machine.

#### Requirements
- build-essential
- The RedisTimeSeries repository: `git clone https://github.com/RedisLabsModules/RedisTimeSeries.git`
- The RedisTimeSeries repository: `git clone https://github.com/RedisTimeSeries/RedisTimeSeries.git`

#### Build

Expand Down Expand Up @@ -93,6 +94,7 @@ Some languages have client libraries that provide support for RedisTimeSeries co
| ------- | -------- | ------- | ------ | --- |
| JRedisTimeSeries | Java | BSD-3 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/JRedisTimeSeries/) |
| redistimeseries-go | Go | Apache-2 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/redistimeseries-go) |
| redistimeseries-py | Python | BSD-3 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/redistimeseries-py) |

## Tests
Tests are written in python using the [rmtest](https://github.com/RedisLabs/rmtest) library.
Expand Down
8 changes: 5 additions & 3 deletions docs/index.md
Expand Up @@ -17,7 +17,7 @@ In the [RedisTimeSeries](https://github.com/RedisTimeSeries) organization you ca
find projects that help you integrate RedisTimeSeries with other tools, including:

1. Prometheus - [Adapter for Prometheus](https://github.com/RedisTimeSeries/prometheus-redistimeseries-adapter) to use RedisTimeSeries as backend db.
2. StatsD, Graphite exports using graphite protocol.
2. StatsD, Graphite exports using graphiGote protocol.
3. Grafana - using SimpleJson datasource.

## Memory model
Expand All @@ -43,7 +43,7 @@ You can also build and run RedisTimeSeries on your own machine.

#### Requirements
- build-essential
- The RedisTimeSeries repository: `git clone https://github.com/RedisLabsModules/RedisTimeSeries.git`
- The RedisTimeSeries repository: `git clone https://github.com/RedisTimeSeries/RedisTimeSeries.git`

#### Build

Expand Down Expand Up @@ -86,10 +86,12 @@ OK
```

### Client libraries

Go
Some languages have client libraries that provide support for RedisTimeSeries commands:

| Project | Language | License | Author | URL |
| ------- | -------- | ------- | ------ | --- |
| JRedisTimeSeries | Java | BSD-3 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/JRedisTimeSeries/) |
| redistimeseries-go | Go | Apache-2 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/redistimeseries-go) |
| redistimeseries-py | Python | BSD-3 | [RedisLabs](https://redislabs.com/) | [Github](https://github.com/RedisTimeSeries/redistimeseries-py) |

4 changes: 2 additions & 2 deletions mkdocs.yml
@@ -1,7 +1,7 @@
site_name: RedisTimeSeries - Time-Series data structure for Redis
site_url: http://redistimeseries.io
repo_url: https://github.com/RedisLabsModules/RedisTimeSeries
repo_name: RedisLabsModules/RedisTimeSeries
repo_url: https://github.com/RedisTimeSeries/RedisTimeSeries
repo_name: RedisTimeSeries/RedisTimeSeries

google_analytics:
- 'UA-92003007-1'
Expand Down
11 changes: 11 additions & 0 deletions src/Makefile
@@ -1,3 +1,7 @@
PREFIX?=/usr/local
INSTALL_LIB=$(PREFIX)/lib
INSTALL=/usr/bin/install

#set environment variable RM_INCLUDE_DIR to the location of redismodule.h
ifndef RM_INCLUDE_DIR
RM_INCLUDE_DIR=../RedisModulesSDK
Expand Down Expand Up @@ -31,6 +35,13 @@ module.o: module.c version.h
redistimeseries.so: rmutil module.o tsdb.o compaction.o rdb.o chunk.o parse_policies.o config.o indexer.o endianconv.o version.h
$(LD) -o $@ module.o tsdb.o rdb.o compaction.o chunk.o parse_policies.o config.o indexer.o $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc

install: all
@mkdir -p $(INSTALL_LIB)
$(INSTALL) redistimeseries.so $(INSTALL_LIB)

uninstall:
rm -f $(INSTALL_LIB)/redistimeseries.so

clean:
rm -rf *.xo *.so *.o ./tests_runner

Expand Down
101 changes: 101 additions & 0 deletions src/module.c
Expand Up @@ -6,6 +6,8 @@
#include <time.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>

#include "redismodule.h"
#include "rmutil/util.h"
#include "rmutil/strings.h"
Expand Down Expand Up @@ -555,6 +557,58 @@ int TSDB_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_OK;
}

int TSDB_alter(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if (argc < 2)
return RedisModule_WrongArity(ctx);

Series *series;
RedisModuleString *keyName = argv[1];
long long retentionSecs;
long long maxSamplesPerChunk;
size_t labelsCount;
Label *newLabels;

if (parseCreateArgs(ctx, argv, argc, &retentionSecs, &maxSamplesPerChunk, &labelsCount, &newLabels) != REDISMODULE_OK) {
return REDISMODULE_ERR;
}

RedisModuleKey *key = RedisModule_OpenKey(ctx, keyName, REDISMODULE_READ|REDISMODULE_WRITE);
if (RedisModule_ModuleTypeGetType(key) != SeriesType) {
RedisModule_CloseKey(key);
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
}
series = RedisModule_ModuleTypeGetValue(key);
if (RMUtil_ArgIndex("RETENTION", argv, argc) > 0) {
series->retentionSecs = retentionSecs;
}

if (RMUtil_ArgIndex("CHUNK_SIZE", argv, argc) > 0) {
series->maxSamplesPerChunk = maxSamplesPerChunk;
}

if (RMUtil_ArgIndex("LABELS", argv, argc) > 0) {
RemoveIndexedMetric(ctx, keyName, series->labels, series->labelsCount);
// free current labels
if (series->labelsCount > 0) {
for (int i = 0; i < series->labelsCount; i++) {
RedisModule_FreeString(ctx, series->labels[i].key);
RedisModule_FreeString(ctx, series->labels[i].value);
}
free(series->labels);
}

// set new newLabels
series->labels = newLabels;
series->labelsCount = labelsCount;
IndexMetric(ctx, keyName, series->labels, series->labelsCount);
}

RedisModule_CloseKey(key);
RedisModule_ReplyWithSimpleString(ctx, "OK");
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}

//TS.DELETERULE SOURCE_KEY DEST_KEY
int TSDB_deleteRule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 3)
Expand Down Expand Up @@ -743,6 +797,51 @@ int TSDB_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_OK;
}

int TSDB_mget(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_AutoMemory(ctx);
int filter_location = RMUtil_ArgIndex("FILTER", argv, argc);
if (filter_location == -1) {
return RedisModule_WrongArity(ctx);
}
size_t query_count = argc - 1 - filter_location;
QueryPredicate *queries = RedisModule_PoolAlloc(ctx, sizeof(QueryPredicate) * query_count);
if (parseLabelListFromArgs(ctx, argv, filter_location + 1, query_count, queries) == TSDB_ERROR) {
return RedisModule_ReplyWithError(ctx, "TSDB: failed parsing labels");
}

if (CountPredicateType(queries, (size_t) query_count, EQ) == 0) {
return RedisModule_ReplyWithError(ctx, "TSDB: please provide at least one matcher");
}

RedisModuleDict *result = QueryIndex(ctx, queries, query_count);
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(result, "^", NULL, 0);
char *currentKey;
size_t currentKeyLen;
long long replylen = 0;
Series *series;
while((currentKey = RedisModule_DictNextC(iter, &currentKeyLen, NULL)) != NULL) {
RedisModuleKey *key = RedisModule_OpenKey(ctx, RedisModule_CreateString(ctx, currentKey, currentKeyLen),
REDISMODULE_READ);
if (key == NULL || RedisModule_ModuleTypeGetType(key) != SeriesType){
RedisModule_Log(ctx, "warning", "couldn't open key or key is not a Timeseries. key=%s", currentKey);
continue;
}
series = RedisModule_ModuleTypeGetValue(key);

RedisModule_ReplyWithArray(ctx, 4);
RedisModule_ReplyWithStringBuffer(ctx, currentKey, currentKeyLen);
ReplyWithSeriesLabels(ctx, series);
RedisModule_ReplyWithLongLong(ctx, series->lastTimestamp);
RedisModule_ReplyWithDouble(ctx, series->lastValue);
replylen++;
RedisModule_CloseKey(key);
}
RedisModule_DictIteratorStop(iter);
RedisModule_ReplySetArrayLength(ctx, replylen);

return REDISMODULE_OK;
}

/*
module loading function, possible arguments:
Expand Down Expand Up @@ -774,6 +873,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (SeriesType == NULL) return REDISMODULE_ERR;
IndexInit();
RMUtil_RegisterWriteCmd(ctx, "ts.create", TSDB_create);
RMUtil_RegisterWriteCmd(ctx, "ts.alter", TSDB_alter);
RMUtil_RegisterWriteCmd(ctx, "ts.createrule", TSDB_createRule);
RMUtil_RegisterWriteCmd(ctx, "ts.deleterule", TSDB_deleteRule);
RMUtil_RegisterWriteCmd(ctx, "ts.add", TSDB_add);
Expand All @@ -784,6 +884,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RMUtil_RegisterReadCmd(ctx, "ts.queryindex", TSDB_queryindex);
RMUtil_RegisterReadCmd(ctx, "ts.info", TSDB_info);
RMUtil_RegisterReadCmd(ctx, "ts.get", TSDB_get);
RMUtil_RegisterReadCmd(ctx, "ts.mget", TSDB_mget);

return REDISMODULE_OK;
}
114 changes: 114 additions & 0 deletions src/tests/test_module.py
Expand Up @@ -11,6 +11,54 @@ def _get_ts_info(self, redis, key):
info = redis.execute_command('TS.INFO', key)
return dict([(info[i], info[i+1]) for i in range(0, len(info), 2)])

def _assert_alter_cmd(self, r, key, start_ts, end_ts,
expected_data=None,
expected_retention=None,
expected_chunk_size=None,
expected_labels=None):
"""
Test modifications didn't change the data
:param r: Redis instance
:param key: redis key
:param start_ts:
:param end_ts:
:param expected_data:
:return:
"""

actual_result = self._get_ts_info(r, key)

if expected_data:
actual_data = r.execute_command('TS.range', key, start_ts, end_ts)
assert expected_data == actual_data
if expected_retention:
assert expected_retention == actual_result['retentionSecs']
if expected_labels:
assert expected_labels == actual_result['labels']
if expected_chunk_size:
assert expected_chunk_size == actual_result['maxSamplesPerChunk']

@staticmethod
def _ts_alter_cmd(r, key, set_retention=None, set_chunk_size=None, set_labels=None):
"""
Assert that changed data is the same as expected, with or without modification of label or retention.
:param r: Redis instance
:param key: redis key to work on
:param set_retention: If none will skip (and test that didn't change from expected result)
:param set_labels: Format is list. If none will skip (and test that didn't change from expected result)
:return:
"""
cmd = ['TS.ALTER', key]
if set_retention:
cmd.extend(['RETENTION', set_retention])
if set_chunk_size:
cmd.extend(['CHUNK_SIZE', set_chunk_size])
if set_labels:
new_labels = [item for sublist in set_labels for item in sublist]
cmd.extend(['LABELS'])
cmd.extend(new_labels)
r.execute_command(*cmd)

@staticmethod
def _insert_data(redis, key, start_ts, samples_count, value):
"""
Expand Down Expand Up @@ -178,6 +226,72 @@ def test_sanity_pipeline(self):
actual_result = r.execute_command('TS.range', 'tester', start_ts, start_ts + samples_count)
assert expected_result == actual_result

def test_alter_cmd(self):
start_ts = 1511885909L
samples_count = 1500
end_ts = start_ts + samples_count
key = 'tester'

with self.redis() as r:
assert r.execute_command('TS.CREATE', key, 'CHUNK_SIZE', '360',
'LABELS', 'name', 'brown', 'color', 'pink')
self._insert_data(r, key, start_ts, samples_count, 5)

expected_data = [[start_ts + i, str(5)] for i in range(samples_count)]

# test alter retention, chunk size and labels
expected_retention = 100
expected_chunk_size = 100
expected_labels = [['A', '1'], ['B', '2'], ['C', '3']]
self._ts_alter_cmd(r, key, expected_retention, expected_chunk_size, expected_labels)
self._assert_alter_cmd(r, key, start_ts, end_ts, expected_data, expected_retention,
expected_chunk_size, expected_labels)

# test alter retention
expected_retention = 200
self._ts_alter_cmd(r, key, set_retention=expected_retention)
self._assert_alter_cmd(r, key, start_ts, end_ts, expected_data, expected_retention,
expected_chunk_size, expected_labels)

# test alter chunk size
expected_chunk_size = 500
self._ts_alter_cmd(r, key, set_chunk_size=expected_chunk_size)
self._assert_alter_cmd(r, key, start_ts, end_ts, expected_data, expected_retention,
expected_chunk_size, expected_labels)

# test alter labels
expected_labels = [['A', '1']]
self._ts_alter_cmd(r, key, expected_retention, set_labels=expected_labels)
self._assert_alter_cmd(r, key, start_ts, end_ts, expected_data, expected_retention,
expected_chunk_size, expected_labels)

# test indexer was updated
assert r.execute_command('TS.QUERYINDEX', 'A=1') == [key]
assert r.execute_command('TS.QUERYINDEX', 'name=brown') == []

def test_mget_cmd(self):
num_of_keys = 3
time_stamp = 1511885909L
keys = ['k1', 'k2', 'k3']
labels = ['a', 'a', 'b']
values = [100, 200, 300]

with self.redis() as r:
for i in range(num_of_keys):
assert r.execute_command('TS.CREATE', keys[i], 'LABELS', labels[i], '1')

assert r.execute_command('TS.ADD', keys[i], time_stamp - 1, values[i] - 1)
assert r.execute_command('TS.ADD', keys[i], time_stamp, values[i])

assert r.execute_command('TS.MGET', 'FILTER', 'a=1') == [
[keys[0], [[labels[0], '1']], time_stamp, str(values[0])],
[keys[1], [[labels[1], '1']], time_stamp, str(values[1])]
]

# negative test
assert not r.execute_command('TS.MGET', 'FILTER', 'a=100')
assert not r.execute_command('TS.MGET', 'FILTER', 'k=1')

def test_range_query(self):
start_ts = 1488823384L
samples_count = 1500
Expand Down

0 comments on commit d935ec1

Please sign in to comment.