redis-time-series 0.2.0 → 0.5.2

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: 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