featurevisor 0.2.0 → 1.0.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 +172 -75
- data/bin/cli.rb +15 -2
- data/bin/commands/assess_distribution.rb +34 -15
- data/bin/commands/benchmark.rb +65 -63
- data/bin/commands/test.rb +250 -100
- data/lib/featurevisor/child_instance.rb +22 -24
- data/lib/featurevisor/datafile_reader.rb +18 -13
- data/lib/featurevisor/emitter.rb +2 -2
- data/lib/featurevisor/evaluate.rb +106 -100
- data/lib/featurevisor/events.rb +5 -4
- data/lib/featurevisor/instance.rb +225 -39
- data/lib/featurevisor/logger.rb +0 -7
- data/lib/featurevisor/modules.rb +184 -0
- data/lib/featurevisor/version.rb +1 -1
- data/lib/featurevisor.rb +4 -1
- metadata +16 -2
- data/lib/featurevisor/hooks.rb +0 -159
data/bin/commands/benchmark.rb
CHANGED
|
@@ -27,29 +27,32 @@ module FeaturevisorCLI
|
|
|
27
27
|
exit 1
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
puts ""
|
|
31
|
-
puts "Running benchmark for feature \"#{@options.feature}\"..."
|
|
32
|
-
puts ""
|
|
33
|
-
|
|
34
|
-
# Parse context if provided
|
|
35
30
|
context = parse_context
|
|
31
|
+
targets = resolve_targets
|
|
32
|
+
(targets.empty? ? [nil] : targets).each { |target| run_for_target(context, target) }
|
|
33
|
+
end
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
datafile_build_start = Time.now
|
|
35
|
+
private
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
def run_for_target(context, target)
|
|
38
|
+
datafile_build_start = Time.now
|
|
39
|
+
datafile = build_datafile(@options.environment, target)
|
|
42
40
|
datafile_build_duration = Time.now - datafile_build_start
|
|
43
41
|
datafile_build_duration_ms = (datafile_build_duration * 1000).round
|
|
44
42
|
|
|
45
|
-
puts "
|
|
43
|
+
puts "\nBenchmark Featurevisor feature"
|
|
44
|
+
puts " Feature: #{@options.feature}"
|
|
45
|
+
puts " Environment: #{@options.environment}"
|
|
46
|
+
puts " Target: #{target}" if target
|
|
47
|
+
puts " Iterations: #{@options.n}"
|
|
48
|
+
puts " Build duration: #{datafile_build_duration_ms}ms"
|
|
46
49
|
|
|
47
50
|
# Calculate datafile size
|
|
48
51
|
datafile_size = datafile.to_json.bytesize
|
|
49
52
|
puts "Datafile size: #{(datafile_size / 1024.0).round(2)} kB"
|
|
50
53
|
|
|
51
54
|
# Create SDK instance with the datafile
|
|
52
|
-
instance =
|
|
55
|
+
instance = create_featurevisor(datafile)
|
|
53
56
|
puts "...SDK initialized"
|
|
54
57
|
|
|
55
58
|
puts ""
|
|
@@ -73,10 +76,26 @@ module FeaturevisorCLI
|
|
|
73
76
|
value_output = format_value(output[:value])
|
|
74
77
|
puts "Evaluated value : #{value_output}"
|
|
75
78
|
puts "Total duration : #{pretty_duration(output[:duration])}"
|
|
76
|
-
puts "
|
|
79
|
+
puts "Minimum duration: #{format_duration_ms(output[:min_duration])}"
|
|
80
|
+
puts "Average duration: #{format_duration_ms(output[:average_duration])}"
|
|
81
|
+
puts "Maximum duration: #{format_duration_ms(output[:max_duration])}"
|
|
77
82
|
end
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
def resolve_targets
|
|
85
|
+
return [] if @options.targets.empty?
|
|
86
|
+
stdout, stderr, status = Open3.capture3("npx", "featurevisor", "list", "--targets", "--json", chdir: @project_path)
|
|
87
|
+
unless status.success?
|
|
88
|
+
puts stderr
|
|
89
|
+
exit 1
|
|
90
|
+
end
|
|
91
|
+
available = JSON.parse(stdout).map { |target| target.is_a?(Hash) ? (target["key"] || target["name"]) : target }
|
|
92
|
+
unknown = @options.targets.find { |target| !available.include?(target) }
|
|
93
|
+
if unknown
|
|
94
|
+
puts "Unknown target \"#{unknown}\". Available targets: #{available.empty? ? "none" : available.join(", ")}."
|
|
95
|
+
exit 1
|
|
96
|
+
end
|
|
97
|
+
@options.targets
|
|
98
|
+
end
|
|
80
99
|
|
|
81
100
|
def parse_context
|
|
82
101
|
if @options.context
|
|
@@ -93,24 +112,15 @@ module FeaturevisorCLI
|
|
|
93
112
|
end
|
|
94
113
|
end
|
|
95
114
|
|
|
96
|
-
def build_datafile(environment)
|
|
115
|
+
def build_datafile(environment, target = nil)
|
|
97
116
|
puts "Building datafile for environment: #{environment}..."
|
|
98
117
|
|
|
99
|
-
# Build the command similar to Go implementation
|
|
100
118
|
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--json"]
|
|
101
|
-
|
|
102
|
-
# Add schema version if specified
|
|
103
|
-
if @options.schema_version && !@options.schema_version.empty?
|
|
104
|
-
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--schemaVersion=#{@options.schema_version}", "--json"]
|
|
105
|
-
end
|
|
119
|
+
command_parts << "--target=#{target}" if target
|
|
106
120
|
|
|
107
121
|
# Add inflate if specified
|
|
108
122
|
if @options.inflate && @options.inflate > 0
|
|
109
|
-
|
|
110
|
-
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--schemaVersion=#{@options.schema_version}", "--inflate=#{@options.inflate}", "--json"]
|
|
111
|
-
else
|
|
112
|
-
command_parts = ["cd", @project_path, "&&", "npx", "featurevisor", "build", "--environment=#{environment}", "--inflate=#{@options.inflate}", "--json"]
|
|
113
|
-
end
|
|
123
|
+
command_parts << "--inflate=#{@options.inflate}"
|
|
114
124
|
end
|
|
115
125
|
|
|
116
126
|
command = command_parts.join(" ")
|
|
@@ -142,9 +152,9 @@ module FeaturevisorCLI
|
|
|
142
152
|
stdout
|
|
143
153
|
end
|
|
144
154
|
|
|
145
|
-
def
|
|
155
|
+
def create_featurevisor(datafile)
|
|
146
156
|
# Create a real Featurevisor instance
|
|
147
|
-
instance = Featurevisor.
|
|
157
|
+
instance = Featurevisor.create_featurevisor(
|
|
148
158
|
log_level: get_logger_level
|
|
149
159
|
)
|
|
150
160
|
|
|
@@ -164,61 +174,53 @@ module FeaturevisorCLI
|
|
|
164
174
|
end
|
|
165
175
|
end
|
|
166
176
|
|
|
167
|
-
def
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
def benchmark_evaluation(n)
|
|
178
|
+
value = nil
|
|
179
|
+
total_duration_ns = 0
|
|
180
|
+
min_duration_ns = nil
|
|
181
|
+
max_duration_ns = 0
|
|
172
182
|
|
|
173
|
-
# Benchmark the evaluation
|
|
174
183
|
n.times do
|
|
175
|
-
|
|
184
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
185
|
+
value = yield
|
|
186
|
+
duration_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
|
|
187
|
+
|
|
188
|
+
total_duration_ns += duration_ns
|
|
189
|
+
min_duration_ns = duration_ns if min_duration_ns.nil? || duration_ns < min_duration_ns
|
|
190
|
+
max_duration_ns = duration_ns if duration_ns > max_duration_ns
|
|
176
191
|
end
|
|
177
192
|
|
|
178
|
-
duration =
|
|
193
|
+
duration = total_duration_ns / 1_000_000_000.0
|
|
179
194
|
|
|
180
195
|
{
|
|
181
196
|
value: value,
|
|
182
|
-
duration: duration
|
|
197
|
+
duration: duration,
|
|
198
|
+
min_duration: (min_duration_ns || 0) / 1_000_000_000.0,
|
|
199
|
+
average_duration: duration / n,
|
|
200
|
+
max_duration: max_duration_ns / 1_000_000_000.0
|
|
183
201
|
}
|
|
184
202
|
end
|
|
185
203
|
|
|
186
|
-
def
|
|
187
|
-
|
|
204
|
+
def format_duration_ms(duration)
|
|
205
|
+
format("%.6fms", duration * 1000)
|
|
206
|
+
end
|
|
188
207
|
|
|
189
|
-
|
|
190
|
-
|
|
208
|
+
def benchmark_feature_flag(instance, feature_key, context, n)
|
|
209
|
+
benchmark_evaluation(n) do
|
|
210
|
+
instance.is_enabled(feature_key, context)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
191
213
|
|
|
192
|
-
|
|
193
|
-
n
|
|
214
|
+
def benchmark_feature_variation(instance, feature_key, context, n)
|
|
215
|
+
benchmark_evaluation(n) do
|
|
194
216
|
instance.get_variation(feature_key, context)
|
|
195
217
|
end
|
|
196
|
-
|
|
197
|
-
duration = Time.now - start_time
|
|
198
|
-
|
|
199
|
-
{
|
|
200
|
-
value: value,
|
|
201
|
-
duration: duration
|
|
202
|
-
}
|
|
203
218
|
end
|
|
204
219
|
|
|
205
220
|
def benchmark_feature_variable(instance, feature_key, variable_key, context, n)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# Get the actual variable value from the SDK
|
|
209
|
-
value = instance.get_variable(feature_key, variable_key, context)
|
|
210
|
-
|
|
211
|
-
# Benchmark the evaluation
|
|
212
|
-
n.times do
|
|
221
|
+
benchmark_evaluation(n) do
|
|
213
222
|
instance.get_variable(feature_key, variable_key, context)
|
|
214
223
|
end
|
|
215
|
-
|
|
216
|
-
duration = Time.now - start_time
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
value: value,
|
|
220
|
-
duration: duration
|
|
221
|
-
}
|
|
222
224
|
end
|
|
223
225
|
|
|
224
226
|
def format_value(value)
|