Skip to content

Commit 266b36d

Browse files
committed
Updated TagMap to track size
Adding size tracking to TagMap - reduced visibility of checkIfEmpty and computeSize - isEmpty and size are no longer deprecated - updated callers of computeSize to use size instead - adding more size checking into tests - added additional test for putAll with non-fully overlapping maps for source and destination - restored checkIntegrity method in TagMap - checkIntegrity is now called by TagMapFuzzTest after each step to further verify correctness
1 parent c5aa244 commit 266b36d

File tree

4 files changed

+140
-78
lines changed

4 files changed

+140
-78
lines changed

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public static PayloadTagsProcessor create(Config config) {
7272
@Override
7373
public void processTags(
7474
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
75-
int spanMaxTags = maxTags + unsafeTags.computeSize();
75+
int spanMaxTags = maxTags + unsafeTags.size();
7676
for (Map.Entry<String, RedactionRules> tagPrefixRedactionRules :
7777
redactionRulesByTagPrefix.entrySet()) {
7878
String tagPrefix = tagPrefixRedactionRules.getKey();
@@ -260,7 +260,7 @@ public boolean keepParsing(PathCursor path) {
260260
}
261261

262262
public boolean keepCollectingTags() {
263-
if (collectedTags.computeSize() < maxTags) {
263+
if (collectedTags.size() < maxTags) {
264264
return true;
265265
}
266266
collectedTags.put(DD_PAYLOAD_TAGS_INCOMPLETE, true);

internal-api/src/main/java/datadog/trace/api/TagMap.java

Lines changed: 107 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -103,32 +103,29 @@ public static final TagMap fromMapImmutable(Map<String, ?> map) {
103103
}
104104

105105
private final Object[] buckets;
106+
private int size;
106107
private boolean frozen;
107108

108109
public TagMap() {
109110
// needs to be a power of 2 for bucket masking calculation to work as intended
110-
this.buckets = new Object[1 << 5];
111+
this.buckets = new Object[1 << 4];
112+
this.size = 0;
111113
this.frozen = false;
112114
}
113115

114116
/** Used for inexpensive immutable */
115117
private TagMap(Object[] buckets) {
116118
this.buckets = buckets;
119+
this.size = 0;
117120
this.frozen = true;
118121
}
119122

120-
@Deprecated
121123
@Override
122124
public final int size() {
123-
return this.computeSize();
125+
return this.size;
124126
}
125127

126-
/**
127-
* Computes the size of the TagMap
128-
*
129-
* <p>computeSize is fast but is an O(n) operation.
130-
*/
131-
public final int computeSize() {
128+
final int computeSize() {
132129
Object[] thisBuckets = this.buckets;
133130

134131
int size = 0;
@@ -145,18 +142,12 @@ public final int computeSize() {
145142
return size;
146143
}
147144

148-
@Deprecated
149145
@Override
150-
public boolean isEmpty() {
151-
return this.checkIfEmpty();
146+
public final boolean isEmpty() {
147+
return (this.size == 0);
152148
}
153-
154-
/**
155-
* Checks if TagMap is empty
156-
*
157-
* <p>checkIfEmpty is fast but is an O(n) operation.
158-
*/
159-
public final boolean checkIfEmpty() {
149+
150+
final boolean checkIfEmpty() {
160151
Object[] thisBuckets = this.buckets;
161152

162153
for (int i = 0; i < thisBuckets.length; ++i) {
@@ -337,32 +328,39 @@ public final Entry putEntry(Entry newEntry) {
337328
if (bucket == null) {
338329
thisBuckets[bucketIndex] = newEntry;
339330

331+
this.size += 1;
340332
return null;
341333
} else if (bucket instanceof Entry) {
342334
Entry existingEntry = (Entry) bucket;
343335
if (existingEntry.matches(newEntry.tag)) {
344336
thisBuckets[bucketIndex] = newEntry;
345337

338+
// replaced existing entry - no size change
346339
return existingEntry;
347340
} else {
348341
thisBuckets[bucketIndex] =
349342
new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry);
350343

344+
this.size += 1;
351345
return null;
352346
}
353347
} else if (bucket instanceof BucketGroup) {
354348
BucketGroup lastGroup = (BucketGroup) bucket;
355349

356350
BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag);
357351
if (containingGroup != null) {
352+
// replaced existing entry - no size change
358353
return containingGroup._replace(newHash, newEntry);
359354
}
360355

361356
if (!lastGroup.insertInChain(newHash, newEntry)) {
362357
thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup);
363358
}
359+
this.size += 1;
364360
return null;
365361
}
362+
363+
// unreachable
366364
return null;
367365
}
368366

@@ -394,6 +392,18 @@ private final void putAll(Entry[] tagEntries, int size) {
394392
}
395393
}
396394

395+
public final void putAll(Map<? extends String, ? extends Object> map) {
396+
this.checkWriteAccess();
397+
398+
if (map instanceof TagMap) {
399+
this.putAll((TagMap) map);
400+
} else {
401+
for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
402+
this.put(entry.getKey(), entry.getValue());
403+
}
404+
}
405+
}
406+
397407
/**
398408
* Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another
399409
*
@@ -422,10 +432,13 @@ public final void putAll(TagMap that) {
422432

423433
if (thatBucket instanceof Entry) {
424434
thisBuckets[i] = thatBucket;
435+
this.size += 1;
425436
} else if (thatBucket instanceof BucketGroup) {
426437
BucketGroup thatGroup = (BucketGroup) thatBucket;
427438

428-
thisBuckets[i] = thatGroup.cloneChain();
439+
BucketGroup thisNewGroup = thatGroup.cloneChain();
440+
thisBuckets[i] = thisNewGroup;
441+
this.size += thisNewGroup.sizeInChain();
429442
}
430443
} else if (thisBucket instanceof Entry) {
431444
// This bucket is a single entry, medium complexity case
@@ -442,28 +455,39 @@ public final void putAll(TagMap that) {
442455

443456
if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) {
444457
thisBuckets[i] = thatEntry;
458+
// replacing entry, no size change
445459
} else {
446460
thisBuckets[i] =
447461
new BucketGroup(
448462
thisHash, thisEntry,
449463
thatHash, thatEntry);
464+
this.size += 1;
450465
}
451466
} else if (thatBucket instanceof BucketGroup) {
452467
BucketGroup thatGroup = (BucketGroup) thatBucket;
453468

454469
// Clone the other group, then place this entry into that group
455470
BucketGroup thisNewGroup = thatGroup.cloneChain();
471+
int thisNewGroupSize = thisNewGroup.sizeInChain();
456472

457473
Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag());
458474
if (incomingEntry != null) {
459-
// there's already an entry w/ the same tag from the incoming TagMap - done
475+
// there's already an entry w/ the same tag from the incoming TagMap
476+
// incoming entry clobbers the existing try, so we're done
460477
thisBuckets[i] = thisNewGroup;
478+
this.size +=
479+
thisNewGroupSize
480+
- 1; // overlapping group - subtract one for clobbered existing entry
461481
} else if (thisNewGroup.insertInChain(thisHash, thisEntry)) {
462482
// able to add thisEntry into the existing groups
463483
thisBuckets[i] = thisNewGroup;
484+
this.size +=
485+
thisNewGroupSize; // non overlapping group - existing entry already accounted for
464486
} else {
465487
// unable to add into the existing groups
466488
thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup);
489+
this.size +=
490+
thisNewGroupSize; // non overlapping group - existing entry already accounted for
467491
}
468492
}
469493
} else if (thisBucket instanceof BucketGroup) {
@@ -475,27 +499,27 @@ public final void putAll(TagMap that) {
475499
Entry thatEntry = (Entry) thatBucket;
476500
int thatHash = thatEntry.hash();
477501

478-
if (!thisGroup.replaceOrInsertInChain(thatHash, thatEntry)) {
502+
if (thisGroup.replaceInChain(thatHash, thatEntry) != null) {
503+
// replaced existing entry no size change
504+
} else if (thisGroup.insertInChain(thatHash, thatEntry)) {
505+
this.size += 1;
506+
} else {
479507
thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup);
508+
this.size += 1;
480509
}
481510
} else if (thatBucket instanceof BucketGroup) {
482511
// Most complicated case - need to walk that bucket group chain and update this chain
483512
BucketGroup thatGroup = (BucketGroup) thatBucket;
484513

485-
thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup);
486-
}
487-
}
488-
}
489-
}
514+
// Taking the easy / expensive way out for updating size
515+
int thisPrevGroupSize = thisGroup.sizeInChain();
490516

491-
public final void putAll(Map<? extends String, ? extends Object> map) {
492-
this.checkWriteAccess();
517+
BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup);
518+
int thisNewGroupSize = thisNewGroup.sizeInChain();
493519

494-
if (map instanceof TagMap) {
495-
this.putAll((TagMap) map);
496-
} else {
497-
for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
498-
this.put(entry.getKey(), entry.getValue());
520+
thisBuckets[i] = thisNewGroup;
521+
this.size += (thisNewGroupSize - thisPrevGroupSize);
522+
}
499523
}
500524
}
501525
}
@@ -563,6 +587,8 @@ public final Entry removeEntry(String tag) {
563587
Entry existingEntry = (Entry) bucket;
564588
if (existingEntry.matches(tag)) {
565589
thisBuckets[bucketIndex] = null;
590+
591+
this.size -= 1;
566592
return existingEntry;
567593
} else {
568594
return null;
@@ -580,6 +606,7 @@ public final Entry removeEntry(String tag) {
580606
this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup);
581607
}
582608

609+
this.size -= 1;
583610
return existingEntry;
584611
}
585612
return null;
@@ -697,6 +724,7 @@ public final void clear() {
697724
this.checkWriteAccess();
698725

699726
Arrays.fill(this.buckets, null);
727+
this.size = 0;
700728
}
701729

702730
/** Freeze the TagMap preventing further modification - returns <code>this</code> TagMap */
@@ -716,39 +744,50 @@ public final void checkWriteAccess() {
716744
if (this.frozen) throw new IllegalStateException("TagMap frozen");
717745
}
718746

719-
// final void check() {
720-
// Object[] thisBuckets = this.buckets;
721-
//
722-
// for ( int i = 0; i < thisBuckets.length; ++i ) {
723-
// Object thisBucket = thisBuckets[i];
724-
//
725-
// if ( thisBucket instanceof Entry ) {
726-
// Entry thisEntry = (Entry)thisBucket;
727-
// int thisHash = thisEntry.hash();
728-
//
729-
// int expectedBucket = thisHash & (thisBuckets.length - 1);
730-
// assert expectedBucket == i;
731-
// } else if ( thisBucket instanceof BucketGroup ) {
732-
// BucketGroup thisGroup = (BucketGroup)thisBucket;
733-
//
734-
// for ( BucketGroup curGroup = thisGroup;
735-
// curGroup != null;
736-
// curGroup = curGroup.prev )
737-
// {
738-
// for ( int j = 0; j < BucketGroup.LEN; ++j ) {
739-
// Entry thisEntry = curGroup._entryAt(i);
740-
// if ( thisEntry == null ) continue;
741-
//
742-
// int thisHash = thisEntry.hash();
743-
// assert curGroup._hashAt(i) == thisHash;
744-
//
745-
// int expectedBucket = thisHash & (thisBuckets.length - 1);
746-
// assert expectedBucket == i;
747-
// }
748-
// }
749-
// }
750-
// }
751-
// }
747+
final void checkIntegrity() {
748+
Object[] thisBuckets = this.buckets;
749+
750+
for ( int i = 0; i < thisBuckets.length; ++i ) {
751+
Object thisBucket = thisBuckets[i];
752+
753+
if ( thisBucket instanceof Entry ) {
754+
Entry thisEntry = (Entry)thisBucket;
755+
int thisHash = thisEntry.hash();
756+
757+
int expectedBucket = thisHash & (thisBuckets.length - 1);
758+
if ( expectedBucket != i ) {
759+
throw new IllegalStateException("incorrect bucket");
760+
}
761+
} else if ( thisBucket instanceof BucketGroup ) {
762+
BucketGroup thisGroup = (BucketGroup)thisBucket;
763+
764+
for ( BucketGroup curGroup = thisGroup;
765+
curGroup != null;
766+
curGroup = curGroup.prev )
767+
{
768+
for ( int j = 0; j < BucketGroup.LEN; ++j ) {
769+
Entry thisEntry = curGroup._entryAt(i);
770+
if ( thisEntry == null ) continue;
771+
772+
int thisHash = thisEntry.hash();
773+
assert curGroup._hashAt(i) == thisHash;
774+
775+
int expectedBucket = thisHash & (thisBuckets.length - 1);
776+
if ( expectedBucket != i ) {
777+
throw new IllegalStateException("incorrect bucket");
778+
}
779+
}
780+
}
781+
}
782+
}
783+
784+
if ( this.size != this.computeSize() ) {
785+
throw new IllegalStateException("incorrect size");
786+
}
787+
if ( this.isEmpty() != this.checkIfEmpty() ) {
788+
throw new IllegalStateException("incorrect empty status");
789+
}
790+
}
752791

753792
@Override
754793
public final String toString() {
@@ -1882,10 +1921,6 @@ BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) {
18821921
return thisNewestHeadGroup;
18831922
}
18841923

1885-
boolean replaceOrInsertInChain(int hash, Entry entry) {
1886-
return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry);
1887-
}
1888-
18891924
Entry replaceInChain(int hash, Entry entry) {
18901925
for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) {
18911926
Entry prevEntry = curGroup._replace(hash, entry);
@@ -2097,7 +2132,7 @@ public String toString() {
20972132
StringBuilder builder = new StringBuilder(32);
20982133
builder.append('[');
20992134
for (int i = 0; i < BucketGroup.LEN; ++i) {
2100-
if (builder.length() != 0) builder.append(", ");
2135+
if (i != 0) builder.append(", ");
21012136

21022137
builder.append(this._entryAt(i));
21032138
}

internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,8 @@ static final void assertMapEquals(Map<String, Object> expected, TagMap actual) {
996996
Object expectedValue = expected.get(actualEntry.tag());
997997
assertEquals(expectedValue, actualEntry.objectValue());
998998
}
999+
1000+
actual.checkIntegrity();
9991001
}
10001002

10011003
static final String randomKey() {

0 commit comments

Comments
 (0)