Skip to content

Use the specification's defaults for the performance profile and Update Matched User Agent - #57

Draft
jwrosewell wants to merge 4 commits into
rcfrom
fix/dd-update-matched-useragent-default
Draft

jwrosewell wants to merge 4 commits into
rcfrom
fix/dd-update-matched-useragent-default

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The Device Detection on-premise engine in Python used two defaults that differ from the specification and from the C library it is built on.

  1. Performance profile. The specification gives "Balanced" as the default (device-detection-on-premise.md, the Performance Profile row at line 292). The C library's default configuration is the Balanced one, fiftyoneDegreesHashDefaultConfig = { FIFTYONE_DEGREES_HASH_CONFIG_BALANCED } (51degrees-cxx src/dd/hash/hash.c lines 356 to 379, at the commit the cxx submodule points to), and ConfigHash() starts from it (ConfigHash.cpp lines 28 to 32). Python defaulted to "LowMemory".
  2. Update Matched User Agent. The specification says the option is on by default, so the UserAgents match metric holds the characters of the evidence that were matched unless the option is turned off (the UserAgents row at line 273 and the Update Matched User Agent row at line 289, from specifications#95), with the default "true, from native code". The C library sets it on, #define FIFTYONE_DEGREES_CONFIG_DEVICE_DETECTION_UPDATE_DEFAULT true (src/dd/config-dd.h line 82). Python turned it off.

The .NET builder leaves the native default alone for Update Matched User Agent unless SetUpdateMatchedUserAgent is called, and documents it as "true - " + NATIVE_DEFAULTS (DeviceDetectionHashEngineBuilderBase.cs lines 187 to 201).

What I checked on rc

All line numbers in this section are on rc at 0507eef, in device-detection/fiftyone_devicedetection_onpremise/src/fiftyone_devicedetection_onpremise/devicedetection_onpremise.py unless another file is named.

  • DeviceDetectionOnPremise.__init__ took update_matched_useragent=False (line 64) and performance_profile="LowMemory" (line 65).
  • The profile switch (lines 175 to 189) accepts "LowMemory", "MaxPerformance", "Balanced", "BalancedTemp" and "HighPerformance", and maps "Balanced" to config.setBalanced(). In the C++ wrapper setBalanced copies the collection settings of fiftyoneDegreesHashBalancedConfig (ConfigHash.cpp lines 58 to 60), so no new mapping was needed.
  • The engine always called config.setUpdateMatchedUserAgent(update_matched_useragent) (line 212), so it turned the native default off.
  • DeviceDetectionOnPremisePipelineBuilder, DeviceDetectionPipelineBuilder and PipelineBuilder.build_from_configuration hold no default of their own for either option. They pass their settings to that constructor, so every route, a configuration file's elementParameters included, took the engine's defaults. A configuration file can set either option by name, as performance_profile and update_matched_useragent.
  • With Update Matched User Agent off, the native code leaves each result's matched User-Agent unset (results-dd.c lines 86 to 96), and ResultsHash::getUserAgent then answers with an empty string (ResultsHash.cpp lines 280 to 291). So UserAgents was a list of empty strings, one per result. The existing test_matched_user_agents only checks the list has at least one entry, which an empty string satisfies, so it passed either way.
  • The docstrings in the engine and both builders gave no default for either option, and no type for Update Matched User Agent. No readme names the default profile, and the examples that set a profile set it by name.
  • The same docstrings said max_matched_useragent_length is ignored when update_matched_useragent is false, and named it max_matched_useragents_length. The C header says the limit applies whether or not the matched User-Agent is recorded (config-dd.h lines 57 to 68), and its default is 500 (line 106). The engine only sets the limit when a value is given.

Change

  • DeviceDetectionOnPremise takes performance_profile="Balanced" (line 65).
  • It takes update_matched_useragent=None (line 64) and only calls setUpdateMatchedUserAgent when a value is given (lines 225 to 227), so the native default applies otherwise, as in .NET.
  • The docstrings for both options in devicedetection_onpremise.py, devicedetection_onpremise_pipelinebuilder.py and fiftyone_devicedetection/devicedetection_pipelinebuilder.py give the defaults. Balanced is the default profile, as in the native code. Update Matched User Agent is a bool, fills UserAgents, is on by default, and False saves recording the characters when UserAgents is not needed.
  • The max_matched_useragent_length docstrings in the same three files now spell the option's name correctly and say what the C header says. The limit applies to a single header such as User-Agent, to a pseudo header including the separators between its values, and to the matched User-Agent. A longer value is ignored and detection moves on to the next header. The limit applies whether or not update_matched_useragent is on, the native default of 500 applies when it is left unset, and a larger limit raises the memory each set of results holds. No code changes for this option.
  • The comment in the match metrics example, which still sets Update Matched User Agent to True, no longer says the example has to set it.

A caller that names a profile, or passes update_matched_useragent=False, keeps what they asked for. Only a caller that left either option unset sees a difference.

For the release notes

  • The Device Detection on-premise engine now uses the Balanced performance profile by default, instead of LowMemory, as the specification and the C library give. Anyone relying on the default profile will use more memory after upgrading, because Balanced loads some collections into memory and caches others, where LowMemory reads everything from the data file. Pass performance_profile="LowMemory" to keep the previous behaviour.
  • The engine now leaves Update Matched User Agent on by default, so UserAgents holds the matched characters of the evidence. Pass update_matched_useragent=False to turn it off.

Tests

Both new files are in device-detection/fiftyone_devicedetection_onpremise/tests. Each finds the free Lite data file at cxx/data/device-detection/51Degrees-LiteV4.1.hash from its own folder, where cxx/data/get-data-files.ps1 and get-data-files.sh download it and where device-detection/ci/fetch-assets.ps1 links it in, and skips with a message naming the download scripts when the file is not there.

test_performance_profile_default.py. This test is indirect. Python keeps no name for the profile in use, so the tests compare the configuration the engine gives the native code (engine.config) with the settings the native code gives each profile. They compare whether the strings, properties, values, profiles, nodes and profile offsets collections are loaded into memory, and how many items each caches, which are what a profile sets and what Balanced and LowMemory differ in. MaxPerformance and LowMemory have the same collection settings, as do Balanced and BalancedTemp, so the test cannot tell either pair apart.

  • test_balanced_and_low_memory_differ checks the two profiles' settings differ, so the comparisons below can tell them apart.
  • test_engine_defaults_use_balanced builds the engine at its defaults in a pipeline and checks its settings are the Balanced ones.
  • test_builder_defaults_use_balanced does the same through DeviceDetectionOnPremisePipelineBuilder.
  • test_each_named_profile_is_used checks each of the five names gives the settings its own setter gives.

test_update_matched_useragent.py builds a pipeline with DeviceDetectionOnPremisePipelineBuilder, processes one User-Agent and reads UserAgents.

  • test_builder_defaults_populate_user_agents leaves the option unset, and checks that at least one character was matched and that every matched character is the evidence's character at the same position.
  • test_turned_on_populates_user_agents does the same with the option set to True.
  • test_turned_off_leaves_user_agents_empty sets it to False and checks every entry is an empty string.

Verification

From device-detection/fiftyone_devicedetection_onpremise, on Windows with Python 3.14.7 and the Visual Studio 2022 Build Tools. I installed the pipeline and Device Detection packages from this checkout into a virtual environment, building the native module with pip install -e ., and downloaded the Lite file and 20000 User Agents.csv with cxx/data/get-data-files.ps1. Then I ran

python -m pytest tests -p no:cacheprovider -q -rfE
  • rc's engine and builder with rc's tests (the two new files left out with --ignore) gave 57 passed.
  • rc's engine and builder with the two new files gave 3 failed, 4 passed, 5 subtests passed. The three failures are the three defaults tests. test_engine_defaults_use_balanced and test_builder_defaults_use_balanced found the LowMemory settings, being nothing loaded and no cache, where Balanced caches 10000 strings and 50000 nodes and loads the properties. test_builder_defaults_populate_user_agents found ['', '', '', ''].
  • This branch gave 64 passed, 5 subtests passed, being the 57 existing tests and the 7 new ones.

At the builder's defaults on this branch, the first of the four matched User-Agents for the test's iPhone User-Agent reads ______________Phone;______________ 11_2_______________________________________________________ Mobile_______, where _ marks a character that was not matched.

I did not run the Device Detection cloud tests or the examples' tests locally.

Remaining

  • fiftyone_devicedetection_cloud/devicedetection_cloud_pipelinebuilder.py still carries a copy of the on-premise settings in its docstring, including the old wording for max_matched_useragent_length. The cloud builder does not use those settings, so I have left that copy alone.
  • The IP Intelligence on-premise engine is not changed here.

…s it

The Device Detection on-premise specification says the option is on by
default, so the UserAgents property holds the matched characters of the
evidence. The C library sets it on, but the Python engine took
update_matched_useragent=False and always passed it on, so UserAgents was
a list of empty strings unless a caller turned the option on.

The engine now takes None and only sets the option when a value is given,
so the native default applies, as the .NET builder does. The docstrings in
the engine and both builders say the option is on by default, and the
match metrics example's comment no longer says it has to be set.

A new test builds a pipeline on the free Lite data file with the option
left unset, set on and set off, and skips when that file is not there.
The Device Detection on-premise specification gives Balanced as the
default performance profile, and the C library's default configuration is
the Balanced one, but the Python engine defaulted to LowMemory. The engine
now defaults to "Balanced", which its profile switch already maps to the
native Balanced configuration, and the docstrings in the engine and both
builders say so.

Balanced loads some collections into memory and caches others, where
LowMemory reads everything from the data file, so a caller relying on the
default uses more memory. Naming "LowMemory" keeps the previous behaviour.

A new test compares the collection settings of the configuration an
engine built at its defaults gives the native code with those of each
profile, because the engine keeps no name for the profile in use.
@jwrosewell jwrosewell changed the title Leave Update Matched User Agent on by default, as the native code sets it Use the specification's defaults for the performance profile and Update Matched User Agent Nov 7, 2026
MaxPerformance and LowMemory have the same collection settings, as do Balanced and BalancedTemp, so the test's comparison of collection settings cannot separate either pair. The test's docstring now says so.
The max_matched_useragent_length docstrings in the engine and both
builders said the option is ignored when update_matched_useragent is
false, and named it max_matched_useragents_length. The C library's header
(src/dd/config-dd.h) says the limit applies to a single header, a pseudo
header and the matched User-Agent, that a longer value is ignored and
detection moves on, and that the limit applies whether or not the matched
User-Agent is recorded. The docstrings now say the same, give the native
default of 500, and spell the option's name correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant