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.
@@ -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
- puts "Building datafile containing all features for \"#{@options.environment}\"..."
38
- datafile_build_start = Time.now
35
+ private
39
36
 
40
- # Build datafile by executing the featurevisor build command
41
- datafile = build_datafile(@options.environment)
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 "Datafile build duration: #{datafile_build_duration_ms}ms"
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 = create_instance(datafile)
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 "Average duration: #{pretty_duration(output[:duration] / @options.n)}"
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
- private
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
- if @options.schema_version && !@options.schema_version.empty?
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 create_instance(datafile)
155
+ def create_featurevisor(datafile)
146
156
  # Create a real Featurevisor instance
147
- instance = Featurevisor.create_instance(
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 benchmark_feature_flag(instance, feature_key, context, n)
168
- start_time = Time.now
169
-
170
- # Get the actual feature value from the SDK
171
- value = instance.is_enabled(feature_key, context)
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
- instance.is_enabled(feature_key, context)
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 = Time.now - start_time
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 benchmark_feature_variation(instance, feature_key, context, n)
187
- start_time = Time.now
204
+ def format_duration_ms(duration)
205
+ format("%.6fms", duration * 1000)
206
+ end
188
207
 
189
- # Get the actual feature variation from the SDK
190
- value = instance.get_variation(feature_key, context)
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
- # Benchmark the evaluation
193
- n.times do
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
- start_time = Time.now
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)