toggly 0.3.0 → 0.5.1
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/CHANGELOG.md +44 -0
- data/README.md +34 -2
- data/lib/toggly/client/cache_telemetry.rb +36 -0
- data/lib/toggly/client/snapshot_support.rb +38 -0
- data/lib/toggly/client.rb +173 -51
- data/lib/toggly/config.rb +73 -2
- data/lib/toggly/definition_cache.rb +78 -0
- data/lib/toggly/definitions_provider.rb +72 -18
- data/lib/toggly/telemetry/grpc_clients.rb +333 -0
- data/lib/toggly/telemetry/metrics_batcher.rb +140 -0
- data/lib/toggly/telemetry/pb/metrics_pb.rb +28 -0
- data/lib/toggly/telemetry/pb/metrics_services_pb.rb +28 -0
- data/lib/toggly/telemetry/pb/usage_pb.rb +27 -0
- data/lib/toggly/telemetry/pb/usage_services_pb.rb +28 -0
- data/lib/toggly/telemetry/runtime.rb +262 -0
- data/lib/toggly/telemetry/runtime_flush.rb +89 -0
- data/lib/toggly/telemetry/usage_batcher.rb +277 -0
- data/lib/toggly/telemetry.rb +37 -0
- data/lib/toggly/version.rb +1 -1
- data/lib/toggly.rb +3 -0
- data/proto/metrics.proto +60 -0
- data/proto/usage.proto +55 -0
- metadata +18 -3
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Toggly
|
|
4
|
+
module Telemetry
|
|
5
|
+
# In-memory feature usage aggregator (Usage.SendStats payload shape).
|
|
6
|
+
class UsageBatcher
|
|
7
|
+
VariantStatsAgg = Struct.new(:check_count, :request_count, :used_count, :viewed_count, keyword_init: true) do
|
|
8
|
+
def initialize(check_count: 0, request_count: 0, used_count: 0, viewed_count: 0)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_wire
|
|
13
|
+
{
|
|
14
|
+
checkCount: check_count,
|
|
15
|
+
requestCount: request_count,
|
|
16
|
+
usedCount: used_count,
|
|
17
|
+
viewedCount: viewed_count
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clone
|
|
22
|
+
VariantStatsAgg.new(
|
|
23
|
+
check_count: check_count,
|
|
24
|
+
request_count: request_count,
|
|
25
|
+
used_count: used_count,
|
|
26
|
+
viewed_count: viewed_count
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
FeatureUsageAgg = Struct.new(
|
|
32
|
+
:variant_stats,
|
|
33
|
+
:unique_users_enabled,
|
|
34
|
+
:unique_users_disabled,
|
|
35
|
+
:unique_users_used,
|
|
36
|
+
:unique_user_hashes,
|
|
37
|
+
:unique_viewed_user_hashes,
|
|
38
|
+
keyword_init: true
|
|
39
|
+
) do
|
|
40
|
+
def initialize(
|
|
41
|
+
variant_stats: {},
|
|
42
|
+
unique_users_enabled: Set.new,
|
|
43
|
+
unique_users_disabled: Set.new,
|
|
44
|
+
unique_users_used: Set.new,
|
|
45
|
+
unique_user_hashes: Set.new,
|
|
46
|
+
unique_viewed_user_hashes: Set.new
|
|
47
|
+
)
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clone
|
|
52
|
+
FeatureUsageAgg.new(
|
|
53
|
+
variant_stats: variant_stats.transform_values(&:clone),
|
|
54
|
+
unique_users_enabled: unique_users_enabled.dup,
|
|
55
|
+
unique_users_disabled: unique_users_disabled.dup,
|
|
56
|
+
unique_users_used: unique_users_used.dup,
|
|
57
|
+
unique_user_hashes: unique_user_hashes.dup,
|
|
58
|
+
unique_viewed_user_hashes: unique_viewed_user_hashes.dup
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Restoreable copy of drained batcher state.
|
|
64
|
+
BatchSnapshot = Struct.new(
|
|
65
|
+
:per_feature,
|
|
66
|
+
:app_unique,
|
|
67
|
+
:definition_cache_hits,
|
|
68
|
+
:definition_cache_misses,
|
|
69
|
+
keyword_init: true
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Wire payload plus snapshot for failed-send restore.
|
|
73
|
+
DrainedBatch = Struct.new(:payload, :snapshot, keyword_init: true)
|
|
74
|
+
|
|
75
|
+
def initialize(app_key, environment, instance_name: nil, app_version: nil, process_start_time: nil)
|
|
76
|
+
@app_key = app_key
|
|
77
|
+
@environment = environment
|
|
78
|
+
@instance_name = instance_name
|
|
79
|
+
@app_version = app_version
|
|
80
|
+
@process_start_time = process_start_time || Time.now.utc
|
|
81
|
+
@per_feature = {}
|
|
82
|
+
@app_unique = Set.new
|
|
83
|
+
@definition_cache_hits = 0
|
|
84
|
+
@definition_cache_misses = 0
|
|
85
|
+
@mutex = Mutex.new
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def record_definition_cache_hit
|
|
89
|
+
@mutex.synchronize { @definition_cache_hits += 1 }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def record_definition_cache_miss
|
|
93
|
+
@mutex.synchronize { @definition_cache_misses += 1 }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def record_check(feature, enabled, identity = nil, variant: nil, unique_request: false)
|
|
97
|
+
@mutex.synchronize do
|
|
98
|
+
agg = get_feature(feature)
|
|
99
|
+
name = if variant.nil?
|
|
100
|
+
enabled ? "enabled" : "disabled"
|
|
101
|
+
else
|
|
102
|
+
variant.to_s
|
|
103
|
+
end
|
|
104
|
+
stats = get_variant(agg, name)
|
|
105
|
+
stats.check_count += 1
|
|
106
|
+
stats.request_count += 1 if unique_request
|
|
107
|
+
|
|
108
|
+
if identity && !identity.to_s.empty?
|
|
109
|
+
hashed = GrpcClients.hash_identity(identity)
|
|
110
|
+
@app_unique.add(hashed)
|
|
111
|
+
if enabled
|
|
112
|
+
agg.unique_users_enabled.add(hashed)
|
|
113
|
+
else
|
|
114
|
+
agg.unique_users_disabled.add(hashed)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def record_usage(feature, identity = nil, variant: "enabled")
|
|
121
|
+
@mutex.synchronize do
|
|
122
|
+
agg = get_feature(feature)
|
|
123
|
+
get_variant(agg, variant.to_s).used_count += 1
|
|
124
|
+
track_identity(identity, agg.unique_users_used)
|
|
125
|
+
agg.unique_user_hashes.add(GrpcClients.hash_identity(identity)) if identity && !identity.to_s.empty?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def record_view(feature, identity = nil, variant: "enabled")
|
|
130
|
+
@mutex.synchronize do
|
|
131
|
+
agg = get_feature(feature)
|
|
132
|
+
get_variant(agg, variant.to_s).viewed_count += 1
|
|
133
|
+
if identity && !identity.to_s.empty?
|
|
134
|
+
hashed = GrpcClients.hash_identity(identity)
|
|
135
|
+
@app_unique.add(hashed)
|
|
136
|
+
agg.unique_viewed_user_hashes.add(hashed)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def empty?
|
|
142
|
+
@mutex.synchronize { empty_unlocked? }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Build the SendStats payload, clear pending state, and return a restore snapshot.
|
|
146
|
+
#
|
|
147
|
+
# @return [DrainedBatch, nil]
|
|
148
|
+
def export_and_reset
|
|
149
|
+
@mutex.synchronize do
|
|
150
|
+
return nil if empty_unlocked?
|
|
151
|
+
|
|
152
|
+
snapshot = clone_snapshot_unlocked
|
|
153
|
+
payload = build_payload_unlocked
|
|
154
|
+
clear_unlocked
|
|
155
|
+
DrainedBatch.new(payload: payload, snapshot: snapshot)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def build_and_reset
|
|
160
|
+
drained = export_and_reset
|
|
161
|
+
drained&.payload
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Merge a drained snapshot after send_stats failure (additive).
|
|
165
|
+
#
|
|
166
|
+
# @param snapshot [BatchSnapshot, nil]
|
|
167
|
+
def restore(snapshot)
|
|
168
|
+
return if snapshot.nil?
|
|
169
|
+
|
|
170
|
+
@mutex.synchronize do
|
|
171
|
+
@definition_cache_hits += snapshot.definition_cache_hits
|
|
172
|
+
@definition_cache_misses += snapshot.definition_cache_misses
|
|
173
|
+
@app_unique.merge(snapshot.app_unique)
|
|
174
|
+
|
|
175
|
+
snapshot.per_feature.each do |feature, snap_agg|
|
|
176
|
+
agg = get_feature(feature)
|
|
177
|
+
snap_agg.variant_stats.each do |name, snap_stats|
|
|
178
|
+
stats = get_variant(agg, name)
|
|
179
|
+
stats.check_count += snap_stats.check_count
|
|
180
|
+
stats.request_count += snap_stats.request_count
|
|
181
|
+
stats.used_count += snap_stats.used_count
|
|
182
|
+
stats.viewed_count += snap_stats.viewed_count
|
|
183
|
+
end
|
|
184
|
+
agg.unique_users_enabled.merge(snap_agg.unique_users_enabled)
|
|
185
|
+
agg.unique_users_disabled.merge(snap_agg.unique_users_disabled)
|
|
186
|
+
agg.unique_users_used.merge(snap_agg.unique_users_used)
|
|
187
|
+
agg.unique_user_hashes.merge(snap_agg.unique_user_hashes)
|
|
188
|
+
agg.unique_viewed_user_hashes.merge(snap_agg.unique_viewed_user_hashes)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def empty_unlocked?
|
|
196
|
+
@per_feature.empty? &&
|
|
197
|
+
@app_unique.empty? &&
|
|
198
|
+
@definition_cache_hits.zero? &&
|
|
199
|
+
@definition_cache_misses.zero?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def clone_snapshot_unlocked
|
|
203
|
+
BatchSnapshot.new(
|
|
204
|
+
per_feature: @per_feature.transform_values(&:clone),
|
|
205
|
+
app_unique: @app_unique.dup,
|
|
206
|
+
definition_cache_hits: @definition_cache_hits,
|
|
207
|
+
definition_cache_misses: @definition_cache_misses
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def build_payload_unlocked
|
|
212
|
+
payload = {
|
|
213
|
+
appKey: @app_key,
|
|
214
|
+
environment: @environment,
|
|
215
|
+
time: GrpcClients.to_protobuf_timestamp,
|
|
216
|
+
stats: drain_feature_stats,
|
|
217
|
+
totalUniqueUsers: @app_unique.size,
|
|
218
|
+
uniqueUserHashes: @app_unique.to_a,
|
|
219
|
+
processStartTime: GrpcClients.to_protobuf_timestamp(@process_start_time)
|
|
220
|
+
}
|
|
221
|
+
payload[:instanceName] = @instance_name if @instance_name
|
|
222
|
+
payload[:appVersion] = @app_version if @app_version
|
|
223
|
+
payload[:definitionCacheHits] = @definition_cache_hits if @definition_cache_hits.positive?
|
|
224
|
+
payload[:definitionCacheMisses] = @definition_cache_misses if @definition_cache_misses.positive?
|
|
225
|
+
payload
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def clear_unlocked
|
|
229
|
+
@per_feature = {}
|
|
230
|
+
@app_unique = Set.new
|
|
231
|
+
@definition_cache_hits = 0
|
|
232
|
+
@definition_cache_misses = 0
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def drain_feature_stats
|
|
236
|
+
@per_feature.map do |feature, agg|
|
|
237
|
+
{
|
|
238
|
+
feature: feature,
|
|
239
|
+
uniqueContextIdentifierEnabledCount: agg.unique_users_enabled.size,
|
|
240
|
+
uniqueContextIdentifierDisabledCount: agg.unique_users_disabled.size,
|
|
241
|
+
uniqueUsersUsedCount: agg.unique_users_used.size,
|
|
242
|
+
uniqueUserHashes: agg.unique_user_hashes.to_a,
|
|
243
|
+
uniqueViewedUserHashes: agg.unique_viewed_user_hashes.to_a,
|
|
244
|
+
variantStats: wire_variant_stats(agg)
|
|
245
|
+
}
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def wire_variant_stats(agg)
|
|
250
|
+
variant_stats = {}
|
|
251
|
+
agg.variant_stats.each do |name, vs|
|
|
252
|
+
next unless vs.check_count.positive? || vs.request_count.positive? ||
|
|
253
|
+
vs.used_count.positive? || vs.viewed_count.positive?
|
|
254
|
+
|
|
255
|
+
variant_stats[name] = vs.to_wire
|
|
256
|
+
end
|
|
257
|
+
variant_stats
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def get_feature(feature)
|
|
261
|
+
@per_feature[feature] ||= FeatureUsageAgg.new
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def get_variant(agg, variant)
|
|
265
|
+
agg.variant_stats[variant] ||= VariantStatsAgg.new
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def track_identity(identity, into)
|
|
269
|
+
return if identity.nil? || identity.to_s.empty?
|
|
270
|
+
|
|
271
|
+
hashed = GrpcClients.hash_identity(identity)
|
|
272
|
+
@app_unique.add(hashed)
|
|
273
|
+
into.add(hashed)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "telemetry/grpc_clients"
|
|
4
|
+
require_relative "telemetry/usage_batcher"
|
|
5
|
+
require_relative "telemetry/metrics_batcher"
|
|
6
|
+
require_relative "telemetry/runtime"
|
|
7
|
+
|
|
8
|
+
module Toggly
|
|
9
|
+
# Usage and business-metrics telemetry (optional gRPC transport).
|
|
10
|
+
module Telemetry
|
|
11
|
+
DEFAULT_METRICS_BASE_URL = GrpcClients::DEFAULT_METRICS_BASE_URL
|
|
12
|
+
DEFAULT_TELEMETRY_FLUSH_SECONDS = GrpcClients::DEFAULT_TELEMETRY_FLUSH_SECONDS
|
|
13
|
+
GRPC_USER_AGENT_METADATA_KEY = GrpcClients::GRPC_USER_AGENT_METADATA_KEY
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def hash_identity(identity)
|
|
18
|
+
GrpcClients.hash_identity(identity)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def feature_stat_from_payload(payload)
|
|
22
|
+
GrpcClients.feature_stat_from_payload(payload)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def metric_stat_from_payload(payload)
|
|
26
|
+
GrpcClients.metric_stat_from_payload(payload)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def grpc_available?
|
|
30
|
+
GrpcClients.grpc_available?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def resolve_user_agent(override = nil)
|
|
34
|
+
GrpcClients.resolve_user_agent(override)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/toggly/version.rb
CHANGED
data/lib/toggly.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "toggly/version"
|
|
4
|
+
require_relative "toggly/telemetry"
|
|
4
5
|
require_relative "toggly/config"
|
|
5
6
|
require_relative "toggly/request_context"
|
|
6
7
|
require_relative "toggly/http_request_mapper"
|
|
@@ -30,6 +31,7 @@ require_relative "toggly/evaluation_engine"
|
|
|
30
31
|
require_relative "toggly/snapshot_providers/base"
|
|
31
32
|
require_relative "toggly/snapshot_providers/memory"
|
|
32
33
|
require_relative "toggly/snapshot_providers/file"
|
|
34
|
+
require_relative "toggly/definition_cache"
|
|
33
35
|
require_relative "toggly/definitions_provider"
|
|
34
36
|
require_relative "toggly/client"
|
|
35
37
|
|
|
@@ -77,6 +79,7 @@ module Toggly
|
|
|
77
79
|
def configure
|
|
78
80
|
config = Config.new
|
|
79
81
|
yield(config) if block_given?
|
|
82
|
+
config.apply_telemetry_defaults!
|
|
80
83
|
@client = Client.new(config)
|
|
81
84
|
end
|
|
82
85
|
|
data/proto/metrics.proto
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package Metrics;
|
|
4
|
+
|
|
5
|
+
option go_package = "github.com/ops-ai/Toggly.FeatureManagement/toggly-go/toggly/metrics/metricspb";
|
|
6
|
+
|
|
7
|
+
import "google/protobuf/timestamp.proto";
|
|
8
|
+
|
|
9
|
+
message MetricStat {
|
|
10
|
+
string appKey = 1;
|
|
11
|
+
string environment = 2;
|
|
12
|
+
google.protobuf.Timestamp time = 3;
|
|
13
|
+
repeated MetricStatMessage stats = 4;
|
|
14
|
+
repeated MetricCounterMessage counters = 5;
|
|
15
|
+
repeated MetricObservationMessage observations = 6;
|
|
16
|
+
optional string instanceName = 7;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
message MetricStatMessage {
|
|
20
|
+
string metric = 1;
|
|
21
|
+
int32 enabledCount = 2 [deprecated = true];
|
|
22
|
+
int32 disabledCount = 3 [deprecated = true];
|
|
23
|
+
optional string feature = 4;
|
|
24
|
+
double value = 5 [deprecated = true];
|
|
25
|
+
optional double valueDisabled = 6 [deprecated = true];
|
|
26
|
+
|
|
27
|
+
// Multi-variate support - Key: variant name (e.g., "enabled", "control")
|
|
28
|
+
map<string, double> variantValues = 7;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
message MetricCounterMessage {
|
|
32
|
+
string metric = 1;
|
|
33
|
+
int32 enabledCount = 2 [deprecated = true];
|
|
34
|
+
int32 disabledCount = 3 [deprecated = true];
|
|
35
|
+
optional string feature = 4;
|
|
36
|
+
double value = 5 [deprecated = true];
|
|
37
|
+
optional double valueDisabled = 6 [deprecated = true];
|
|
38
|
+
|
|
39
|
+
map<string, double> variantValues = 7;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
message MetricObservationMessage {
|
|
43
|
+
google.protobuf.Timestamp time = 1;
|
|
44
|
+
string metric = 2;
|
|
45
|
+
int32 enabledCount = 3 [deprecated = true];
|
|
46
|
+
int32 disabledCount = 4 [deprecated = true];
|
|
47
|
+
optional string feature = 5;
|
|
48
|
+
double value = 6 [deprecated = true];
|
|
49
|
+
optional double valueDisabled = 7 [deprecated = true];
|
|
50
|
+
|
|
51
|
+
map<string, double> variantValues = 8;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
message MetricResult {
|
|
55
|
+
int32 count = 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
service Metrics {
|
|
59
|
+
rpc SendMetrics(MetricStat) returns (MetricResult);
|
|
60
|
+
}
|
data/proto/usage.proto
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package Usage;
|
|
4
|
+
|
|
5
|
+
option go_package = "github.com/ops-ai/Toggly.FeatureManagement/toggly-go/toggly/usage/usagepb";
|
|
6
|
+
|
|
7
|
+
import "google/protobuf/timestamp.proto";
|
|
8
|
+
|
|
9
|
+
message FeatureStat {
|
|
10
|
+
string appKey = 1;
|
|
11
|
+
string environment = 2;
|
|
12
|
+
google.protobuf.Timestamp time = 3;
|
|
13
|
+
repeated StatMessage stats = 4;
|
|
14
|
+
int32 totalUniqueUsers = 5;
|
|
15
|
+
optional string instanceName = 6;
|
|
16
|
+
optional google.protobuf.Timestamp processStartTime = 7;
|
|
17
|
+
optional string appVersion = 8;
|
|
18
|
+
repeated int32 uniqueUserHashes = 9; // Application-level unique user ID hashes
|
|
19
|
+
optional int32 definitionCacheHits = 10; // SDK-reported definition-refresh cache hits (batch delta)
|
|
20
|
+
optional int32 definitionCacheMisses = 11; // SDK-reported definition-refresh cache misses (batch delta)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
message StatMessage {
|
|
24
|
+
string feature = 1;
|
|
25
|
+
int32 enabledCount = 2 [deprecated = true]; // Legacy: use variantStats instead
|
|
26
|
+
int32 disabledCount = 3 [deprecated = true]; // Legacy: use variantStats instead
|
|
27
|
+
int32 uniqueContextIdentifierEnabledCount = 4;
|
|
28
|
+
int32 uniqueContextIdentifierDisabledCount = 5;
|
|
29
|
+
int32 uniqueRequestEnabledCount = 6 [deprecated = true]; // Legacy: use variantStats instead
|
|
30
|
+
int32 uniqueRequestDisabledCount = 7 [deprecated = true]; // Legacy: use variantStats instead
|
|
31
|
+
int32 usedCount = 8 [deprecated = true]; // Legacy: use variantStats instead
|
|
32
|
+
int32 uniqueUsersUsedCount = 9;
|
|
33
|
+
repeated int32 uniqueUserHashes = 10; // Unique user ID hashes for "used" tracking
|
|
34
|
+
|
|
35
|
+
// Multi-variate support - all new metrics should use this
|
|
36
|
+
map<string, VariantStats> variantStats = 11; // Key: variant name (e.g., "enabled", "disabled")
|
|
37
|
+
|
|
38
|
+
// Unique user tracking for "viewed" metric (per feature, not per variant)
|
|
39
|
+
repeated int32 uniqueViewedUserHashes = 12;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
message VariantStats {
|
|
43
|
+
int32 checkCount = 1;
|
|
44
|
+
int32 requestCount = 2;
|
|
45
|
+
int32 usedCount = 3;
|
|
46
|
+
int32 viewedCount = 4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
message StatResult {
|
|
50
|
+
int32 featureCount = 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
service Usage {
|
|
54
|
+
rpc SendStats(FeatureStat) returns (StatResult);
|
|
55
|
+
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toggly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ops.ai
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: High-performance Ruby SDK for Toggly feature flag management. Works with
|
|
14
14
|
or without Toggly.io.
|
|
@@ -23,8 +23,11 @@ files:
|
|
|
23
23
|
- README.md
|
|
24
24
|
- lib/toggly.rb
|
|
25
25
|
- lib/toggly/client.rb
|
|
26
|
+
- lib/toggly/client/cache_telemetry.rb
|
|
27
|
+
- lib/toggly/client/snapshot_support.rb
|
|
26
28
|
- lib/toggly/config.rb
|
|
27
29
|
- lib/toggly/context.rb
|
|
30
|
+
- lib/toggly/definition_cache.rb
|
|
28
31
|
- lib/toggly/definitions_provider.rb
|
|
29
32
|
- lib/toggly/entity_context.rb
|
|
30
33
|
- lib/toggly/errors.rb
|
|
@@ -52,8 +55,20 @@ files:
|
|
|
52
55
|
- lib/toggly/snapshot_providers/file.rb
|
|
53
56
|
- lib/toggly/snapshot_providers/memory.rb
|
|
54
57
|
- lib/toggly/sticky_hash.rb
|
|
58
|
+
- lib/toggly/telemetry.rb
|
|
59
|
+
- lib/toggly/telemetry/grpc_clients.rb
|
|
60
|
+
- lib/toggly/telemetry/metrics_batcher.rb
|
|
61
|
+
- lib/toggly/telemetry/pb/metrics_pb.rb
|
|
62
|
+
- lib/toggly/telemetry/pb/metrics_services_pb.rb
|
|
63
|
+
- lib/toggly/telemetry/pb/usage_pb.rb
|
|
64
|
+
- lib/toggly/telemetry/pb/usage_services_pb.rb
|
|
65
|
+
- lib/toggly/telemetry/runtime.rb
|
|
66
|
+
- lib/toggly/telemetry/runtime_flush.rb
|
|
67
|
+
- lib/toggly/telemetry/usage_batcher.rb
|
|
55
68
|
- lib/toggly/user_agent_parser.rb
|
|
56
69
|
- lib/toggly/version.rb
|
|
70
|
+
- proto/metrics.proto
|
|
71
|
+
- proto/usage.proto
|
|
57
72
|
homepage: https://toggly.io
|
|
58
73
|
licenses:
|
|
59
74
|
- MIT
|
|
@@ -63,7 +78,7 @@ metadata:
|
|
|
63
78
|
changelog_uri: https://github.com/ops-ai/toggly-ruby/blob/main/CHANGELOG.md
|
|
64
79
|
documentation_uri: https://docs.toggly.io/sdks/ruby
|
|
65
80
|
rubygems_mfa_required: 'true'
|
|
66
|
-
optional_dependencies: websocket-client-simple
|
|
81
|
+
optional_dependencies: websocket-client-simple, grpc, google-protobuf
|
|
67
82
|
post_install_message:
|
|
68
83
|
rdoc_options: []
|
|
69
84
|
require_paths:
|