Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[featureflag]: use float to check for flag probability #1237

Merged
merged 20 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
439c65e
use float to check for flag probability
puckpuck Nov 6, 2023
fbef816
use float to check for flag probability
puckpuck Nov 6, 2023
59f682d
Merge branch 'main' into ffs.add-probability-based-flags
austinlparker Nov 6, 2023
8ff0b27
Update CHANGELOG.md
austinlparker Nov 6, 2023
0f057df
Add help text for float feature flags; Make upgrades possible for pos…
joshleecreates Nov 16, 2023
beaa193
Merge branch 'main' into ffs.add-probability-based-flags
puckpuck Nov 21, 2023
149a3de
add description for feature flag value
puckpuck Nov 21, 2023
4c3c438
Merge branch 'main' into ffs.add-probability-based-flags
austinlparker Nov 28, 2023
5faad08
Merge branch 'ffs.add-probability-based-flags' into ffs.add_probabili…
puckpuck Dec 3, 2023
729561a
Merge pull request #1 from joshleecreates/ffs.add_probability_based_f…
puckpuck Dec 3, 2023
53697ac
Merge branch 'main' into ffs.add-probability-based-flags
puckpuck Dec 3, 2023
24b5922
clean up help text
puckpuck Dec 3, 2023
4bd1de8
Merge branch 'main' into ffs.add-probability-based-flags
julianocosta89 Dec 4, 2023
4e6b326
Merge branch 'main' into ffs.add-probability-based-flags
julianocosta89 Dec 7, 2023
125ad35
return to single migration with decimal type
joshleecreates Dec 9, 2023
2abc9ed
Merge pull request #2 from joshleecreates/ffs-float-fix
puckpuck Dec 12, 2023
5e22911
Merge branch 'main' into ffs.add-probability-based-flags
puckpuck Dec 12, 2023
ba5f5d0
Merge branch 'main' into ffs.add-probability-based-flags
puckpuck Dec 13, 2023
71388f0
use float instead of decimal
puckpuck Dec 13, 2023
916be2e
Merge branch 'main' into ffs.add-probability-based-flags
austinlparker Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ release.

* update PHP quoteservice to use 1.0.0
([#1236](https://github.com/open-telemetry/opentelemetry-demo/pull/1236))
* Add ability to do probabilistic A/B testing with feature flags
([#1237](https://github.com/open-telemetry/opentelemetry-demo/pull/1237))
* add env var for pinning trace-based test tool version
([#1239](https://github.com/open-telemetry/opentelemetry-demo/pull/1239))
* [cartservice] Add .NET memory, CPU, and thread metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0


defmodule Featureflagservice.FeatureFlags.FeatureFlag do
use Ecto.Schema
import Ecto.Changeset

schema "featureflags" do
field :description, :string
field :enabled, :boolean, default: false
field :enabled, :float, default: 0.0
puckpuck marked this conversation as resolved.
Show resolved Hide resolved
field :name, :string

timestamps()
Expand All @@ -19,6 +18,7 @@ defmodule Featureflagservice.FeatureFlags.FeatureFlag do
feature_flag
|> cast(attrs, [:name, :description, :enabled])
|> validate_required([:name, :description, :enabled])
|> validate_number(:enabled, greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0)
|> unique_constraint(:name)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@
<%= text_input f, :description %>
<%= error_tag f, :description %>

<div class="float-right">
<%= checkbox f, :enabled %>
<%= label f, :enabled, class: "label-inline" %>
</div>
<%= label f, :enabled %>
<%= number_input f, :enabled, min: 0, max: 1, step: 0.01, "aria-describedby": "enabled_help_text" %>
<p id="enabled_help_text" style="font-size: smaller; margin-top: -10px;">
A decimal value between 0 and 1 (inclusive)<br />
0.0 is always disabled<br />
1.0 is always enabled<br />
All values between set a percentage chance on each request<br />
example: 0.55 is enabled 55% of the time<br />
</p>
<%= error_tag f, :enabled %>

<%= submit "Save" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@


defmodule Featureflagservice.Repo.Migrations.CreateFeatureflags do
use Ecto.Migration

def change do
create table(:featureflags) do
add :name, :string
add :description, :string
add :enabled, :boolean, default: false, null: false
add :enabled, :float, default: 0.0, null: false

timestamps()
end
Expand All @@ -21,22 +19,26 @@ defmodule Featureflagservice.Repo.Migrations.CreateFeatureflags do
repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "productCatalogFailure",
description: "Fail product catalog service on a specific product",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "recommendationCache",
description: "Cache recommendations",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "adServiceFailure",
description: "Fail ad service requests sporadically",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "cartServiceFailure",
description: "Fail cart service requests sporadically",
enabled: false})
enabled: 0.0
})
end

defp execute_down do
Expand Down
76 changes: 42 additions & 34 deletions src/featureflagservice/src/ffs_service.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,64 @@
-behaviour(ffs_service_bhvr).

-export([get_flag/2,
create_flag/2,
update_flag/2,
list_flags/2,
delete_flag/2]).
create_flag/2,
update_flag/2,
list_flags/2,
delete_flag/2]).

-include_lib("grpcbox/include/grpcbox.hrl").

-include_lib("opentelemetry_api/include/otel_tracer.hrl").

-spec get_flag(ctx:t(), ffs_demo_pb:get_flag_request()) ->
{ok, ffs_demo_pb:get_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:get_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
get_flag(Ctx, #{name := Name}) ->
case 'Elixir.Featureflagservice.FeatureFlags':get_feature_flag_by_name(Name) of
nil ->
{grpc_error, {?GRPC_STATUS_NOT_FOUND, <<"the requested feature flag does not exist">>}};
#{'__struct__' := 'Elixir.Featureflagservice.FeatureFlags.FeatureFlag',
description := Description,
enabled := Enabled,
inserted_at := CreatedAt,
updated_at := UpdatedAt
} ->
?set_attribute('app.featureflag.name', Name),
?set_attribute('app.featureflag.enabled', Enabled),
{ok, Epoch} = 'Elixir.NaiveDateTime':from_erl({{1970, 1, 1}, {0, 0, 0}}),
CreatedAtSeconds = 'Elixir.NaiveDateTime':diff(CreatedAt, Epoch),
UpdatedAtSeconds = 'Elixir.NaiveDateTime':diff(UpdatedAt, Epoch),
Flag = #{name => Name,
description => Description,
enabled => Enabled,
created_at => #{seconds => CreatedAtSeconds, nanos => 0},
updated_at => #{seconds => UpdatedAtSeconds, nanos => 0}},
{ok, #{flag => Flag}, Ctx}
end.
case 'Elixir.Featureflagservice.FeatureFlags':get_feature_flag_by_name(Name) of
nil ->
{grpc_error, {?GRPC_STATUS_NOT_FOUND, <<"the requested feature flag does not exist">>}};
#{'__struct__' := 'Elixir.Featureflagservice.FeatureFlags.FeatureFlag',
description := Description,
enabled := Enabled,
inserted_at := CreatedAt,
updated_at := UpdatedAt
} ->
RandomNumber = rand:uniform(100), % Generate a random number between 0 and 100
Probability = trunc(Enabled * 100), % Convert the Enabled value to a percentage
FlagEnabledValue = RandomNumber =< Probability, % Determine if the random number falls within the probability range

?set_attribute('app.featureflag.name', Name),
?set_attribute('app.featureflag.raw_value', Enabled),
?set_attribute('app.featureflag.enabled', FlagEnabledValue),

{ok, Epoch} = 'Elixir.NaiveDateTime':from_erl({{1970, 1, 1}, {0, 0, 0}}),
CreatedAtSeconds = 'Elixir.NaiveDateTime':diff(CreatedAt, Epoch),
UpdatedAtSeconds = 'Elixir.NaiveDateTime':diff(UpdatedAt, Epoch),

Flag = #{name => Name,
description => Description,
enabled => FlagEnabledValue,
created_at => #{seconds => CreatedAtSeconds, nanos => 0},
updated_at => #{seconds => UpdatedAtSeconds, nanos => 0}},

{ok, #{flag => Flag}, Ctx}
end.

-spec create_flag(ctx:t(), ffs_demo_pb:create_flag_request()) ->
{ok, ffs_demo_pb:create_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:create_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
create_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to create flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to create flags.">>}}.

-spec update_flag(ctx:t(), ffs_demo_pb:update_flag_request()) ->
{ok, ffs_demo_pb:update_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:update_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
update_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to update flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to update flags.">>}}.

-spec list_flags(ctx:t(), ffs_demo_pb:list_flags_request()) ->
{ok, ffs_demo_pb:list_flags_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:list_flags_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
list_flags(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to view all flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to view all flags.">>}}.

-spec delete_flag(ctx:t(), ffs_demo_pb:delete_flag_request()) ->
{ok, ffs_demo_pb:delete_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:delete_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
delete_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to delete flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to delete flags.">>}}.
Loading