Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions js/optify-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ yarn build:ts
yarn test
```

## Benchmarking

Run:
```shell
rm -rf target config.*.node
yarn build
yarn build:ts
node benchmarks/get_all_options.mjs
```

## Formatting

To automatically change the Rust code, run:
Expand Down
78 changes: 78 additions & 0 deletions js/optify-config/benchmarks/get_all_options.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Bench } from "tinybench";
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Import the native module
const { OptionsProvider, GetOptionsPreferences } = await import(
"../dist/index.js"
);

const configurableConfigsPath = path.join(
__dirname,
"../../../tests/test_suites/configurable_values/configs"
);

const provider = OptionsProvider.build(configurableConfigsPath);

const preferences = new GetOptionsPreferences();
preferences.enableConfigurableStrings();

const featureTrials = [
["simple"],
["simple", "imports"],
["imports_imports"],
["simple", "override_name"],
["simple", "raw_overrides"],
["simple", "with_files"],
["simple", "with_files_in_arguments"],
["simple", "complex_deep_merge"],
["simple", "complex_wide_structure"],
[
"simple",
"complex_deep_merge",
"complex_nested_objects",
"complex_wide_structure",
],
];

const WARMUP_ITERATIONS = 20;
const ITERATIONS = 5000;

console.log(
"Benchmarking getAllOptions vs JSON.parse(getAllOptionsJson(...))\n"
);
console.log("=".repeat(80));

for (const features of featureTrials) {
const featureLabel = features.join(", ");

// Warmup both methods
for (let i = 0; i < WARMUP_ITERATIONS; ++i) {
provider.getAllOptions(features, preferences);
JSON.parse(provider.getAllOptionsJson(features, preferences));
}

const bench = new Bench({
time: 3000,
iterations: ITERATIONS,
});

bench
.add(`getAllOptions`, () => {
provider.getAllOptions(features, preferences);
})
.add(`JSON.parse(getAllOptionsJson)`, () => {
JSON.parse(provider.getAllOptionsJson(features, preferences));
});

await bench.run();

console.log(`\nFeatures: [${featureLabel}]`);
console.log("-".repeat(80));
console.table(bench.table());
}

console.log("\n" + "=".repeat(80));
console.log("Benchmark complete.");
1 change: 1 addition & 0 deletions js/optify-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@types/mocha": "^10.0.10",
"corepack": "^0.32.0",
"jest": "^29.7.0",
"tinybench": "^5.1.0",
"ts-jest": "^29.3.4",
"typescript": "^5.8.3"
},
Expand Down
36 changes: 36 additions & 0 deletions js/optify-config/src/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![deny(clippy::all)]

use napi::Env;

pub fn convert_to_js(env: Env, value: &serde_json::Value) -> napi::JsUnknown {
match value {
serde_json::Value::Null => env.get_null().unwrap().into_unknown(),
serde_json::Value::Bool(b) => env.get_boolean(*b).unwrap().into_unknown(),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
env.create_int64(i).unwrap().into_unknown()
} else if let Some(f) = n.as_f64() {
env.create_double(f).unwrap().into_unknown()
} else {
env.get_null().unwrap().into_unknown()
}
}
serde_json::Value::String(s) => env.create_string(s).unwrap().into_unknown(),
serde_json::Value::Array(arr) => {
let mut js_array = env.create_array_with_length(arr.len()).unwrap();
for (i, item) in arr.iter().enumerate() {
js_array
.set_element(i as u32, convert_to_js(env, item))
.unwrap();
}
js_array.into_unknown()
}
serde_json::Value::Object(map) => {
let mut js_obj = env.create_object().unwrap();
for (key, val) in map.iter() {
js_obj.set(key, convert_to_js(env, val)).unwrap();
}
js_obj.into_unknown()
}
}
}
1 change: 1 addition & 0 deletions js/optify-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![deny(clippy::all)]

mod convert;
mod metadata;
mod preferences;
mod provider;
Expand Down
33 changes: 33 additions & 0 deletions js/optify-config/src/provider.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![deny(clippy::all)]

use napi::Env;
use optify::builder::{OptionsProviderBuilder, OptionsRegistryBuilder};
use optify::provider::{OptionsProvider, OptionsRegistry};

use crate::convert::convert_to_js;
use crate::metadata::{to_js_options_metadata, JsOptionsMetadata};
use crate::preferences::JsGetOptionsPreferences;

Expand Down Expand Up @@ -58,6 +60,37 @@ impl JsOptionsProvider {
.collect()
}

/// Gets all options for the specified feature names.
/// Should return a JavaScript object.
///
/// There normally isn't much of a performance difference between using
/// `JSON.parse(get_all_options_json(...))` and `get_all_options(...)`.
/// Large JSON objects with over 50 keys may be slightly slower with `get_all_options(...)`.
#[napi]
pub fn get_all_options(
&self,
env: Env,
feature_names: Vec<String>,
preferences: Option<&JsGetOptionsPreferences>,
) -> napi::Result<napi::JsUnknown> {
let preferences = preferences.map(|p| &p.inner);
match self
.inner
.as_ref()
.unwrap()
.get_all_options(&feature_names, None, preferences)
{
Ok(options) => Ok(convert_to_js(env, &options)),
Err(e) => Err(napi::Error::from_reason(e.to_string())),
}
}

/// Gets all options for the specified feature names.
/// Returns a string which can be parsed as JSON to get the options.
///
/// There normally isn't much of a performance difference between using
/// `JSON.parse(get_all_options_json(...))` and `get_all_options(...)`.
/// Large JSON objects with over 50 keys may be slightly slower with `get_all_options(...)`.
#[napi]
pub fn get_all_options_json(
&self,
Expand Down
32 changes: 32 additions & 0 deletions js/optify-config/src/watcher.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#![deny(clippy::all)]

use napi::Env;
use optify::builder::{OptionsRegistryBuilder, OptionsWatcherBuilder};
use optify::provider::{OptionsRegistry, OptionsWatcher};
use std::sync::Arc;

use crate::convert::convert_to_js;
use crate::metadata::{to_js_options_metadata, JsOptionsMetadata};
use crate::preferences::JsGetOptionsPreferences;
use crate::watcher_options::JsWatcherOptions;
Expand Down Expand Up @@ -119,7 +121,37 @@ impl JsOptionsWatcher {
.map(|(k, v)| (k, to_js_options_metadata(v)))
.collect()
}
/// Gets all options for the specified feature names.
/// Should return a JavaScript object.
///
/// There normally isn't much of a performance difference between using
/// `JSON.parse(get_all_options_json(...))` and `get_all_options(...)`.
/// Large JSON objects with over 50 keys may be slightly slower with `get_all_options(...)`.
#[napi]
pub fn get_all_options(
&self,
env: Env,
feature_names: Vec<String>,
preferences: Option<&JsGetOptionsPreferences>,
) -> napi::Result<napi::JsUnknown> {
let preferences = preferences.map(|p| &p.inner);
match self
.inner
.as_ref()
.unwrap()
.get_all_options(&feature_names, None, preferences)
{
Ok(options) => Ok(convert_to_js(env, &options)),
Err(e) => Err(napi::Error::from_reason(e.to_string())),
}
}

/// Gets all options for the specified feature names.
/// Returns a string which can be parsed as JSON to get the options.
///
/// There normally isn't much of a performance difference between using
/// `JSON.parse(get_all_options_json(...))` and `get_all_options(...)`.
/// Large JSON objects with over 50 keys may be slightly slower with `get_all_options(...)`.
#[napi]
pub fn get_all_options_json(
&self,
Expand Down
3 changes: 3 additions & 0 deletions js/optify-config/tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ describe('Provider', () => {
const options = JSON.parse(provider.getAllOptionsJson(['feature_A']))
const expectedOptions = JSON.parse(fs.readFileSync(path.join(configsPath, 'feature_A.json'), 'utf8'))['options']
expect(options).toEqual(expectedOptions)

const optionsObj = provider.getAllOptions(['feature_A'])
expect(optionsObj).toEqual(expectedOptions)
})

test(`${name} get_all_options_json A and B`, () => {
Expand Down
8 changes: 8 additions & 0 deletions js/optify-config/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ __metadata:
"@types/mocha": "npm:^10.0.10"
corepack: "npm:^0.32.0"
jest: "npm:^29.7.0"
tinybench: "npm:^5.1.0"
ts-jest: "npm:^29.3.4"
typescript: "npm:^5.8.3"
languageName: unknown
Expand Down Expand Up @@ -3276,6 +3277,13 @@ __metadata:
languageName: node
linkType: hard

"tinybench@npm:^5.1.0":
version: 5.1.0
resolution: "tinybench@npm:5.1.0"
checksum: 10c0/a9d53204888711c6e67d374b96b9a8a096aac80dda755562de3ed7afa2a39f04abd3cd2a64e401a3f0ece4d49ae20230be0986c733030756bde2f0d56e78cd0f
languageName: node
linkType: hard

"tinyglobby@npm:^0.2.12":
version: 0.2.14
resolution: "tinyglobby@npm:0.2.14"
Expand Down
10 changes: 1 addition & 9 deletions ruby/optify/benchmarks/get_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
feature_b = simple_provider.get_canonical_feature_name('b')

simple_feature_trials = [
['a'],
[feature_a],
['a', feature_a, 'b', feature_b],
['a', feature_a, 'b', feature_b, 'A_with_comments', 'a', 'B'],
['a', feature_a, 'b', feature_b, 'A_with_comments', 'a', 'B', 'a', feature_a, 'b', feature_b, 'A_with_comments', 'a', 'B', 'a', feature_a, 'b', feature_b, 'A_with_comments',
'a', 'B']
]
Expand All @@ -37,17 +35,11 @@
['imports_imports'],
%w[simple override_name],
%w[simple raw_overrides],
['with_files'],
%w[simple with_files],
['with_files_in_arguments'],
%w[simple with_files_in_arguments],
['complex_deep_merge'],
%w[simple complex_deep_merge],
['complex_wide_structure'],
%w[simple complex_wide_structure],
['complex_nested_objects'],
%w[simple complex_nested_objects],
%w[complex_deep_merge complex_nested_objects complex_wide_structure]
%w[simple complex_deep_merge complex_nested_objects complex_wide_structure]
]

Benchmark.bm do |x|
Expand Down
2 changes: 1 addition & 1 deletion rust/optify/src/provider/provider_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait OptionsRegistry {
/// Gets all feature names and alias names.
fn get_features_and_aliases(&self) -> Vec<String>;

/// Gets all options for the specified feature names
/// Gets all options for the specified feature names.
fn get_all_options(
&self,
feature_names: &[impl AsRef<str>],
Expand Down
Loading