redis-time-series 0.1.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2bb0c44197c39258529e093bb34aa0a04576743c4f3d07016f607f9d84cd15e
4
- data.tar.gz: 95ce400fcbbdeff4adcd326dd3927f535410be3180526c34068926c1908ac447
3
+ metadata.gz: 60ddc8ba631c4016031490a9eb8c3e8659d6b38d3f21e3798a253ec3f63b2815
4
+ data.tar.gz: 7993209c75f9f23ed0fae6f918020f2a7a793575e6636e57c1bc225db3f3a63d
5
5
  SHA512:
6
- metadata.gz: ea5a10679174714e5f7ad63fe00f093c0d65c2c5954d11c19ea5a6b52a1660d897cb63e8ba6f6c037ca7f31e5281857f90a9fe2485dc0b401d37f281bd82ec70
7
- data.tar.gz: c3cfb8e9632e9876b7cb39888fe89aab86e8becda9b64f3c6c2f4f78e4c58f59595255111820e89914c46a1a29d0a25c8c50f80c6b4311bb60e5b5141137c400
6
+ metadata.gz: 1bd033ed2dfef155ba923ebee94089e41f507dcb8fcb63711a56585703d1603c32ff86b99067641b8a248587925d2bf489affd79822118e5fb42b05dbcf1c374
7
+ data.tar.gz: 553ffbea629efff4b1b436faf7542f35204cd9a0f47c2cf28dcd3ecc0261bd399eb19ac57a56985fc3fbeaef0e4b2e24786483bc385e06928fedcbad4f6709d0
@@ -0,0 +1,43 @@
1
+ name: RSpec
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+ schedule:
9
+ - cron: '0 0 * * *'
10
+
11
+ jobs:
12
+ spec:
13
+ runs-on: ubuntu-latest
14
+ services:
15
+ redis:
16
+ image: redislabs/redistimeseries:latest
17
+ ports:
18
+ - 6379:6379/tcp
19
+ env:
20
+ CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
21
+ GIT_COMMIT_SHA: ${{ github.sha }}
22
+ GIT_BRANCH: ${{ github.head_ref }}
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: 2.6
29
+ - name: Set up CodeClimate
30
+ run: |
31
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
32
+ chmod +x ./cc-test-reporter
33
+ ./cc-test-reporter before-build
34
+ - name: Install dependencies
35
+ run: bundle install
36
+ - name: Run specs
37
+ run: bundle exec rake spec
38
+ - name: Upload coverage report
39
+ run: ./cc-test-reporter after-build -t simplecov coverage/.resultset.json
40
+ - uses: actions/upload-artifact@v2
41
+ with:
42
+ name: coverage
43
+ path: coverage/
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.5.0
6
+ * Fix aggregations for TS.RANGE command (#34)
7
+ * Extract client handling into Client module (#32)
8
+ * Add `uncompressed` param to TS.ADD, TS.INCRBY, TS.DECRBY (#35)
9
+ * Add `Redis::TimeSeries::Rule` object (#38)
10
+ * Add [YARD documentation](https://rubydoc.info/gems/redis-time-series) (#40)
11
+
12
+ ## 0.4.0
13
+ * Added [hash-based filter DSL](https://github.com/dzunk/redis-time-series/tree/7173c73588da50614c02f9c89bf2ecef77766a78#filter-dsl)
14
+ * Removed `Time#ts_msec` monkey-patch
15
+ * Renamed `TimeSeries.queryindex` to `.query_index`
16
+ * Added `TS.CREATERULE` and `TS.DELETERULE` commands
17
+ * Renamed `InvalidFilters` to `FilterError`
18
+
19
+ ## 0.3.0
20
+ * Added `TS.QUERYINDEX` command
21
+
22
+ ## 0.2.0
23
+ * Converted `#info` to a struct instead of a hash.
24
+ * Added methods on time series for getting info attributes.
25
+
26
+ ## 0.1.1
27
+ Fix setting labels on `TS.CREATE` and `TS.ALTER`
28
+
29
+ ## 0.1.0
30
+
31
+ Basic functionality. Includes commands:
32
+ * `TS.CREATE`
33
+ * `TS.ALTER`
34
+ * `TS.ADD`
35
+ * `TS.MADD`
36
+ * `TS.INCRBY`
37
+ * `TS.DECRBY`
38
+ * `TS.RANGE`
39
+ * `TS.GET`
40
+ * `TS.INFO`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-time-series (0.1.0)
4
+ redis-time-series (0.5.0)
5
5
  redis (~> 4.0)
6
6
 
7
7
  GEM
@@ -16,15 +16,17 @@ GEM
16
16
  coderay (1.1.3)
17
17
  concurrent-ruby (1.1.6)
18
18
  diff-lcs (1.3)
19
+ docile (1.3.2)
19
20
  i18n (1.8.3)
20
21
  concurrent-ruby (~> 1.0)
22
+ json (2.3.1)
21
23
  method_source (1.0.0)
22
24
  minitest (5.14.1)
23
25
  pry (0.13.1)
24
26
  coderay (~> 1.1)
25
27
  method_source (~> 1.0)
26
28
  rake (13.0.1)
27
- redis (4.1.4)
29
+ redis (4.2.1)
28
30
  rspec (3.9.0)
29
31
  rspec-core (~> 3.9.0)
30
32
  rspec-expectations (~> 3.9.0)
@@ -38,6 +40,11 @@ GEM
38
40
  diff-lcs (>= 1.2.0, < 2.0)
39
41
  rspec-support (~> 3.9.0)
40
42
  rspec-support (3.9.3)
43
+ simplecov (0.17.1)
44
+ docile (~> 1.1)
45
+ json (>= 1.8, < 3)
46
+ simplecov-html (~> 0.10.0)
47
+ simplecov-html (0.10.2)
41
48
  thread_safe (0.3.6)
42
49
  tzinfo (1.2.7)
43
50
  thread_safe (~> 0.1)
@@ -53,6 +60,7 @@ DEPENDENCIES
53
60
  rake (~> 13.0)
54
61
  redis-time-series!
55
62
  rspec (~> 3.0)
63
+ simplecov (< 0.18)
56
64
 
57
65
  BUNDLED WITH
58
66
  1.17.2
data/README.md CHANGED
@@ -1,3 +1,9 @@
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)
3
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-brightgreen)](https://rubydoc.info/gems/redis-time-series)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/19a5925c20318508b4a4/maintainability)](https://codeclimate.com/github/dzunk/redis-time-series/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/19a5925c20318508b4a4/test_coverage)](https://codeclimate.com/github/dzunk/redis-time-series/test_coverage)
6
+
1
7
  # RedisTimeSeries
2
8
 
3
9
  A Ruby adapter for the [RedisTimeSeries module](https://oss.redislabs.com/redistimeseries).
@@ -10,6 +16,7 @@ docker run -p 6379:6379 -it --rm redislabs/redistimeseries
10
16
 
11
17
  **TL;DR**
12
18
  ```ruby
19
+ require 'redis-time-series'
13
20
  ts = Redis::TimeSeries.new('foo')
14
21
  ts.add 1234
15
22
  => #<Redis::TimeSeries::Sample:0x00007f8c0d2561d8 @time=2020-06-25 23:23:04 -0700, @value=0.1234e4>
@@ -48,7 +55,7 @@ Create a series (issues `TS.CREATE` command) and return a Redis::TimeSeries obje
48
55
  ```ruby
49
56
  ts = Redis::TimeSeries.create(
50
57
  'your_ts_key',
51
- labels: ['foo', 'bar'],
58
+ labels: { foo: 'bar' },
52
59
  retention: 600,
53
60
  uncompressed: false,
54
61
  redis: Redis.new(url: ENV['REDIS_URL']) # defaults to Redis.current
@@ -69,6 +76,10 @@ Add a single value with a timestamp
69
76
  ```ruby
70
77
  ts.add 1234, 3.minutes.ago # Used ActiveSupport here, but any Time object works fine
71
78
  => #<Redis::TimeSeries::Sample:0x00007fa6ce05f3f8 @time=2020-06-25 23:39:54 -0700, @value=0.1234e4>
79
+
80
+ # Optionally store data uncompressed
81
+ ts.add 5678, uncompressed: true
82
+ => #<Redis::TimeSeries::Sample:0x00007f93f43cdf68 @time=2020-07-18 23:15:29 -0700, @value=0.5678e4>
72
83
  ```
73
84
  Add multiple values with timestamps
74
85
  ```ruby
@@ -85,6 +96,10 @@ ts.increment # alias of incrby
85
96
  => 1593154255069
86
97
  ts.decrement # alias of decrby
87
98
  => 1593154257344
99
+
100
+ # Optionally store data uncompressed
101
+ ts.incrby 4, uncompressed: true
102
+ => 1595139299769
88
103
  ```
89
104
  ```ruby
90
105
  ts.get
@@ -116,31 +131,202 @@ ts.get
116
131
  ```
117
132
  Get a range of values
118
133
  ```ruby
119
- ts.range 10.minutes.ago..Time.current # Time range as an argument
134
+ # Time range as an argument
135
+ ts.range(10.minutes.ago..Time.current)
120
136
  => [#<Redis::TimeSeries::Sample:0x00007fa25f13fc28 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
121
- #<Redis::TimeSeries::Sample:0x00007fa25f13db58 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
122
- #<Redis::TimeSeries::Sample:0x00007fa25f13d900 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
123
- #<Redis::TimeSeries::Sample:0x00007fa25f13d680 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
124
- ts.range from: 10.minutes.ago, to: Time.current # Time range as keyword args
137
+ #<Redis::TimeSeries::Sample:0x00007fa25f13db58 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
138
+ #<Redis::TimeSeries::Sample:0x00007fa25f13d900 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
139
+ #<Redis::TimeSeries::Sample:0x00007fa25f13d680 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
140
+
141
+ # Time range as keyword args
142
+ ts.range(from: 10.minutes.ago, to: Time.current)
143
+ => [#<Redis::TimeSeries::Sample:0x00007fa25dc01f00 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
144
+ #<Redis::TimeSeries::Sample:0x00007fa25dc01d20 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
145
+ #<Redis::TimeSeries::Sample:0x00007fa25dc01b68 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
146
+ #<Redis::TimeSeries::Sample:0x00007fa25dc019b0 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
147
+
148
+ # Limit number of results with count argument
149
+ ts.range(10.minutes.ago..Time.current, count: 2)
125
150
  => [#<Redis::TimeSeries::Sample:0x00007fa25dc01f00 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
126
- #<Redis::TimeSeries::Sample:0x00007fa25dc01d20 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
127
- #<Redis::TimeSeries::Sample:0x00007fa25dc01b68 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
128
- #<Redis::TimeSeries::Sample:0x00007fa25dc019b0 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
151
+ #<Redis::TimeSeries::Sample:0x00007fa25dc01d20 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>]
152
+
153
+ # Apply aggregations to the range
154
+ ts.range(from: 10.minutes.ago, to: Time.current, aggregation: [:avg, 10.minutes])
155
+ => [#<Redis::TimeSeries::Sample:0x00007fa25dc01f00 @time=2020-06-25 23:50:00 -0700, @value=0.575e2>]
156
+ ```
157
+ Get info about the series
158
+ ```ruby
159
+ ts.info
160
+ => #<struct Redis::TimeSeries::Info
161
+ series=
162
+ #<Redis::TimeSeries:0x00007ff46da9b578 @key="ts3", @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>>,
163
+ total_samples=3,
164
+ memory_usage=4264,
165
+ first_timestamp=1595187993605,
166
+ last_timestamp=1595187993629,
167
+ retention_time=0,
168
+ chunk_count=1,
169
+ max_samples_per_chunk=256,
170
+ labels={"foo"=>"bar"},
171
+ source_key=nil,
172
+ rules=
173
+ [#<Redis::TimeSeries::Rule:0x00007ff46db30c68
174
+ @aggregation=#<Redis::TimeSeries::Aggregation:0x00007ff46db30c18 @duration=3600000, @type="avg">,
175
+ @destination_key="ts1",
176
+ @source=
177
+ #<Redis::TimeSeries:0x00007ff46da9b578
178
+ @key="ts3",
179
+ @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>>>]>
180
+
181
+ # Each info property is also a method on the time series object
182
+ ts.memory_usage
183
+ => 4208
184
+ ts.labels
185
+ => {"foo"=>"bar"}
186
+ ts.total_samples
187
+ => 3
188
+
189
+ # Total samples also available as #count, #length, and #size
190
+ ts.count
191
+ => 3
192
+ ts.length
193
+ => 3
194
+ ts.size
195
+ => 3
129
196
  ```
197
+ Find series matching specific label(s)
198
+ ```ruby
199
+ Redis::TimeSeries.query_index('foo=bar')
200
+ => [#<Redis::TimeSeries:0x00007fc115ba1610
201
+ @key="ts3",
202
+ @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>,
203
+ @retention=nil,
204
+ @uncompressed=false>]
205
+ # Note that you need at least one "label equals value" filter
206
+ Redis::TimeSeries.query_index('foo!=bar')
207
+ => RuntimeError: Filtering requires at least one equality comparison
208
+ # query_index is also aliased as .where for fluency
209
+ Redis::TimeSeries.where('foo=bar')
210
+ => [#<Redis::TimeSeries:0x00007fb8981010c8
211
+ @key="ts3",
212
+ @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>,
213
+ @retention=nil,
214
+ @uncompressed=false>]
215
+ ```
216
+ ### Filter DSL
217
+ You can provide filter strings directly, per the time series documentation.
218
+ ```ruby
219
+ Redis::TimeSeries.where('foo=bar')
220
+ => [#<Redis::TimeSeries:0x00007fb8981010c8...>]
221
+ ```
222
+ There is also a hash-based syntax available, which may be more pleasant to work with.
223
+ ```ruby
224
+ Redis::TimeSeries.where(foo: 'bar')
225
+ => [#<Redis::TimeSeries:0x00007fb89811dca0...>]
226
+ ```
227
+ All six filter types are represented in hash format below.
228
+ ```ruby
229
+ {
230
+ foo: 'bar', # label=value (equality)
231
+ foo: { not: 'bar' }, # label!=value (inequality)
232
+ foo: true, # label= (presence)
233
+ foo: false, # label!= (absence)
234
+ foo: [1, 2], # label=(1,2) (any value)
235
+ foo: { not: [1, 2] } # label!=(1,2) (no values)
236
+ }
237
+ ```
238
+ Note the special use of `true` and `false`. If you're representing a boolean value with a label, rather than setting its value to "true" or "false" (which would be treated as strings in Redis anyway), you should add or remove the label from the series.
239
+
240
+ Values can be any object that responds to `.to_s`:
241
+ ```ruby
242
+ class Person
243
+ def initialize(name)
244
+ @name = name
245
+ end
246
+
247
+ def to_s
248
+ @name
249
+ end
250
+ end
251
+
252
+ Redis::TimeSeries.where(person: Person.new('John'))
253
+ #=> TS.QUERYINDEX person=John
254
+ ```
255
+
256
+ ### Compaction Rules
257
+ Add a compaction rule to a series.
258
+ ```ruby
259
+ # Destintation time series needs to be created before the rule is added.
260
+ other_ts = Redis::TimeSeries.create('other_ts')
261
+
262
+ # Aggregation buckets are measured in milliseconds
263
+ ts.create_rule(dest: other_ts, aggregation: [:count, 60000]) # 1 minute
264
+
265
+ # Can provide a string key instead of a time series object
266
+ ts.create_rule(dest: 'other_ts', aggregation: [:avg, 120000])
267
+
268
+ # If you're using Rails or ActiveSupport, you can provide an
269
+ # ActiveSupport::Duration instead of an integer
270
+ ts.create_rule(dest: other_ts, aggregation: [:avg, 2.minutes])
271
+
272
+ # Can also provide an Aggregation object instead of an array
273
+ agg = Redis::TimeSeries::Aggregation.new(:avg, 120000)
274
+ ts.create_rule(dest: other_ts, aggregation: agg)
275
+
276
+ # Class-level method also available
277
+ Redis::TimeSeries.create_rule(source: ts, dest: other_ts, aggregation: ['std.p', 150000])
278
+ ```
279
+ Get existing compaction rules
280
+ ```ruby
281
+ ts.rules
282
+ => [#<Redis::TimeSeries::Rule:0x00007ff46e91c728
283
+ @aggregation=#<Redis::TimeSeries::Aggregation:0x00007ff46e91c6d8 @duration=3600000, @type="avg">,
284
+ @destination_key="ts1",
285
+ @source=
286
+ #<Redis::TimeSeries:0x00007ff46da9b578 @key="ts3", @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>>>]
287
+
288
+ # Get properties of a rule too
289
+ ts.rules.first.aggregation
290
+ => #<Redis::TimeSeries::Aggregation:0x00007ff46d146d38 @duration=3600000, @type="avg">
291
+ ts.rules.first.destination
292
+ => #<Redis::TimeSeries:0x00007ff46d8a3d60 @key="ts1", @redis=#<Redis client v4.2.1 for redis://127.0.0.1:6379/0>>
293
+ ```
294
+
295
+ Remove an existing compaction rule
296
+ ```ruby
297
+ ts.delete_rule(dest: 'other_ts')
298
+ ts.rules.first.delete
299
+ Redis::TimeSeries.delete_rule(source: ts, dest: 'other_ts')
300
+ ```
301
+
130
302
 
131
303
  ### TODO
132
304
  * `TS.REVRANGE`
133
305
  * `TS.MRANGE`/`TS.MREVRANGE`
134
- * `TS.QUERYINDEX`
135
- * Compaction rules
136
- * Filters
137
306
  * Probably a bunch more stuff
138
307
 
139
308
  ## Development
140
309
 
141
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
310
+ After checking out the repo, run `bin/setup`. You need the `docker` daemon installed and running. This script will:
311
+ * Install gem dependencies
312
+ * Pull the latest `redislabs/redistimeseries` image
313
+ * Start a Redis server on port 6379
314
+ * Seed three time series with some sample data
315
+ * Attach to the running server and print logs to `STDOUT`
316
+
317
+ 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.
318
+
319
+ 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.
320
+ ```ruby
321
+ [1] pry(main)> @ts1.increment
322
+ DEBUG: TS.INCRBY ts1 1
323
+ => 1593159795467
324
+ [2] pry(main)> @ts1.get
325
+ DEBUG: TS.GET ts1
326
+ => #<Redis::TimeSeries::Sample:0x00007f8e1a190cf8 @time=2020-06-26 01:23:15 -0700, @value=0.4e1>
327
+ ```
142
328
 
143
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
329
+ Use `rake spec` to run the test suite.
144
330
 
145
331
  ## Contributing
146
332
 
@@ -3,17 +3,12 @@
3
3
  require 'bundler/setup'
4
4
  require 'active_support/core_ext/numeric/time'
5
5
  require 'pry'
6
+ require 'redis'
6
7
  require 'redis-time-series'
7
8
 
8
- Redis.current.flushall
9
-
10
- @ts1 = Redis::TimeSeries.create('foo')
11
- @ts2 = Redis::TimeSeries.create('bar')
12
- @ts3 = Redis::TimeSeries.create('baz')
13
-
9
+ @ts1 = Redis::TimeSeries.new('ts1')
10
+ @ts2 = Redis::TimeSeries.new('ts2')
11
+ @ts3 = Redis::TimeSeries.new('ts3')
14
12
  @series = [@ts1, @ts2, @ts3]
15
- @series.each do |ts|
16
- 3.times { ts.increment }
17
- end
18
13
 
19
14
  Pry.start
data/bin/setup CHANGED
@@ -1,8 +1,31 @@
1
- #!/usr/bin/env bash
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`
6
+ at_exit { system "docker stop #{container_id}" }
7
7
 
8
- # Do any other automated setup that you need to do here
8
+ require 'bundler/setup'
9
+ require 'active_support/core_ext/numeric/time'
10
+ require 'redis'
11
+ require 'redis-time-series'
12
+
13
+ Redis.current.flushall
14
+ ts1 = Redis::TimeSeries.create('ts1')
15
+ ts2 = Redis::TimeSeries.create('ts2')
16
+ ts3 = Redis::TimeSeries.create('ts3')
17
+
18
+ ts1.add 12, 6.minutes.ago
19
+ ts1.add 34, 4.minutes.ago
20
+ ts1.add 56, 2.minutes.ago
21
+
22
+ 10.times { ts2.increment; sleep 0.01 }
23
+
24
+ ts3.labels = { foo: 'bar' }
25
+ ts3.add 1
26
+ sleep 0.01
27
+ ts3.incrby 2
28
+ sleep 0.01
29
+ ts3.decrement
30
+
31
+ system "docker logs -f #{container_id}"