From 3505a52cd087425d9d18e9092d39d6090fc5e773 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Fri, 17 Jul 2026 16:08:19 +0200 Subject: [PATCH] feat: add provider_init_rate to telemetry On the first telemetry flush after provider initialization, include a ProviderInitRate entry with labels describing the SDK configuration (e.g. encryption: true/false). This lets the backend track which customers have adopted encryption without tagging every metric. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../flags/resolver/v1/internal_api.proto | 10 + confidence-resolver/src/telemetry.rs | 1 + .../assets/confidence_resolver.wasm | Bin 493172 -> 494746 bytes .../internal/local_resolver/local_resolver.go | 11 +- .../internal/local_resolver/wasm.go | 27 +- .../proto/resolverinternal/internal_api.pb.go | 183 +++++++++---- .../go/confidence/provider_builder.go | 6 +- .../sdk/OpenFeatureLocalResolveProvider.java | 11 +- .../confidence/sdk/WasmLocalResolver.java | 27 +- .../sdk/ApplyFlagsErrorHandlingTest.java | 7 +- .../spotify/confidence/sdk/ResolveTest.java | 2 +- .../confidence/sdk/WasmMemoryLeakTest.java | 3 +- .../sdk/WasmResolveApiFlushCloseRaceTest.java | 3 +- openfeature-provider/js/demo.mjs | 246 ++++++++++++++++++ openfeature-provider/js/proto/test-only.proto | 8 + .../js/src/ConfidenceServerProviderLocal.ts | 15 +- .../flags/resolver/v1/internal_api.proto | 8 + .../python/src/confidence/provider.py | 27 +- openfeature-provider/rust/AGENTS.md | 1 + openfeature-provider/rust/src/logger.rs | 24 +- openfeature-provider/rust/src/provider.rs | 6 +- 21 files changed, 547 insertions(+), 79 deletions(-) create mode 100644 openfeature-provider/js/demo.mjs create mode 120000 openfeature-provider/rust/AGENTS.md diff --git a/confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto b/confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto index 9ab42a0d..9a9c781f 100644 --- a/confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto +++ b/confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto @@ -112,6 +112,8 @@ message WriteFlagAssignedResponse { // consumers everything needed to reconstruct bucket boundaries and resample // between different histogram configurations if needed. message TelemetryData { + reserved 1; // was: int64 dropped_events + // Information about the SDK/provider Sdk sdk = 2 [ (google.api.field_behavior) = OPTIONAL @@ -128,6 +130,14 @@ message TelemetryData { // Set from the confidence-resolver crate version at build time. string resolver_version = 8; + repeated ProviderInitRate provider_init_rate = 9; + + message ProviderInitRate { + uint32 count = 1; + reserved 2; // status — tbd + map labels = 3; + } + message ResolveLatency { // Delta sum of observed values since the last flush. uint32 sum = 1; diff --git a/confidence-resolver/src/telemetry.rs b/confidence-resolver/src/telemetry.rs index e075953c..bfd18de5 100644 --- a/confidence-resolver/src/telemetry.rs +++ b/confidence-resolver/src/telemetry.rs @@ -498,6 +498,7 @@ impl Telemetry { state_age, memory_bytes: (self.memory_provider)(), resolver_version: crate::version::VERSION.to_string(), + provider_init_rate: Vec::new(), } } } diff --git a/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm b/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm index b8f24839090c53c63b9b32421a87f011d9a8c2a5..75d31fa59b7240aa0237fef8fbe9b8e338687ae5 100755 GIT binary patch delta 60590 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 B>Gc2r delta 59103 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 diff --git a/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go b/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go index 1e7675db..f961f4af 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go @@ -44,7 +44,16 @@ type localResolverImpl struct { } func NewLocalResolverWithPoolSize(ctx context.Context, logSink LogSink, poolSize int) LocalResolver { - factory := NewWasmResolverFactory(logSink) + return NewLocalResolverWithLabels(ctx, logSink, poolSize, nil) +} + +func NewLocalResolverWithLabels(ctx context.Context, logSink LogSink, poolSize int, initLabels map[string]string) LocalResolver { + var factory LocalResolverFactory + if len(initLabels) > 0 { + factory = NewWasmResolverFactoryWithLabels(logSink, initLabels) + } else { + factory = NewWasmResolverFactory(logSink) + } factory = NewRecoveringResolverFactory(factory) if poolSize <= 0 { poolSize = DefaultPoolSize diff --git a/openfeature-provider/go/confidence/internal/local_resolver/wasm.go b/openfeature-provider/go/confidence/internal/local_resolver/wasm.go index 533faa86..e22388e3 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/wasm.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/wasm.go @@ -42,6 +42,8 @@ type WasmResolver struct { mu *sync.Mutex instanceID string fnCache sync.Map + initLabels map[string]string + firstFlush bool } var _ LocalResolver = (*WasmResolver)(nil) @@ -85,6 +87,16 @@ func (r *WasmResolver) ApplyFlags(request *resolver.ApplyFlagsRequest) error { func (r *WasmResolver) FlushAllLogs() error { resp := &resolverv1.WriteFlagLogsRequest{} err := r.call("wasm_msg_guest_bounded_flush_logs", nil, resp) + if err == nil && r.firstFlush { + r.firstFlush = false + if resp.TelemetryData == nil { + resp.TelemetryData = &resolverv1.TelemetryData{} + } + resp.TelemetryData.ProviderInitRate = append( + resp.TelemetryData.ProviderInitRate, + &resolverv1.TelemetryData_ProviderInitRate{Count: 1, Labels: r.initLabels}, + ) + } if err == nil && proto.Size(resp) > 0 { r.logSink(resp) } @@ -154,9 +166,10 @@ func (r *WasmResolver) call(fnName string, request proto.Message, response proto } type WasmResolverFactory struct { - runtime wazero.Runtime - module wazero.CompiledModule - logSink LogSink + runtime wazero.Runtime + module wazero.CompiledModule + logSink LogSink + initLabels map[string]string } var _ LocalResolverFactory = (*WasmResolverFactory)(nil) @@ -208,6 +221,12 @@ func NewWasmResolverFactory(logSink LogSink) LocalResolverFactory { } } +func NewWasmResolverFactoryWithLabels(logSink LogSink, initLabels map[string]string) LocalResolverFactory { + factory := NewWasmResolverFactory(logSink).(*WasmResolverFactory) + factory.initLabels = initLabels + return factory +} + func (wrf *WasmResolverFactory) New() LocalResolver { ctx := context.Background() config := wazero.NewModuleConfig().WithName("") @@ -221,6 +240,8 @@ func (wrf *WasmResolverFactory) New() LocalResolver { logSink: wrf.logSink, mu: &sync.Mutex{}, instanceID: fmt.Sprintf("%d", id), + initLabels: wrf.initLabels, + firstFlush: true, } } diff --git a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go index 3ca0b257..06eaed3d 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go @@ -190,9 +190,10 @@ func (*WriteFlagLogsResponse) Descriptor() ([]byte, []int) { type TelemetryData struct { state protoimpl.MessageState `protogen:"open.v1"` // Information about the SDK/provider - Sdk *resolver.Sdk `protobuf:"bytes,2,opt,name=sdk,proto3" json:"sdk,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Sdk *resolver.Sdk `protobuf:"bytes,2,opt,name=sdk,proto3" json:"sdk,omitempty"` + ProviderInitRate []*TelemetryData_ProviderInitRate `protobuf:"bytes,9,rep,name=provider_init_rate,json=providerInitRate,proto3" json:"provider_init_rate,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TelemetryData) Reset() { @@ -232,6 +233,13 @@ func (x *TelemetryData) GetSdk() *resolver.Sdk { return nil } +func (x *TelemetryData) GetProviderInitRate() []*TelemetryData_ProviderInitRate { + if x != nil { + return x.ProviderInitRate + } + return nil +} + type ClientInfo struct { state protoimpl.MessageState `protogen:"open.v1"` Client string `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` @@ -1110,6 +1118,58 @@ func (x *ReadOperationsResult) GetResults() []*ReadResult { return nil } +type TelemetryData_ProviderInitRate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Count uint32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TelemetryData_ProviderInitRate) Reset() { + *x = TelemetryData_ProviderInitRate{} + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TelemetryData_ProviderInitRate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryData_ProviderInitRate) ProtoMessage() {} + +func (x *TelemetryData_ProviderInitRate) ProtoReflect() protoreflect.Message { + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryData_ProviderInitRate.ProtoReflect.Descriptor instead. +func (*TelemetryData_ProviderInitRate) Descriptor() ([]byte, []int) { + return file_confidence_flags_resolver_v1_internal_api_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *TelemetryData_ProviderInitRate) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *TelemetryData_ProviderInitRate) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + type FlagAssigned_AppliedFlag struct { state protoimpl.MessageState `protogen:"open.v1"` Flag string `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"` @@ -1130,7 +1190,7 @@ type FlagAssigned_AppliedFlag struct { func (x *FlagAssigned_AppliedFlag) Reset() { *x = FlagAssigned_AppliedFlag{} - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[18] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1142,7 +1202,7 @@ func (x *FlagAssigned_AppliedFlag) String() string { func (*FlagAssigned_AppliedFlag) ProtoMessage() {} func (x *FlagAssigned_AppliedFlag) ProtoReflect() protoreflect.Message { - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[18] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1258,7 +1318,7 @@ type FlagAssigned_AssignmentInfo struct { func (x *FlagAssigned_AssignmentInfo) Reset() { *x = FlagAssigned_AssignmentInfo{} - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[19] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1270,7 +1330,7 @@ func (x *FlagAssigned_AssignmentInfo) String() string { func (*FlagAssigned_AssignmentInfo) ProtoMessage() {} func (x *FlagAssigned_AssignmentInfo) ProtoReflect() protoreflect.Message { - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[19] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1309,7 +1369,7 @@ type FlagAssigned_DefaultAssignment struct { func (x *FlagAssigned_DefaultAssignment) Reset() { *x = FlagAssigned_DefaultAssignment{} - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[20] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1321,7 +1381,7 @@ func (x *FlagAssigned_DefaultAssignment) String() string { func (*FlagAssigned_DefaultAssignment) ProtoMessage() {} func (x *FlagAssigned_DefaultAssignment) ProtoReflect() protoreflect.Message { - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[20] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1355,7 +1415,7 @@ type ClientResolveInfo_EvaluationContextSchemaInstance struct { func (x *ClientResolveInfo_EvaluationContextSchemaInstance) Reset() { *x = ClientResolveInfo_EvaluationContextSchemaInstance{} - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[21] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1367,7 +1427,7 @@ func (x *ClientResolveInfo_EvaluationContextSchemaInstance) String() string { func (*ClientResolveInfo_EvaluationContextSchemaInstance) ProtoMessage() {} func (x *ClientResolveInfo_EvaluationContextSchemaInstance) ProtoReflect() protoreflect.Message { - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[21] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1403,7 +1463,7 @@ type FlagResolveInfo_VariantResolveInfo struct { func (x *FlagResolveInfo_VariantResolveInfo) Reset() { *x = FlagResolveInfo_VariantResolveInfo{} - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[23] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1415,7 +1475,7 @@ func (x *FlagResolveInfo_VariantResolveInfo) String() string { func (*FlagResolveInfo_VariantResolveInfo) ProtoMessage() {} func (x *FlagResolveInfo_VariantResolveInfo) ProtoReflect() protoreflect.Message { - mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[23] + mi := &file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1455,9 +1515,16 @@ const file_confidence_flags_resolver_v1_internal_api_proto_rawDesc = "" + "\x0etelemetry_data\x18\x02 \x01(\v2+.confidence.flags.resolver.v1.TelemetryDataR\rtelemetryData\x12_\n" + "\x13client_resolve_info\x18\x03 \x03(\v2/.confidence.flags.resolver.v1.ClientResolveInfoR\x11clientResolveInfo\x12Y\n" + "\x11flag_resolve_info\x18\x04 \x03(\v2-.confidence.flags.resolver.v1.FlagResolveInfoR\x0fflagResolveInfo\"\x17\n" + - "\x15WriteFlagLogsResponse\"D\n" + + "\x15WriteFlagLogsResponse\"\xfe\x02\n" + "\rTelemetryData\x123\n" + - "\x03sdk\x18\x02 \x01(\v2!.confidence.flags.resolver.v1.SdkR\x03sdk\"\x86\x01\n" + + "\x03sdk\x18\x02 \x01(\v2!.confidence.flags.resolver.v1.SdkR\x03sdk\x12j\n" + + "\x12provider_init_rate\x18\t \x03(\v2<.confidence.flags.resolver.v1.TelemetryData.ProviderInitRateR\x10providerInitRate\x1a\xcb\x01\n" + + "\x10ProviderInitRate\x12\x14\n" + + "\x05count\x18\x01 \x01(\rR\x05count\x12`\n" + + "\x06labels\x18\x03 \x03(\v2H.confidence.flags.resolver.v1.TelemetryData.ProviderInitRate.LabelsEntryR\x06labels\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x02\x10\x03\"\x86\x01\n" + "\n" + "ClientInfo\x12\x16\n" + "\x06client\x18\x01 \x01(\tR\x06client\x12+\n" + @@ -1565,7 +1632,7 @@ func file_confidence_flags_resolver_v1_internal_api_proto_rawDescGZIP() []byte { } var file_confidence_flags_resolver_v1_internal_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_confidence_flags_resolver_v1_internal_api_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_confidence_flags_resolver_v1_internal_api_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_confidence_flags_resolver_v1_internal_api_proto_goTypes = []any{ (FlagAssigned_DefaultAssignment_DefaultAssignmentReason)(0), // 0: confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment.DefaultAssignmentReason (*WriteFlagLogsRequest)(nil), // 1: confidence.flags.resolver.v1.WriteFlagLogsRequest @@ -1586,50 +1653,54 @@ var file_confidence_flags_resolver_v1_internal_api_proto_goTypes = []any{ (*InclusionData)(nil), // 16: confidence.flags.resolver.v1.InclusionData (*ReadResult)(nil), // 17: confidence.flags.resolver.v1.ReadResult (*ReadOperationsResult)(nil), // 18: confidence.flags.resolver.v1.ReadOperationsResult - (*FlagAssigned_AppliedFlag)(nil), // 19: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag - (*FlagAssigned_AssignmentInfo)(nil), // 20: confidence.flags.resolver.v1.FlagAssigned.AssignmentInfo - (*FlagAssigned_DefaultAssignment)(nil), // 21: confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment - (*ClientResolveInfo_EvaluationContextSchemaInstance)(nil), // 22: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance - nil, // 23: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.SchemaEntry - (*FlagResolveInfo_VariantResolveInfo)(nil), // 24: confidence.flags.resolver.v1.FlagResolveInfo.VariantResolveInfo - (*resolver.Sdk)(nil), // 25: confidence.flags.resolver.v1.Sdk - (*timestamppb.Timestamp)(nil), // 26: google.protobuf.Timestamp + (*TelemetryData_ProviderInitRate)(nil), // 19: confidence.flags.resolver.v1.TelemetryData.ProviderInitRate + nil, // 20: confidence.flags.resolver.v1.TelemetryData.ProviderInitRate.LabelsEntry + (*FlagAssigned_AppliedFlag)(nil), // 21: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag + (*FlagAssigned_AssignmentInfo)(nil), // 22: confidence.flags.resolver.v1.FlagAssigned.AssignmentInfo + (*FlagAssigned_DefaultAssignment)(nil), // 23: confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment + (*ClientResolveInfo_EvaluationContextSchemaInstance)(nil), // 24: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance + nil, // 25: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.SchemaEntry + (*FlagResolveInfo_VariantResolveInfo)(nil), // 26: confidence.flags.resolver.v1.FlagResolveInfo.VariantResolveInfo + (*resolver.Sdk)(nil), // 27: confidence.flags.resolver.v1.Sdk + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp } var file_confidence_flags_resolver_v1_internal_api_proto_depIdxs = []int32{ 5, // 0: confidence.flags.resolver.v1.WriteFlagLogsRequest.flag_assigned:type_name -> confidence.flags.resolver.v1.FlagAssigned 3, // 1: confidence.flags.resolver.v1.WriteFlagLogsRequest.telemetry_data:type_name -> confidence.flags.resolver.v1.TelemetryData 7, // 2: confidence.flags.resolver.v1.WriteFlagLogsRequest.client_resolve_info:type_name -> confidence.flags.resolver.v1.ClientResolveInfo 8, // 3: confidence.flags.resolver.v1.WriteFlagLogsRequest.flag_resolve_info:type_name -> confidence.flags.resolver.v1.FlagResolveInfo - 25, // 4: confidence.flags.resolver.v1.TelemetryData.sdk:type_name -> confidence.flags.resolver.v1.Sdk - 25, // 5: confidence.flags.resolver.v1.ClientInfo.sdk:type_name -> confidence.flags.resolver.v1.Sdk - 4, // 6: confidence.flags.resolver.v1.FlagAssigned.client_info:type_name -> confidence.flags.resolver.v1.ClientInfo - 19, // 7: confidence.flags.resolver.v1.FlagAssigned.flags:type_name -> confidence.flags.resolver.v1.FlagAssigned.AppliedFlag - 22, // 8: confidence.flags.resolver.v1.ClientResolveInfo.schema:type_name -> confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance - 24, // 9: confidence.flags.resolver.v1.FlagResolveInfo.variant_resolve_info:type_name -> confidence.flags.resolver.v1.FlagResolveInfo.VariantResolveInfo - 15, // 10: confidence.flags.resolver.v1.WriteOperationsRequest.store_variant_op:type_name -> confidence.flags.resolver.v1.VariantData - 11, // 11: confidence.flags.resolver.v1.ReadOp.variant_read_op:type_name -> confidence.flags.resolver.v1.VariantReadOp - 12, // 12: confidence.flags.resolver.v1.ReadOp.inclusion_read_op:type_name -> confidence.flags.resolver.v1.InclusionReadOp - 13, // 13: confidence.flags.resolver.v1.ReadOperationsRequest.ops:type_name -> confidence.flags.resolver.v1.ReadOp - 15, // 14: confidence.flags.resolver.v1.ReadResult.variant_result:type_name -> confidence.flags.resolver.v1.VariantData - 16, // 15: confidence.flags.resolver.v1.ReadResult.inclusion_result:type_name -> confidence.flags.resolver.v1.InclusionData - 17, // 16: confidence.flags.resolver.v1.ReadOperationsResult.results:type_name -> confidence.flags.resolver.v1.ReadResult - 20, // 17: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.assignment_info:type_name -> confidence.flags.resolver.v1.FlagAssigned.AssignmentInfo - 21, // 18: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.default_assignment:type_name -> confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment - 6, // 19: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.fallthrough_assignments:type_name -> confidence.flags.resolver.v1.FallthroughAssignment - 26, // 20: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.apply_time:type_name -> google.protobuf.Timestamp - 0, // 21: confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment.reason:type_name -> confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment.DefaultAssignmentReason - 23, // 22: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.schema:type_name -> confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.SchemaEntry - 1, // 23: confidence.flags.resolver.v1.InternalFlagLoggerService.ClientWriteFlagLogs:input_type -> confidence.flags.resolver.v1.WriteFlagLogsRequest - 9, // 24: confidence.flags.resolver.v1.InternalFlagLoggerService.WriteMaterializedOperations:input_type -> confidence.flags.resolver.v1.WriteOperationsRequest - 14, // 25: confidence.flags.resolver.v1.InternalFlagLoggerService.ReadMaterializedOperations:input_type -> confidence.flags.resolver.v1.ReadOperationsRequest - 2, // 26: confidence.flags.resolver.v1.InternalFlagLoggerService.ClientWriteFlagLogs:output_type -> confidence.flags.resolver.v1.WriteFlagLogsResponse - 10, // 27: confidence.flags.resolver.v1.InternalFlagLoggerService.WriteMaterializedOperations:output_type -> confidence.flags.resolver.v1.WriteOperationsResult - 18, // 28: confidence.flags.resolver.v1.InternalFlagLoggerService.ReadMaterializedOperations:output_type -> confidence.flags.resolver.v1.ReadOperationsResult - 26, // [26:29] is the sub-list for method output_type - 23, // [23:26] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 27, // 4: confidence.flags.resolver.v1.TelemetryData.sdk:type_name -> confidence.flags.resolver.v1.Sdk + 19, // 5: confidence.flags.resolver.v1.TelemetryData.provider_init_rate:type_name -> confidence.flags.resolver.v1.TelemetryData.ProviderInitRate + 27, // 6: confidence.flags.resolver.v1.ClientInfo.sdk:type_name -> confidence.flags.resolver.v1.Sdk + 4, // 7: confidence.flags.resolver.v1.FlagAssigned.client_info:type_name -> confidence.flags.resolver.v1.ClientInfo + 21, // 8: confidence.flags.resolver.v1.FlagAssigned.flags:type_name -> confidence.flags.resolver.v1.FlagAssigned.AppliedFlag + 24, // 9: confidence.flags.resolver.v1.ClientResolveInfo.schema:type_name -> confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance + 26, // 10: confidence.flags.resolver.v1.FlagResolveInfo.variant_resolve_info:type_name -> confidence.flags.resolver.v1.FlagResolveInfo.VariantResolveInfo + 15, // 11: confidence.flags.resolver.v1.WriteOperationsRequest.store_variant_op:type_name -> confidence.flags.resolver.v1.VariantData + 11, // 12: confidence.flags.resolver.v1.ReadOp.variant_read_op:type_name -> confidence.flags.resolver.v1.VariantReadOp + 12, // 13: confidence.flags.resolver.v1.ReadOp.inclusion_read_op:type_name -> confidence.flags.resolver.v1.InclusionReadOp + 13, // 14: confidence.flags.resolver.v1.ReadOperationsRequest.ops:type_name -> confidence.flags.resolver.v1.ReadOp + 15, // 15: confidence.flags.resolver.v1.ReadResult.variant_result:type_name -> confidence.flags.resolver.v1.VariantData + 16, // 16: confidence.flags.resolver.v1.ReadResult.inclusion_result:type_name -> confidence.flags.resolver.v1.InclusionData + 17, // 17: confidence.flags.resolver.v1.ReadOperationsResult.results:type_name -> confidence.flags.resolver.v1.ReadResult + 20, // 18: confidence.flags.resolver.v1.TelemetryData.ProviderInitRate.labels:type_name -> confidence.flags.resolver.v1.TelemetryData.ProviderInitRate.LabelsEntry + 22, // 19: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.assignment_info:type_name -> confidence.flags.resolver.v1.FlagAssigned.AssignmentInfo + 23, // 20: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.default_assignment:type_name -> confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment + 6, // 21: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.fallthrough_assignments:type_name -> confidence.flags.resolver.v1.FallthroughAssignment + 28, // 22: confidence.flags.resolver.v1.FlagAssigned.AppliedFlag.apply_time:type_name -> google.protobuf.Timestamp + 0, // 23: confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment.reason:type_name -> confidence.flags.resolver.v1.FlagAssigned.DefaultAssignment.DefaultAssignmentReason + 25, // 24: confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.schema:type_name -> confidence.flags.resolver.v1.ClientResolveInfo.EvaluationContextSchemaInstance.SchemaEntry + 1, // 25: confidence.flags.resolver.v1.InternalFlagLoggerService.ClientWriteFlagLogs:input_type -> confidence.flags.resolver.v1.WriteFlagLogsRequest + 9, // 26: confidence.flags.resolver.v1.InternalFlagLoggerService.WriteMaterializedOperations:input_type -> confidence.flags.resolver.v1.WriteOperationsRequest + 14, // 27: confidence.flags.resolver.v1.InternalFlagLoggerService.ReadMaterializedOperations:input_type -> confidence.flags.resolver.v1.ReadOperationsRequest + 2, // 28: confidence.flags.resolver.v1.InternalFlagLoggerService.ClientWriteFlagLogs:output_type -> confidence.flags.resolver.v1.WriteFlagLogsResponse + 10, // 29: confidence.flags.resolver.v1.InternalFlagLoggerService.WriteMaterializedOperations:output_type -> confidence.flags.resolver.v1.WriteOperationsResult + 18, // 30: confidence.flags.resolver.v1.InternalFlagLoggerService.ReadMaterializedOperations:output_type -> confidence.flags.resolver.v1.ReadOperationsResult + 28, // [28:31] is the sub-list for method output_type + 25, // [25:28] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_confidence_flags_resolver_v1_internal_api_proto_init() } @@ -1645,7 +1716,7 @@ func file_confidence_flags_resolver_v1_internal_api_proto_init() { (*ReadResult_VariantResult)(nil), (*ReadResult_InclusionResult)(nil), } - file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[18].OneofWrappers = []any{ + file_confidence_flags_resolver_v1_internal_api_proto_msgTypes[20].OneofWrappers = []any{ (*FlagAssigned_AppliedFlag_AssignmentInfo)(nil), (*FlagAssigned_AppliedFlag_DefaultAssignment)(nil), } @@ -1655,7 +1726,7 @@ func file_confidence_flags_resolver_v1_internal_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_confidence_flags_resolver_v1_internal_api_proto_rawDesc), len(file_confidence_flags_resolver_v1_internal_api_proto_rawDesc)), NumEnums: 1, - NumMessages: 24, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/openfeature-provider/go/confidence/provider_builder.go b/openfeature-provider/go/confidence/provider_builder.go index 8337e42a..16eb2dfa 100644 --- a/openfeature-provider/go/confidence/provider_builder.go +++ b/openfeature-provider/go/confidence/provider_builder.go @@ -6,6 +6,7 @@ import ( "log/slog" "net/http" "os" + "strconv" "time" fl "github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/flag_logger" @@ -87,8 +88,11 @@ func NewProvider(ctx context.Context, config ProviderConfig) (*LocalResolverProv materializationStore = newRemoteMaterializationStore(resolverv1.NewInternalFlagLoggerServiceClient(conn), config.ClientSecret) } + initLabels := map[string]string{ + "encryption": strconv.FormatBool(config.EncryptionKey != ""), + } resolverSupplier := func(ctx context.Context, logSink lr.LogSink) lr.LocalResolver { - return lr.NewLocalResolverWithPoolSize(ctx, logSink, config.ResolverPoolSize) + return lr.NewLocalResolverWithLabels(ctx, logSink, config.ResolverPoolSize, initLabels) } resolverSupplierWithMaterialization := wrapResolverSupplierWithMaterializations(resolverSupplier, materializationStore) providerOpts := buildProviderOptions(config.StatePollInterval, config.LogPollInterval) diff --git a/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/OpenFeatureLocalResolveProvider.java b/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/OpenFeatureLocalResolveProvider.java index 6ae099dd..30205f0c 100644 --- a/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/OpenFeatureLocalResolveProvider.java +++ b/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/OpenFeatureLocalResolveProvider.java @@ -20,6 +20,7 @@ import io.grpc.StatusRuntimeException; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -158,11 +159,15 @@ public OpenFeatureLocalResolveProvider( clientSecret, config.getHttpClientFactory(), config.getEncryptionKey()); final var wasmFlagLogger = new GrpcWasmFlagLogger(clientSecret, config.getChannelFactory()); this.flagLogger = wasmFlagLogger; + final Map initLabels = + Map.of("encryption", String.valueOf(config.getEncryptionKey() != null)); final int numInstances = PooledResolver.getNumInstances(config.getResolverPoolSize()); final LocalResolver inner = new PooledResolver( numInstances, - () -> new RecoveringResolver(() -> new WasmLocalResolver(flagLogger::write))); + () -> + new RecoveringResolver( + () -> new WasmLocalResolver(flagLogger::write, initLabels))); this.resolver = new MaterializingResolver(inner, materializationStore); } @@ -189,7 +194,9 @@ public OpenFeatureLocalResolveProvider( final LocalResolver inner = new PooledResolver( numInstances, - () -> new RecoveringResolver(() -> new WasmLocalResolver(wasmFlagLogger::write))); + () -> + new RecoveringResolver( + () -> new WasmLocalResolver(wasmFlagLogger::write, Map.of()))); this.resolver = new MaterializingResolver(inner, materializationStore); } diff --git a/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/WasmLocalResolver.java b/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/WasmLocalResolver.java index 17c8bd1b..036d9451 100644 --- a/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/WasmLocalResolver.java +++ b/openfeature-provider/java/src/main/java/com/spotify/confidence/sdk/WasmLocalResolver.java @@ -18,10 +18,12 @@ import com.spotify.confidence.sdk.flags.resolver.v1.ResolveProcessRequest; import com.spotify.confidence.sdk.flags.resolver.v1.ResolveProcessResponse; import com.spotify.confidence.sdk.flags.resolver.v1.Sdk; +import com.spotify.confidence.sdk.flags.resolver.v1.TelemetryData; import com.spotify.confidence.sdk.flags.resolver.v1.WriteFlagLogsRequest; import com.spotify.confidence.sdk.wasm.Messages; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; @@ -49,6 +51,8 @@ class WasmLocalResolver implements LocalResolver { private final ExportFunction wasmMsgAlloc; private final ExportFunction wasmMsgFree; private final Consumer logSink; + private final Map initLabels; + private boolean firstFlush = true; // api private final ExportFunction wasmMsgGuestSetResolverState; @@ -60,8 +64,9 @@ class WasmLocalResolver implements LocalResolver { private final ExportFunction wasmMsgGuestPrometheusSnapshot; private final ReentrantLock lock = new ReentrantLock(); - public WasmLocalResolver(Consumer logSink) { + public WasmLocalResolver(Consumer logSink, Map initLabels) { this.logSink = logSink; + this.initLabels = initLabels; this.instanceId = String.valueOf(INSTANCE_COUNTER.getAndIncrement()); instance = Instance.builder(ConfidenceResolverModule.load()) @@ -192,7 +197,21 @@ public void flushAllLogs() { final var voidRequest = Messages.Void.getDefaultInstance(); final var reqPtr = transferRequest(voidRequest); final var respPtr = (int) wasmMsgBoundedFlushLogs.apply(reqPtr)[0]; - final var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); + var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); + if (firstFlush) { + firstFlush = false; + request = + request.toBuilder() + .setTelemetryData( + request.getTelemetryData().toBuilder() + .addProviderInitRate( + TelemetryData.ProviderInitRate.newBuilder() + .setCount(1) + .putAllLabels(initLabels) + .build()) + .build()) + .build(); + } if (!isEmptyLogRequest(request)) { logSink.accept(request); } @@ -279,9 +298,8 @@ private T consumeResponse(int addr, ParserFn codec) { final Messages.Response response = Messages.Response.parseFrom(consume(addr)); if (response.hasError()) { throw new RuntimeException(response.getError()); - } else { - return codec.apply(response.getData().toByteArray()); } + return codec.apply(response.getData().toByteArray()); } catch (InvalidProtocolBufferException e) { throw new RuntimeException(e); } @@ -350,4 +368,5 @@ private interface ParserFn { T apply(byte[] data) throws InvalidProtocolBufferException; } + } diff --git a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ApplyFlagsErrorHandlingTest.java b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ApplyFlagsErrorHandlingTest.java index f463d4c6..632fc38f 100644 --- a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ApplyFlagsErrorHandlingTest.java +++ b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ApplyFlagsErrorHandlingTest.java @@ -25,6 +25,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -154,7 +155,7 @@ private static Timestamp toTs(Instant t) { @Test void applyWithoutSendTime_isSwallowed_andRecordsNoFlagAssigned() { final List captured = new ArrayList<>(); - final var resolver = new WasmLocalResolver(captured::add); + final var resolver = new WasmLocalResolver(captured::add, Map.of()); resolver.setResolverState(buildState(), ACCOUNT, null); final var resolveResp = resolveWithApplyFalse(resolver); @@ -185,7 +186,7 @@ void applyWithoutSendTime_isSwallowed_andRecordsNoFlagAssigned() { @Test void applyWithSendTime_recordsOneFlagAssigned() { final List captured = new ArrayList<>(); - final var resolver = new WasmLocalResolver(captured::add); + final var resolver = new WasmLocalResolver(captured::add, Map.of()); resolver.setResolverState(buildState(), ACCOUNT, null); final var resolveResp = resolveWithApplyFalse(resolver); @@ -233,7 +234,7 @@ void applyWithSendTime_recordsOneFlagAssigned() { @Test void applyAfterStateRotated_secretRemoved_isSwallowedAndLogged() { final List captured = new ArrayList<>(); - final var resolver = new WasmLocalResolver(captured::add); + final var resolver = new WasmLocalResolver(captured::add, Map.of()); resolver.setResolverState(buildState(SECRET), ACCOUNT, null); final var resolveResp = resolveWithApplyFalse(resolver); diff --git a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ResolveTest.java b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ResolveTest.java index 70f93095..cbd7f549 100644 --- a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ResolveTest.java +++ b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/ResolveTest.java @@ -36,7 +36,7 @@ class ResolveTest { private final LocalResolver resolver; public ResolveTest() { - resolver = new WasmLocalResolver(request -> {}); + resolver = new WasmLocalResolver(request -> {}, Map.of()); } @BeforeEach diff --git a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmMemoryLeakTest.java b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmMemoryLeakTest.java index 7982a1e6..5f28c179 100644 --- a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmMemoryLeakTest.java +++ b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmMemoryLeakTest.java @@ -10,6 +10,7 @@ import com.spotify.confidence.sdk.flags.resolver.v1.ResolveProcessRequest; import java.lang.reflect.Field; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; /** @@ -32,7 +33,7 @@ private static int getWasmMemoryPages(WasmLocalResolver resolver) { @Test void wasmMemoryStableOnRepeatedResolveCalls() { - WasmLocalResolver resolver = new WasmLocalResolver(request -> {}); + WasmLocalResolver resolver = new WasmLocalResolver(request -> {}, Map.of()); resolver.setResolverState(ResolveTest.exampleStateBytes, "account", null); ResolveProcessRequest request = diff --git a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmResolveApiFlushCloseRaceTest.java b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmResolveApiFlushCloseRaceTest.java index cdd63258..933a6655 100644 --- a/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmResolveApiFlushCloseRaceTest.java +++ b/openfeature-provider/java/src/test/java/com/spotify/confidence/sdk/WasmResolveApiFlushCloseRaceTest.java @@ -9,6 +9,7 @@ import com.spotify.confidence.sdk.flags.resolver.v1.Sdk; import com.spotify.confidence.sdk.flags.resolver.v1.SdkId; import com.spotify.confidence.sdk.flags.resolver.v1.WriteFlagLogsRequest; +import java.util.Map; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -44,7 +45,7 @@ void concurrentFlushAndCloseShouldNotLoseAssignments() throws Exception { for (int i = 0; i < iterations; i++) { final var logger = new CapturingWasmFlagLogger(); - final var resolver = new WasmLocalResolver(logger::write); + final var resolver = new WasmLocalResolver(logger::write, Map.of()); resolver.setResolverState(resolverState, accountId, null); // Resolve a flag to create a flag assignment in the WASM buffer diff --git a/openfeature-provider/js/demo.mjs b/openfeature-provider/js/demo.mjs new file mode 100644 index 00000000..56748ec8 --- /dev/null +++ b/openfeature-provider/js/demo.mjs @@ -0,0 +1,246 @@ +#!/usr/bin/env node +/** + * Demo: run the local provider, resolve flags, and inspect telemetry. + * + * Usage: + * node demo.mjs --secret [--encryption-key ] [--flag ] [--duration ] + * + * Examples: + * node demo.mjs --secret abc123 --flag web-sdk-e2e-flag --duration 60 + * node demo.mjs --secret abc123 --encryption-key deadbeef... --flag web-sdk-e2e-flag --duration 300 + */ + +import { OpenFeature } from '@openfeature/server-sdk'; +import { createConfidenceServerProvider } from './dist/index.node.js'; + +function parseArgs() { + const args = process.argv.slice(2); + const opts = { flags: [], duration: 60 }; + for (let i = 0; i < args.length; i++) { + switch (args[i]) { + case '--secret': + opts.secret = args[++i]; + break; + case '--encryption-key': + opts.encryptionKey = args[++i]; + break; + case '--flag': + opts.flags.push(args[++i]); + break; + case '--duration': + opts.duration = parseInt(args[++i], 10); + break; + } + } + if (!opts.secret) { + console.error('Usage: node demo.mjs --secret [--encryption-key ] [--flag ] [--duration ]'); + process.exit(1); + } + if (opts.flags.length === 0) opts.flags.push('web-sdk-e2e-flag'); + return opts; +} + +const opts = parseArgs(); + +console.log(`=== Confidence Provider Demo ===`); +console.log(` encryption: ${opts.encryptionKey ? 'enabled' : 'disabled'}`); +console.log(` flags: ${opts.flags.join(', ')}`); +console.log(` duration: ${opts.duration}s`); +console.log(); + +// Minimal protobuf varint + length-delimited decoder for TelemetryData inspection +function decodeProviderInitRate(buf) { + // Walk WriteFlagLogsRequest looking for field 2 (telemetry_data), + // then inside that look for field 9 (provider_init_rate) + let pos = 0; + while (pos < buf.length) { + const tag = buf[pos++]; + const fieldNum = tag >> 3; + const wireType = tag & 0x7; + if (wireType === 2) { + // length-delimited + let len = 0, + shift = 0; + while (buf[pos] & 0x80) { + len |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + len |= buf[pos++] << shift; + if (fieldNum === 2) { + // telemetry_data — recurse into it + const inner = buf.subarray(pos, pos + len); + const result = findInitRate(inner); + if (result) return result; + } + pos += len; + } else if (wireType === 0) { + // varint + while (buf[pos++] & 0x80) {} + } else { + break; + } + } + return null; +} + +function findInitRate(buf) { + let pos = 0; + while (pos < buf.length) { + const tag = buf[pos++]; + const fieldNum = tag >> 3; + const wireType = tag & 0x7; + if (wireType === 2) { + let len = 0, + shift = 0; + while (buf[pos] & 0x80) { + len |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + len |= buf[pos++] << shift; + if (fieldNum === 9) { + // provider_init_rate + return parseInitRate(buf.subarray(pos, pos + len)); + } + pos += len; + } else if (wireType === 0) { + while (buf[pos++] & 0x80) {} + } else { + break; + } + } + return null; +} + +function parseInitRate(buf) { + let count = 0; + const labels = {}; + let pos = 0; + while (pos < buf.length) { + const tag = buf[pos++]; + const fieldNum = tag >> 3; + const wireType = tag & 0x7; + if (wireType === 0 && fieldNum === 1) { + // count + count = 0; + let shift = 0; + while (buf[pos] & 0x80) { + count |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + count |= buf[pos++] << shift; + } else if (wireType === 2 && fieldNum === 3) { + // labels map entry + let len = 0, + shift = 0; + while (buf[pos] & 0x80) { + len |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + len |= buf[pos++] << shift; + const entry = parseMapEntry(buf.subarray(pos, pos + len)); + if (entry) labels[entry.key] = entry.value; + pos += len; + } else if (wireType === 2) { + let len = 0, + shift = 0; + while (buf[pos] & 0x80) { + len |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + len |= buf[pos++] << shift; + pos += len; + } else if (wireType === 0) { + while (buf[pos++] & 0x80) {} + } else { + break; + } + } + return { count, labels }; +} + +function parseMapEntry(buf) { + let key = '', + value = ''; + let pos = 0; + while (pos < buf.length) { + const tag = buf[pos++]; + const fieldNum = tag >> 3; + const wireType = tag & 0x7; + if (wireType === 2) { + let len = 0, + shift = 0; + while (buf[pos] & 0x80) { + len |= (buf[pos++] & 0x7f) << shift; + shift += 7; + } + len |= buf[pos++] << shift; + const str = new TextDecoder().decode(buf.subarray(pos, pos + len)); + if (fieldNum === 1) key = str; + else if (fieldNum === 2) value = str; + pos += len; + } else { + break; + } + } + return { key, value }; +} + +let flushCount = 0; + +// Intercept outgoing fetches to log telemetry +const originalFetch = globalThis.fetch; +const wrappedFetch = async (url, init) => { + let bodyBytes = null; + if (typeof url === 'string' && url.includes('clientFlagLogs') && init?.body) { + bodyBytes = new Uint8Array(init.body); + } + const resp = await originalFetch(url, init); + if (bodyBytes) { + flushCount++; + const initRate = decodeProviderInitRate(bodyBytes); + if (initRate) { + console.log( + `\n[flush #${flushCount}] → ${resp.status} ★ provider_init_rate: count=${ + initRate.count + }, labels=${JSON.stringify(initRate.labels)}`, + ); + } else { + console.log(`\n[flush #${flushCount}] → ${resp.status} (no provider_init_rate)`); + } + } + return resp; +}; + +const provider = createConfidenceServerProvider({ + flagClientSecret: opts.secret, + encryptionKey: opts.encryptionKey, + flushInterval: 10_000, + fetch: wrappedFetch, +}); + +console.log('Initializing provider...'); +await OpenFeature.setProviderAndWait(provider); +console.log('Provider ready.\n'); + +const client = OpenFeature.getClient(); +const endTime = Date.now() + opts.duration * 1000; +let resolveCount = 0; + +const ctx = { targetingKey: 'demo-user', sticky: false }; + +while (Date.now() < endTime) { + for (const flag of opts.flags) { + const result = client.getBooleanDetails(`${flag}.bool`, false, ctx); + resolveCount++; + } + + const remaining = Math.ceil((endTime - Date.now()) / 1000); + process.stdout.write(`\r resolves: ${resolveCount} | remaining: ${remaining}s `); + + await new Promise(r => setTimeout(r, 2000)); +} + +console.log(`\n\nDone. Total resolves: ${resolveCount}`); +console.log('Shutting down (final flush)...'); +await OpenFeature.close(); +console.log('Bye.'); diff --git a/openfeature-provider/js/proto/test-only.proto b/openfeature-provider/js/proto/test-only.proto index 99f26668..e5d59fef 100644 --- a/openfeature-provider/js/proto/test-only.proto +++ b/openfeature-provider/js/proto/test-only.proto @@ -35,6 +35,14 @@ message TelemetryData { string resolver_version = 8; + repeated ProviderInitRate provider_init_rate = 9; + + message ProviderInitRate { + uint32 count = 1; + reserved 2; // status — tbd + map labels = 3; + } + message ResolveLatency { uint32 sum = 1; uint32 count = 2; diff --git a/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts b/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts index 7caa249a..59c3e356 100644 --- a/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts +++ b/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts @@ -17,6 +17,7 @@ import { } from './materialization'; import { SetResolverStateRequest } from './proto/confidence/wasm/messages'; import { ClientResolverState } from './proto/confidence/flags/admin/v1/resolver'; +import { WriteFlagLogsRequest } from './proto/confidence/flags/resolver/v1/internal_api'; import FlagBundleType, * as FlagBundle from './flag-bundle'; import { ErrorCode, ResolutionDetails } from './types'; @@ -64,6 +65,8 @@ export class ConfidenceServerProviderLocal implements Provider { private readonly stateUpdateInterval: number; private readonly flushInterval: number; private readonly materializationStore: MaterializationStore | null; + private readonly initLabels: Record; + private firstFlush = true; private stateEtag: string | null = null; private get resolver(): LocalResolver { @@ -142,6 +145,7 @@ export class ConfidenceServerProviderLocal implements Provider { } else { this.materializationStore = null; } + this.initLabels = { encryption: options.encryptionKey ? 'true' : 'false' }; } async initialize(context?: EvaluationContext): Promise { @@ -342,8 +346,17 @@ export class ConfidenceServerProviderLocal implements Provider { // TODO should this return success/failure, or even throw? async flush(signal?: AbortSignal): Promise { - const writeFlagLogRequest = this.resolver.flushLogs(); + let writeFlagLogRequest = this.resolver.flushLogs(); if (writeFlagLogRequest.length > 0) { + if (this.firstFlush) { + this.firstFlush = false; + const decoded = WriteFlagLogsRequest.decode(writeFlagLogRequest); + if (!decoded.telemetryData) { + decoded.telemetryData = { providerInitRate: [] }; + } + decoded.telemetryData!.providerInitRate = [{ count: 1, labels: this.initLabels }]; + writeFlagLogRequest = WriteFlagLogsRequest.encode(decoded).finish(); + } await this.sendFlagLogs(writeFlagLogRequest, signal); } } diff --git a/openfeature-provider/proto/confidence/flags/resolver/v1/internal_api.proto b/openfeature-provider/proto/confidence/flags/resolver/v1/internal_api.proto index ad5e1019..c3105a43 100644 --- a/openfeature-provider/proto/confidence/flags/resolver/v1/internal_api.proto +++ b/openfeature-provider/proto/confidence/flags/resolver/v1/internal_api.proto @@ -44,6 +44,14 @@ message WriteFlagLogsResponse {} message TelemetryData { // Information about the SDK/provider Sdk sdk = 2; + + repeated ProviderInitRate provider_init_rate = 9; + + message ProviderInitRate { + uint32 count = 1; + reserved 2; // status — tbd + map labels = 3; + } } message ClientInfo { diff --git a/openfeature-provider/python/src/confidence/provider.py b/openfeature-provider/python/src/confidence/provider.py index 990d6f0c..5575672a 100644 --- a/openfeature-provider/python/src/confidence/provider.py +++ b/openfeature-provider/python/src/confidence/provider.py @@ -32,7 +32,11 @@ VariantReadResult, VariantWriteOp, ) -from confidence.proto.confidence.flags.resolver.v1 import api_pb2, types_pb2 +from confidence.proto.confidence.flags.resolver.v1 import ( + api_pb2, + internal_api_pb2, + types_pb2, +) from confidence.proto.confidence.wasm import wasm_api_pb2 from confidence.state_fetcher import StateFetcher from confidence.version import __version__ @@ -152,6 +156,10 @@ def __init__( """ self._client_secret = client_secret self._encryption_key = encryption_key + self._init_labels: Dict[str, str] = { + "encryption": str(bool(encryption_key)).lower() + } + self._first_flush = True self._state_poll_interval = state_poll_interval self._log_poll_interval = log_poll_interval self._assign_poll_interval = assign_poll_interval @@ -300,7 +308,7 @@ def shutdown(self) -> None: # Flush final logs if self._resolver is not None: try: - log_data = self._resolver.flush_logs() + log_data = self._append_init(self._resolver.flush_logs()) if log_data and self._flag_logger is not None: self._flag_logger.write(log_data) except Exception as e: @@ -744,6 +752,19 @@ def _write_materializations( except Exception as e: logger.error("Failed to write materializations: %s", e) + def _append_init(self, log_data: bytes) -> bytes: + if self._first_flush and log_data: + self._first_flush = False + request = internal_api_pb2.WriteFlagLogsRequest() + request.ParseFromString(log_data) + init_rate = request.telemetry_data.provider_init_rate.add() + init_rate.count = 1 + for k, v in self._init_labels.items(): + init_rate.labels[k] = v + return request.SerializeToString() + self._first_flush = False + return log_data + def _flush_assigned(self) -> None: """Flush assigned logs.""" if self._resolver is None or self._flag_logger is None: @@ -804,6 +825,7 @@ def _state_poll_loop(self) -> None: with self._resolver_lock: flushed_logs = self._resolver.flush_logs() self._resolver.set_resolver_state(state, account_id, sdk) + flushed_logs = self._append_init(flushed_logs) if flushed_logs and self._flag_logger is not None: self._flag_logger.write(flushed_logs) logger.debug("Resolver state updated") @@ -831,6 +853,7 @@ def _log_flush_loop(self) -> None: try: with self._resolver_lock: log_data = self._resolver.flush_logs() + log_data = self._append_init(log_data) if log_data and self._flag_logger is not None: self._flag_logger.write(log_data) except Exception as e: diff --git a/openfeature-provider/rust/AGENTS.md b/openfeature-provider/rust/AGENTS.md new file mode 120000 index 00000000..681311eb --- /dev/null +++ b/openfeature-provider/rust/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/openfeature-provider/rust/src/logger.rs b/openfeature-provider/rust/src/logger.rs index 18615d87..e5a856ed 100644 --- a/openfeature-provider/rust/src/logger.rs +++ b/openfeature-provider/rust/src/logger.rs @@ -1,10 +1,15 @@ //! Log management for sending flag logs to the Confidence API. +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicBool, Ordering}; + use prost::Message; use reqwest_middleware::ClientWithMiddleware; use confidence_resolver::assign_logger::AssignLogger; -use confidence_resolver::proto::confidence::flags::resolver::v1::{Sdk, WriteFlagLogsRequest}; +use confidence_resolver::proto::confidence::flags::resolver::v1::{ + telemetry_data::ProviderInitRate, Sdk, WriteFlagLogsRequest, +}; use confidence_resolver::resolve_logger::ResolveLogger; use crate::error::Result; @@ -64,14 +69,23 @@ impl LogSender { pub struct LogManager { sender: LogSender, sdk: Sdk, + init_labels: BTreeMap, + first_flush: AtomicBool, } impl LogManager { /// Create a new log manager with the given client, client secret, and SDK identity. - pub fn new(client: ClientWithMiddleware, client_secret: String, sdk: Sdk) -> Self { + pub fn new( + client: ClientWithMiddleware, + client_secret: String, + sdk: Sdk, + init_labels: BTreeMap, + ) -> Self { Self { sender: LogSender::new(client, client_secret), sdk, + init_labels, + first_flush: AtomicBool::new(true), } } @@ -86,6 +100,12 @@ impl LogManager { let mut td = TELEMETRY.delta_snapshot(&LAST_FLUSHED); td.sdk = Some(self.sdk.clone()); + if self.first_flush.swap(false, Ordering::Relaxed) { + td.provider_init_rate.push(ProviderInitRate { + count: 1, + labels: self.init_labels.clone(), + }); + } request.telemetry_data = Some(td); let encoded = request.encode_to_vec(); diff --git a/openfeature-provider/rust/src/provider.rs b/openfeature-provider/rust/src/provider.rs index 20e13d77..3ebf252a 100644 --- a/openfeature-provider/rust/src/provider.rs +++ b/openfeature-provider/rust/src/provider.rs @@ -1,6 +1,6 @@ //! OpenFeature provider implementation for Confidence. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -168,16 +168,20 @@ impl ConfidenceProvider { let client = client_builder.build(); let sdk = provider_sdk(); + let encryption_enabled = options.encryption_key.is_some(); let state_fetcher = Arc::new(StateFetcher::new( client.clone(), options.client_secret.clone(), Some(sdk.clone()), options.encryption_key, )); + let init_labels = + BTreeMap::from([("encryption".to_string(), encryption_enabled.to_string())]); let log_manager = Arc::new(LogManager::new( client.clone(), options.client_secret.clone(), sdk, + init_labels, )); // Create materialization store if configured