From dcf80287531e42f60c223598dd78d136a5a9ac0d Mon Sep 17 00:00:00 2001 From: Sameeran Kunche Date: Mon, 28 Sep 2026 15:18:34 -0700 Subject: [PATCH 1/6] [FFL-3347] Benchmark JS and native flag parsing and evaluation --- benchmarks/flags-smoke.env | 4 + benchmarks/flags.env | 3 + benchmarks/flags.jest.config.js | 21 + .../BenchmarkRunner.xcodeproj/project.pbxproj | 15 + .../ios/FlagsBenchmark/FlagsBenchmark.h | 15 + .../ios/FlagsBenchmark/FlagsBenchmark.mm | 71 ++ benchmarks/ios/Podfile | 5 + benchmarks/ios/Podfile.lock | 121 +- benchmarks/metro.config.js | 14 + benchmarks/package.json | 6 +- benchmarks/scripts/export-flags-fixture.cjs | 65 + benchmarks/src/App.tsx | 8 + benchmarks/src/flags/FlagsScenario.tsx | 107 ++ benchmarks/src/flags/README.md | 166 +++ benchmarks/src/flags/fixtures.test.ts | 98 ++ benchmarks/src/flags/fixtures.ts | 118 ++ benchmarks/src/flags/runner.test.ts | 114 ++ benchmarks/src/flags/runner.ts | 320 +++++ benchmarks/src/flags/statistics.test.ts | 36 + benchmarks/src/flags/statistics.ts | 53 + benchmarks/src/flags/ufc_pb.ts | 1091 +++++++++++++++++ benchmarks/src/specs/NativeFlagsBenchmark.ts | 16 + benchmarks/src/testSetup/types/testConfig.ts | 1 + yarn.lock | 19 + 24 files changed, 2430 insertions(+), 57 deletions(-) create mode 100644 benchmarks/flags-smoke.env create mode 100644 benchmarks/flags.env create mode 100644 benchmarks/flags.jest.config.js create mode 100644 benchmarks/ios/FlagsBenchmark/FlagsBenchmark.h create mode 100644 benchmarks/ios/FlagsBenchmark/FlagsBenchmark.mm create mode 100644 benchmarks/scripts/export-flags-fixture.cjs create mode 100644 benchmarks/src/flags/FlagsScenario.tsx create mode 100644 benchmarks/src/flags/README.md create mode 100644 benchmarks/src/flags/fixtures.test.ts create mode 100644 benchmarks/src/flags/fixtures.ts create mode 100644 benchmarks/src/flags/runner.test.ts create mode 100644 benchmarks/src/flags/runner.ts create mode 100644 benchmarks/src/flags/statistics.test.ts create mode 100644 benchmarks/src/flags/statistics.ts create mode 100644 benchmarks/src/flags/ufc_pb.ts create mode 100644 benchmarks/src/specs/NativeFlagsBenchmark.ts diff --git a/benchmarks/flags-smoke.env b/benchmarks/flags-smoke.env new file mode 100644 index 000000000..beb035046 --- /dev/null +++ b/benchmarks/flags-smoke.env @@ -0,0 +1,4 @@ +# Headless correctness run only, not a performance report. +BENCH_SCENARIO=flags +BENCH_RUN_TYPE=baseline +BENCH_FLAGS_AUTORUN=smoke diff --git a/benchmarks/flags.env b/benchmarks/flags.env new file mode 100644 index 000000000..50e5d7e40 --- /dev/null +++ b/benchmarks/flags.env @@ -0,0 +1,3 @@ +# Synthetic benchmark only. No Datadog credentials or telemetry initialization. +BENCH_SCENARIO=flags +BENCH_RUN_TYPE=baseline diff --git a/benchmarks/flags.jest.config.js b/benchmarks/flags.jest.config.js new file mode 100644 index 000000000..6f3632e73 --- /dev/null +++ b/benchmarks/flags.jest.config.js @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2026-Present Datadog, Inc. + */ + +module.exports = { + watchman: false, + testEnvironment: 'node', + testMatch: ['/src/flags/**/*.test.ts'], + transform: { + '^.+\\.[jt]sx?$': [ + 'babel-jest', + { + babelrc: false, + configFile: false, + presets: ['module:@react-native/babel-preset'], + }, + ], + }, +}; diff --git a/benchmarks/ios/BenchmarkRunner.xcodeproj/project.pbxproj b/benchmarks/ios/BenchmarkRunner.xcodeproj/project.pbxproj index d845ae407..d26f607b0 100644 --- a/benchmarks/ios/BenchmarkRunner.xcodeproj/project.pbxproj +++ b/benchmarks/ios/BenchmarkRunner.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 043403262DCB9CD700803AF8 /* BenchmarkVitals.mm in Sources */ = {isa = PBXBuildFile; fileRef = 043403242DCB9CD700803AF8 /* BenchmarkVitals.mm */; }; 04576C962DCBAF900078DCAB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 04576C952DCBAF900078DCAB /* main.m */; }; 04726DD72F46402A008D1A48 /* TracerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04726DD62F46402A008D1A48 /* TracerProvider.swift */; }; + 0BFD883AEE0A41CC521C5662 /* FlagsBenchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2FED7A81171836D1D57F12AB /* FlagsBenchmark.mm */; settings = {COMPILER_FLAGS = "-fmodules -fcxx-modules"; }; }; 0C80B921A6F3F58F76C31292 /* libPods-BenchmarkRunner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-BenchmarkRunner.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 20A397FC280595A8A9851556 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; @@ -40,11 +41,13 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BenchmarkRunner/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BenchmarkRunner/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = BenchmarkRunner/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 2FED7A81171836D1D57F12AB /* FlagsBenchmark.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FlagsBenchmark.mm; sourceTree = ""; }; 3B4392A12AC88292D35C810B /* Pods-BenchmarkRunner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BenchmarkRunner.debug.xcconfig"; path = "Target Support Files/Pods-BenchmarkRunner/Pods-BenchmarkRunner.debug.xcconfig"; sourceTree = ""; }; 5709B34CF0A7D63546082F79 /* Pods-BenchmarkRunner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BenchmarkRunner.release.xcconfig"; path = "Target Support Files/Pods-BenchmarkRunner/Pods-BenchmarkRunner.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-BenchmarkRunner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BenchmarkRunner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = BenchmarkRunner/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = BenchmarkRunner/LaunchScreen.storyboard; sourceTree = ""; }; + A3B9C892D474AFE597364EE6 /* FlagsBenchmark.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = FlagsBenchmark.h; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -131,6 +134,7 @@ 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, BBD78D7AC51CEA395F1C20DB /* Pods */, + BEF778B92318D45B0AE84BA5 /* FlagsBenchmark */, ); indentWidth = 2; sourceTree = ""; @@ -154,6 +158,16 @@ path = Pods; sourceTree = ""; }; + BEF778B92318D45B0AE84BA5 /* FlagsBenchmark */ = { + isa = PBXGroup; + children = ( + A3B9C892D474AFE597364EE6 /* FlagsBenchmark.h */, + 2FED7A81171836D1D57F12AB /* FlagsBenchmark.mm */, + ); + name = FlagsBenchmark; + path = FlagsBenchmark; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -313,6 +327,7 @@ 04576C962DCBAF900078DCAB /* main.m in Sources */, 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, 04726DD72F46402A008D1A48 /* TracerProvider.swift in Sources */, + 0BFD883AEE0A41CC521C5662 /* FlagsBenchmark.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.h b/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.h new file mode 100644 index 000000000..3aff6e34d --- /dev/null +++ b/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.h @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2026-Present Datadog, Inc. + */ + +#import +#ifdef RCT_NEW_ARCH_ENABLED +#import "BenchmarkVitalsSpec.h" +@interface FlagsBenchmark : NSObject +#else +#import +@interface FlagsBenchmark : NSObject +#endif +@end diff --git a/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.mm b/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.mm new file mode 100644 index 000000000..e964b5637 --- /dev/null +++ b/benchmarks/ios/FlagsBenchmark/FlagsBenchmark.mm @@ -0,0 +1,71 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2026-Present Datadog, Inc. + */ + +#import "FlagsBenchmark.h" +#ifdef DD_FLAGS_PROTOTYPE_ENABLED +@import RulesEvaluationPrototype; +#endif + +@implementation FlagsBenchmark { + dispatch_queue_t _queue; + NSLock *_lock; +#ifdef DD_FLAGS_PROTOTYPE_ENABLED + DDFlagsBenchmark *_engine; +#endif +} + +RCT_EXPORT_MODULE() + ++ (BOOL)requiresMainQueueSetup { return NO; } + +- (instancetype)init { + if ((self = [super init])) { + _queue = dispatch_queue_create("com.datadog.flags.benchmark", DISPATCH_QUEUE_SERIAL); + _lock = [NSLock new]; +#ifdef DD_FLAGS_PROTOTYPE_ENABLED + _engine = [DDFlagsBenchmark new]; +#endif + } + return self; +} + +- (NSDictionary *)execute:(NSDictionary *)request { +#ifdef DD_FLAGS_PROTOTYPE_ENABLED + [_lock lock]; + NSDictionary *result = [_engine run:request]; + [_lock unlock]; + if ([request[@"op"] isEqual:@"metadata"]) { + NSMutableDictionary *metadata = [result mutableCopy]; +#ifdef RCT_NEW_ARCH_ENABLED + metadata[@"newArchitecture"] = @YES; +#else + metadata[@"newArchitecture"] = @NO; +#endif + return metadata; + } + return result; +#else + return @{ @"benchmarkError": @"Set DD_FLAGS_PROTOTYPE_PATH and run pod install" }; +#endif +} + +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(runSync:(NSDictionary *)request) { + // Inline synchronous reads avoid adding an artificial dispatch hop to the comparison. + return [self execute:request]; +} + +RCT_REMAP_METHOD(runAsync, runAsync:(NSDictionary *)request + resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + dispatch_async(_queue, ^{ resolve([self execute:request]); }); +} + +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} +#endif +@end diff --git a/benchmarks/ios/Podfile b/benchmarks/ios/Podfile index 92841ceb1..42b740e6b 100644 --- a/benchmarks/ios/Podfile +++ b/benchmarks/ios/Podfile @@ -29,6 +29,11 @@ target 'BenchmarkRunner' do # OpenTelemetryApi is already pulled in transitively via DatadogTrace, but OpenTelemetrySdk is not. pod 'OpenTelemetry-Swift-Sdk', '~> 2.5.0' + # Opt-in experiment only; does not alter any shipping SDK podspec. + if ENV['DD_FLAGS_PROTOTYPE_PATH'] + pod 'RulesEvaluationPrototype', :path => ENV.fetch('DD_FLAGS_PROTOTYPE_PATH') + end + post_install do |installer| # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( diff --git a/benchmarks/ios/Podfile.lock b/benchmarks/ios/Podfile.lock index 577819ad4..346101308 100644 --- a/benchmarks/ios/Podfile.lock +++ b/benchmarks/ios/Podfile.lock @@ -1877,7 +1877,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - RulesEvaluationPrototype (0.0.1): + - SwiftProtobuf (= 1.38.1) - SocketRocket (0.7.1) + - SwiftProtobuf (1.38.1) - Yoga (0.0.0) DEPENDENCIES: @@ -1959,6 +1962,7 @@ DEPENDENCIES: - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) + - RulesEvaluationPrototype (from `../../../dd-sdk-ios/DatadogFlags/Prototypes/RulesEvaluation`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1976,6 +1980,7 @@ SPEC REPOS: - OpenTelemetry-Swift-Api - OpenTelemetry-Swift-Sdk - SocketRocket + - SwiftProtobuf EXTERNAL SOURCES: boost: @@ -2129,6 +2134,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-screens" RNSVG: :path: "../node_modules/react-native-svg" + RulesEvaluationPrototype: + :path: "../../../dd-sdk-ios/DatadogFlags/Prototypes/RulesEvaluation" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -2140,9 +2147,9 @@ SPEC CHECKSUMS: DatadogInternal: e5a4652ee266986e29c6b8491aff7f78dc3b731d DatadogLogs: 52792add5827042f619771a23656e2bfb6b0f4e7 DatadogRUM: b78d608e013f39c5679ed31ad97dad323c6113e8 - DatadogSDKReactNative: 09dad2b43643ba603e1d8bdb241847917ba6f15d - DatadogSDKReactNativeSessionReplay: 6c6ec0adf5645e7167a4a6b1a6d1ceb656c29c51 - DatadogSDKReactNativeWebView: 34b05bdd6f540bb48c2ed19a3744698075c1b463 + DatadogSDKReactNative: 2a1b6213a4756c19489f4dcc16972ec8b600471b + DatadogSDKReactNativeSessionReplay: 61c1c412a267d8379dacbcfd85f37c5f6556b566 + DatadogSDKReactNativeWebView: 661b7dfa95ca6c276535580604ffd8ad91e6d423 DatadogSessionReplay: 932b2077b1a5367de1450479d72093621b9fd618 DatadogTrace: 5ef84e254e2e1d337bc52e0cdf16396ca102aeb1 DatadogWebViewTracking: e0470157f55a83680a549d79ba2985e0d17465db @@ -2161,68 +2168,70 @@ SPEC CHECKSUMS: RCTTypeSafety: 48ad3c858926b1c46f46a81a58822b476e178e2c React: 3b5754191f1b65f1dbc52fbea7959c3d2d9e39c9 React-callinvoker: 6beeaf4c7db11b6cc953fac45f2c76e3fb125013 - React-Core: 88e817c42de035378cc71e009193b9a044d3f595 - React-CoreModules: dcf764d71efb4f75d38fcae8d4513b6729f49360 - React-cxxreact: 8cdcc937c5fbc406fe843a381102fd69440ca78a + React-Core: 8a10ac9de53373a3ecb5dfcbcf56df1d3dad0861 + React-CoreModules: af6999b35c7c01b0e12b59d27f3e054e13da43b1 + React-cxxreact: 833f00155ce8c2fda17f6d286f8eaeff2ececc69 React-debug: 440175830c448e7e53e61ebb8d8468c3256b645e - React-defaultsnativemodule: 4824bcd7b96ee2d75c28b1ca21f58976867f5535 - React-domnativemodule: a421118b475618961cf282e8ea85347cc9bb453c - React-Fabric: 6ac7de06009eb96b609a770b17abba6e460b5f45 - React-FabricComponents: e3bc2680a5a9a4917ff0c8d7f390688c30ef753c - React-FabricImage: 8bad558dec7478077974caa96acc79692d6b71f5 + React-defaultsnativemodule: a970effe18fe50bdbbb7115c3297f873b666d0d4 + React-domnativemodule: 45f886342a724e61531b18fba1859bb6782e5d62 + React-Fabric: 69f1881f2177a8512304a64157943548ab6df0cf + React-FabricComponents: f54111c8e2439fc273ab07483e3a7054ca1e75af + React-FabricImage: 9ad2619dfe8c386d79e8aaa87da6e8f018ab9592 React-featureflags: b9cf9b35baca1c7f20c06a104ffc325a02752faa - React-featureflagsnativemodule: dc93d81da9f41f7132e24455ec8b4b60802fd5b0 - React-graphics: aaa5a38bea15d7b895b210d95d554af45a07002a - React-hermes: 08ad9fb832d1b9faef391be17309aa6a69fad23b - React-idlecallbacksnativemodule: aacea33ef6c511a9781f9286cc7cdf93f39bba14 - React-ImageManager: c596c3b658c9c14607f9183ed0f635c8dd77987c - React-jserrorhandler: 987609b2f16b7d79d63fcd621bf0110dd7400b35 - React-jsi: afa286d7e0c102c2478dc420d4f8935e13c973fc - React-jsiexecutor: 08f5b512b4db9e2f147416d60a0a797576b9cfef - React-jsinspector: 5a94bcae66e3637711c4d96a00038ab9ec935bf5 - React-jsinspectortracing: a12589a0adbb2703cbc4380dabe9a58800810923 - React-jsitracing: 0b1a403d7757cec66b7dd8b308d04db85eef75f3 - React-logger: 304814ae37503c8eb54359851cc55bd4f936b39c - React-Mapbuffer: b588d1ca18d2ce626f868f04ab12d8b1f004f12c - React-microtasksnativemodule: 11831d070aa47755bb5739069eb04ec621fec548 - react-native-config: 3367df9c1f25bb96197007ec531c7087ed4554c3 - react-native-safe-area-context: 9b169299f9dc95f1d7fe1dd266fde53bd899cd0c - react-native-slider: 27263d134d55db948a4706f1e47d0ec88fb354dd - react-native-webview: be9957759cb73cb64f2ed5359e32a85f1f5bdff8 - React-NativeModulesApple: 79a4404ac301b40bec3b367879c5e9a9ce81683c - React-perflogger: 0ea25c109dba33d47dec36b2634bf7ea67c1a555 - React-performancetimeline: f74480de6efbcd8541c34317c0baedb433f27296 + React-featureflagsnativemodule: 7f1bc76d1d2c5bede5e753b8d188dbde7c59b12f + React-graphics: 069e0d0b31ed1e80feb023ad4f7e97f00e84f7b9 + React-hermes: 63df5ac5a944889c8758a6213b39ed825863adb7 + React-idlecallbacksnativemodule: 4c700bd7c0012adf904929075a79418b828b5ffc + React-ImageManager: 5d1ba8a7bae44ebba43fc93da64937c713d42941 + React-jserrorhandler: 0defd58f8bb797cdd0a820f733bf42d8bee708ce + React-jsi: 99d6207ec802ad73473a0dad3c9ad48cd98463f6 + React-jsiexecutor: 8c8097b4ba7e7f480582d6e6238b01be5dcc01c0 + React-jsinspector: ea148ec45bc7ff830e443383ea715f9780c15934 + React-jsinspectortracing: 46bb2841982f01e7b63eaab98140fa1de5b2a1db + React-jsitracing: c1063fc2233960d1c8322291e74bca51d25c10d7 + React-logger: 763728cf4eebc9c5dc9bfc3649e22295784f69f3 + React-Mapbuffer: 63278529b5cf531a7eaf8fc71244fabb062ca90c + React-microtasksnativemodule: 6a39463c32ce831c4c2aa8469273114d894b6be9 + react-native-config: 644074ab88db883fcfaa584f03520ec29589d7df + react-native-safe-area-context: afcc2e2b3e78ae8ef90d81e658aacee34ebc27ea + react-native-slider: 310d3f89edd6ca8344a974bfe83a29a3fbb60e5a + react-native-webview: 80ef603d1df42e24fdde765686fbb9b8a6ecd554 + React-NativeModulesApple: fd0545efbb7f936f78edd15a6564a72d2c34bb32 + React-perflogger: 5f8fa36a8e168fb355efe72099efe77213bc2ac6 + React-performancetimeline: 8c0ecfa1ae459cc5678a65f95ac3bf85644d6feb React-RCTActionSheet: 2ef95837e89b9b154f13cd8401f9054fc3076aff - React-RCTAnimation: 33d960d7f58a81779eea6dea47ad0364c67e1517 - React-RCTAppDelegate: 85c13403fd6f6b6cc630428d52bd8bd76a670dc9 - React-RCTBlob: 74c986a02d951931d2f6ed0e07ed5a7eb385bfc0 - React-RCTFabric: 384a8fea4f22fc0f21299d771971862883ba630a - React-RCTFBReactNativeSpec: eb1c3ec5149f76133593a516ff9d5efe32ebcecd - React-RCTImage: 2c58b5ddeb3c65e52f942bbe13ff9c59bd649b09 - React-RCTLinking: b6b14f8a3e62c02fc627ac4f3fb0c7bd941f907c - React-RCTNetwork: 1d050f2466c1541b339587d46f78d5eee218d626 - React-RCTSettings: 8148f6be0ccc0cfe6e313417ebf8a479caaa2146 - React-RCTText: 64114531ad1359e4e02a4a8af60df606dbbabc25 - React-RCTVibration: f4859417a7dd859b6bf18b1aba897e52beb72ef6 + React-RCTAnimation: 46abefd5acfda7e6629f9e153646deecc70babd2 + React-RCTAppDelegate: 7e58e0299e304cceee3f7019fa77bc6990f66b22 + React-RCTBlob: f68c63a801ef1d27e83c4011e3b083cc86a200d7 + React-RCTFabric: c59f41d0c4edbaac8baa232731ca09925ae4dda7 + React-RCTFBReactNativeSpec: 3240b9b8d792aa4be0fb85c9898fc183125ba8de + React-RCTImage: 34e0bba1507e55f1c614bd759eb91d9be48c8c5b + React-RCTLinking: a0b6c9f4871c18b0b81ea952f43e752718bd5f1d + React-RCTNetwork: bdafd661ac2b20d23b779e45bf7ac3e4c8bd1b60 + React-RCTSettings: 98aa5163796f43789314787b584a84eba47787a9 + React-RCTText: 424a274fc9015b29de89cf3cbcdf4dd85dd69f83 + React-RCTVibration: 92d9875a955b0adb34b4b773528fdbbbc5addd6c React-rendererconsistency: 5ac4164ec18cfdd76ed5f864dbfdc56a5a948bc9 - React-rendererdebug: 3dc1d97bbee0c0c13191e501a96ed9325bbd920e + React-rendererdebug: 710dbd7990e355852c786aa6bc7753f6028f357a React-rncore: 0bace3b991d8843bb5b57c5f2301ec6e9c94718b - React-RuntimeApple: 1e1e0a0c6086bc8c3b07e8f1a2f6ca99b50419a0 - React-RuntimeCore: d39322c59bef2a4b343fda663d20649f29f57fcc + React-RuntimeApple: 701ec44a8b5d863ee9b6a2b2447b6a26bb6805a1 + React-RuntimeCore: a82767065b9a936b05e209dc6987bc1ea9eb5d2d React-runtimeexecutor: 876dfc1d8daa819dfd039c40f78f277c5a3e66a6 - React-RuntimeHermes: 44f5f2baf039f249b31ea4f3e224484fd1731e0e - React-runtimescheduler: 3b3c5b50743bb8743ca49b9e5a70c2c385f156e1 + React-RuntimeHermes: e7a051fd91cab8849df56ac917022ef6064ad621 + React-runtimescheduler: c544141f2124ee3d5f3d5bf0d69f4029a61a68b0 React-timing: 1ee3572c398f5579c9df5bf76aacddf5683ff74e - React-utils: 0cfb7c7fb37d4e5f31cc18ffc7426be0ae6bf907 - ReactAppDependencyProvider: b48473fe434569ff8f6cb6ed4421217ebcbda878 - ReactCodegen: 653a0d8532d8c7dab50c391392044d98e20c9f79 - ReactCommon: 547db015202a80a5b3e7e041586ea54c4a087180 - RNCPicker: ffbd7b9fc7c1341929e61dbef6219f7860f57418 - RNScreens: 0f01bbed9bd8045a8d58e4b46993c28c7f498f3c - RNSVG: 082ae2874b288a96c95326c4f6029793f7009d4b + React-utils: 18703928768cb37e70cf2efff09def12d74a399e + ReactAppDependencyProvider: 4893bde33952f997a323eb1a1ee87a72764018ff + ReactCodegen: 6d003cb0cf0b773a52b3fbcc7984e5df1a3447a5 + ReactCommon: 865ebe76504a95e115b6229dd00a31e56d2d4bfe + RNCPicker: cfb51a08c6e10357d9a65832e791825b0747b483 + RNScreens: 790123c4a28783d80a342ce42e8c7381bed62db1 + RNSVG: 8ad5c94593e5e4a6e133e6934e2188e3c688d27a + RulesEvaluationPrototype: 5a25c0531ad96bdbae78052e57d6a2000c36f18d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 + SwiftProtobuf: 409d3aaec90e51b1705b1dd8065b56893450e77c Yoga: e14bad835e12b6c7e2260fc320bd00e0f4b45add -PODFILE CHECKSUM: d0574c1c0410627561bf4b1b55d0f1754283e022 +PODFILE CHECKSUM: e378cab35e06dc537c13c9dee54995cd3bb922ac COCOAPODS: 1.16.2 diff --git a/benchmarks/metro.config.js b/benchmarks/metro.config.js index 80b2747c5..93e30a5a7 100644 --- a/benchmarks/metro.config.js +++ b/benchmarks/metro.config.js @@ -34,6 +34,20 @@ const config = { // while it should be only one React instance in the runtime. exclusionList relies on the modules which are // declared as peer dependencies in the core package. resolver: { + // The optional flags benchmark uses Protobuf-ES package subpath exports. + unstable_enablePackageExports: true, + resolveRequest: (context, moduleName, platform) => { + // Keep the benchmark adapter and core on the same decoder instance. + // Metro 0.81 otherwise selects core's bundled legacy sub-entrypoint. + if (moduleName === '@datadog/flagging-core/rules-based' || + /^@bufbuild\/protobuf(?:\/|$)/.test(moduleName)) { + return { + type: 'sourceFile', + filePath: require.resolve(moduleName, { paths: [path.dirname(context.originModulePath)] }) + }; + } + return context.resolveRequest(context, moduleName, platform); + }, // Remove svg from asset extensions and add it to source extensions so Metro // sends it through the transformer rather than copying it as a static asset. assetExts: defaultConfig.resolver.assetExts.filter(ext => ext !== 'svg'), diff --git a/benchmarks/package.json b/benchmarks/package.json index d208e29fd..53993b556 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -7,7 +7,9 @@ "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", - "test": "jest" + "test": "jest", + "test:flags": "jest --config flags.jest.config.js --runInBand", + "flags:compile-fixture": "tsc --outDir build/flags --target es2020 --module commonjs --moduleResolution node --skipLibCheck src/flags/fixtures.ts" }, "codegenConfig": { "name": "BenchmarkVitalsSpec", @@ -19,6 +21,8 @@ "library": "BenchmarkRunner" }, "dependencies": { + "@bufbuild/protobuf": "2.15.0", + "@datadog/flagging-core": "3.1.1", "@datadog/mobile-react-native": "workspace:packages/core", "@datadog/mobile-react-native-session-replay": "workspace:packages/react-native-session-replay", "@datadog/mobile-react-native-webview": "workspace:packages/react-native-webview", diff --git a/benchmarks/scripts/export-flags-fixture.cjs b/benchmarks/scripts/export-flags-fixture.cjs new file mode 100644 index 000000000..e560fd75b --- /dev/null +++ b/benchmarks/scripts/export-flags-fixture.cjs @@ -0,0 +1,65 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2026-Present Datadog, Inc. + */ + +// Run after flags:compile-fixture. The destination must be an explicit file path. +const fs = require('node:fs'); +const crypto = require('node:crypto'); +const { + configurationFromRulesBinary, +} = require('@datadog/flagging-core/rules-based'); +const {evaluateRulesBasedConfiguration} = require('@datadog/flagging-core'); +const {makeFixture, requests, comparable} = require('../build/flags/fixtures'); + +const destination = process.argv[2]; +if (!destination) + throw new Error('Usage: node scripts/export-flags-fixture.cjs '); +const logger = {debug() {}, info() {}, warn() {}, error() {}}; +const configurations = [0, 1].map(revision => { + const fixture = makeFixture(10, revision); + const configuration = configurationFromRulesBinary(fixture.bytes).rules + .response; + const cases = requests.map(request => ({ + request, + expected: comparable( + evaluateRulesBasedConfiguration( + configuration, + 'boolean', + request.flagKey, + false, + request.context, + logger, + ), + ), + })); + if (cases.some(({expected}) => expected.reason === 'ERROR')) + throw new Error('Invalid fixture'); + return { + revision, + protobufBase64: Buffer.from(fixture.bytes).toString('base64'), + protoJson: fixture.json, + sha256: crypto.createHash('sha256').update(fixture.bytes).digest('hex'), + cases, + }; +}); +fs.writeFileSync( + destination, + JSON.stringify( + { + generator: 'dd-sdk-reactnative/benchmarks/src/flags/fixtures.ts', + fixtureVersion: 1, + flaggingCore: '3.1.1', + protobufES: '2.15.0', + configurations, + }, + null, + 2, + ) + '\n', +); +console.log( + `Wrote ${ + configurations.length * requests.length + } reference cases to ${destination}`, +); diff --git a/benchmarks/src/App.tsx b/benchmarks/src/App.tsx index 4a3bc0255..abc04234b 100644 --- a/benchmarks/src/App.tsx +++ b/benchmarks/src/App.tsx @@ -45,6 +45,10 @@ function App(): React.JSX.Element { switch (testConfig?.command) { case Command.Start: if (isValidScenario(testConfig?.scenario)) { + if (testConfig.scenario === Scenario.Flags) { + setTestConfig(testConfig); + return; + } const datadogConfig = getDatadogConfig(); testConfig.datadogConfig = datadogConfig; @@ -69,6 +73,10 @@ function App(): React.JSX.Element { } switch(testConfig?.scenario) { + case Scenario.Flags: { + const FlagsScenario = require('./flags/FlagsScenario').default; + return ; + } case Scenario.Default: return ; case Scenario.NavigationExample: diff --git a/benchmarks/src/flags/FlagsScenario.tsx b/benchmarks/src/flags/FlagsScenario.tsx new file mode 100644 index 000000000..0a7b28a07 --- /dev/null +++ b/benchmarks/src/flags/FlagsScenario.tsx @@ -0,0 +1,107 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2026-Present Datadog, Inc. + */ + +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import { + Button, + SafeAreaView, + ScrollView, + Share, + StyleSheet, + Text, + View, +} from 'react-native'; +import Config from 'react-native-config'; +import {runBenchmarks, saveReport} from './runner'; + +export default function FlagsScenario() { + const [busy, setBusy] = useState(false); + const [status, setStatus] = useState('Ready'); + const [report, setReport] = useState(''); + const running = useRef(false); + const run = useCallback(async (smoke: boolean) => { + if (running.current) return; + running.current = true; + setBusy(true); + setReport(''); + try { + saveReport( + JSON.stringify({ + correctness: 'running', + startedAt: new Date().toISOString(), + }), + ); + const result = await runBenchmarks(setStatus, smoke); + const json = JSON.stringify(result, null, 2); + saveReport(json); + setReport(json); + setStatus('Completed; correctness checks passed'); + console.log(`FLAGS_BENCHMARK_RESULT ${JSON.stringify(result)}`); + } catch (error) { + setStatus(`Failed: ${String(error)}`); + const failure = JSON.stringify({ + correctness: 'failed', + error: String(error), + stack: error instanceof Error ? error.stack : undefined, + }); + try { + saveReport(failure); + } catch { + console.error(failure); + } + } finally { + running.current = false; + setBusy(false); + } + }, []); + useEffect(() => { + if (Config.BENCH_FLAGS_AUTORUN === 'smoke') void run(true); + }, [run]); + return ( + + + Flag evaluation benchmarks + + {__DEV__ ? 'Debug build: smoke testing only' : 'Release build'} + + + {status} + + +