trifle-stats 2.5.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '014813aa6a45f6fece54f7fc332272c85023a37236cd3b4eeb270f3f00fed3a0'
4
- data.tar.gz: f83d0bfecc64f03cd3160e3762eb09a12d76ea8c9cda78c8108ee063bd8caf50
3
+ metadata.gz: 053aa86693f158ba5a395ab80396f0b3274bff69481ec80dd4335e782d000bfe
4
+ data.tar.gz: 86be7c68e870d4fe38eac8dcae73634d8237a5334c8071c610932b22b57c735d
5
5
  SHA512:
6
- metadata.gz: 71ecb7d927d16ec3f29fec1f57624b8f72bb482841a7a6c0d948ddf8ddc42b0d8b88b858f12436a330189a0731ba41d9514332cfc25e4721c3b7387f11d1c809
7
- data.tar.gz: da222c50726b1241f2fbfe86c21553c1fea4393c80e53c065e94ea4895fbf755fb6f1e59d15b61b3d1219d114fb444b1bc4eb935b21e9970a4b704b0348a23fc
6
+ metadata.gz: cfc8e1a9b317808a37acc6799d87a99d856fe4e4f4b792ac223d96f4ac500b2e6c77e25a5e99e1861047524984cf7ce2bd691a6d930404384b973a173ed49e99
7
+ data.tar.gz: 383c36c748fa4266b2d741054f1f22a35baff5806ccb5ab169d3a0cf49c9fabad2fae97e64acb86fe2fbd534600acad0fe5ee6edac7a01faea6340c5b1eb1c13
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trifle-stats (2.5.0)
4
+ trifle-stats (2.6.0)
5
5
  tzinfo (~> 2.0)
6
6
 
7
7
  GEM
@@ -11,7 +11,7 @@ GEM
11
11
  bigdecimal (4.0.1)
12
12
  bson (4.12.1)
13
13
  byebug (11.1.3)
14
- concurrent-ruby (1.3.6)
14
+ concurrent-ruby (1.3.8)
15
15
  diff-lcs (1.4.4)
16
16
  dotenv (2.7.6)
17
17
  mini_portile2 (2.8.9)
data/README.md CHANGED
@@ -80,6 +80,7 @@ series.aggregate.sum(path: 'count')
80
80
 
81
81
  | Driver | Backend | Best for |
82
82
  |--------|---------|----------|
83
+ | **API** | Trifle Cloud Projects | Hosted metrics without your own database |
83
84
  | **Postgres** | JSONB upsert | Most production apps |
84
85
  | **Redis** | Hash increment | High-throughput counters |
85
86
  | **MongoDB** | Document upsert | Document-oriented stacks |
@@ -42,6 +42,7 @@ module Trifle
42
42
 
43
43
  def storage
44
44
  return driver unless buffer_enabled
45
+ return driver if driver.respond_to?(:bypass_buffer?) && driver.bypass_buffer?
45
46
 
46
47
  @storage ||= Trifle::Stats::Buffer.new(
47
48
  driver: driver,
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'stringio'
6
+ require 'time'
7
+ require 'uri'
8
+ require 'zlib'
9
+ require_relative '../version'
10
+
11
+ module Trifle
12
+ module Stats
13
+ module Driver
14
+ class Api
15
+ ENDPOINT = URI('https://app.trifle.io/api/v1/metrics')
16
+ TIMEOUT = 10
17
+ ERROR_BODY_LIMIT = 1024
18
+
19
+ class Error < Trifle::Stats::Error
20
+ attr_reader :status, :response_body, :retry_after, :delivery_unknown
21
+
22
+ def initialize(message, status: nil, response_body: nil, retry_after: nil, delivery_unknown: false)
23
+ super(message)
24
+ @status = status
25
+ @response_body = response_body
26
+ @retry_after = retry_after
27
+ @delivery_unknown = delivery_unknown
28
+ end
29
+ end
30
+
31
+ class NetHttpTransport
32
+ def call(uri:, request:, timeout:)
33
+ Net::HTTP.start(
34
+ uri.hostname,
35
+ uri.port,
36
+ use_ssl: uri.scheme == 'https',
37
+ open_timeout: timeout,
38
+ read_timeout: timeout
39
+ ) { |http| http.request(request) }
40
+ end
41
+ end
42
+
43
+ attr_reader :token, :project_id
44
+
45
+ def initialize(token:, project_id:, transport: NetHttpTransport.new)
46
+ raise ArgumentError, 'token must not be empty' if token.to_s.strip.empty?
47
+ raise ArgumentError, 'project_id must not be empty' if project_id.to_s.strip.empty?
48
+
49
+ @token = token.to_s
50
+ @project_id = project_id.to_s
51
+ @transport = transport
52
+ end
53
+
54
+ def description
55
+ self.class.name
56
+ end
57
+
58
+ def bypass_buffer?
59
+ true
60
+ end
61
+
62
+ def direct_write(operation:, key:, at:, values:, untracked: false)
63
+ body = gzip(JSON.generate(
64
+ operation: operation.to_s,
65
+ key: key,
66
+ at: at.to_time.iso8601(6),
67
+ values: values,
68
+ untracked: untracked
69
+ ))
70
+ request = Net::HTTP::Post.new(ENDPOINT)
71
+ request['Authorization'] = "Bearer #{token}"
72
+ request['X-Trifle-Source-Id'] = project_id
73
+ request['Content-Type'] = 'application/json'
74
+ request['Accept'] = 'application/json'
75
+ request['Content-Encoding'] = 'gzip'
76
+ request['User-Agent'] = "trifle-stats-ruby/#{Trifle::Stats::VERSION}"
77
+ request.body = body
78
+
79
+ response = @transport.call(uri: ENDPOINT, request: request, timeout: TIMEOUT)
80
+ return true if response.code.to_i.between?(200, 299)
81
+
82
+ response_body = response.body.to_s.byteslice(0, ERROR_BODY_LIMIT)
83
+ raise Error.new(
84
+ "Trifle API returned HTTP #{response.code}",
85
+ status: response.code.to_i,
86
+ response_body: response_body,
87
+ retry_after: response['Retry-After']
88
+ )
89
+ rescue Error
90
+ raise
91
+ rescue StandardError => e
92
+ raise Error.new("Trifle API request failed: #{e.message}", delivery_unknown: true), cause: e
93
+ end
94
+
95
+ def inc(*)
96
+ unsupported!(:track)
97
+ end
98
+
99
+ def set(*)
100
+ unsupported!(:assert)
101
+ end
102
+
103
+ def get(*)
104
+ unsupported!(:values)
105
+ end
106
+
107
+ def ping(*)
108
+ unsupported!(:beam)
109
+ end
110
+
111
+ def scan(*)
112
+ unsupported!(:scan)
113
+ end
114
+
115
+ private
116
+
117
+ def gzip(value)
118
+ output = StringIO.new
119
+ Zlib::GzipWriter.wrap(output) { |writer| writer.write(value) }
120
+ output.string
121
+ end
122
+
123
+ def unsupported!(operation)
124
+ message = "#{operation} must be called through Trifle::Stats; " \
125
+ 'API driver does not support direct storage operations'
126
+ raise Error, message
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -26,6 +26,16 @@ module Trifle
26
26
  end
27
27
 
28
28
  def perform
29
+ if config.driver.respond_to?(:direct_write)
30
+ return config.driver.direct_write(
31
+ operation: :track,
32
+ key: key,
33
+ at: @at,
34
+ values: values,
35
+ untracked: @untracked
36
+ )
37
+ end
38
+
29
39
  payload = {
30
40
  keys: config.granularities.map { |granularity| key_for(granularity: granularity) },
31
41
  values: values
@@ -26,6 +26,16 @@ module Trifle
26
26
  end
27
27
 
28
28
  def perform
29
+ if config.driver.respond_to?(:direct_write)
30
+ return config.driver.direct_write(
31
+ operation: :assert,
32
+ key: key,
33
+ at: @at,
34
+ values: values,
35
+ untracked: @untracked
36
+ )
37
+ end
38
+
29
39
  payload = {
30
40
  keys: config.granularities.map { |granularity| key_for(granularity: granularity) },
31
41
  values: values
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Trifle
4
4
  module Stats
5
- VERSION = '2.5.0'
5
+ VERSION = '2.6.0'
6
6
  end
7
7
  end
data/lib/trifle/stats.rb CHANGED
@@ -99,3 +99,5 @@ module Trifle
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ require 'trifle/stats/driver/api'
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: 2.5.0
4
+ version: 2.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: 2026-07-14 00:00:00.000000000 Z
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -225,6 +225,7 @@ files:
225
225
  - lib/trifle/stats/designator/geometric.rb
226
226
  - lib/trifle/stats/designator/linear.rb
227
227
  - lib/trifle/stats/driver/README.md
228
+ - lib/trifle/stats/driver/api.rb
228
229
  - lib/trifle/stats/driver/mongo.rb
229
230
  - lib/trifle/stats/driver/mysql.rb
230
231
  - lib/trifle/stats/driver/postgres.rb