redis-time-series 0.2.0 → 0.5.2

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: 51234a8f3542da255912f95982ebab2bf66dd657da9e38e55afdedd97029685d
4
- data.tar.gz: 42a49fdafb136d8ebe4b094f935b0427430672ab810691a35ca2f78328d92135
3
+ metadata.gz: 461a6f3842867d1ba51440e7576aad3e9b92ecd02700cd59e5508c0e8bbfcaa7
4
+ data.tar.gz: 0c03af91af0eaaa37917db2cad21417c38709a53b223ed2864bad0f9857fa1f7
5
5
  SHA512:
6
- metadata.gz: acc08d1745cf24bfc87c22d3809570daa0b2feae4e5d5b97d4a43a0626217b44ea3ffdb12c3f6799c068bb6e62f0cc7a1b65274e5d8eac5cb13017bfea73dff4
7
- data.tar.gz: 1ff7aa6fb44dd2729612ac24e2be45496d45bd5a0fc86ed692bd18c38bcb160971c0efed05e598ffabb6471cf4d6903d303a1d0b74e55672c479829b98b26012
6
+ metadata.gz: 56dcd4439b2f4f06469237d2925d84b2ffabc40e2dccb608d5489b78f0e2a5b299881487b55e67fb48f1c9d0f7f56951ee98254ae84229b1827fb4cf3683ca44
7
+ data.tar.gz: 860e638f906b9dc19358e6156ad6a0488452be0be499853926742073cf6e85d00f4e95755f1659feae9763334eebbab044147fa3d94512dbb2a91360ec08728b
@@ -16,13 +16,28 @@ jobs:
16
16
  image: redislabs/redistimeseries:latest
17
17
  ports:
18
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 }}
19
23
  steps:
20
24
  - uses: actions/checkout@v2
21
25
  - name: Set up Ruby
22
26
  uses: ruby/setup-ruby@v1
23
27
  with:
24
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
25
34
  - name: Install dependencies
26
35
  run: bundle install
27
36
  - name: Run specs
28
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/
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.2
6
+ * Add chunk_type to info struct (#47)
7
+
8
+ ## 0.5.1
9
+ * Update Info struct for RTS 1.4 compatibility (#45)
10
+
11
+ ## 0.5.0
12
+ * Fix aggregations for TS.RANGE command (#34)
13
+ * Extract client handling into Client module (#32)
14
+ * Add `uncompressed` param to TS.ADD, TS.INCRBY, TS.DECRBY (#35)
15
+ * Add `Redis::TimeSeries::Rule` object (#38)
16
+ * Add [YARD documentation](https://rubydoc.info/gems/redis-time-series) (#40)
17
+
18
+ ## 0.4.0
19
+ * Added [hash-based filter DSL](https://github.com/dzunk/redis-time-series/tree/7173c73588da50614c02f9c89bf2ecef77766a78#filter-dsl)
20
+ * Removed `Time#ts_msec` monkey-patch
21
+ * Renamed `TimeSeries.queryindex` to `.query_index`
22
+ * Added `TS.CREATERULE` and `TS.DELETERULE` commands
23
+ * Renamed `InvalidFilters` to `FilterError`
24
+
25
+ ## 0.3.0
26
+ * Added `TS.QUERYINDEX` command
27
+
5
28
  ## 0.2.0
6
29
  * Converted `#info` to a struct instead of a hash.
7
30
  * Added methods on time series for getting info attributes.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-time-series (0.1.1)
4
+ redis-time-series (0.5.2)
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.2.1)
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,31 +131,53 @@ 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
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>>,
137
163
  total_samples=3,
138
- memory_usage=4184,
139
- first_timestamp=1594060993011,
140
- last_timestamp=1594060993060,
164
+ memory_usage=4264,
165
+ first_timestamp=1595187993605,
166
+ last_timestamp=1595187993629,
141
167
  retention_time=0,
142
168
  chunk_count=1,
143
169
  max_samples_per_chunk=256,
144
170
  labels={"foo"=>"bar"},
145
171
  source_key=nil,
146
- rules=[]>
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
+
147
181
  # Each info property is also a method on the time series object
148
182
  ts.memory_usage
149
183
  => 4208
@@ -151,6 +185,7 @@ ts.labels
151
185
  => {"foo"=>"bar"}
152
186
  ts.total_samples
153
187
  => 3
188
+
154
189
  # Total samples also available as #count, #length, and #size
155
190
  ts.count
156
191
  => 3
@@ -159,36 +194,140 @@ ts.length
159
194
  ts.size
160
195
  => 3
161
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
+
162
302
 
163
303
  ### TODO
164
304
  * `TS.REVRANGE`
165
305
  * `TS.MRANGE`/`TS.MREVRANGE`
166
- * `TS.QUERYINDEX`
167
- * Compaction rules
168
- * Filters
169
306
  * Probably a bunch more stuff
170
307
 
171
308
  ## Development
172
309
 
173
- 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`
174
316
 
175
- 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:
176
- ```
177
- docker run -p 6379:6379 -it --rm redislabs/redistimeseries
178
- ```
179
-
180
- 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.
181
318
 
182
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.
183
320
  ```ruby
184
321
  [1] pry(main)> @ts1.increment
185
- DEBUG: TS.INCRBY foo 1
322
+ DEBUG: TS.INCRBY ts1 1
186
323
  => 1593159795467
187
324
  [2] pry(main)> @ts1.get
188
- DEBUG: TS.GET foo
325
+ DEBUG: TS.GET ts1
189
326
  => #<Redis::TimeSeries::Sample:0x00007f8e1a190cf8 @time=2020-06-26 01:23:15 -0700, @value=0.4e1>
190
327
  ```
191
328
 
329
+ Use `rake spec` to run the test suite.
330
+
192
331
  ## Contributing
193
332
 
194
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; sleep 0.01 }
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}"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The +TimeMsec+ module is a refinement for the +Time+ class that makes it easier
4
+ # to work with millisecond timestamps.
5
+ #
6
+ # @example
7
+ # Time.now.to_i # 1595194259
8
+ # Time.now.ts_msec # NoMethodError
9
+ #
10
+ # using TimeMsec
11
+ #
12
+ # Time.now.to_i # 1595194259
13
+ # Time.now.ts_msec # 1595194259000
14
+ #
15
+ # Time.from_msec(1595194259000) # 2020-07-19 14:30:59 -0700
16
+ module TimeMsec
17
+ refine Time do
18
+ # TODO: convert to #to_msec
19
+ def ts_msec
20
+ (to_f * 1000.0).to_i
21
+ end
22
+ end
23
+
24
+ refine Time.singleton_class do
25
+ def from_msec(timestamp)
26
+ at(timestamp / 1000.0)
27
+ end
28
+ end
29
+ end