redis-time-series 0.1.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23852f9c6baca09369bbcce49642070c13d1212ba2ca9e7eec93dd691702eb72
4
- data.tar.gz: 9edac086b6fc8119382bac0777160d96279df597cdf7cb4f84d27a36532118d2
3
+ metadata.gz: 32031c61eb040add6088dc94a31be33c08108d9127da7e006bb377a247340d03
4
+ data.tar.gz: 8d2cccb22bd414a9fefbcbcdda8b52cce58d6d55e4c3ae67750e699e8aca8d60
5
5
  SHA512:
6
- metadata.gz: c8efbc6e6426d316715fc134f71d5d2d7bc334ac2d088340eba39d050185fdbd5cbf2a8d7c6cd80274e9bb3b53766905c30124d551b0d5b863180cb12c89eec0
7
- data.tar.gz: 5a1d988f13fbace8a269b7156fea1858072a15d030439fbad38e2dfcf720503c71182267d424c5557f8bacf61305975c75c35ad2050c8e307c3b46fb8dc8b5f8
6
+ metadata.gz: f6c008474c8e8d3a297f0a2f7953a784ee5a530423c3f6be6f4d9953277bb182ed1bdafe2edb52964afdfac9c4d2cd66be32a87e1f3ac7290e011a0059b94464
7
+ data.tar.gz: 043e562af28011213dfeb6692e61b803a84dbb87cff0d49fad7f9fc46e2650f19b44f5637efeb5810ecefaff1683ee4f14bd41cc0a88bb90808480f9f7f9a616
@@ -5,6 +5,8 @@ on:
5
5
  branches: [ master ]
6
6
  pull_request:
7
7
  branches: [ master ]
8
+ schedule:
9
+ - cron: '0 0 * * *'
8
10
 
9
11
  jobs:
10
12
  spec:
@@ -14,13 +16,28 @@ jobs:
14
16
  image: redislabs/redistimeseries:latest
15
17
  ports:
16
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 }}
17
23
  steps:
18
24
  - uses: actions/checkout@v2
19
25
  - name: Set up Ruby
20
26
  uses: ruby/setup-ruby@v1
21
27
  with:
22
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
23
34
  - name: Install dependencies
24
35
  run: bundle install
25
36
  - name: Run specs
26
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/
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.5.1
6
+ * Update Info struct for RTS 1.4 compatibility (#45)
7
+
8
+ ## 0.5.0
9
+ * Fix aggregations for TS.RANGE command (#34)
10
+ * Extract client handling into Client module (#32)
11
+ * Add `uncompressed` param to TS.ADD, TS.INCRBY, TS.DECRBY (#35)
12
+ * Add `Redis::TimeSeries::Rule` object (#38)
13
+ * Add [YARD documentation](https://rubydoc.info/gems/redis-time-series) (#40)
14
+
15
+ ## 0.4.0
16
+ * Added [hash-based filter DSL](https://github.com/dzunk/redis-time-series/tree/7173c73588da50614c02f9c89bf2ecef77766a78#filter-dsl)
17
+ * Removed `Time#ts_msec` monkey-patch
18
+ * Renamed `TimeSeries.queryindex` to `.query_index`
19
+ * Added `TS.CREATERULE` and `TS.DELETERULE` commands
20
+ * Renamed `InvalidFilters` to `FilterError`
21
+
22
+ ## 0.3.0
23
+ * Added `TS.QUERYINDEX` command
24
+
25
+ ## 0.2.0
26
+ * Converted `#info` to a struct instead of a hash.
27
+ * Added methods on time series for getting info attributes.
28
+
3
29
  ## 0.1.1
4
30
  Fix setting labels on `TS.CREATE` and `TS.ALTER`
5
31
 
@@ -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.1)
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.2)
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,4 +1,8 @@
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)
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)
2
6
 
3
7
  # RedisTimeSeries
4
8
 
@@ -72,6 +76,10 @@ Add a single value with a timestamp
72
76
  ```ruby
73
77
  ts.add 1234, 3.minutes.ago # Used ActiveSupport here, but any Time object works fine
74
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>
75
83
  ```
76
84
  Add multiple values with timestamps
77
85
  ```ruby
@@ -88,6 +96,10 @@ ts.increment # alias of incrby
88
96
  => 1593154255069
89
97
  ts.decrement # alias of decrby
90
98
  => 1593154257344
99
+
100
+ # Optionally store data uncompressed
101
+ ts.incrby 4, uncompressed: true
102
+ => 1595139299769
91
103
  ```
92
104
  ```ruby
93
105
  ts.get
@@ -119,61 +131,203 @@ ts.get
119
131
  ```
120
132
  Get a range of values
121
133
  ```ruby
122
- 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)
123
136
  => [#<Redis::TimeSeries::Sample:0x00007fa25f13fc28 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
124
- #<Redis::TimeSeries::Sample:0x00007fa25f13db58 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
125
- #<Redis::TimeSeries::Sample:0x00007fa25f13d900 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
126
- #<Redis::TimeSeries::Sample:0x00007fa25f13d680 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
127
- 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)
128
143
  => [#<Redis::TimeSeries::Sample:0x00007fa25dc01f00 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
129
- #<Redis::TimeSeries::Sample:0x00007fa25dc01d20 @time=2020-06-25 23:50:55 -0700, @value=0.58e2>,
130
- #<Redis::TimeSeries::Sample:0x00007fa25dc01b68 @time=2020-06-25 23:50:57 -0700, @value=0.57e2>,
131
- #<Redis::TimeSeries::Sample:0x00007fa25dc019b0 @time=2020-06-25 23:51:30 -0700, @value=0.58e2>]
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)
150
+ => [#<Redis::TimeSeries::Sample:0x00007fa25dc01f00 @time=2020-06-25 23:50:51 -0700, @value=0.57e2>,
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>]
132
156
  ```
133
157
  Get info about the series
134
158
  ```ruby
135
159
  ts.info
136
- => {"total_samples"=>3,
137
- "memory_usage"=>4184,
138
- "first_timestamp"=>1593155422582,
139
- "last_timestamp"=>1593155709333,
140
- "retention_time"=>0,
141
- "chunk_count"=>1,
142
- "max_samples_per_chunk"=>256,
143
- "labels"=>[],
144
- "source_key"=>nil,
145
- "rules"=>[]}
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
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
146
254
  ```
147
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
+
302
+
148
303
  ### TODO
149
304
  * `TS.REVRANGE`
150
305
  * `TS.MRANGE`/`TS.MREVRANGE`
151
- * `TS.QUERYINDEX`
152
- * Compaction rules
153
- * Filters
154
306
  * Probably a bunch more stuff
155
307
 
156
308
  ## Development
157
309
 
158
- 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`
159
316
 
160
- In order to run the specs or use the console, you'll need a Redis server running on the default port 6379 with the RedisTimeSeries module enabled. The easiest way to do so is by running the Docker image:
161
- ```
162
- docker run -p 6379:6379 -it --rm redislabs/redistimeseries
163
- ```
164
-
165
- The `bin/console` script will set up three time series, `@ts1`, `@ts2`, and `@ts3`, with three values in each. **It will also flush the local Redis server each time you run it**, so don't try it if you have data you don't want to lose!
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.
166
318
 
167
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.
168
320
  ```ruby
169
321
  [1] pry(main)> @ts1.increment
170
- DEBUG: TS.INCRBY foo 1
322
+ DEBUG: TS.INCRBY ts1 1
171
323
  => 1593159795467
172
324
  [2] pry(main)> @ts1.get
173
- DEBUG: TS.GET foo
325
+ DEBUG: TS.GET ts1
174
326
  => #<Redis::TimeSeries::Sample:0x00007f8e1a190cf8 @time=2020-06-26 01:23:15 -0700, @value=0.4e1>
175
327
  ```
176
328
 
329
+ Use `rake spec` to run the test suite.
330
+
177
331
  ## Contributing
178
332
 
179
333
  Bug reports and pull requests are welcome on GitHub at https://github.com/dzunk/redis-time-series.
@@ -6,15 +6,9 @@ require 'pry'
6
6
  require 'redis'
7
7
  require 'redis-time-series'
8
8
 
9
- Redis.current.flushall
10
-
11
- @ts1 = Redis::TimeSeries.create('foo')
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 }
18
- end
19
13
 
20
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}"