redis-time-series 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +26 -11
- data/bin/console +3 -9
- data/bin/setup +28 -6
- data/lib/redis-time-series.rb +3 -2
- data/lib/redis/time_series.rb +6 -0
- data/lib/redis/time_series/filter.rb +118 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d699daec01bb891952501de6a51cc4b966be74a0f8585030bafb4f6a908b36e
|
4
|
+
data.tar.gz: dfa027d344c4d5485b0a22a33edd67973c2a60cb60dbbaa1a57101ccce8833fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acadcad9c6919e810f0e100bfe20b60055507e2efa98d0853fca6c15a252f99b65c722c71acc99d37d8d9f72964d91f862c9604fb96458ca3faaddce9f841752
|
7
|
+
data.tar.gz: 489415e8247d8cffefcf13e4e70c82cde752170c4416bc0973495c5d38b1d27d2b554cdd62db8729c9c71b125cb4848aa5a1efd81b998aa5f6e5683c13be8ccc
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
![](https://github.com/dzunk/redis-time-series/workflows/RSpec/badge.svg)
|
1
|
+
[![RSpec](https://github.com/dzunk/redis-time-series/workflows/RSpec/badge.svg)](https://github.com/dzunk/redis-time-series/actions?query=workflow%3ARSpec+branch%3Amaster)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/redis-time-series.svg)](https://badge.fury.io/rb/redis-time-series)
|
2
3
|
|
3
4
|
# RedisTimeSeries
|
4
5
|
|
@@ -159,36 +160,50 @@ ts.length
|
|
159
160
|
ts.size
|
160
161
|
=> 3
|
161
162
|
```
|
163
|
+
Find series matching specific label(s)
|
164
|
+
```ruby
|
165
|
+
Redis::TimeSeries.queryindex('foo=bar')
|
166
|
+
=> [#<Redis::TimeSeries:0x00007fc115ba1610
|
167
|
+
@key="ts3",
|
168
|
+
@redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>,
|
169
|
+
@retention=nil,
|
170
|
+
@uncompressed=false>]
|
171
|
+
# Note that you need at least one "label equals value" filter
|
172
|
+
Redis::TimeSeries.queryindex('foo!=bar')
|
173
|
+
=> RuntimeError: Filtering requires at least one equality comparison
|
174
|
+
```
|
175
|
+
|
162
176
|
|
163
177
|
### TODO
|
164
178
|
* `TS.REVRANGE`
|
165
179
|
* `TS.MRANGE`/`TS.MREVRANGE`
|
166
|
-
* `TS.QUERYINDEX`
|
167
180
|
* Compaction rules
|
168
181
|
* Filters
|
169
182
|
* Probably a bunch more stuff
|
170
183
|
|
171
184
|
## Development
|
172
185
|
|
173
|
-
After checking out the repo, run `bin/setup
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
186
|
+
After checking out the repo, run `bin/setup`. You need the `docker` daemon installed and running. This script will:
|
187
|
+
* Install gem dependencies
|
188
|
+
* Pull the latest `redislabs/redistimeseries` image
|
189
|
+
* Start a Redis server on port 6379
|
190
|
+
* Seed three time series with some sample data
|
191
|
+
* Attach to the running server and print logs to `STDOUT`
|
179
192
|
|
180
|
-
|
193
|
+
With the above script running, or after starting a server manually, you can run `bin/console` to interact with it. The three series are named `ts1`, `ts2`, and `ts3`, and are available as instance variables in the console.
|
181
194
|
|
182
195
|
If you want to see the commands being executed, run the console with `DEBUG=true bin/console` and it will output the raw command strings as they're executed.
|
183
196
|
```ruby
|
184
197
|
[1] pry(main)> @ts1.increment
|
185
|
-
DEBUG: TS.INCRBY
|
198
|
+
DEBUG: TS.INCRBY ts1 1
|
186
199
|
=> 1593159795467
|
187
200
|
[2] pry(main)> @ts1.get
|
188
|
-
DEBUG: TS.GET
|
201
|
+
DEBUG: TS.GET ts1
|
189
202
|
=> #<Redis::TimeSeries::Sample:0x00007f8e1a190cf8 @time=2020-06-26 01:23:15 -0700, @value=0.4e1>
|
190
203
|
```
|
191
204
|
|
205
|
+
Use `rake spec` to run the test suite.
|
206
|
+
|
192
207
|
## Contributing
|
193
208
|
|
194
209
|
Bug reports and pull requests are welcome on GitHub at https://github.com/dzunk/redis-time-series.
|
data/bin/console
CHANGED
@@ -6,15 +6,9 @@ require 'pry'
|
|
6
6
|
require 'redis'
|
7
7
|
require 'redis-time-series'
|
8
8
|
|
9
|
-
Redis.
|
10
|
-
|
11
|
-
@
|
12
|
-
@ts2 = Redis::TimeSeries.create('bar')
|
13
|
-
@ts3 = Redis::TimeSeries.create('baz')
|
14
|
-
|
9
|
+
@ts1 = Redis::TimeSeries.new('ts1')
|
10
|
+
@ts2 = Redis::TimeSeries.new('ts2')
|
11
|
+
@ts3 = Redis::TimeSeries.new('ts3')
|
15
12
|
@series = [@ts1, @ts2, @ts3]
|
16
|
-
@series.each do |ts|
|
17
|
-
3.times { ts.increment; sleep 0.01 }
|
18
|
-
end
|
19
13
|
|
20
14
|
Pry.start
|
data/bin/setup
CHANGED
@@ -1,8 +1,30 @@
|
|
1
|
-
#!/usr/bin/env
|
2
|
-
set -euo pipefail
|
3
|
-
IFS=$'\n\t'
|
4
|
-
set -vx
|
1
|
+
#!/usr/bin/env ruby
|
5
2
|
|
6
|
-
bundle install
|
3
|
+
system 'bundle install'
|
4
|
+
system 'docker pull redislabs/redistimeseries:latest'
|
5
|
+
container_id = `docker run -p 6379:6379 -dit --rm redislabs/redistimeseries`
|
7
6
|
|
8
|
-
|
7
|
+
require 'bundler/setup'
|
8
|
+
require 'active_support/core_ext/numeric/time'
|
9
|
+
require 'redis'
|
10
|
+
|
11
|
+
Redis.current.flushall
|
12
|
+
ts1 = Redis::TimeSeries.create('ts1')
|
13
|
+
ts2 = Redis::TimeSeries.create('ts2')
|
14
|
+
ts3 = Redis::TimeSeries.create('ts3')
|
15
|
+
|
16
|
+
ts1.add 12, 6.minutes.ago
|
17
|
+
ts1.add 34, 4.minutes.ago
|
18
|
+
ts1.add 56, 2.minutes.ago
|
19
|
+
|
20
|
+
10.times { ts2.increment; sleep 0.01 }
|
21
|
+
|
22
|
+
ts3.labels = { foo: 'bar' }
|
23
|
+
ts3.add 1
|
24
|
+
sleep 0.01
|
25
|
+
ts3.incrby 2
|
26
|
+
sleep 0.01
|
27
|
+
ts3.decrement
|
28
|
+
|
29
|
+
at_exit { system "docker stop #{container_id}" }
|
30
|
+
system "docker logs -f #{container_id}"
|
data/lib/redis-time-series.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'bigdecimal'
|
2
2
|
require 'forwardable'
|
3
3
|
require 'time/msec'
|
4
|
+
require 'redis/time_series/filter'
|
4
5
|
require 'redis/time_series/info'
|
5
|
-
require 'redis/time_series'
|
6
6
|
require 'redis/time_series/sample'
|
7
|
+
require 'redis/time_series'
|
7
8
|
|
8
9
|
class RedisTimeSeries
|
9
|
-
VERSION = '0.
|
10
|
+
VERSION = '0.3.0'
|
10
11
|
end
|
data/lib/redis/time_series.rb
CHANGED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Redis
|
3
|
+
class TimeSeries
|
4
|
+
class Filter
|
5
|
+
Equal = Struct.new(:label, :value) do
|
6
|
+
self::REGEX = /^[^!]+=[^(]+/
|
7
|
+
|
8
|
+
def self.parse(str)
|
9
|
+
new(*str.split('='))
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{label}=#{value}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
NotEqual = Struct.new(:label, :value) do
|
18
|
+
self::REGEX = /^.+!=[^(]+/
|
19
|
+
|
20
|
+
def self.parse(str)
|
21
|
+
new(*str.split('!='))
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{label}!=#{value}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Absent = Struct.new(:label) do
|
30
|
+
self::REGEX = /^[^!]+=$/
|
31
|
+
|
32
|
+
def self.parse(str)
|
33
|
+
new(str.delete('='))
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"#{label}="
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Present = Struct.new(:label) do
|
42
|
+
self::REGEX = /^.+!=$/
|
43
|
+
|
44
|
+
def self.parse(str)
|
45
|
+
new(str.delete('!='))
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"#{label}!="
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
AnyValue = Struct.new(:label, :values) do
|
54
|
+
self::REGEX = /^[^!]+=\(.+\)/
|
55
|
+
|
56
|
+
def self.parse(str)
|
57
|
+
label, values = str.split('=')
|
58
|
+
values = values.tr('()', '').split(',')
|
59
|
+
new(label, values)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"#{label}=(#{values.join(',')})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
NoValues = Struct.new(:label, :values) do
|
68
|
+
self::REGEX = /^.+!=\(.+\)/
|
69
|
+
|
70
|
+
def self.parse(str)
|
71
|
+
label, values = str.split('!=')
|
72
|
+
values = values.tr('()', '').split(',')
|
73
|
+
new(label, values)
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"#{label}!=(#{values.join(',')})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
TYPES = [Equal, NotEqual, Absent, Present, AnyValue, NoValues]
|
82
|
+
TYPES.each do |type|
|
83
|
+
define_method "#{type.to_s.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase}_filters" do
|
84
|
+
filters.select { |f| f.is_a? type }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_reader :filters
|
89
|
+
|
90
|
+
def initialize(filters = nil)
|
91
|
+
filters = parse_string(filters) if filters.is_a?(String)
|
92
|
+
@filters = filters.presence || {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate!
|
96
|
+
valid? || raise('Filtering requires at least one equality comparison')
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid?
|
100
|
+
!!filters.find { |f| f.is_a? Equal }
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_a
|
104
|
+
filters.map(&:to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def parse_string(filter_string)
|
110
|
+
filter_string.split(' ').map do |str|
|
111
|
+
match = TYPES.find { |f| f::REGEX.match? str }
|
112
|
+
raise "Unable to parse '#{str}'" unless match
|
113
|
+
match.parse(str)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-time-series
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Duszynski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- bin/setup
|
115
115
|
- lib/redis-time-series.rb
|
116
116
|
- lib/redis/time_series.rb
|
117
|
+
- lib/redis/time_series/filter.rb
|
117
118
|
- lib/redis/time_series/info.rb
|
118
119
|
- lib/redis/time_series/sample.rb
|
119
120
|
- lib/time/msec.rb
|