trifle-stats 1.3.1 → 1.6.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer.json +1 -1
  3. data/.devops/docker/codespaces/Dockerfile +1 -1
  4. data/.devops/docker/environment/Dockerfile +3 -2
  5. data/.devops/docker/local/Dockerfile +1 -0
  6. data/.devops/docker/local/docker-compose.yml +30 -0
  7. data/.github/workflows/ruby.yml +85 -6
  8. data/.gitignore +1 -0
  9. data/.ruby-version +1 -1
  10. data/.tool-versions +1 -1
  11. data/Gemfile +1 -1
  12. data/Gemfile.lock +8 -7
  13. data/README.md +31 -0
  14. data/lib/trifle/stats/aggregator/avg.rb +32 -0
  15. data/lib/trifle/stats/aggregator/max.rb +29 -0
  16. data/lib/trifle/stats/aggregator/min.rb +29 -0
  17. data/lib/trifle/stats/aggregator/sum.rb +29 -0
  18. data/lib/trifle/stats/driver/mongo.rb +81 -27
  19. data/lib/trifle/stats/driver/postgres.rb +14 -2
  20. data/lib/trifle/stats/driver/process.rb +12 -0
  21. data/lib/trifle/stats/driver/redis.rb +19 -4
  22. data/lib/trifle/stats/driver/sqlite.rb +15 -2
  23. data/lib/trifle/stats/formatter/category.rb +32 -0
  24. data/lib/trifle/stats/formatter/timeline.rb +29 -0
  25. data/lib/trifle/stats/mixins/packer.rb +16 -1
  26. data/lib/trifle/stats/nocturnal.rb +24 -0
  27. data/lib/trifle/stats/operations/status/beam.rb +31 -0
  28. data/lib/trifle/stats/operations/status/scan.rb +35 -0
  29. data/lib/trifle/stats/operations/timeseries/classify.rb +1 -1
  30. data/lib/trifle/stats/operations/timeseries/increment.rb +1 -1
  31. data/lib/trifle/stats/operations/timeseries/set.rb +1 -1
  32. data/lib/trifle/stats/operations/timeseries/values.rb +25 -7
  33. data/lib/trifle/stats/series.rb +64 -0
  34. data/lib/trifle/stats/transponder/average.rb +31 -0
  35. data/lib/trifle/stats/transponder/ratio.rb +31 -0
  36. data/lib/trifle/stats/transponder/standard_deviation.rb +35 -0
  37. data/lib/trifle/stats/version.rb +1 -1
  38. data/lib/trifle/stats.rb +36 -3
  39. data/trifle-stats.gemspec +1 -0
  40. metadata +18 -3
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require_relative '../mixins/packer'
4
5
 
5
6
  module Trifle
@@ -20,6 +21,10 @@ module Trifle
20
21
  client.execute("CREATE UNIQUE INDEX idx_#{table_name}_key ON #{table_name} (key);")
21
22
  end
22
23
 
24
+ def description
25
+ "#{self.class.name}(J)"
26
+ end
27
+
23
28
  def inc(keys:, **values)
24
29
  data = self.class.pack(hash: values)
25
30
  client.transaction do |c|
@@ -52,7 +57,7 @@ module Trifle
52
57
  <<-SQL
53
58
  INSERT INTO #{table_name} (key, data) values('#{key}', json('#{data.to_json}'))
54
59
  ON CONFLICT (key) DO UPDATE SET data =
55
- #{data.inject('data') { |o, (k, v)| "json_set(#{o}, '$.#{k}', #{v})" }};
60
+ #{data.inject('data') { |o, (k, v)| "json_set(#{o}, '$.#{k}', #{v.to_json})" }};
56
61
  SQL
57
62
  end
58
63
 
@@ -61,7 +66,7 @@ module Trifle
61
66
  data = get_all(keys: pkeys)
62
67
  map = data.inject({}) { |o, d| o.merge(d[:key] => d[:data]) }
63
68
 
64
- pkeys.map { |pkey| map.fetch(pkey, {}) }
69
+ pkeys.map { |pkey| self.class.unpack(hash: map.fetch(pkey, {})) }
65
70
  end
66
71
 
67
72
  def get_all(keys:)
@@ -79,6 +84,14 @@ module Trifle
79
84
  SELECT key, data FROM #{table_name} WHERE key IN ('#{keys.join("', '")}');
80
85
  SQL
81
86
  end
87
+
88
+ def ping(*)
89
+ []
90
+ end
91
+
92
+ def scan(*)
93
+ []
94
+ end
82
95
  end
83
96
  end
84
97
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Formatter
6
+ class Category
7
+ Trifle::Stats::Series.register_formatter(:category, self)
8
+
9
+ def format(series:, path:, slices: 1, &block)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:at].zip(series[:values].map { |v| v.dig(*keys) || {} })
14
+ sliced(result: result, slices: slices, block: block)
15
+ end
16
+
17
+ private
18
+
19
+ def sliced(result:, slices:, block: nil) # rubocop:disable Metrics/AbcSize
20
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
21
+ slice.each_with_object(Hash.new(0)) do |(_at, data), map|
22
+ data.each do |key, value|
23
+ k, v = block ? block.call(key, value) : [key.to_s, value.to_f]
24
+ map[k] += v
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Formatter
6
+ class Timeline
7
+ Trifle::Stats::Series.register_formatter(:timeline, self)
8
+
9
+ def format(series:, path:, slices: 1, &block)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:at].zip(series[:values].map { |v| v.dig(*keys) })
14
+ sliced(result: result, slices: slices, block: block)
15
+ end
16
+
17
+ private
18
+
19
+ def sliced(result:, slices:, block: nil)
20
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
21
+ slice.map do |at, value|
22
+ block ? block.call(at, value) : [at, value.to_f]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bigdecimal'
4
+
3
5
  module Trifle
4
6
  module Stats
5
7
  module Mixins
@@ -26,7 +28,7 @@ module Trifle
26
28
  hash.inject({}) do |out, (key, v)|
27
29
  deep_merge(
28
30
  out,
29
- key.split('.').reverse.inject(v.to_i) { |o, k| { k => o } }
31
+ key.split('.').reverse.inject(v) { |o, k| { k => o } }
30
32
  )
31
33
  end
32
34
  end
@@ -46,6 +48,19 @@ module Trifle
46
48
  end
47
49
  end
48
50
  end
51
+
52
+ def normalize(object)
53
+ case object
54
+ when Hash
55
+ object.each_with_object({}) do |(key, value), result|
56
+ result[key.to_s] = normalize(value)
57
+ end
58
+ when Array
59
+ object.map { |v| normalize(v) }
60
+ else
61
+ BigDecimal(object)
62
+ end
63
+ end
49
64
  end
50
65
  end
51
66
  end
@@ -3,6 +3,30 @@
3
3
  module Trifle
4
4
  module Stats
5
5
  class Nocturnal # rubocop:disable Metrics/ClassLength
6
+ class Key
7
+ attr_reader :key, :range, :at
8
+ attr_accessor :prefix
9
+
10
+ def initialize(key:, range: nil, at: nil)
11
+ @prefix = nil
12
+ @key = key
13
+ @range = range
14
+ @at = at
15
+ end
16
+
17
+ def join(separator)
18
+ [prefix, key, range, at&.to_i].compact.join(separator)
19
+ end
20
+
21
+ def identifier(separator)
22
+ if separator
23
+ { key: join(separator) }
24
+ else
25
+ { key: key, range: range, at: at }.compact
26
+ end
27
+ end
28
+ end
29
+
6
30
  DAYS_INTO_WEEK = {
7
31
  sunday: 0, monday: 1, tuesday: 2, wednesday: 3,
8
32
  thursday: 4, friday: 5, saturday: 6
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ module Operations
6
+ module Status
7
+ class Beam
8
+ attr_reader :key, :at, :values
9
+
10
+ def initialize(**keywords)
11
+ @key = keywords.fetch(:key)
12
+ @at = keywords.fetch(:at)
13
+ @values = keywords.fetch(:values)
14
+ @config = keywords[:config]
15
+ end
16
+
17
+ def config
18
+ @config || Trifle::Stats.default
19
+ end
20
+
21
+ def perform
22
+ config.driver.ping(
23
+ key: Nocturnal::Key.new(key: key, at: at),
24
+ **values
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ module Operations
6
+ module Status
7
+ class Scan
8
+ attr_reader :key
9
+
10
+ def initialize(**keywords)
11
+ @key = keywords.fetch(:key)
12
+ @config = keywords[:config]
13
+ end
14
+
15
+ def config
16
+ @config || Trifle::Stats.default
17
+ end
18
+
19
+ def data
20
+ @data ||= config.driver.scan(
21
+ key: Nocturnal::Key.new(key: key)
22
+ )
23
+ end
24
+
25
+ def perform
26
+ {
27
+ at: data.first,
28
+ values: data.last
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -32,7 +32,7 @@ module Trifle
32
32
 
33
33
  def key_for(range:)
34
34
  at = Nocturnal.new(@at, config: config).send(range)
35
- [key, range, at.to_i]
35
+ Nocturnal::Key.new(key: key, range: range, at: at)
36
36
  end
37
37
 
38
38
  def perform
@@ -20,7 +20,7 @@ module Trifle
20
20
 
21
21
  def key_for(range:)
22
22
  at = Nocturnal.new(@at, config: config).send(range)
23
- [key, range, at.to_i]
23
+ Nocturnal::Key.new(key: key, range: range, at: at)
24
24
  end
25
25
 
26
26
  def perform
@@ -20,7 +20,7 @@ module Trifle
20
20
 
21
21
  def key_for(range:)
22
22
  at = Nocturnal.new(@at, config: config).send(range)
23
- [key, range, at.to_i]
23
+ Nocturnal::Key.new(key: key, range: range, at: at)
24
24
  end
25
25
 
26
26
  def perform
@@ -13,6 +13,7 @@ module Trifle
13
13
  @to = keywords.fetch(:to)
14
14
  @range = keywords.fetch(:range)
15
15
  @config = keywords[:config]
16
+ @skip_blanks = keywords[:skip_blanks]
16
17
  end
17
18
 
18
19
  def config
@@ -20,19 +21,36 @@ module Trifle
20
21
  end
21
22
 
22
23
  def timeline
23
- Nocturnal.timeline(from: @from, to: @to, range: range)
24
+ @timeline ||= Nocturnal.timeline(from: @from, to: @to, range: range)
24
25
  end
25
26
 
26
- def perform
27
+ def data
28
+ @data ||= config.driver.get(
29
+ keys: timeline.map do |at|
30
+ Nocturnal::Key.new(key: key, range: range, at: at)
31
+ end
32
+ )
33
+ end
34
+
35
+ def clean_values
36
+ timeline.each_with_object({ at: [], values: [] }).with_index do |(_at, res), idx|
37
+ next if data[idx].empty?
38
+
39
+ res[:at] << timeline[idx]
40
+ res[:values] << data[idx]
41
+ end
42
+ end
43
+
44
+ def values
27
45
  {
28
46
  at: timeline,
29
- values: config.driver.get(
30
- keys: timeline.map do |at|
31
- [key, range, at.to_i]
32
- end
33
- )
47
+ values: data
34
48
  }
35
49
  end
50
+
51
+ def perform
52
+ @skip_blanks ? clean_values : values
53
+ end
36
54
  end
37
55
  end
38
56
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Series
6
+ include Trifle::Stats::Mixins::Packer
7
+
8
+ attr_accessor :series
9
+
10
+ def initialize(series)
11
+ @series = series
12
+ @series[:values] = self.class.normalize(@series[:values])
13
+ end
14
+
15
+ class Aggregator
16
+ def initialize(series)
17
+ @series = series
18
+ end
19
+ end
20
+
21
+ def aggregate
22
+ @aggregate ||= Aggregator.new(self)
23
+ end
24
+
25
+ def self.register_aggregator(name, klass)
26
+ Aggregator.define_method(name) do |params|
27
+ klass.new.aggregate(series: @series.series, **params)
28
+ end
29
+ end
30
+
31
+ class Formatter
32
+ def initialize(series)
33
+ @series = series
34
+ end
35
+ end
36
+
37
+ def format
38
+ @format ||= Formatter.new(self)
39
+ end
40
+
41
+ def self.register_formatter(name, klass)
42
+ Formatter.define_method(name) do |params, &block|
43
+ klass.new.format(series: @series.series, **params, &block)
44
+ end
45
+ end
46
+
47
+ class Transponder
48
+ def initialize(series)
49
+ @series = series
50
+ end
51
+ end
52
+
53
+ def transpond
54
+ @transpond ||= Transponder.new(self)
55
+ end
56
+
57
+ def self.register_transponder(name, klass)
58
+ Transponder.define_method(name) do |params|
59
+ @series.series = klass.new.transpond(series: @series.series, **params)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Transponder
6
+ class Average
7
+ include Trifle::Stats::Mixins::Packer
8
+ Trifle::Stats::Series.register_transponder(:average, self)
9
+
10
+ def transpond(series:, path:, key: 'average', sum: 'sum', count: 'count') # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
11
+ keys = path.to_s.split('.')
12
+ sum = sum.to_s.split('.')
13
+ count = count.to_s.split('.')
14
+ key = path.to_s.empty? ? key : [path, key].join('.')
15
+ series[:values] = series[:values].map do |data|
16
+ dsum = data.dig(*keys, *sum)
17
+ dcount = data.dig(*keys, *count)
18
+ next data unless dsum && dcount
19
+
20
+ dres = (dsum / dcount)
21
+ signal = {
22
+ key => dres.nan? ? BigDecimal(0) : dres
23
+ }
24
+ self.class.deep_merge(data, self.class.unpack(hash: signal))
25
+ end
26
+ series
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Transponder
6
+ class Ratio
7
+ include Trifle::Stats::Mixins::Packer
8
+ Trifle::Stats::Series.register_transponder(:ratio, self)
9
+
10
+ def transpond(series:, path:, key: 'ratio', sample: 'sample', total: 'total') # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
11
+ keys = path.to_s.split('.')
12
+ sample = sample.to_s.split('.')
13
+ total = total.to_s.split('.')
14
+ key = path.to_s.empty? ? key : [path, key].join('.')
15
+ series[:values] = series[:values].map do |data|
16
+ dsample = data.dig(*keys, *sample)
17
+ dtotal = data.dig(*keys, *total)
18
+ next data unless dsample && dtotal
19
+
20
+ dres = (dsample / dtotal) * 100
21
+ signal = {
22
+ key => dres.nan? ? BigDecimal(0) : dres
23
+ }
24
+ self.class.deep_merge(data, self.class.unpack(hash: signal))
25
+ end
26
+ series
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Transponder
6
+ class StandardDeviation
7
+ include Trifle::Stats::Mixins::Packer
8
+ Trifle::Stats::Series.register_transponder(:standard_deviation, self)
9
+
10
+ def transpond(series:, path:, key: 'sd', sum: 'sum', count: 'count', square: 'square') # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
11
+ keys = path.to_s.split('.')
12
+ sum = sum.to_s.split('.')
13
+ count = count.to_s.split('.')
14
+ square = square.to_s.split('.')
15
+ key = path.to_s.empty? ? key : [path, key].join('.')
16
+ series[:values] = series[:values].map do |data|
17
+ dcount = data.dig(*keys, *count)
18
+ dsquare = data.dig(*keys, *square)
19
+ dsum = data.dig(*keys, *sum)
20
+ next data unless dcount && dsquare && dsum
21
+
22
+ dres = Math.sqrt(
23
+ (dcount * dsquare - dsum * dsum) / (dcount * (dcount - 1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
24
+ )
25
+ signal = {
26
+ key => dres.nan? ? BigDecimal(0) : dres
27
+ }
28
+ self.class.deep_merge(data, self.class.unpack(hash: signal))
29
+ end
30
+ series
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Trifle
4
4
  module Stats
5
- VERSION = '1.3.1'
5
+ VERSION = '1.6.0'
6
6
  end
7
7
  end
data/lib/trifle/stats.rb CHANGED
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'trifle/stats/mixins/packer'
4
+ require 'trifle/stats/nocturnal'
5
+ require 'trifle/stats/series'
6
+ require 'trifle/stats/aggregator/avg'
7
+ require 'trifle/stats/aggregator/max'
8
+ require 'trifle/stats/aggregator/min'
9
+ require 'trifle/stats/aggregator/sum'
3
10
  require 'trifle/stats/designator/custom'
4
11
  require 'trifle/stats/designator/geometric'
5
12
  require 'trifle/stats/designator/linear'
@@ -8,13 +15,18 @@ require 'trifle/stats/driver/postgres'
8
15
  require 'trifle/stats/driver/process'
9
16
  require 'trifle/stats/driver/redis'
10
17
  require 'trifle/stats/driver/sqlite'
11
- require 'trifle/stats/mixins/packer'
12
- require 'trifle/stats/nocturnal'
18
+ require 'trifle/stats/formatter/category'
19
+ require 'trifle/stats/formatter/timeline'
13
20
  require 'trifle/stats/configuration'
14
21
  require 'trifle/stats/operations/timeseries/classify'
15
22
  require 'trifle/stats/operations/timeseries/increment'
16
23
  require 'trifle/stats/operations/timeseries/set'
17
24
  require 'trifle/stats/operations/timeseries/values'
25
+ require 'trifle/stats/operations/status/beam'
26
+ require 'trifle/stats/operations/status/scan'
27
+ require 'trifle/stats/transponder/average'
28
+ require 'trifle/stats/transponder/ratio'
29
+ require 'trifle/stats/transponder/standard_deviation'
18
30
  require 'trifle/stats/version'
19
31
 
20
32
  module Trifle
@@ -59,12 +71,33 @@ module Trifle
59
71
  ).perform
60
72
  end
61
73
 
62
- def self.values(key:, from:, to:, range:, config: nil)
74
+ def self.values(key:, from:, to:, range:, skip_blanks: false, config: nil) # rubocop:disable Metrics/ParameterLists
63
75
  Trifle::Stats::Operations::Timeseries::Values.new(
64
76
  key: key,
65
77
  from: from,
66
78
  to: to,
67
79
  range: range,
80
+ skip_blanks: skip_blanks,
81
+ config: config
82
+ ).perform
83
+ end
84
+
85
+ def self.series(**params)
86
+ Trifle::Stats::Series.new(values(**params))
87
+ end
88
+
89
+ def self.beam(key:, at:, values:, config: nil)
90
+ Trifle::Stats::Operations::Status::Beam.new(
91
+ key: key,
92
+ at: at,
93
+ values: values,
94
+ config: config
95
+ ).perform
96
+ end
97
+
98
+ def self.scan(key:, config: nil)
99
+ Trifle::Stats::Operations::Status::Scan.new(
100
+ key: key,
68
101
  config: config
69
102
  ).perform
70
103
  end
data/trifle-stats.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.metadata['homepage_uri'] = spec.homepage
20
20
  spec.metadata['source_code_uri'] = 'https://github.com/trifle-io/trifle-stats'
21
+ spec.metadata['changelog_uri'] = 'https://trifle.io/trifle-stats/changelog'
21
22
 
22
23
  # Specify which files should be added to the gem when it is released.
23
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trifle-stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jozef Vaclavik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-29 00:00:00.000000000 Z
11
+ date: 2025-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -183,6 +183,8 @@ files:
183
183
  - ".devops/docker/gitpod/base/.p10k.zsh"
184
184
  - ".devops/docker/gitpod/base/.zshrc"
185
185
  - ".devops/docker/gitpod/base/Dockerfile"
186
+ - ".devops/docker/local/Dockerfile"
187
+ - ".devops/docker/local/docker-compose.yml"
186
188
  - ".github/workflows/ruby.yml"
187
189
  - ".gitignore"
188
190
  - ".gitpod.yml"
@@ -198,6 +200,10 @@ files:
198
200
  - bin/console
199
201
  - bin/setup
200
202
  - lib/trifle/stats.rb
203
+ - lib/trifle/stats/aggregator/avg.rb
204
+ - lib/trifle/stats/aggregator/max.rb
205
+ - lib/trifle/stats/aggregator/min.rb
206
+ - lib/trifle/stats/aggregator/sum.rb
201
207
  - lib/trifle/stats/configuration.rb
202
208
  - lib/trifle/stats/designator/custom.rb
203
209
  - lib/trifle/stats/designator/geometric.rb
@@ -208,12 +214,20 @@ files:
208
214
  - lib/trifle/stats/driver/process.rb
209
215
  - lib/trifle/stats/driver/redis.rb
210
216
  - lib/trifle/stats/driver/sqlite.rb
217
+ - lib/trifle/stats/formatter/category.rb
218
+ - lib/trifle/stats/formatter/timeline.rb
211
219
  - lib/trifle/stats/mixins/packer.rb
212
220
  - lib/trifle/stats/nocturnal.rb
221
+ - lib/trifle/stats/operations/status/beam.rb
222
+ - lib/trifle/stats/operations/status/scan.rb
213
223
  - lib/trifle/stats/operations/timeseries/classify.rb
214
224
  - lib/trifle/stats/operations/timeseries/increment.rb
215
225
  - lib/trifle/stats/operations/timeseries/set.rb
216
226
  - lib/trifle/stats/operations/timeseries/values.rb
227
+ - lib/trifle/stats/series.rb
228
+ - lib/trifle/stats/transponder/average.rb
229
+ - lib/trifle/stats/transponder/ratio.rb
230
+ - lib/trifle/stats/transponder/standard_deviation.rb
217
231
  - lib/trifle/stats/version.rb
218
232
  - trifle-stats.gemspec
219
233
  homepage: https://trifle.io
@@ -222,6 +236,7 @@ licenses:
222
236
  metadata:
223
237
  homepage_uri: https://trifle.io
224
238
  source_code_uri: https://github.com/trifle-io/trifle-stats
239
+ changelog_uri: https://trifle.io/trifle-stats/changelog
225
240
  post_install_message:
226
241
  rdoc_options: []
227
242
  require_paths:
@@ -237,7 +252,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
252
  - !ruby/object:Gem::Version
238
253
  version: '0'
239
254
  requirements: []
240
- rubygems_version: 3.2.3
255
+ rubygems_version: 3.3.3
241
256
  signing_key:
242
257
  specification_version: 4
243
258
  summary: Simple analytics backed by Redis, Postgres, MongoDB, Google Analytics, Segment,