Repository navigation
Usage sharing finds its values once a call, without copying the Evidence - #120
Merged
Merged
Conversation
Usage sharing finds the values it shares on every call, and most calls are repeats it does not share. A call that is not shared copies every Evidence value twice, once in the element and once in its tracker, and a call that is shared copies them three times. A web request is copied as often.
Without a web request ShareUsageEvidence.find returns the Evidence's own read-only map, which is already keyed without regard to case and holds no null value, rather than a copy of every value. With a web request the element makes the one copy and gives it to its ShareUsageTracker, which makes its key from it once for both the look up and the store of its cache, rather than finding the values again for each.
The read count tests assert what a call that is not shared costs, which is the call the tracker turns down, as most calls repeat one already shared. A call that is shared reads its values again to share them.
The element no longer finds the values on every call. It finds them for the ignore filter when one is set, and otherwise only once the tracker allows the call to be shared. The tracker finds them for its key as it did, which without a web request copies nothing. Giving the element's copy to the tracker for each call measured slower than the copy it saved on a call with one Evidence value, so ShareUsageTracker is as it is on rc.
jwrosewell
marked this pull request as ready for review
November 13, 2026 02:28
This was referenced Nov 13, 2026
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Usage sharing finds the values it shares from the Evidence and, when the web integration supplies it, the web request. Since #68 it copied every Evidence value into a new case-insensitive map twice on every call, whether or not the call was shared. This draft finds the values only when something needs them, and without a copy when the Evidence holds no web request.
The problem
rcmeasured slower thanmainin the usage sharing overhead test, and swapping #68 alone moved the cost both ways. These are the figures of run 43309004655, oneubuntu-22.04-armrunner, 32 rounds, every tree once a round with the order turning, milliseconds per call, median (lowest to highest).ShareUsageOverhead_HundredEvidence_msShareUsageOverhead_SingleEvidence_msmainf69da52rc36c153drcwithout #68mainwith #68The figure named
ShareUsageOverhead_HundredEvidence_mscomes fromShareUsageOverhead_ThousandEvidence, which gives each call 1,001 Evidence values (ShareUsageOverheadTests.javalines 158 to 184 at 36c153d5), so this description calls it the 1,001 value figure. The single value figure is 10,000 calls with the User-Agent alone (lines 123 to 146).On
rc,ShareUsageBase.processInternalcallsShareUsageEvidence.findon every call, andShareUsageTracker.generateKeycalls it again.findcopies every value into a newTreeMapordered byString.CASE_INSENSITIVE_ORDER.TrackerBase.trackmakes the key once to look the call up and, for a call it has not seen, again to store it (TrackerBase.javalines 63 and 67). So a call that is not shared copied every value twice, and a call that is shared three times.What the specification says
Evidence, line 10 at 48e28393:
Usage sharing element, Accepted Evidence, lines 18 to 29 at 48e28393:
Session tracking, lines 262 to 264:
#68 copied the values so that keys from the two places are compared without regard to case and a key in both keeps the Evidence value. Both still hold, as below.
What changed and why
ShareUsageEvidence.findcopies only when there is a web request (lines 50 to 63 at ee271a37). When the Evidence holds noserver.http-request, it returns the Evidence's own read-only map. FlowData's Evidence is aConcurrentSkipListMapordered byString.CASE_INSENSITIVE_ORDER(EvidenceDefault.javaline 42), so its keys are case-insensitive as the specification requires, and aConcurrentSkipListMapcannot hold a null value. So the map holds the same keys and values, in the same order, as the copy did, which left out null values and the web request and was keyed without regard to case. Every FlowData a Pipeline creates gets anEvidenceDefaultfromFlowDataFactoryDefault, asPipelineDefault, its constructor,EvidenceDefaultandPipelineBuilderBase.getFlowDataFactoryare all package-private (PipelineBuilderBase.javalines 218 to 219). With a web request,findcopies and merges exactly as before.processInternal, lines 645 to 672, andprocessData, lines 793 to 840). It finds them inprocessInternalonly when an ignore filter is set, and otherwise inprocessDataoncetracker.trackhas allowed the call to be shared. A call the tracker turns down, which is most calls, no longer finds them in the element at all. The ignore filter, the cancel check, the share percentage and the tracker run in the same order as before, and on each the same values decide.ShareUsageTrackeris as onrc. It finds the values for its key ingenerateKey, which without a web request no longer copies.Every exit was read.
processInternalleaves through the ignore filter'sbreak, the cancel check, orprocessData.processDatareturns when the share percentage or the tracker says no, and otherwise finds the values if the ignore filter had not, and queues or discards them as before.findreturns the Evidence's map, or the copy.Nothing public is removed or renamed.
ShareUsageEvidenceis new onrcsince #68 and is not onmain.A shape that was measured and not kept
The draft's history first gave the element's copy to the tracker for each call, through a thread-local value, so that a call with a web request copied once whether shared or not (db808b00). It cleared the 1,001 value figure but not the single value one. In run 43332442847 (job 131580208280) the single value figure was
main0.0058 (0.0057 to 0.0061),rc0.0066 (0.0063 to 0.0070), db808b0 0.0066 (0.0064 to 0.0074), and the same with a holder made once for each thread (883ae89b) 0.0064 (0.0061 to 0.0068). Commits c736ca5c and ee271a37 replace it, andShareUsageTracker.javaat the head is the same file as onrc(blob 8bfa52d3).Tests
ShareUsageEvidenceReadTestsgives the element Evidence, and a web request, that count how often all their values are read. Each test makes one call that is shared and a repeat that the default tracker turns down, checks which was shared from the queue, and counts the reads in the repeat.NoWebRequest_EvidenceReadOnceWhenNotSharedasks for one read of 100 Evidence values, for the session tracking key.WebRequest_EachCopiedOnceWhenNotSharedasks for one read each of the Evidence and of a web request of 100 values, for the one copy the key is made from.ShareUsageEvidenceTestschecks thatfindwithout a web request returns the Evidence uncopied and keyed without regard to case, and that with a web request it adds the web request's values, keeps the Evidence value for a key in both, and leaves out the web request itself.Both run on
ubuntu-22.04with JDK 17 in run 43337686624, with every test #68 added or changed, throughmvn test -pl pipeline.engines.fiftyone,pipeline.cloudrequestengine,pipeline.web.packages/pipeline.web,pipeline.web.packages/pipeline.web.mvc -am.rc36c153d with the two test files only. Both files are the same blobs as at the head (d3d919e7 and fee9b726).pipeline.cloudrequestengineran 48 with no failures, andpipeline.engines.fiftyoneprintedTests run: 48, Failures: 3, Errors: 0, Skipped: 1, the three below, so Maven stopped before the web modules.pipeline.engines.fiftyoneran 48 with 1 skipped, the@DisabledShareUsageBuilder_IgnoreData_InvalidFilter.pipeline.cloudrequestengineran 48,pipeline.web26 andpipeline.web.mvc9, with no failures. Accept no Evidence for usage sharing, and give it the whole web request #68's own tests are unchanged and pass.Timing
The head's tree, 178476d5, was measured as b197ca1 in run 43337686624, job 131597923970. It used the measuring steps of run 43309004655 on one
ubuntu-22.04-armrunner, with 32 rounds, every tree once a round and the order turning, and*ShareUsageOverheadTests*only. The figures are milliseconds per call, as the log prints them, median (lowest to highest), and the last column is from the log's own comparison of each tree withmain.maininmainf69da52rc36c153drcwithout #68findchange alone, 637d5a0On 1,001 values the draft is 11.8 per cent below
mainand lower in all 32 rounds, and its range is clear ofmain's. On 1 value its median is insidemain's range and the middle halves overlap (0.0060 to 0.0063 against 0.0058 to 0.0060), but it is 5.1 per cent abovemainand lower in only 2 of 32 rounds.That 1 value test times 10,000 calls in a new JVM. So run 43342968543, job 131615467187, ran it for 100,000 calls, the one change being
int iterations = 10_000;to100_000inShareUsageOverheadTests.java, made the same way in every tree. A cost on every call would show at 100,000 calls as it does at 10,000.maininmainrcrcwithout #68At 100,000 calls the draft is 19.3 per cent below
mainand 2.9 per cent abovercwithout #68, with the middle halves overlapping and the draft lower in 13 of 32 rounds. So the 0.0003 ms the draft shows overmainat 10,000 calls is not a cost on every call, as it would then show at 100,000 calls too. Where in the 10,000 calls it is spent has not been measured.Full build
Run 43338657365, started by the full build script without macOS on
tmp/pr-matrix-120-lazyfromrcef34a03, finished with success on the head ee271a3. Configure, job 131601182237, printed "Merging in any changes from rc", "Merge made by the 'ort' strategy." and "Left out 6 macOS entries". All tenSetting 'PullRequestSha'lines read ee271a3. There is one in Configure and one in each of the nine Build and Test jobs, which are Ubuntu Java 8, 11, 17 and 21, Ubuntu ARM Java 17, and Windows Java 8, 11, 17 and 21. In each of the nine jobsShareUsageEvidenceTestsran 3 andShareUsageEvidenceReadTestsran 2, with no failures and none skipped. The full build of the first shape, run 43329495458, was cancelled when that shape was replaced.macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.
What remains
rc. With an ignore filter set, the element also copies them once for the filter, as onrc. The usage sharing overhead test adds no web request, so neither figure measures that path.rcmoved to ef34a03 (#117) after this branch was made from 36c153d. Ask the IP Intelligence benchmark only for the property it reads #117 changed onlyPerformanceBenchmark.javaand its test in the IP Intelligence console example. The full build mergedrcin, and the draft is mergeable with no conflict.