amplitude-experiment 1.0.0.beta.6 → 1.0.0.beta.9

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: 625ebf6baeed965a20e5b19c0b9655337523b0749edb5ca39d742c07d4421ce6
4
- data.tar.gz: 83d0a1bf035e355ba94804368e44ca11efb8eb80a86288a7a8ea9d81040a30c5
3
+ metadata.gz: 2ca6e5960f24ff9df262ce2dabff4b487e3cf26dbd1a6a48010da199fcf0ee63
4
+ data.tar.gz: b368a468d27f7d739b34272f20175049e839b4ba3ba620eb2ce229a829e9fa3f
5
5
  SHA512:
6
- metadata.gz: a4fa14dff27f5b8d3126df2337169a03c931d22c431b1f30e6067086839f95eff0ac4e6d3c3fcef19bdbf224df82da57f7b7726e40f23ba9656b32996814bbc7
7
- data.tar.gz: b0eb2539296c79d0d1de7c9f878a73d9f1cf93d1089da33fc73989ee29c60c5c8e811550ec4fc8d120ca2187d093db6cf0f6fbac5a03ad6d58806b3d92314681
6
+ metadata.gz: bdb8f048df75ec0f1a55e2adb0191bd1c9c58f6c4137a0d05d5c32b141090935ceba034f6ff7f2dbd534d029c1620a800c01944ad688f2921fc265d279327d57
7
+ data.tar.gz: 71b1cdadbab86a9f77e3a6f4cd55064128d3194029c095473e838e77ad2bcbc5b2410065c182f05378ced43a4803d03d4856d99096b230b17ae6817fe91bc0e5
data/README.md CHANGED
@@ -25,7 +25,7 @@ gem install amplitude-experiment --pre
25
25
  ```
26
26
 
27
27
 
28
- ## Quick Start
28
+ ## Remote Evaluation Quick Start
29
29
  ```ruby
30
30
  require 'amplitude-experiment'
31
31
 
@@ -33,7 +33,7 @@ require 'amplitude-experiment'
33
33
  apiKey = 'YOUR-API-KEY'
34
34
 
35
35
  # (2) Initialize the experiment client
36
- experiment = AmplitudeExperiment.init(api_key)
36
+ experiment = AmplitudeExperiment.initialize_remote(api_key)
37
37
 
38
38
  # (3) Fetch variants for a user
39
39
  user = AmplitudeExperiment::User.new(user_id: 'user@company.com', device_id: 'abcezas123', user_properties: {'premium' => true})
@@ -64,6 +64,33 @@ unless variant.nil?
64
64
  end
65
65
  ```
66
66
 
67
+ ## Local Evaluation Quick Start
68
+
69
+ ```ruby
70
+ require 'amplitude-experiment'
71
+
72
+ # (1) Get your deployment's API key
73
+ apiKey = 'YOUR-API-KEY'
74
+
75
+ # (2) Initialize the experiment client
76
+ experiment = AmplitudeExperiment.initialize_local(api_key)
77
+
78
+ # (3) Start the local evaluation client
79
+ experiment.start
80
+
81
+ # (4) Evaluate a user
82
+ user = AmplitudeExperiment::User.new(user_id: 'user@company.com', device_id: 'abcezas123', user_properties: {'premium' => true})
83
+ variants = experiment.evaluate(user)
84
+ variant = variants['YOUR-FLAG-KEY']
85
+ unless variant.nil?
86
+ if variant.value == 'on'
87
+ # Flag is on
88
+ else
89
+ # Flag is off
90
+ end
91
+ end
92
+ ```
93
+
67
94
  ## More Information
68
95
  Please visit our :100:[Developer Center](https://www.docs.developers.amplitude.com/experiment/sdks/ruby-sdk/) for more instructions on using our the SDK.
69
96
 
@@ -1,11 +1,16 @@
1
1
  require 'experiment/version'
2
- require 'experiment/config'
2
+ require 'experiment/persistent_http_client'
3
+ require 'experiment/remote/config'
3
4
  require 'experiment/cookie'
4
5
  require 'experiment/user'
5
6
  require 'experiment/variant'
6
7
  require 'experiment/factory'
7
- require 'experiment/persistent_http_client'
8
- require 'experiment/client'
8
+ require 'experiment/remote/client'
9
+ require 'experiment/local/client'
10
+ require 'experiment/local/config'
11
+ require 'experiment/local/cache'
12
+ require 'experiment/local/fetcher'
13
+ require 'experiment/local/poller'
9
14
 
10
15
  # Amplitude Experiment Module
11
16
  module AmplitudeExperiment
@@ -1,6 +1,7 @@
1
1
  # Provides factory methods for storing singleton instance of Client
2
2
  module AmplitudeExperiment
3
- @instances = {}
3
+ @local_instance = {}
4
+ @remote_instance = {}
4
5
  @default_instance = '$default_instance'
5
6
 
6
7
  # Initializes a singleton Client. This method returns a default singleton instance, subsequent calls to
@@ -8,8 +9,23 @@ module AmplitudeExperiment
8
9
  #
9
10
  # @param [String] api_key The environment API Key
10
11
  # @param [Config] config Optional Config.
11
- def self.init(api_key, config = nil)
12
- @instances.store(@default_instance, Client.new(api_key, config)) unless @instances.key?(@default_instance)
13
- @instances.fetch(@default_instance)
12
+ def self.initialize_remote(api_key, config = nil)
13
+ unless @local_instance.key?(@default_instance)
14
+ @local_instance.store(@default_instance, RemoteEvaluationClient.new(api_key, config))
15
+ end
16
+ @local_instance.fetch(@default_instance)
17
+ end
18
+
19
+ # Initializes a local evaluation Client. A local evaluation client can evaluate local flags or experiments for a
20
+ # user without requiring a remote call to the amplitude evaluation server. In order to best leverage local
21
+ # evaluation, all flags, and experiments being evaluated server side should be configured as local.
22
+ #
23
+ # @param [String] api_key The environment API Key
24
+ # @param [Config] config Optional Config.
25
+ def self.initialize_local(api_key, config = nil)
26
+ unless @remote_instance.key?(@default_instance)
27
+ @remote_instance.store(@default_instance, LocalEvaluationClient.new(api_key, config))
28
+ end
29
+ @remote_instance.fetch(@default_instance)
14
30
  end
15
31
  end
@@ -0,0 +1,53 @@
1
+ require 'uri'
2
+ require 'logger'
3
+
4
+ module AmplitudeExperiment
5
+ # InMemoryFlagConfigCache
6
+ # The place to store the flag configs fetched from the server
7
+ class InMemoryFlagConfigCache
8
+ attr_accessor :cache
9
+
10
+ def initialize(flag_configs = {})
11
+ @semaphore = Mutex.new
12
+ @cache = flag_configs
13
+ end
14
+
15
+ def get(flag_key)
16
+ @semaphore.synchronize do
17
+ @cache.fetch(flag_key, nil)
18
+ end
19
+ end
20
+
21
+ def caches
22
+ @semaphore.synchronize do
23
+ @cache
24
+ end
25
+ end
26
+
27
+ def put(flag_key, flag_config)
28
+ @semaphore.synchronize do
29
+ @cache.store(flag_key, flag_config.clone)
30
+ end
31
+ end
32
+
33
+ def put_all(flag_configs)
34
+ @semaphore.synchronize do
35
+ flag_configs.each do |key, value|
36
+ @cache.store(key, value.clone) if value
37
+ end
38
+ end
39
+ end
40
+
41
+ def delete(flag_key)
42
+ @semaphore.synchronize do
43
+ @cache.delete(flag_key)
44
+ end
45
+ end
46
+
47
+ def clear
48
+ @semaphore.synchronize do
49
+ @cache = {}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,84 @@
1
+ require 'uri'
2
+ require 'logger'
3
+ require 'experiment/local/evaluation/evaluation'
4
+
5
+ module AmplitudeExperiment
6
+ # Main client for fetching variant data.
7
+ class LocalEvaluationClient
8
+ # Creates a new Experiment Client instance.
9
+ #
10
+ # @param [String] api_key The environment API Key
11
+ # @param [LocalEvaluationConfig] config The config object
12
+ def initialize(api_key, config = nil)
13
+ @api_key = api_key
14
+ @config = config || LocalEvaluationConfig.new
15
+ @cache = InMemoryFlagConfigCache.new(@config.bootstrap)
16
+ @logger = Logger.new($stdout)
17
+ @logger.level = if @config.debug
18
+ Logger::DEBUG
19
+ else
20
+ Logger::INFO
21
+ end
22
+ @fetcher = LocalEvaluationFetcher.new(api_key, @config.debug, @config.server_url)
23
+ @poller = FlagConfigPoller.new(@fetcher, @cache, @config.debug)
24
+
25
+ raise ArgumentError, 'Experiment API key is empty' if @api_key.nil? || @api_key.empty?
26
+ end
27
+
28
+ # Locally evaluates flag variants for a user.
29
+ #
30
+ # @param [User] user The user to evaluate
31
+ # @param [String[]] flag_keys The flags to evaluate with the user. If empty, all flags from the flag cache are evaluated
32
+ #
33
+ # @return [Hash[String, Variant]] The evaluated variants
34
+ def evaluate(user, flag_keys = [])
35
+ flag_configs = []
36
+ if flag_keys.empty?
37
+ @cache.cache.each do |_, value|
38
+ flag_configs.push(value)
39
+ end
40
+ else
41
+ flag_configs = get_flag_configs(flag_keys)
42
+ end
43
+ flag_configs_str = flag_configs.to_json
44
+ user_str = user.to_json
45
+ @logger.debug("[Experiment] Evaluate: User: #{user_str} - Rules: #{flag_configs_str}") if @config.debug
46
+ result_json = evaluation(flag_configs_str, user_str)
47
+ @logger.debug(`[Experiment] evaluate - result: #{variants}`) if @config.debug
48
+ result = JSON.parse(result_json)
49
+ variants = {}
50
+ result.each do |key, value|
51
+ next if value['isDefaultVariant']
52
+
53
+ variant_key = value['variant']['key']
54
+ variant_payload = value['variant']['payload']
55
+ variants.store(key, Variant.new(variant_key, variant_payload))
56
+ end
57
+ variants
58
+ end
59
+
60
+ # Fetch initial flag configurations and start polling for updates.
61
+ # You must call this function to begin polling for flag config updates.
62
+ def start
63
+ @poller.start
64
+ end
65
+
66
+ # Stop polling for flag configurations. Close resource like connection pool with client
67
+ def stop
68
+ @poller.stop
69
+ end
70
+
71
+ private
72
+
73
+ def get_flag_configs(flag_keys = [])
74
+ return @cache.cache if flag_keys.empty?
75
+
76
+ flag_configs = []
77
+ flag_keys.each do |key|
78
+ flag_config = @cache.get(key)
79
+ flag_configs.push(flag_config) if flag_config
80
+ end
81
+ flag_configs
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,35 @@
1
+ module AmplitudeExperiment
2
+ # LocalEvaluationConfig
3
+ class LocalEvaluationConfig
4
+ # Default server url
5
+ DEFAULT_SERVER_URL = 'https://api.lab.amplitude.com'.freeze
6
+
7
+ # Set to true to log some extra information to the console.
8
+ # @return [Boolean] the value of debug
9
+ attr_accessor :debug
10
+
11
+ # The server endpoint from which to request variants.
12
+ # @return [String] the value of server url
13
+ attr_accessor :server_url
14
+
15
+ # The server endpoint from which to request variants.
16
+ # @return [Hash] the value of bootstrap
17
+ attr_accessor :bootstrap
18
+
19
+ # The server endpoint from which to request variants.
20
+ # @return [long] the value of flag config polling interval in million seconds
21
+ attr_accessor :flag_config_polling_interval_millis
22
+
23
+ # @param [Boolean] debug Set to true to log some extra information to the console.
24
+ # @param [String] server_url The server endpoint from which to request variants.
25
+ # @param [Hash] bootstrap The value of bootstrap.
26
+ # @param [long] flag_config_polling_interval_millis The value of flag config polling interval in million seconds.
27
+ def initialize(server_url = DEFAULT_SERVER_URL, bootstrap = {},
28
+ flag_config_polling_interval_millis = 30_000, debug: false)
29
+ @debug = debug || false
30
+ @server_url = server_url
31
+ @bootstrap = bootstrap
32
+ @flag_config_polling_interval_millis = flag_config_polling_interval_millis
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ # rubocop:disable all
2
+ require 'ffi'
3
+
4
+ # The evaluation wrapper
5
+ module EvaluationInterop
6
+ extend FFI::Library
7
+ host_os = RbConfig::CONFIG['host_os']
8
+ cpu = RbConfig::CONFIG['host_cpu']
9
+ evaluation_dir = File.dirname(__FILE__)
10
+ ffi_lib ["#{evaluation_dir}/lib/macosX64/libevaluation_interop.dylib"] if host_os =~ /darwin|mac os/ && cpu =~ /x86_64/
11
+ ffi_lib ["#{evaluation_dir}/lib/macosArm64/libevaluation_interop.dylib"] if host_os =~ /darwin|mac os/ && cpu =~ /arm64/
12
+ ffi_lib ["#{evaluation_dir}/lib/linuxX64/libevaluation_interop.so"] if host_os =~ /linux/ && cpu =~ /x86_64/
13
+ ffi_lib ["#{evaluation_dir}/lib/linuxArm64/libevaluation_interop.so"] if host_os =~ /linux/ && cpu =~ /arm64/
14
+
15
+ class Root < FFI::Struct
16
+ layout :evaluate, callback([:string, :string], :pointer)
17
+ end
18
+
19
+ class Kotlin < FFI::Struct
20
+ layout :root, Root
21
+ end
22
+
23
+ class Libevaluation_interop_ExportedSymbols < FFI::Struct
24
+ layout :DisposeStablePointer, callback([:pointer], :void),
25
+ :DisposeString, callback([:string], :void),
26
+ :IsInstance, callback([:pointer, :string], :pointer),
27
+ :createNullableByte, callback([:string], :pointer),
28
+ :createNullableShort, callback([:pointer], :pointer),
29
+ :createNullableInt, callback([:pointer], :pointer),
30
+ :createNullableLong, callback([:pointer], :pointer),
31
+ :createNullableFloat, callback([:pointer], :pointer),
32
+ :createNullableDouble, callback([:pointer], :pointer),
33
+ :createNullableChar, callback([:pointer], :pointer),
34
+ :createNullableBoolean, callback([:pointer], :pointer),
35
+ :createNullableUnit, callback([], :pointer),
36
+ :kotlin, Kotlin
37
+ end
38
+
39
+ attach_function :libevaluation_interop_symbols, [], Libevaluation_interop_ExportedSymbols.by_ref
40
+ end
41
+
42
+ def evaluation(rule_json, user_json)
43
+ lib = EvaluationInterop.libevaluation_interop_symbols()
44
+ fn = lib[:kotlin][:root][:evaluate]
45
+ evaluation_result = fn.call(rule_json, user_json)
46
+ evaluation_result.read_string
47
+ end
48
+ # rubocop:disable all
@@ -0,0 +1,82 @@
1
+ #ifndef KONAN_LIBEVALUATION_INTEROP_H
2
+ #define KONAN_LIBEVALUATION_INTEROP_H
3
+ #ifdef __cplusplus
4
+ extern "C" {
5
+ #endif
6
+ #ifdef __cplusplus
7
+ typedef bool libevaluation_interop_KBoolean;
8
+ #else
9
+ typedef _Bool libevaluation_interop_KBoolean;
10
+ #endif
11
+ typedef unsigned short libevaluation_interop_KChar;
12
+ typedef signed char libevaluation_interop_KByte;
13
+ typedef short libevaluation_interop_KShort;
14
+ typedef int libevaluation_interop_KInt;
15
+ typedef long long libevaluation_interop_KLong;
16
+ typedef unsigned char libevaluation_interop_KUByte;
17
+ typedef unsigned short libevaluation_interop_KUShort;
18
+ typedef unsigned int libevaluation_interop_KUInt;
19
+ typedef unsigned long long libevaluation_interop_KULong;
20
+ typedef float libevaluation_interop_KFloat;
21
+ typedef double libevaluation_interop_KDouble;
22
+ typedef float __attribute__ ((__vector_size__ (16))) libevaluation_interop_KVector128;
23
+ typedef void* libevaluation_interop_KNativePtr;
24
+ struct libevaluation_interop_KType;
25
+ typedef struct libevaluation_interop_KType libevaluation_interop_KType;
26
+
27
+ typedef struct {
28
+ libevaluation_interop_KNativePtr pinned;
29
+ } libevaluation_interop_kref_kotlin_Byte;
30
+ typedef struct {
31
+ libevaluation_interop_KNativePtr pinned;
32
+ } libevaluation_interop_kref_kotlin_Short;
33
+ typedef struct {
34
+ libevaluation_interop_KNativePtr pinned;
35
+ } libevaluation_interop_kref_kotlin_Int;
36
+ typedef struct {
37
+ libevaluation_interop_KNativePtr pinned;
38
+ } libevaluation_interop_kref_kotlin_Long;
39
+ typedef struct {
40
+ libevaluation_interop_KNativePtr pinned;
41
+ } libevaluation_interop_kref_kotlin_Float;
42
+ typedef struct {
43
+ libevaluation_interop_KNativePtr pinned;
44
+ } libevaluation_interop_kref_kotlin_Double;
45
+ typedef struct {
46
+ libevaluation_interop_KNativePtr pinned;
47
+ } libevaluation_interop_kref_kotlin_Char;
48
+ typedef struct {
49
+ libevaluation_interop_KNativePtr pinned;
50
+ } libevaluation_interop_kref_kotlin_Boolean;
51
+ typedef struct {
52
+ libevaluation_interop_KNativePtr pinned;
53
+ } libevaluation_interop_kref_kotlin_Unit;
54
+
55
+
56
+ typedef struct {
57
+ /* Service functions. */
58
+ void (*DisposeStablePointer)(libevaluation_interop_KNativePtr ptr);
59
+ void (*DisposeString)(const char* string);
60
+ libevaluation_interop_KBoolean (*IsInstance)(libevaluation_interop_KNativePtr ref, const libevaluation_interop_KType* type);
61
+ libevaluation_interop_kref_kotlin_Byte (*createNullableByte)(libevaluation_interop_KByte);
62
+ libevaluation_interop_kref_kotlin_Short (*createNullableShort)(libevaluation_interop_KShort);
63
+ libevaluation_interop_kref_kotlin_Int (*createNullableInt)(libevaluation_interop_KInt);
64
+ libevaluation_interop_kref_kotlin_Long (*createNullableLong)(libevaluation_interop_KLong);
65
+ libevaluation_interop_kref_kotlin_Float (*createNullableFloat)(libevaluation_interop_KFloat);
66
+ libevaluation_interop_kref_kotlin_Double (*createNullableDouble)(libevaluation_interop_KDouble);
67
+ libevaluation_interop_kref_kotlin_Char (*createNullableChar)(libevaluation_interop_KChar);
68
+ libevaluation_interop_kref_kotlin_Boolean (*createNullableBoolean)(libevaluation_interop_KBoolean);
69
+ libevaluation_interop_kref_kotlin_Unit (*createNullableUnit)(void);
70
+
71
+ /* User functions. */
72
+ struct {
73
+ struct {
74
+ const char* (*evaluate)(const char* rules, const char* user);
75
+ } root;
76
+ } kotlin;
77
+ } libevaluation_interop_ExportedSymbols;
78
+ extern libevaluation_interop_ExportedSymbols* libevaluation_interop_symbols(void);
79
+ #ifdef __cplusplus
80
+ } /* extern "C" */
81
+ #endif
82
+ #endif /* KONAN_LIBEVALUATION_INTEROP_H */
@@ -0,0 +1,82 @@
1
+ #ifndef KONAN_LIBEVALUATION_INTEROP_H
2
+ #define KONAN_LIBEVALUATION_INTEROP_H
3
+ #ifdef __cplusplus
4
+ extern "C" {
5
+ #endif
6
+ #ifdef __cplusplus
7
+ typedef bool libevaluation_interop_KBoolean;
8
+ #else
9
+ typedef _Bool libevaluation_interop_KBoolean;
10
+ #endif
11
+ typedef unsigned short libevaluation_interop_KChar;
12
+ typedef signed char libevaluation_interop_KByte;
13
+ typedef short libevaluation_interop_KShort;
14
+ typedef int libevaluation_interop_KInt;
15
+ typedef long long libevaluation_interop_KLong;
16
+ typedef unsigned char libevaluation_interop_KUByte;
17
+ typedef unsigned short libevaluation_interop_KUShort;
18
+ typedef unsigned int libevaluation_interop_KUInt;
19
+ typedef unsigned long long libevaluation_interop_KULong;
20
+ typedef float libevaluation_interop_KFloat;
21
+ typedef double libevaluation_interop_KDouble;
22
+ typedef float __attribute__ ((__vector_size__ (16))) libevaluation_interop_KVector128;
23
+ typedef void* libevaluation_interop_KNativePtr;
24
+ struct libevaluation_interop_KType;
25
+ typedef struct libevaluation_interop_KType libevaluation_interop_KType;
26
+
27
+ typedef struct {
28
+ libevaluation_interop_KNativePtr pinned;
29
+ } libevaluation_interop_kref_kotlin_Byte;
30
+ typedef struct {
31
+ libevaluation_interop_KNativePtr pinned;
32
+ } libevaluation_interop_kref_kotlin_Short;
33
+ typedef struct {
34
+ libevaluation_interop_KNativePtr pinned;
35
+ } libevaluation_interop_kref_kotlin_Int;
36
+ typedef struct {
37
+ libevaluation_interop_KNativePtr pinned;
38
+ } libevaluation_interop_kref_kotlin_Long;
39
+ typedef struct {
40
+ libevaluation_interop_KNativePtr pinned;
41
+ } libevaluation_interop_kref_kotlin_Float;
42
+ typedef struct {
43
+ libevaluation_interop_KNativePtr pinned;
44
+ } libevaluation_interop_kref_kotlin_Double;
45
+ typedef struct {
46
+ libevaluation_interop_KNativePtr pinned;
47
+ } libevaluation_interop_kref_kotlin_Char;
48
+ typedef struct {
49
+ libevaluation_interop_KNativePtr pinned;
50
+ } libevaluation_interop_kref_kotlin_Boolean;
51
+ typedef struct {
52
+ libevaluation_interop_KNativePtr pinned;
53
+ } libevaluation_interop_kref_kotlin_Unit;
54
+
55
+
56
+ typedef struct {
57
+ /* Service functions. */
58
+ void (*DisposeStablePointer)(libevaluation_interop_KNativePtr ptr);
59
+ void (*DisposeString)(const char* string);
60
+ libevaluation_interop_KBoolean (*IsInstance)(libevaluation_interop_KNativePtr ref, const libevaluation_interop_KType* type);
61
+ libevaluation_interop_kref_kotlin_Byte (*createNullableByte)(libevaluation_interop_KByte);
62
+ libevaluation_interop_kref_kotlin_Short (*createNullableShort)(libevaluation_interop_KShort);
63
+ libevaluation_interop_kref_kotlin_Int (*createNullableInt)(libevaluation_interop_KInt);
64
+ libevaluation_interop_kref_kotlin_Long (*createNullableLong)(libevaluation_interop_KLong);
65
+ libevaluation_interop_kref_kotlin_Float (*createNullableFloat)(libevaluation_interop_KFloat);
66
+ libevaluation_interop_kref_kotlin_Double (*createNullableDouble)(libevaluation_interop_KDouble);
67
+ libevaluation_interop_kref_kotlin_Char (*createNullableChar)(libevaluation_interop_KChar);
68
+ libevaluation_interop_kref_kotlin_Boolean (*createNullableBoolean)(libevaluation_interop_KBoolean);
69
+ libevaluation_interop_kref_kotlin_Unit (*createNullableUnit)(void);
70
+
71
+ /* User functions. */
72
+ struct {
73
+ struct {
74
+ const char* (*evaluate)(const char* rules, const char* user);
75
+ } root;
76
+ } kotlin;
77
+ } libevaluation_interop_ExportedSymbols;
78
+ extern libevaluation_interop_ExportedSymbols* libevaluation_interop_symbols(void);
79
+ #ifdef __cplusplus
80
+ } /* extern "C" */
81
+ #endif
82
+ #endif /* KONAN_LIBEVALUATION_INTEROP_H */
@@ -0,0 +1,82 @@
1
+ #ifndef KONAN_LIBEVALUATION_INTEROP_H
2
+ #define KONAN_LIBEVALUATION_INTEROP_H
3
+ #ifdef __cplusplus
4
+ extern "C" {
5
+ #endif
6
+ #ifdef __cplusplus
7
+ typedef bool libevaluation_interop_KBoolean;
8
+ #else
9
+ typedef _Bool libevaluation_interop_KBoolean;
10
+ #endif
11
+ typedef unsigned short libevaluation_interop_KChar;
12
+ typedef signed char libevaluation_interop_KByte;
13
+ typedef short libevaluation_interop_KShort;
14
+ typedef int libevaluation_interop_KInt;
15
+ typedef long long libevaluation_interop_KLong;
16
+ typedef unsigned char libevaluation_interop_KUByte;
17
+ typedef unsigned short libevaluation_interop_KUShort;
18
+ typedef unsigned int libevaluation_interop_KUInt;
19
+ typedef unsigned long long libevaluation_interop_KULong;
20
+ typedef float libevaluation_interop_KFloat;
21
+ typedef double libevaluation_interop_KDouble;
22
+ typedef float __attribute__ ((__vector_size__ (16))) libevaluation_interop_KVector128;
23
+ typedef void* libevaluation_interop_KNativePtr;
24
+ struct libevaluation_interop_KType;
25
+ typedef struct libevaluation_interop_KType libevaluation_interop_KType;
26
+
27
+ typedef struct {
28
+ libevaluation_interop_KNativePtr pinned;
29
+ } libevaluation_interop_kref_kotlin_Byte;
30
+ typedef struct {
31
+ libevaluation_interop_KNativePtr pinned;
32
+ } libevaluation_interop_kref_kotlin_Short;
33
+ typedef struct {
34
+ libevaluation_interop_KNativePtr pinned;
35
+ } libevaluation_interop_kref_kotlin_Int;
36
+ typedef struct {
37
+ libevaluation_interop_KNativePtr pinned;
38
+ } libevaluation_interop_kref_kotlin_Long;
39
+ typedef struct {
40
+ libevaluation_interop_KNativePtr pinned;
41
+ } libevaluation_interop_kref_kotlin_Float;
42
+ typedef struct {
43
+ libevaluation_interop_KNativePtr pinned;
44
+ } libevaluation_interop_kref_kotlin_Double;
45
+ typedef struct {
46
+ libevaluation_interop_KNativePtr pinned;
47
+ } libevaluation_interop_kref_kotlin_Char;
48
+ typedef struct {
49
+ libevaluation_interop_KNativePtr pinned;
50
+ } libevaluation_interop_kref_kotlin_Boolean;
51
+ typedef struct {
52
+ libevaluation_interop_KNativePtr pinned;
53
+ } libevaluation_interop_kref_kotlin_Unit;
54
+
55
+
56
+ typedef struct {
57
+ /* Service functions. */
58
+ void (*DisposeStablePointer)(libevaluation_interop_KNativePtr ptr);
59
+ void (*DisposeString)(const char* string);
60
+ libevaluation_interop_KBoolean (*IsInstance)(libevaluation_interop_KNativePtr ref, const libevaluation_interop_KType* type);
61
+ libevaluation_interop_kref_kotlin_Byte (*createNullableByte)(libevaluation_interop_KByte);
62
+ libevaluation_interop_kref_kotlin_Short (*createNullableShort)(libevaluation_interop_KShort);
63
+ libevaluation_interop_kref_kotlin_Int (*createNullableInt)(libevaluation_interop_KInt);
64
+ libevaluation_interop_kref_kotlin_Long (*createNullableLong)(libevaluation_interop_KLong);
65
+ libevaluation_interop_kref_kotlin_Float (*createNullableFloat)(libevaluation_interop_KFloat);
66
+ libevaluation_interop_kref_kotlin_Double (*createNullableDouble)(libevaluation_interop_KDouble);
67
+ libevaluation_interop_kref_kotlin_Char (*createNullableChar)(libevaluation_interop_KChar);
68
+ libevaluation_interop_kref_kotlin_Boolean (*createNullableBoolean)(libevaluation_interop_KBoolean);
69
+ libevaluation_interop_kref_kotlin_Unit (*createNullableUnit)(void);
70
+
71
+ /* User functions. */
72
+ struct {
73
+ struct {
74
+ const char* (*evaluate)(const char* rules, const char* user);
75
+ } root;
76
+ } kotlin;
77
+ } libevaluation_interop_ExportedSymbols;
78
+ extern libevaluation_interop_ExportedSymbols* libevaluation_interop_symbols(void);
79
+ #ifdef __cplusplus
80
+ } /* extern "C" */
81
+ #endif
82
+ #endif /* KONAN_LIBEVALUATION_INTEROP_H */
@@ -0,0 +1,82 @@
1
+ #ifndef KONAN_LIBEVALUATION_INTEROP_H
2
+ #define KONAN_LIBEVALUATION_INTEROP_H
3
+ #ifdef __cplusplus
4
+ extern "C" {
5
+ #endif
6
+ #ifdef __cplusplus
7
+ typedef bool libevaluation_interop_KBoolean;
8
+ #else
9
+ typedef _Bool libevaluation_interop_KBoolean;
10
+ #endif
11
+ typedef unsigned short libevaluation_interop_KChar;
12
+ typedef signed char libevaluation_interop_KByte;
13
+ typedef short libevaluation_interop_KShort;
14
+ typedef int libevaluation_interop_KInt;
15
+ typedef long long libevaluation_interop_KLong;
16
+ typedef unsigned char libevaluation_interop_KUByte;
17
+ typedef unsigned short libevaluation_interop_KUShort;
18
+ typedef unsigned int libevaluation_interop_KUInt;
19
+ typedef unsigned long long libevaluation_interop_KULong;
20
+ typedef float libevaluation_interop_KFloat;
21
+ typedef double libevaluation_interop_KDouble;
22
+ typedef float __attribute__ ((__vector_size__ (16))) libevaluation_interop_KVector128;
23
+ typedef void* libevaluation_interop_KNativePtr;
24
+ struct libevaluation_interop_KType;
25
+ typedef struct libevaluation_interop_KType libevaluation_interop_KType;
26
+
27
+ typedef struct {
28
+ libevaluation_interop_KNativePtr pinned;
29
+ } libevaluation_interop_kref_kotlin_Byte;
30
+ typedef struct {
31
+ libevaluation_interop_KNativePtr pinned;
32
+ } libevaluation_interop_kref_kotlin_Short;
33
+ typedef struct {
34
+ libevaluation_interop_KNativePtr pinned;
35
+ } libevaluation_interop_kref_kotlin_Int;
36
+ typedef struct {
37
+ libevaluation_interop_KNativePtr pinned;
38
+ } libevaluation_interop_kref_kotlin_Long;
39
+ typedef struct {
40
+ libevaluation_interop_KNativePtr pinned;
41
+ } libevaluation_interop_kref_kotlin_Float;
42
+ typedef struct {
43
+ libevaluation_interop_KNativePtr pinned;
44
+ } libevaluation_interop_kref_kotlin_Double;
45
+ typedef struct {
46
+ libevaluation_interop_KNativePtr pinned;
47
+ } libevaluation_interop_kref_kotlin_Char;
48
+ typedef struct {
49
+ libevaluation_interop_KNativePtr pinned;
50
+ } libevaluation_interop_kref_kotlin_Boolean;
51
+ typedef struct {
52
+ libevaluation_interop_KNativePtr pinned;
53
+ } libevaluation_interop_kref_kotlin_Unit;
54
+
55
+
56
+ typedef struct {
57
+ /* Service functions. */
58
+ void (*DisposeStablePointer)(libevaluation_interop_KNativePtr ptr);
59
+ void (*DisposeString)(const char* string);
60
+ libevaluation_interop_KBoolean (*IsInstance)(libevaluation_interop_KNativePtr ref, const libevaluation_interop_KType* type);
61
+ libevaluation_interop_kref_kotlin_Byte (*createNullableByte)(libevaluation_interop_KByte);
62
+ libevaluation_interop_kref_kotlin_Short (*createNullableShort)(libevaluation_interop_KShort);
63
+ libevaluation_interop_kref_kotlin_Int (*createNullableInt)(libevaluation_interop_KInt);
64
+ libevaluation_interop_kref_kotlin_Long (*createNullableLong)(libevaluation_interop_KLong);
65
+ libevaluation_interop_kref_kotlin_Float (*createNullableFloat)(libevaluation_interop_KFloat);
66
+ libevaluation_interop_kref_kotlin_Double (*createNullableDouble)(libevaluation_interop_KDouble);
67
+ libevaluation_interop_kref_kotlin_Char (*createNullableChar)(libevaluation_interop_KChar);
68
+ libevaluation_interop_kref_kotlin_Boolean (*createNullableBoolean)(libevaluation_interop_KBoolean);
69
+ libevaluation_interop_kref_kotlin_Unit (*createNullableUnit)(void);
70
+
71
+ /* User functions. */
72
+ struct {
73
+ struct {
74
+ const char* (*evaluate)(const char* rules, const char* user);
75
+ } root;
76
+ } kotlin;
77
+ } libevaluation_interop_ExportedSymbols;
78
+ extern libevaluation_interop_ExportedSymbols* libevaluation_interop_symbols(void);
79
+ #ifdef __cplusplus
80
+ } /* extern "C" */
81
+ #endif
82
+ #endif /* KONAN_LIBEVALUATION_INTEROP_H */
@@ -0,0 +1,45 @@
1
+ module AmplitudeExperiment
2
+ # LocalEvaluationFetcher
3
+ # Fetch local evaluation mode flag configs from the Experiment API server.
4
+ # These flag configs can be used to perform local evaluation.
5
+ class LocalEvaluationFetcher
6
+ FLAG_CONFIG_TIMEOUT = 5000
7
+
8
+ def initialize(api_key, debug, server_url = 'https://api.lab.amplitude.com')
9
+ @api_key = api_key
10
+ @uri = "#{server_url}/sdk/rules?eval_mode=local"
11
+ @debug = debug
12
+ @http = PersistentHttpClient.get(@uri, { read_timeout: FLAG_CONFIG_TIMEOUT })
13
+ end
14
+
15
+ # Fetch local evaluation mode flag configs from the Experiment API server.
16
+ # These flag configs can be used to perform local evaluation.
17
+ #
18
+ # @return [Hash] The flag configs
19
+ def fetch
20
+ # fetch flag_configs
21
+ headers = {
22
+ 'Authorization' => "Api-Key #{@api_key}",
23
+ 'Content-Type' => 'application/json;charset=utf-8'
24
+ }
25
+ request = Net::HTTP::Get.new(@uri, headers)
26
+ response = @http.request(request)
27
+ raise `flagConfigs - received error response: #{response.code}: #{response.body}` unless response.is_a?(Net::HTTPOK)
28
+
29
+ flag_configs = parse(response.body)
30
+ @logger.debug("[Experiment] Fetch flag configs: #{request.body}") if @debug
31
+ flag_configs
32
+ end
33
+
34
+ private
35
+
36
+ def parse(flag_configs_str)
37
+ flag_config_obj = {}
38
+ flag_configs_array = JSON.parse(flag_configs_str)
39
+ flag_configs_array.each do |flag_config|
40
+ flag_config_obj.store(flag_config['flagKey'], flag_config) if flag_config.key?('flagKey')
41
+ end
42
+ flag_config_obj
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ module AmplitudeExperiment
2
+ # FlagConfigPoller
3
+ # update the flag_config cache value
4
+ class FlagConfigPoller
5
+ FLAG_CONFIG_POLLING_INTERVAL_MILLIS = 30_000
6
+
7
+ def initialize(fetcher, cache, debug, poll_interval_millis: FLAG_CONFIG_POLLING_INTERVAL_MILLIS)
8
+ @fetcher = fetcher
9
+ @cache = cache
10
+ @poll_interval_millis = poll_interval_millis
11
+ @logger = Logger.new($stdout)
12
+ @debug = debug
13
+ @poller_thread = nil
14
+ @is_running = false
15
+ end
16
+
17
+ # Fetch initial flag configurations and start polling for updates.
18
+ # You must call this function to begin polling for flag config updates.
19
+ # Calling this function while the poller is already running does nothing.
20
+ def start
21
+ return if @is_running
22
+
23
+ @logger.debug('[Experiment] poller - start') if @debug
24
+ run
25
+ end
26
+
27
+ # Stop polling for flag configurations.
28
+ # Calling this function while the poller is not running will do nothing.
29
+ def stop
30
+ @poller_thread&.exit
31
+ @is_running = false
32
+ @poller_thread = nil
33
+ end
34
+
35
+ private
36
+
37
+ def run
38
+ @is_running = true
39
+ flag_configs = @fetcher.fetch
40
+ @cache.clear
41
+ @cache.put_all(flag_configs)
42
+ @poller_thread = Thread.new do
43
+ sleep @poll_interval_millis
44
+ run
45
+ end
46
+ end
47
+ end
48
+ end
@@ -5,14 +5,14 @@ require 'logger'
5
5
 
6
6
  module AmplitudeExperiment
7
7
  # Main client for fetching variant data.
8
- class Client
8
+ class RemoteEvaluationClient
9
9
  # Creates a new Experiment Client instance.
10
10
  #
11
11
  # @param [String] api_key The environment API Key
12
12
  # @param [Config] config
13
13
  def initialize(api_key, config = nil)
14
14
  @api_key = api_key
15
- @config = config || Config.new
15
+ @config = config || RemoteEvaluationConfig.new
16
16
  @logger = Logger.new($stdout)
17
17
  @logger.level = if @config.debug
18
18
  Logger::DEBUG
@@ -1,6 +1,6 @@
1
1
  module AmplitudeExperiment
2
2
  # Configuration
3
- class Config
3
+ class RemoteEvaluationConfig
4
4
  # Default server url
5
5
  DEFAULT_SERVER_URL = 'https://api.lab.amplitude.com'.freeze
6
6
 
@@ -1,3 +1,3 @@
1
1
  module AmplitudeExperiment
2
- VERSION = '1.0.0.beta.6'.freeze
2
+ VERSION = '1.0.0.beta.9'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amplitude-experiment
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.6
4
+ version: 1.0.0.beta.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amplitude
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-17 00:00:00.000000000 Z
11
+ date: 2022-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ffi
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.15.5
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.15.5
111
125
  description: Amplitude Experiment Ruby Server SDK
112
126
  email:
113
127
  - sdk@amplitude.com
@@ -119,11 +133,25 @@ files:
119
133
  - Gemfile
120
134
  - README.md
121
135
  - lib/amplitude-experiment.rb
122
- - lib/experiment/client.rb
123
- - lib/experiment/config.rb
124
136
  - lib/experiment/cookie.rb
125
137
  - lib/experiment/factory.rb
138
+ - lib/experiment/local/cache.rb
139
+ - lib/experiment/local/client.rb
140
+ - lib/experiment/local/config.rb
141
+ - lib/experiment/local/evaluation/evaluation.rb
142
+ - lib/experiment/local/evaluation/lib/linuxArm64/libevaluation_interop.so
143
+ - lib/experiment/local/evaluation/lib/linuxArm64/libevaluation_interop_api.h
144
+ - lib/experiment/local/evaluation/lib/linuxX64/libevaluation_interop.so
145
+ - lib/experiment/local/evaluation/lib/linuxX64/libevaluation_interop_api.h
146
+ - lib/experiment/local/evaluation/lib/macosArm64/libevaluation_interop.dylib
147
+ - lib/experiment/local/evaluation/lib/macosArm64/libevaluation_interop_api.h
148
+ - lib/experiment/local/evaluation/lib/macosX64/libevaluation_interop.dylib
149
+ - lib/experiment/local/evaluation/lib/macosX64/libevaluation_interop_api.h
150
+ - lib/experiment/local/fetcher.rb
151
+ - lib/experiment/local/poller.rb
126
152
  - lib/experiment/persistent_http_client.rb
153
+ - lib/experiment/remote/client.rb
154
+ - lib/experiment/remote/config.rb
127
155
  - lib/experiment/user.rb
128
156
  - lib/experiment/variant.rb
129
157
  - lib/experiment/version.rb