tally 1.0.2 → 2.1.0
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 +4 -4
- data/README.md +24 -3
- data/app/jobs/tally/archiver_job.rb +18 -0
- data/app/jobs/tally/sweeper_job.rb +11 -0
- data/lib/tally/daily.rb +14 -22
- data/lib/tally/increment.rb +2 -2
- data/lib/tally/key_finder.rb +17 -7
- data/lib/tally/sweeper.rb +1 -1
- data/lib/tally/version.rb +1 -1
- data/lib/tally.rb +18 -2
- data/lib/tasks/tally_tasks.rake +7 -7
- metadata +24 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1c7ca6dc7f3a11bd24936a64edb11c8b18a21ef0788b09598a45a5eb746237d
|
4
|
+
data.tar.gz: 112af234512f74e6e284ed8f210084c586bc7eb3b36a7f1c1c12a9d21f373403
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61eb9b0fc574bfcbc23585cdef758bde1b9f9e57afcff608b7b99131ce6b847dedd051d7d358fcc22287547a473da4f75ad3b1e4ece5951ad23dcbc58e253e7e
|
7
|
+
data.tar.gz: 455f9248a7f3427ef635209b806e7b69a17dace683821621b47bcd0608f3efa9d4257575c86fcb9bfad2517d768a7cd47b80c0f6312b25fe1c156f54088130f5
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Tally
|
2
2
|
|
3
|
-
[](https://github.com/jdtornow/tally/actions/workflows/tests.yml)
|
4
4
|
|
5
5
|
Tally is a simple Rails engine for capturing counts of various activities around an app. These counts are quickly captured in Redis then are archived periodically within the app's default relational database.
|
6
6
|
|
@@ -12,8 +12,8 @@ _[Read more about Tally in my blog post introducing it ...](https://johntornow.c
|
|
12
12
|
|
13
13
|
## Requirements
|
14
14
|
|
15
|
-
* Ruby
|
16
|
-
* Rails
|
15
|
+
* Ruby 3.2+
|
16
|
+
* Rails 7+
|
17
17
|
* Redis 4+
|
18
18
|
|
19
19
|
## Installation
|
@@ -204,10 +204,31 @@ The endpoints can be used to display JSON-formatted data from `Tally::Record`. T
|
|
204
204
|
|
205
205
|
Tally works _really_ well with [Sidekiq](https://github.com/mperham/sidekiq/), but it isn't required. If Sidekiq is installed in your app, Tally will use its connection pooling for Redis connections. If Sidekiq isn't in use, the `Redis.current` connection is used to store stats. If you'd like to override the specific connection used for Tally's redis store, you can do so by setting `Tally.redis_connection` to another instance of `Redis`. This can be useful to use an alternate Redis store for just stats, for example.
|
206
206
|
|
207
|
+
As of version 2.0.0 this connection is automatically used within a [ConnectionPool](https://github.com/mperham/connection_pool).
|
208
|
+
|
207
209
|
```ruby
|
208
210
|
# use an alternate Redis connection (for non-sidekiq integrations)
|
209
211
|
Tally.redis_connection = Redis.new(...)
|
210
212
|
```
|
213
|
+
|
214
|
+
Alternatively, you can just set the config for Redis and `Redis.new` will be called for you:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
# provide Redis config
|
218
|
+
Tally.config.redis_config = {
|
219
|
+
driver: :ruby,
|
220
|
+
url: "redis://127.0.0.1:6379/10",
|
221
|
+
ssl_params: {
|
222
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
# then Tally uses the connection within a pool:
|
227
|
+
Tally.redis do |connection|
|
228
|
+
connection.incr("test")
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
211
232
|
## Issues
|
212
233
|
|
213
234
|
If you have any issues or find bugs running Tally, please [report them on Github](https://github.com/jdtornow/tally/issues).
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tally
|
4
|
+
class ArchiverJob < ApplicationJob
|
5
|
+
|
6
|
+
def perform(day = "today")
|
7
|
+
case day.to_s
|
8
|
+
when /^\d{4}-\d{2}-\d{2}$/
|
9
|
+
Tally::Archiver.archive! day: Date.parse(day)
|
10
|
+
when "yesterday"
|
11
|
+
Tally::Archiver.archive! day: 1.day.ago.to_date
|
12
|
+
else
|
13
|
+
Tally::Archiver.archive!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/tally/daily.rb
CHANGED
@@ -10,26 +10,6 @@ module Tally
|
|
10
10
|
|
11
11
|
private
|
12
12
|
|
13
|
-
def all_keys
|
14
|
-
@keys ||= build_keys_from_redis
|
15
|
-
end
|
16
|
-
|
17
|
-
def build_keys_from_redis
|
18
|
-
result = []
|
19
|
-
cursor = ""
|
20
|
-
|
21
|
-
scan = scan_from_redis
|
22
|
-
|
23
|
-
while cursor != "0"
|
24
|
-
result << scan.last
|
25
|
-
cursor = scan.first
|
26
|
-
|
27
|
-
scan = scan_from_redis(cursor: cursor)
|
28
|
-
end
|
29
|
-
|
30
|
-
result.flatten
|
31
|
-
end
|
32
|
-
|
33
13
|
def day_key
|
34
14
|
@day_key ||= day.strftime(Tally.config.date_format)
|
35
15
|
end
|
@@ -39,8 +19,20 @@ module Tally
|
|
39
19
|
end
|
40
20
|
|
41
21
|
def scan_from_redis(cursor: "0")
|
42
|
-
Tally.redis
|
43
|
-
|
22
|
+
klass = Tally.redis { |conn| conn.class.to_s }
|
23
|
+
|
24
|
+
# if we're using sidekiq / RedisClient, scan needs a block, and doesn't worry about the cursor
|
25
|
+
if klass == "Sidekiq::RedisClientAdapter::CompatClient"
|
26
|
+
Tally.redis do |conn|
|
27
|
+
[
|
28
|
+
"0", # fake cursor to match redis-rb output
|
29
|
+
conn.sscan(daily_key, "MATCH", scan_key, "COUNT", 25).to_a
|
30
|
+
]
|
31
|
+
end
|
32
|
+
else
|
33
|
+
Tally.redis do |conn|
|
34
|
+
conn.sscan(daily_key, cursor, match: scan_key, count: 25)
|
35
|
+
end
|
44
36
|
end
|
45
37
|
end
|
46
38
|
|
data/lib/tally/increment.rb
CHANGED
@@ -7,10 +7,10 @@ module Tally
|
|
7
7
|
Tally.redis do |conn|
|
8
8
|
conn.multi do |pipeline|
|
9
9
|
pipeline.incrby(redis_key, by)
|
10
|
-
pipeline.expire(redis_key, Tally.config.ttl) if Tally.config.ttl.present?
|
10
|
+
pipeline.expire(redis_key, Tally.config.ttl.to_i) if Tally.config.ttl.present?
|
11
11
|
|
12
12
|
pipeline.sadd(daily_key, simple_key)
|
13
|
-
pipeline.expire(daily_key, Tally.config.ttl) if Tally.config.ttl.present?
|
13
|
+
pipeline.expire(daily_key, Tally.config.ttl.to_i) if Tally.config.ttl.present?
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/tally/key_finder.rb
CHANGED
@@ -73,18 +73,16 @@ module Tally
|
|
73
73
|
|
74
74
|
def build_keys_from_redis
|
75
75
|
result = []
|
76
|
-
cursor =
|
77
|
-
|
78
|
-
scan = scan_from_redis
|
76
|
+
cursor = nil
|
79
77
|
|
80
78
|
while cursor != "0"
|
79
|
+
scan = scan_from_redis(cursor: cursor.presence || "0")
|
80
|
+
|
81
81
|
result << scan.last
|
82
82
|
cursor = scan.first
|
83
|
-
|
84
|
-
scan = scan_from_redis(cursor: cursor)
|
85
83
|
end
|
86
84
|
|
87
|
-
result.flatten
|
85
|
+
result.flatten.uniq
|
88
86
|
end
|
89
87
|
|
90
88
|
def day_key
|
@@ -100,7 +98,19 @@ module Tally
|
|
100
98
|
end
|
101
99
|
|
102
100
|
def scan_from_redis(cursor: "0")
|
103
|
-
Tally.redis { |conn| conn.
|
101
|
+
klass = Tally.redis { |conn| conn.class.to_s }
|
102
|
+
|
103
|
+
# if we're using sidekiq / RedisClient, scan needs a block, and doesn't worry about the cursor
|
104
|
+
if klass == "Sidekiq::RedisClientAdapter::CompatClient"
|
105
|
+
Tally.redis do |conn|
|
106
|
+
[
|
107
|
+
"0", # fake cursor to match redis-rb output
|
108
|
+
conn.scan("MATCH", scan_key).to_a
|
109
|
+
]
|
110
|
+
end
|
111
|
+
else
|
112
|
+
Tally.redis { |conn| conn.scan(cursor, match: scan_key) }
|
113
|
+
end
|
104
114
|
end
|
105
115
|
|
106
116
|
def scan_key
|
data/lib/tally/sweeper.rb
CHANGED
@@ -22,7 +22,7 @@ module Tally
|
|
22
22
|
|
23
23
|
def sweep!
|
24
24
|
Tally.redis do |conn|
|
25
|
-
purgeable_keys.in_groups_of(25, fill_with =
|
25
|
+
purgeable_keys.in_groups_of(25, fill_with = false).each do |group|
|
26
26
|
conn.pipelined do |pipeline|
|
27
27
|
group.each do |key|
|
28
28
|
pipeline.del(key)
|
data/lib/tally/version.rb
CHANGED
data/lib/tally.rb
CHANGED
@@ -46,6 +46,12 @@ module Tally
|
|
46
46
|
# Archivers get queued into the background with ActiveJob by default
|
47
47
|
# Set to :now to run inline
|
48
48
|
config.perform_calculators = :later
|
49
|
+
|
50
|
+
# override to set default redis configuration for Tally when Sidekiq is not present
|
51
|
+
config.redis_config = {}
|
52
|
+
|
53
|
+
# override to set connection pool details for Redis
|
54
|
+
config.redis_pool_config = {}
|
49
55
|
end
|
50
56
|
|
51
57
|
# If sidekiq is available, piggyback on its pooling
|
@@ -57,18 +63,28 @@ module Tally
|
|
57
63
|
if defined?(Sidekiq)
|
58
64
|
Sidekiq.redis(&block)
|
59
65
|
else
|
60
|
-
|
66
|
+
redis_pool.with(&block)
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
70
|
def self.redis_connection
|
65
|
-
@redis_connection ||= Redis.
|
71
|
+
@redis_connection ||= Redis.new(Tally.config.redis_config)
|
66
72
|
end
|
67
73
|
|
68
74
|
def self.redis_connection=(connection)
|
69
75
|
@redis_connection = connection
|
70
76
|
end
|
71
77
|
|
78
|
+
def self.redis_pool
|
79
|
+
@redis_pool ||= ConnectionPool.new(Tally.config.redis_pool_config) do
|
80
|
+
redis_connection
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.redis_pool=(pool)
|
85
|
+
@redis_pool = pool
|
86
|
+
end
|
87
|
+
|
72
88
|
def self.increment(*args)
|
73
89
|
Increment.public_send(:increment, *args)
|
74
90
|
end
|
data/lib/tasks/tally_tasks.rake
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
namespace :tally do
|
2
2
|
desc "Sweep all outdated keys from the data store"
|
3
|
-
task :
|
4
|
-
Tally::
|
3
|
+
task sweep: :environment do
|
4
|
+
Tally::SweeperJob.perform_now
|
5
5
|
|
6
6
|
Rake::Task["tally:wait_for_async_queue"].execute
|
7
7
|
end
|
8
8
|
|
9
9
|
desc "Archive today's temporary keys into record entries in the database"
|
10
|
-
task :
|
11
|
-
Tally::
|
10
|
+
task archive: :environment do
|
11
|
+
Tally::ArchiverJob.perform_now
|
12
12
|
|
13
13
|
Rake::Task["tally:wait_for_async_queue"].execute
|
14
14
|
end
|
15
15
|
|
16
16
|
desc "Archive yesterday's temporary keys into record entries in the database"
|
17
|
-
task "archive:yesterday"
|
18
|
-
Tally::
|
17
|
+
task "archive:yesterday": :environment do
|
18
|
+
Tally::ArchiverJob.perform_now("yesterday")
|
19
19
|
|
20
20
|
Rake::Task["tally:wait_for_async_queue"].execute
|
21
21
|
end
|
@@ -24,7 +24,7 @@ namespace :tally do
|
|
24
24
|
#
|
25
25
|
# This is not needed for other adapters besides async, but since it is the
|
26
26
|
# Rails default, we're accounting for it here.
|
27
|
-
task :
|
27
|
+
task wait_for_async_queue: :environment do
|
28
28
|
if Rails.application.config.active_job.queue_adapter == :async
|
29
29
|
ActiveJob::Base.queue_adapter.shutdown(wait: true)
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tally
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John D. Tornow
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-21 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rails
|
@@ -19,7 +18,7 @@ dependencies:
|
|
19
18
|
version: 5.2.0
|
20
19
|
- - "<"
|
21
20
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
21
|
+
version: '9'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +28,7 @@ dependencies:
|
|
29
28
|
version: 5.2.0
|
30
29
|
- - "<"
|
31
30
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
31
|
+
version: '9'
|
33
32
|
- !ruby/object:Gem::Dependency
|
34
33
|
name: redis
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,6 +43,20 @@ dependencies:
|
|
44
43
|
- - ">="
|
45
44
|
- !ruby/object:Gem::Version
|
46
45
|
version: '4.1'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: connection_pool
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '2.0'
|
53
|
+
type: :runtime
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.0'
|
47
60
|
- !ruby/object:Gem::Dependency
|
48
61
|
name: kaminari-activerecord
|
49
62
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,14 +91,14 @@ dependencies:
|
|
78
91
|
requirements:
|
79
92
|
- - "~>"
|
80
93
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
94
|
+
version: '6'
|
82
95
|
type: :development
|
83
96
|
prerelease: false
|
84
97
|
version_requirements: !ruby/object:Gem::Requirement
|
85
98
|
requirements:
|
86
99
|
- - "~>"
|
87
100
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
101
|
+
version: '6'
|
89
102
|
- !ruby/object:Gem::Dependency
|
90
103
|
name: factory_bot_rails
|
91
104
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,20 +127,6 @@ dependencies:
|
|
114
127
|
- - "~>"
|
115
128
|
- !ruby/object:Gem::Version
|
116
129
|
version: '5.1'
|
117
|
-
- !ruby/object:Gem::Dependency
|
118
|
-
name: simplecov
|
119
|
-
requirement: !ruby/object:Gem::Requirement
|
120
|
-
requirements:
|
121
|
-
- - "~>"
|
122
|
-
- !ruby/object:Gem::Version
|
123
|
-
version: '0.11'
|
124
|
-
type: :development
|
125
|
-
prerelease: false
|
126
|
-
version_requirements: !ruby/object:Gem::Requirement
|
127
|
-
requirements:
|
128
|
-
- - "~>"
|
129
|
-
- !ruby/object:Gem::Version
|
130
|
-
version: '0.11'
|
131
130
|
- !ruby/object:Gem::Dependency
|
132
131
|
name: rspec_junit_formatter
|
133
132
|
requirement: !ruby/object:Gem::Requirement
|
@@ -191,7 +190,9 @@ files:
|
|
191
190
|
- app/helpers/tally/application_helper.rb
|
192
191
|
- app/helpers/tally/increment_helper.rb
|
193
192
|
- app/jobs/tally/application_job.rb
|
193
|
+
- app/jobs/tally/archiver_job.rb
|
194
194
|
- app/jobs/tally/calculator_runner_job.rb
|
195
|
+
- app/jobs/tally/sweeper_job.rb
|
195
196
|
- app/models/tally/application_record.rb
|
196
197
|
- app/models/tally/record.rb
|
197
198
|
- app/presenters/tally/record_presenter.rb
|
@@ -222,7 +223,6 @@ metadata:
|
|
222
223
|
homepage_uri: https://github.com/jdtornow/tally
|
223
224
|
source_code_uri: https://github.com/jdtornow/tally
|
224
225
|
wiki_uri: https://github.com/jdtornow/tally/wiki
|
225
|
-
post_install_message:
|
226
226
|
rdoc_options: []
|
227
227
|
require_paths:
|
228
228
|
- lib
|
@@ -230,15 +230,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
230
230
|
requirements:
|
231
231
|
- - ">="
|
232
232
|
- !ruby/object:Gem::Version
|
233
|
-
version:
|
233
|
+
version: 3.0.3
|
234
234
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
235
235
|
requirements:
|
236
236
|
- - ">="
|
237
237
|
- !ruby/object:Gem::Version
|
238
238
|
version: 1.8.11
|
239
239
|
requirements: []
|
240
|
-
rubygems_version: 3.
|
241
|
-
signing_key:
|
240
|
+
rubygems_version: 3.6.3
|
242
241
|
specification_version: 4
|
243
242
|
summary: Stats collection and reporting
|
244
243
|
test_files: []
|