diff --git a/internal-api/src/main/java/datadog/trace/api/KnownTagCodec.java b/internal-api/src/main/java/datadog/trace/api/KnownTagCodec.java index 7ed9dff633a..e85294170e7 100644 --- a/internal-api/src/main/java/datadog/trace/api/KnownTagCodec.java +++ b/internal-api/src/main/java/datadog/trace/api/KnownTagCodec.java @@ -22,23 +22,27 @@ public static boolean isActive() { } /* - * tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-32 fieldPos] - * [31-0 nameHash]. Bit 63 (the sign bit) marks a tag the tag interceptor must see, so the check - * is a single {@code tagId < 0}. globalSerial is globally unique per known tag; fieldPos is its - * slot in the global positional layout (TagMap.knownEntries index); nameHash is - * TagMap.Entry#_hash(name) and is layout-independent. Unknown (string-only) tags have the upper - * 32 bits zero. NOTE: TagMap.Entry decodes nameHash inline as (int) tagId on its hot path, so the - * low-32 encoding here must stay in sync with that. + * tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-42 group-decl (6 bits)] + * [41-32 field-decl (10 bits)] [31-0 reserved, zero]. Bit 63 (the sign bit) marks a tag the tag + * interceptor must see, so the check is a single {@code tagId < 0}. globalSerial is globally + * unique per known tag. The middle 16 bits carry the tag's DECLARATION coordinate: group-decl is + * the declaration group (the trace-level tier / each span type / each mixin is a group), + * field-decl is the tag's ordinal within that group (restarts per group). Together they drive the dense + * store's two-tier presence fast path (per-map group mask, then field bloom) — see {@link + * TagMap}. The low 32 bits are unused for known ids (the whole id is fully determined by + * serial + coordinate, so the generator can emit a literal); they are free for a wider + * group-decl/field-decl split later. Unknown (string-only) custom tags are NOT known ids — they + * key off {@code TagMap.Entry#_hash(name)} in their own bucket path and never enter here. */ public static int globalSerial(long tagId) { return (int) ((tagId >>> 48) & 0x7FFF); } /** - * Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved/"virtual" - * tags AND intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and - * also stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single - * sign test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*, + * Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved tags AND + * intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and also + * stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single sign + * test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*, * base.service, …) leave it clear and take the fast store path. Must agree with the interceptor's * name-based {@code needsIntercept} for every assigned id. */ @@ -54,25 +58,54 @@ public static long intercepted(long tagId) { return tagId | INTERCEPTED; } - public static int fieldPos(long tagId) { - return (int) ((tagId >>> 32) & 0xFFFF); + // The middle 16 bits [47-32] hold the declaration coordinate, split so the two-tier presence + // fast path can read each half cheaply: group-decl (high 6 bits, [47-42]) selects the tag's + // declaration group (<=64 groups -> one long group mask, where a clear bit proves the whole + // group absent and enables a bulk fast insert); field-decl (low 10 bits, [41-32]) is the tag's + // ordinal within that group (<=1024 tags/group), which the field bloom hashes (field-decl & 63) + // once a group is present. See TagMap's dense-store fast path. + static final int GROUP_DECL_SHIFT = 42; + static final int GROUP_DECL_MASK = 0x3F; // 6 bits + static final int FIELD_DECL_SHIFT = 32; + static final int FIELD_DECL_MASK = 0x3FF; // 10 bits + + /** + * The tag's declaration group: the trace-level tier, a span type, or a mixin. Assigned by the tag + * registry; drives the two-tier presence fast path's group mask. + */ + public static int groupDecl(long tagId) { + return (int) ((tagId >>> GROUP_DECL_SHIFT) & GROUP_DECL_MASK); } - public static int nameHash(long tagId) { - return (int) tagId; + /** The tag's ordinal within its declaration group. */ + public static int fieldDecl(long tagId) { + return (int) ((tagId >>> FIELD_DECL_SHIFT) & FIELD_DECL_MASK); } /** - * globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is reserved for "virtual" tags that - * are specially handled (redirected to span fields or processed by the tag interceptor) and are - * NOT stored in the TagMap — these are hand-assigned in tracer core. {@code [FIRST_STORED_SERIAL, - * ..]} is for generated convention tags that ARE stored (slotted/bucketed). {@code globalSerial - * == 0} means unknown / string-only. Both core and the code generator must agree on this - * boundary. + * globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is the RESERVED tier and {@code + * [FIRST_STORED_SERIAL, ..]} is the STORED tier; {@code globalSerial == 0} means unknown / + * string-only. Both core and the code generator must agree on this boundary. + * + *

Reserved is the shared mechanism: the tracer reserves the key and handles it itself + * instead of putting it in the TagMap. It says nothing about whether a value exists — that splits + * into two kinds (the {@code kind:} in the overlay): + * + *

+ * + * "virtual" over-claims non-existence (wrong for structural) and "built-in" over-claims existence + * (wrong for directive), so the tier is named for the mechanism they share: reserved. These are + * hand-assigned in the overlay. Stored tags are the generated convention tags that ARE put + * in the map (slotted/bucketed). */ public static final int FIRST_STORED_SERIAL = 256; - /** True if the tagId names a reserved "virtual"/specially-handled tag (not stored in the map). */ + /** True if the tagId names a reserved (structural/directive) tag — handled, not stored. */ public static boolean isReserved(long tagId) { int globalSerial = globalSerial(tagId); return globalSerial > 0 && globalSerial < FIRST_STORED_SERIAL; @@ -84,48 +117,53 @@ public static boolean isStored(long tagId) { } /** - * Sentinel {@code fieldPos} meaning "no positional slot". It is the maximum value the 16-bit - * fieldPos field can hold, so it always compares {@code >= slotCount()} and routes to the hash + * Sentinel {@code field-decl} meaning "no positional slot". It is the maximum value the 10-bit + * field-decl field can hold, so it always compares {@code >= slotCount()} and routes to the hash * buckets rather than the fast positional array. Two kinds of tagId use it: * * */ - public static final int NO_SLOT = 0xFFFF; + public static final int NO_SLOT = FIELD_DECL_MASK; // field-decl all-ones sentinel /** * True if the tagId names a stored tag that deliberately has no positional slot (bucket-only). */ public static boolean isUnslotted(long tagId) { - return isStored(tagId) && fieldPos(tagId) == NO_SLOT; + return isStored(tagId) && fieldDecl(tagId) == NO_SLOT; } /** - * Builds a tagId from its parts: {@code globalSerial} (globally unique per known tag), {@code - * fieldPos} (the tag's slot within its span type's positional table), and the tag {@code name} - * (whose hash is computed via the same function the runtime uses, so the low 32 bits match {@link - * TagMap.Entry#hash()}). Inverse of {@link #globalSerial}/{@link #fieldPos}/{@link #nameHash}. + * Builds a tagId from all three parts: {@code globalSerial} (globally unique per known tag), + * {@code groupDecl} (its declaration group), and {@code fieldDecl} (its ordinal within that + * group). The low 32 bits are zero, so the id is fully determined by these parts — the generator + * emits it as a literal. Inverse of {@link #globalSerial}/{@link #groupDecl}/{@link #fieldDecl}. * Intended for the code generator and tests. */ - public static long tagId(int globalSerial, int fieldPos, String name) { - long nameHash = TagMap.Entry._hash(name) & 0xFFFFFFFFL; - return ((long) globalSerial << 48) | ((long) (fieldPos & 0xFFFF) << 32) | nameHash; + public static long tagId(int globalSerial, int groupDecl, int fieldDecl) { + return ((long) globalSerial << 48) + | ((long) (groupDecl & GROUP_DECL_MASK) << GROUP_DECL_SHIFT) + | ((long) (fieldDecl & FIELD_DECL_MASK) << FIELD_DECL_SHIFT); + } + + /** Builds a tagId in group 0 — shorthand for {@link #tagId(int, int, int)} with group-decl 0. */ + public static long tagId(int globalSerial, int fieldDecl) { + return tagId(globalSerial, 0, fieldDecl); } /** - * Builds a tagId with no positional slot ({@code fieldPos == }{@link #NO_SLOT}). Use for reserved - * "virtual" tags and for "low-priority" stored tags that get a stable id but are intentionally + * Builds a tagId with no positional slot ({@code field-decl == }{@link #NO_SLOT}). Use for + * reserved tags and for "low-priority" stored tags that get a stable id but are intentionally * kept out of the fast slot array (they route to the hash buckets). See {@link #NO_SLOT}. */ - public static long tagId(int globalSerial, String name) { - return tagId(globalSerial, NO_SLOT, name); + public static long tagId(int globalSerial) { + return tagId(globalSerial, NO_SLOT); } // Number of positional slots in the global layout = (max stored fieldPos) + 1, declared by the diff --git a/internal-api/src/main/java/datadog/trace/api/KnownTags.java b/internal-api/src/main/java/datadog/trace/api/KnownTags.java index 391b000b844..c1e6ccd4a49 100644 --- a/internal-api/src/main/java/datadog/trace/api/KnownTags.java +++ b/internal-api/src/main/java/datadog/trace/api/KnownTags.java @@ -1,225 +1,336 @@ package datadog.trace.api; -import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.util.StringIndex; -/** - * Hand-assigned tag-id constants for well-known tags, plus the {@link KnownTagCodec.Resolver} that - * resolves them. This is the single registry shared by the tracer core and by instrumentation - * (decorators) — it lives in {@code internal-api} so both layers can reference the ids; the - * eventual code generator will replace the hand assignment here. - * - *

Reserved serials {@code [1, KnownTagCodec.FIRST_STORED_SERIAL)} name "virtual" tags handled by - * the tag interceptor / span fields and are NOT stored in the {@code TagMap}; their {@code - * fieldPos} is the {@link KnownTagCodec#NO_SLOT} sentinel that is out of slot range, so any - * incidental store routes to the hash buckets rather than a positional slot. Serials {@code >= - * FIRST_STORED_SERIAL} name stored tags that slot/bucket normally (or, with {@code NO_SLOT}, are - * stored bucket-only). - * - *

The resolver registers on class initialization, so simply referencing any constant here makes - * tag-id resolution live before the first span is built. - * - *

Slice-1 note (keyOf substrate): the {@code fieldPos} assignments below (and {@link - * #SLOT_COUNT}) describe a single universal positional layout (slots 0..25). That layout is - * currently dormant — no dense store consumes {@code fieldPos} yet — and is provisional: the - * dense-store slice replaces the universal layout with per-role / per-type sizing (see the - * over-provision finding in {@code dense-tagmap-design.md}). {@code keyOf}/{@code nameOf} depend - * only on {@code globalSerial} + name, not {@code fieldPos}, so the ids themselves are stable - * across any layout scheme. - */ +// Interim hand-maintained known-tag table with coordinate-assigned ids (globalSerial + group-decl +// + field-decl). The tag-registry code generator replaces this with a generated equivalent +// (byte-for-byte-equal ids) in a follow-up change; kept hand-written here so the two-tier presence +// bloom has a coordinate substrate to build on independently of the generator. public final class KnownTags { - // slot count = (max stored fieldPos) + 1. Stored tags use fieldPos 0..25. PROVISIONAL universal - // layout — see the slice-1 note above; the dense-store slice supersedes this with role/type - // sizing. - static final int SLOT_COUNT = 26; + static final int SLOT_COUNT = 13; + + // ---- reserved (routed to span fields or directives; not stored) ---- + static final int ERROR_SERIAL = 1; + // tagId(serial=1, intercepted=true, group=0, field=NO_SLOT) [structural -> error] "error" + public static final long ERROR = 0x800103FF00000000L; + static final int SERVICE_SERIAL = 2; + // tagId(serial=2, intercepted=true, group=0, field=NO_SLOT) [structural -> service] "service" + public static final long SERVICE = 0x800203FF00000000L; + static final int RESOURCE_NAME_SERIAL = 3; + // tagId(serial=3, intercepted=true, group=0, field=NO_SLOT) [structural -> resource] + // "resource.name" + public static final long RESOURCE_NAME = 0x800303FF00000000L; + static final int SPAN_TYPE_SERIAL = 4; + // tagId(serial=4, intercepted=true, group=0, field=NO_SLOT) [structural -> type] "span.type" + public static final long SPAN_TYPE = 0x800403FF00000000L; + static final int ORIGIN_SERIAL = 5; + // tagId(serial=5, intercepted=true, group=0, field=NO_SLOT) [structural -> origin] "origin" + public static final long ORIGIN = 0x800503FF00000000L; + static final int SAMPLING_PRIORITY_SERIAL = 6; + // tagId(serial=6, intercepted=true, group=0, field=NO_SLOT) [directive] "sampling.priority" + public static final long SAMPLING_PRIORITY = 0x800603FF00000000L; + static final int MANUAL_KEEP_SERIAL = 7; + // tagId(serial=7, intercepted=true, group=0, field=NO_SLOT) [directive] "manual.keep" + public static final long MANUAL_KEEP = 0x800703FF00000000L; + static final int MANUAL_DROP_SERIAL = 8; + // tagId(serial=8, intercepted=true, group=0, field=NO_SLOT) [directive] "manual.drop" + public static final long MANUAL_DROP = 0x800803FF00000000L; + static final int MEASURED_SERIAL = 9; + // tagId(serial=9, intercepted=true, group=0, field=NO_SLOT) [directive] "measured" + public static final long MEASURED = 0x800903FF00000000L; + static final int ANALYTICS_SAMPLE_RATE_SERIAL = 10; + // tagId(serial=10, intercepted=true, group=0, field=NO_SLOT) [directive] + // "analytics.sample_rate" + public static final long ANALYTICS_SAMPLE_RATE = 0x800A03FF00000000L; + + // ---- stored (dense field-decl, or bucketed when field=NO_SLOT) ---- + static final int DD_BASE_SERVICE_SERIAL = 256; + // tagId(serial=256, intercepted=false, group=0, field=0) + // "_dd.base_service" + public static final long DD_BASE_SERVICE = 0x0100000000000000L; + static final int VERSION_SERIAL = 257; + // tagId(serial=257, intercepted=false, group=0, field=1) "version" + public static final long VERSION = 0x0101000100000000L; + static final int ENV_SERIAL = 258; + // tagId(serial=258, intercepted=false, group=0, field=2) "env" + public static final long ENV = 0x0102000200000000L; + static final int LANGUAGE_SERIAL = 259; + // tagId(serial=259, intercepted=false, group=0, field=3) "language" + public static final long LANGUAGE = 0x0103000300000000L; + static final int RUNTIME_ID_SERIAL = 260; + // tagId(serial=260, intercepted=false, group=0, field=4) "runtime-id" + public static final long RUNTIME_ID = 0x0104000400000000L; + static final int DD_TRACER_HOST_SERIAL = 261; + // tagId(serial=261, intercepted=false, group=0, field=5) + // "_dd.tracer_host" + public static final long DD_TRACER_HOST = 0x0105000500000000L; + static final int DD_GIT_COMMIT_SHA_SERIAL = 262; + // tagId(serial=262, intercepted=false, group=0, field=6) + // "_dd.git.commit.sha" + public static final long DD_GIT_COMMIT_SHA = 0x0106000600000000L; + static final int DD_GIT_REPOSITORY_URL_SERIAL = 263; + // tagId(serial=263, intercepted=false, group=0, field=7) + // "_dd.git.repository_url" + public static final long DD_GIT_REPOSITORY_URL = 0x0107000700000000L; + static final int DD_PROFILING_ENABLED_SERIAL = 264; + // tagId(serial=264, intercepted=false, group=0, field=8) + // "_dd.profiling.enabled" + public static final long DD_PROFILING_ENABLED = 0x0108000800000000L; + static final int DD_DSM_ENABLED_SERIAL = 265; + // tagId(serial=265, intercepted=false, group=0, field=9) + // "_dd.dsm.enabled" + public static final long DD_DSM_ENABLED = 0x0109000900000000L; + static final int DD_APPSEC_ENABLED_SERIAL = 266; + // tagId(serial=266, intercepted=false, group=0, field=10) + // "_dd.appsec.enabled" + public static final long DD_APPSEC_ENABLED = 0x010A000A00000000L; + static final int DD_DJM_ENABLED_SERIAL = 267; + // tagId(serial=267, intercepted=false, group=0, field=11) + // "_dd.djm.enabled" + public static final long DD_DJM_ENABLED = 0x010B000B00000000L; + static final int DD_CIVISIBILITY_ENABLED_SERIAL = 268; + // tagId(serial=268, intercepted=false, group=0, field=12) + // "_dd.civisibility.enabled" + public static final long DD_CIVISIBILITY_ENABLED = 0x010C000C00000000L; + static final int DD_PARENT_ID_SERIAL = 269; + // tagId(serial=269, intercepted=false, group=1, field=0) "_dd.parent_id" + public static final long DD_PARENT_ID = 0x010D040000000000L; + static final int COMPONENT_SERIAL = 270; + // tagId(serial=270, intercepted=false, group=1, field=1) "component" + public static final long COMPONENT = 0x010E040100000000L; + static final int SPAN_KIND_SERIAL = 271; + // tagId(serial=271, intercepted=true, group=1, field=2) "span.kind" + public static final long SPAN_KIND = 0x810F040200000000L; + static final int DD_INTEGRATION_SERIAL = 272; + // tagId(serial=272, intercepted=false, group=1, field=3) "_dd.integration" + public static final long DD_INTEGRATION = 0x0110040300000000L; + static final int DD_SVC_SRC_SERIAL = 273; + // tagId(serial=273, intercepted=false, group=1, field=NO_SLOT) "_dd.svc_src" + public static final long DD_SVC_SRC = 0x011107FF00000000L; + static final int ERROR_TYPE_SERIAL = 274; + // tagId(serial=274, intercepted=false, group=1, field=4) "error.type" + public static final long ERROR_TYPE = 0x0112040400000000L; + static final int ERROR_MESSAGE_SERIAL = 275; + // tagId(serial=275, intercepted=false, group=1, field=5) "error.message" + public static final long ERROR_MESSAGE = 0x0113040500000000L; + static final int ERROR_STACK_SERIAL = 276; + // tagId(serial=276, intercepted=false, group=1, field=6) "error.stack" + public static final long ERROR_STACK = 0x0114040600000000L; + static final int DB_TYPE_SERIAL = 277; + // tagId(serial=277, intercepted=false, group=2, field=0) "db.type" + public static final long DB_TYPE = 0x0115080000000000L; + static final int DB_INSTANCE_SERIAL = 278; + // tagId(serial=278, intercepted=false, group=2, field=1) "db.instance" + public static final long DB_INSTANCE = 0x0116080100000000L; + static final int DB_OPERATION_SERIAL = 279; + // tagId(serial=279, intercepted=false, group=2, field=2) "db.operation" + public static final long DB_OPERATION = 0x0117080200000000L; + static final int DB_USER_SERIAL = 280; + // tagId(serial=280, intercepted=false, group=2, field=3) "db.user" + public static final long DB_USER = 0x0118080300000000L; + static final int DB_POOL_NAME_SERIAL = 281; + // tagId(serial=281, intercepted=false, group=2, field=NO_SLOT) "db.pool.name" + public static final long DB_POOL_NAME = 0x01190BFF00000000L; + static final int DB_STATEMENT_SERIAL = 282; + // tagId(serial=282, intercepted=true, group=2, field=4) "db.statement" + public static final long DB_STATEMENT = 0x811A080400000000L; + static final int HTTP_METHOD_SERIAL = 283; + // tagId(serial=283, intercepted=true, group=3, field=0) "http.method" + public static final long HTTP_METHOD = 0x811B0C0000000000L; + static final int HTTP_STATUS_CODE_SERIAL = 284; + // tagId(serial=284, intercepted=false, group=3, field=1) "http.status_code" + public static final long HTTP_STATUS_CODE = 0x011C0C0100000000L; + static final int NETWORK_PROTOCOL_VERSION_SERIAL = 285; + // tagId(serial=285, intercepted=false, group=3, field=2) + // "network.protocol.version" + public static final long NETWORK_PROTOCOL_VERSION = 0x011D0C0200000000L; + static final int HTTP_URL_SERIAL = 286; + // tagId(serial=286, intercepted=true, group=4, field=0) "http.url" + public static final long HTTP_URL = 0x811E100000000000L; + static final int HTTP_RESEND_COUNT_SERIAL = 287; + // tagId(serial=287, intercepted=false, group=4, field=1) "http.resend_count" + public static final long HTTP_RESEND_COUNT = 0x011F100100000000L; + static final int HTTP_ROUTE_SERIAL = 288; + // tagId(serial=288, intercepted=false, group=5, field=0) "http.route" + public static final long HTTP_ROUTE = 0x0120140000000000L; + static final int HTTP_HOSTNAME_SERIAL = 289; + // tagId(serial=289, intercepted=false, group=5, field=1) "http.hostname" + public static final long HTTP_HOSTNAME = 0x0121140100000000L; + static final int HTTP_USERAGENT_SERIAL = 290; + // tagId(serial=290, intercepted=false, group=5, field=2) "http.useragent" + public static final long HTTP_USERAGENT = 0x0122140200000000L; + static final int HTTP_QUERY_STRING_SERIAL = 291; + // tagId(serial=291, intercepted=false, group=5, field=3) "http.query.string" + public static final long HTTP_QUERY_STRING = 0x0123140300000000L; + static final int SERVLET_PATH_SERIAL = 292; + // tagId(serial=292, intercepted=false, group=5, field=NO_SLOT) "servlet.path" + public static final long SERVLET_PATH = 0x012417FF00000000L; + static final int SERVLET_CONTEXT_SERIAL = 293; + // tagId(serial=293, intercepted=true, group=5, field=NO_SLOT) "servlet.context" + public static final long SERVLET_CONTEXT = 0x812517FF00000000L; + static final int VIEW_NAME_SERIAL = 294; + // tagId(serial=294, intercepted=false, group=6, field=0) "view.name" + public static final long VIEW_NAME = 0x0126180000000000L; + static final int TEST_NAME_SERIAL = 295; + // tagId(serial=295, intercepted=false, group=7, field=0) "test.name" + public static final long TEST_NAME = 0x01271C0000000000L; + static final int TEST_SUITE_SERIAL = 296; + // tagId(serial=296, intercepted=false, group=7, field=1) "test.suite" + public static final long TEST_SUITE = 0x01281C0100000000L; + static final int TEST_STATUS_SERIAL = 297; + // tagId(serial=297, intercepted=false, group=7, field=2) "test.status" + public static final long TEST_STATUS = 0x01291C0200000000L; + static final int TEST_FRAMEWORK_SERIAL = 298; + // tagId(serial=298, intercepted=false, group=7, field=3) "test.framework" + public static final long TEST_FRAMEWORK = 0x012A1C0300000000L; + static final int PEER_SERVICE_SERIAL = 299; + // tagId(serial=299, intercepted=true, group=8, field=0) "peer.service" + public static final long PEER_SERVICE = 0x812B200000000000L; + static final int DD_PEER_SERVICE_SOURCE_SERIAL = 300; + // tagId(serial=300, intercepted=false, group=8, field=1) + // "_dd.peer.service.source" + public static final long DD_PEER_SERVICE_SOURCE = 0x012C200100000000L; + static final int DD_PEER_SERVICE_REMAPPED_FROM_SERIAL = 301; + // tagId(serial=301, intercepted=false, group=8, field=2) + // "_dd.peer.service.remapped_from" + public static final long DD_PEER_SERVICE_REMAPPED_FROM = 0x012D200200000000L; + static final int PEER_HOSTNAME_SERIAL = 302; + // tagId(serial=302, intercepted=false, group=8, field=3) "peer.hostname" + public static final long PEER_HOSTNAME = 0x012E200300000000L; + static final int PEER_IPV4_SERIAL = 303; + // tagId(serial=303, intercepted=false, group=8, field=NO_SLOT) "peer.ipv4" + public static final long PEER_IPV4 = 0x012F23FF00000000L; + static final int PEER_IPV6_SERIAL = 304; + // tagId(serial=304, intercepted=false, group=8, field=NO_SLOT) "peer.ipv6" + public static final long PEER_IPV6 = 0x013023FF00000000L; + static final int PEER_PORT_SERIAL = 305; + // tagId(serial=305, intercepted=false, group=8, field=NO_SLOT) "peer.port" + public static final long PEER_PORT = 0x013123FF00000000L; - // ---- reserved / virtual (tag-interceptor handled, not stored) ---- - // Reserved tags are always intercepted -> set the INTERCEPTED flag. - public static final int ERROR_SERIAL = 1; - public static final long ERROR_ID = - KnownTagCodec.intercepted(KnownTagCodec.tagId(ERROR_SERIAL, Tags.ERROR)); - - // ---- stored (slotted / bucketed) ---- - public static final int PARENT_ID_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL; - public static final long PARENT_ID = KnownTagCodec.tagId(PARENT_ID_SERIAL, 0, DDTags.PARENT_ID); - - // common (process-constant) tags added by InternalTagsAdder to ~every span - public static final int BASE_SERVICE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 1; - public static final long BASE_SERVICE_ID = - KnownTagCodec.tagId(BASE_SERVICE_SERIAL, 1, DDTags.BASE_SERVICE); - - public static final int VERSION_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 2; - public static final long VERSION_ID = KnownTagCodec.tagId(VERSION_SERIAL, 2, Tags.VERSION); - - // build-time-known constant tags merged into defaultSpanTags (see CoreTracer.withTracerTags). - // "env" is a base-mixin tag; the *_ENABLED flags are product-mixin tags. Hand-assigned for now. - public static final String ENV = "env"; - public static final int ENV_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 3; - public static final long ENV_ID = KnownTagCodec.tagId(ENV_SERIAL, 3, ENV); - - public static final int DJM_ENABLED_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 4; - public static final long DJM_ENABLED_ID = - KnownTagCodec.tagId(DJM_ENABLED_SERIAL, 4, DDTags.DJM_ENABLED); - - public static final int DSM_ENABLED_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 5; - public static final long DSM_ENABLED_ID = - KnownTagCodec.tagId(DSM_ENABLED_SERIAL, 5, DDTags.DSM_ENABLED); - - // common tags added by the tag post-processors (RemoteHostnameAdder / IntegrationAdder / - // ServiceNameSourceAdder). Not intercepted; stored. - public static final int TRACER_HOST_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 6; - public static final long TRACER_HOST_ID = - KnownTagCodec.tagId(TRACER_HOST_SERIAL, 6, DDTags.TRACER_HOST); - - public static final int INTEGRATION_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 7; - public static final long INTEGRATION_ID = - KnownTagCodec.tagId(INTEGRATION_SERIAL, 7, DDTags.DD_INTEGRATION); - - public static final int SVC_SRC_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 8; - public static final long SVC_SRC_ID = KnownTagCodec.tagId(SVC_SRC_SERIAL, 8, DDTags.DD_SVC_SRC); - - // peer.service tags, read/written by PeerServiceCalculator (post-processor; uses Map put/get that - // bypass the interceptor). peer.service is intercepted on the set-path but STORED, so it slots. - public static final int PEER_SERVICE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 9; - public static final long PEER_SERVICE_ID = - KnownTagCodec.intercepted(KnownTagCodec.tagId(PEER_SERVICE_SERIAL, 9, Tags.PEER_SERVICE)); - - public static final int PEER_SERVICE_REMAPPED_FROM_SERIAL = - KnownTagCodec.FIRST_STORED_SERIAL + 10; - public static final long PEER_SERVICE_REMAPPED_FROM_ID = - KnownTagCodec.tagId(PEER_SERVICE_REMAPPED_FROM_SERIAL, 10, DDTags.PEER_SERVICE_REMAPPED_FROM); - - // HTTP tags read by HttpEndpointPostProcessor. http.method/http.url are intercepted-but-stored - // (interceptTag side-effects then returns false → stored); http.route is not intercepted. All - // stored, so the string set-path slots them via keyOf and the id reads here find them. - public static final int HTTP_METHOD_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 11; - public static final long HTTP_METHOD_ID = - KnownTagCodec.intercepted(KnownTagCodec.tagId(HTTP_METHOD_SERIAL, 11, Tags.HTTP_METHOD)); - - public static final int HTTP_ROUTE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 12; - public static final long HTTP_ROUTE_ID = - KnownTagCodec.tagId(HTTP_ROUTE_SERIAL, 12, Tags.HTTP_ROUTE); - - public static final int HTTP_URL_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 13; - public static final long HTTP_URL_ID = - KnownTagCodec.intercepted(KnownTagCodec.tagId(HTTP_URL_SERIAL, 13, Tags.HTTP_URL)); - - // peer connection tags set by BaseDecorator.onPeerConnection on ~every client/producer span. - // Not intercepted; stored. Slotted (common across client instrumentations). - public static final int PEER_HOSTNAME_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 14; - public static final long PEER_HOSTNAME_ID = - KnownTagCodec.tagId(PEER_HOSTNAME_SERIAL, 14, Tags.PEER_HOSTNAME); - - public static final int PEER_HOST_IPV4_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 15; - public static final long PEER_HOST_IPV4_ID = - KnownTagCodec.tagId(PEER_HOST_IPV4_SERIAL, 15, Tags.PEER_HOST_IPV4); - - public static final int PEER_HOST_IPV6_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 16; - public static final long PEER_HOST_IPV6_ID = - KnownTagCodec.tagId(PEER_HOST_IPV6_SERIAL, 16, Tags.PEER_HOST_IPV6); - - public static final int PEER_PORT_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 17; - public static final long PEER_PORT_ID = KnownTagCodec.tagId(PEER_PORT_SERIAL, 17, Tags.PEER_PORT); - - // Universal decorator tags — set on ~every span (component/span.kind via Base/Server/Client - // decorators, language via ServerDecorator). span.kind is intercepted (setSpanKindOrdinal). - public static final int COMPONENT_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 18; - public static final long COMPONENT_ID = KnownTagCodec.tagId(COMPONENT_SERIAL, 18, Tags.COMPONENT); - - public static final int SPAN_KIND_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 19; - public static final long SPAN_KIND_ID = - KnownTagCodec.intercepted(KnownTagCodec.tagId(SPAN_KIND_SERIAL, 19, Tags.SPAN_KIND)); - - public static final int LANGUAGE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 20; - public static final long LANGUAGE_ID = - KnownTagCodec.tagId(LANGUAGE_SERIAL, 20, DDTags.LANGUAGE_TAG_KEY); - - // JDBC / database-client tags — set on every db span (58% of petclinic spans). Not intercepted - // (only db.statement is, and that's handled separately). - public static final int DB_TYPE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 21; - public static final long DB_TYPE_ID = KnownTagCodec.tagId(DB_TYPE_SERIAL, 21, Tags.DB_TYPE); - - public static final int DB_INSTANCE_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 22; - public static final long DB_INSTANCE_ID = - KnownTagCodec.tagId(DB_INSTANCE_SERIAL, 22, Tags.DB_INSTANCE); - - public static final int DB_USER_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 23; - public static final long DB_USER_ID = KnownTagCodec.tagId(DB_USER_SERIAL, 23, Tags.DB_USER); - - public static final int DB_OPERATION_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 24; - public static final long DB_OPERATION_ID = - KnownTagCodec.tagId(DB_OPERATION_SERIAL, 24, Tags.DB_OPERATION); - - public static final int DB_POOL_NAME_SERIAL = KnownTagCodec.FIRST_STORED_SERIAL + 25; - public static final long DB_POOL_NAME_ID = - KnownTagCodec.tagId(DB_POOL_NAME_SERIAL, 25, Tags.DB_POOL_NAME); - - // Open-addressed name -> id table backing keyOf (data, not a switch): scales flat as the known - // set grows, where a generated switch eventually falls off the inline threshold. KEYOF_NAMES and - // KEYOF_VALUES are parallel; the table places names by hash and a parallel ids[] by slot. private static final String[] KEYOF_NAMES = { - Tags.ERROR, - DDTags.PARENT_ID, - DDTags.BASE_SERVICE, - Tags.VERSION, - ENV, - DDTags.DJM_ENABLED, - DDTags.DSM_ENABLED, - DDTags.TRACER_HOST, - DDTags.DD_INTEGRATION, - DDTags.DD_SVC_SRC, - Tags.PEER_SERVICE, - DDTags.PEER_SERVICE_REMAPPED_FROM, - Tags.HTTP_METHOD, - Tags.HTTP_ROUTE, - Tags.HTTP_URL, - Tags.PEER_HOSTNAME, - Tags.PEER_HOST_IPV4, - Tags.PEER_HOST_IPV6, - Tags.PEER_PORT, - Tags.COMPONENT, - Tags.SPAN_KIND, - DDTags.LANGUAGE_TAG_KEY, - Tags.DB_TYPE, - Tags.DB_INSTANCE, - Tags.DB_USER, - Tags.DB_OPERATION, - Tags.DB_POOL_NAME, + "error", + "service", + "resource.name", + "span.type", + "origin", + "sampling.priority", + "manual.keep", + "manual.drop", + "measured", + "analytics.sample_rate", + "_dd.base_service", + "version", + "env", + "language", + "runtime-id", + "_dd.tracer_host", + "_dd.git.commit.sha", + "_dd.git.repository_url", + "_dd.profiling.enabled", + "_dd.dsm.enabled", + "_dd.appsec.enabled", + "_dd.djm.enabled", + "_dd.civisibility.enabled", + "_dd.parent_id", + "component", + "span.kind", + "_dd.integration", + "_dd.svc_src", + "error.type", + "error.message", + "error.stack", + "db.type", + "db.instance", + "db.operation", + "db.user", + "db.pool.name", + "db.statement", + "http.method", + "http.status_code", + "network.protocol.version", + "http.url", + "http.resend_count", + "http.route", + "http.hostname", + "http.useragent", + "http.query.string", + "servlet.path", + "servlet.context", + "view.name", + "test.name", + "test.suite", + "test.status", + "test.framework", + "peer.service", + "_dd.peer.service.source", + "_dd.peer.service.remapped_from", + "peer.hostname", + "peer.ipv4", + "peer.ipv6", + "peer.port", }; - private static final long[] KEYOF_VALUES = { - ERROR_ID, - PARENT_ID, - BASE_SERVICE_ID, - VERSION_ID, - ENV_ID, - DJM_ENABLED_ID, - DSM_ENABLED_ID, - TRACER_HOST_ID, - INTEGRATION_ID, - SVC_SRC_ID, - PEER_SERVICE_ID, - PEER_SERVICE_REMAPPED_FROM_ID, - HTTP_METHOD_ID, - HTTP_ROUTE_ID, - HTTP_URL_ID, - PEER_HOSTNAME_ID, - PEER_HOST_IPV4_ID, - PEER_HOST_IPV6_ID, - PEER_PORT_ID, - COMPONENT_ID, - SPAN_KIND_ID, - LANGUAGE_ID, - DB_TYPE_ID, - DB_INSTANCE_ID, - DB_USER_ID, - DB_OPERATION_ID, - DB_POOL_NAME_ID, + ERROR, + SERVICE, + RESOURCE_NAME, + SPAN_TYPE, + ORIGIN, + SAMPLING_PRIORITY, + MANUAL_KEEP, + MANUAL_DROP, + MEASURED, + ANALYTICS_SAMPLE_RATE, + DD_BASE_SERVICE, + VERSION, + ENV, + LANGUAGE, + RUNTIME_ID, + DD_TRACER_HOST, + DD_GIT_COMMIT_SHA, + DD_GIT_REPOSITORY_URL, + DD_PROFILING_ENABLED, + DD_DSM_ENABLED, + DD_APPSEC_ENABLED, + DD_DJM_ENABLED, + DD_CIVISIBILITY_ENABLED, + DD_PARENT_ID, + COMPONENT, + SPAN_KIND, + DD_INTEGRATION, + DD_SVC_SRC, + ERROR_TYPE, + ERROR_MESSAGE, + ERROR_STACK, + DB_TYPE, + DB_INSTANCE, + DB_OPERATION, + DB_USER, + DB_POOL_NAME, + DB_STATEMENT, + HTTP_METHOD, + HTTP_STATUS_CODE, + NETWORK_PROTOCOL_VERSION, + HTTP_URL, + HTTP_RESEND_COUNT, + HTTP_ROUTE, + HTTP_HOSTNAME, + HTTP_USERAGENT, + HTTP_QUERY_STRING, + SERVLET_PATH, + SERVLET_CONTEXT, + VIEW_NAME, + TEST_NAME, + TEST_SUITE, + TEST_STATUS, + TEST_FRAMEWORK, + PEER_SERVICE, + DD_PEER_SERVICE_SOURCE, + DD_PEER_SERVICE_REMAPPED_FROM, + PEER_HOSTNAME, + PEER_IPV4, + PEER_IPV6, + PEER_PORT, }; - - // Static-final raw arrays placed by StringIndex.EmbeddingSupport: the JIT folds these refs to - // constants on - // the keyOf hot path (the fastest of StringIndex's three usage modes — no instance dereference). private static final int[] KEYOF_HASHES; private static final String[] KEYOF_KEYS; private static final long[] KEYOF_IDS; @@ -242,59 +353,125 @@ public final class KnownTags { public String nameOf(long tagId) { switch (KnownTagCodec.globalSerial(tagId)) { case ERROR_SERIAL: - return Tags.ERROR; - case PARENT_ID_SERIAL: - return DDTags.PARENT_ID; - case BASE_SERVICE_SERIAL: - return DDTags.BASE_SERVICE; + return "error"; + case SERVICE_SERIAL: + return "service"; + case RESOURCE_NAME_SERIAL: + return "resource.name"; + case SPAN_TYPE_SERIAL: + return "span.type"; + case ORIGIN_SERIAL: + return "origin"; + case SAMPLING_PRIORITY_SERIAL: + return "sampling.priority"; + case MANUAL_KEEP_SERIAL: + return "manual.keep"; + case MANUAL_DROP_SERIAL: + return "manual.drop"; + case MEASURED_SERIAL: + return "measured"; + case ANALYTICS_SAMPLE_RATE_SERIAL: + return "analytics.sample_rate"; + case DD_BASE_SERVICE_SERIAL: + return "_dd.base_service"; case VERSION_SERIAL: - return Tags.VERSION; + return "version"; case ENV_SERIAL: - return ENV; - case DJM_ENABLED_SERIAL: - return DDTags.DJM_ENABLED; - case DSM_ENABLED_SERIAL: - return DDTags.DSM_ENABLED; - case TRACER_HOST_SERIAL: - return DDTags.TRACER_HOST; - case INTEGRATION_SERIAL: - return DDTags.DD_INTEGRATION; - case SVC_SRC_SERIAL: - return DDTags.DD_SVC_SRC; - case PEER_SERVICE_SERIAL: - return Tags.PEER_SERVICE; - case PEER_SERVICE_REMAPPED_FROM_SERIAL: - return DDTags.PEER_SERVICE_REMAPPED_FROM; - case HTTP_METHOD_SERIAL: - return Tags.HTTP_METHOD; - case HTTP_ROUTE_SERIAL: - return Tags.HTTP_ROUTE; - case HTTP_URL_SERIAL: - return Tags.HTTP_URL; - case PEER_HOSTNAME_SERIAL: - return Tags.PEER_HOSTNAME; - case PEER_HOST_IPV4_SERIAL: - return Tags.PEER_HOST_IPV4; - case PEER_HOST_IPV6_SERIAL: - return Tags.PEER_HOST_IPV6; - case PEER_PORT_SERIAL: - return Tags.PEER_PORT; + return "env"; + case LANGUAGE_SERIAL: + return "language"; + case RUNTIME_ID_SERIAL: + return "runtime-id"; + case DD_TRACER_HOST_SERIAL: + return "_dd.tracer_host"; + case DD_GIT_COMMIT_SHA_SERIAL: + return "_dd.git.commit.sha"; + case DD_GIT_REPOSITORY_URL_SERIAL: + return "_dd.git.repository_url"; + case DD_PROFILING_ENABLED_SERIAL: + return "_dd.profiling.enabled"; + case DD_DSM_ENABLED_SERIAL: + return "_dd.dsm.enabled"; + case DD_APPSEC_ENABLED_SERIAL: + return "_dd.appsec.enabled"; + case DD_DJM_ENABLED_SERIAL: + return "_dd.djm.enabled"; + case DD_CIVISIBILITY_ENABLED_SERIAL: + return "_dd.civisibility.enabled"; + case DD_PARENT_ID_SERIAL: + return "_dd.parent_id"; case COMPONENT_SERIAL: - return Tags.COMPONENT; + return "component"; case SPAN_KIND_SERIAL: - return Tags.SPAN_KIND; - case LANGUAGE_SERIAL: - return DDTags.LANGUAGE_TAG_KEY; + return "span.kind"; + case DD_INTEGRATION_SERIAL: + return "_dd.integration"; + case DD_SVC_SRC_SERIAL: + return "_dd.svc_src"; + case ERROR_TYPE_SERIAL: + return "error.type"; + case ERROR_MESSAGE_SERIAL: + return "error.message"; + case ERROR_STACK_SERIAL: + return "error.stack"; case DB_TYPE_SERIAL: - return Tags.DB_TYPE; + return "db.type"; case DB_INSTANCE_SERIAL: - return Tags.DB_INSTANCE; - case DB_USER_SERIAL: - return Tags.DB_USER; + return "db.instance"; case DB_OPERATION_SERIAL: - return Tags.DB_OPERATION; + return "db.operation"; + case DB_USER_SERIAL: + return "db.user"; case DB_POOL_NAME_SERIAL: - return Tags.DB_POOL_NAME; + return "db.pool.name"; + case DB_STATEMENT_SERIAL: + return "db.statement"; + case HTTP_METHOD_SERIAL: + return "http.method"; + case HTTP_STATUS_CODE_SERIAL: + return "http.status_code"; + case NETWORK_PROTOCOL_VERSION_SERIAL: + return "network.protocol.version"; + case HTTP_URL_SERIAL: + return "http.url"; + case HTTP_RESEND_COUNT_SERIAL: + return "http.resend_count"; + case HTTP_ROUTE_SERIAL: + return "http.route"; + case HTTP_HOSTNAME_SERIAL: + return "http.hostname"; + case HTTP_USERAGENT_SERIAL: + return "http.useragent"; + case HTTP_QUERY_STRING_SERIAL: + return "http.query.string"; + case SERVLET_PATH_SERIAL: + return "servlet.path"; + case SERVLET_CONTEXT_SERIAL: + return "servlet.context"; + case VIEW_NAME_SERIAL: + return "view.name"; + case TEST_NAME_SERIAL: + return "test.name"; + case TEST_SUITE_SERIAL: + return "test.suite"; + case TEST_STATUS_SERIAL: + return "test.status"; + case TEST_FRAMEWORK_SERIAL: + return "test.framework"; + case PEER_SERVICE_SERIAL: + return "peer.service"; + case DD_PEER_SERVICE_SOURCE_SERIAL: + return "_dd.peer.service.source"; + case DD_PEER_SERVICE_REMAPPED_FROM_SERIAL: + return "_dd.peer.service.remapped_from"; + case PEER_HOSTNAME_SERIAL: + return "peer.hostname"; + case PEER_IPV4_SERIAL: + return "peer.ipv4"; + case PEER_IPV6_SERIAL: + return "peer.ipv6"; + case PEER_PORT_SERIAL: + return "peer.port"; default: return null; } @@ -316,12 +493,7 @@ public long keyOf(String name) { KnownTagCodec.register(RESOLVER); } - /** - * Forces resolver registration. Merely invoking this static method runs {@code } (which - * registers {@link #RESOLVER}), so calling it once at tracer init flips the dense store live; - * idempotent. Until something references this class the registry stays dormant and {@code keyOf} - * returns 0, so tag storage is byte-identical to the bucket-only behavior. - */ + /** Forces resolver registration by triggering . Idempotent. */ public static void init() {} private KnownTags() {} diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 7c30f373881..28732f2fc00 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1052,6 +1052,37 @@ public EntryChange next() { private Object[] knownValues; private int knownCount; + /** + * Two-tier presence filter over the dense store — the fast path that lets a definitely-absent + * known tag append in O(1) instead of paying the {@link #knownIndexOf} scan (the common per-build + * insert). Each tag's id carries a declaration coordinate {@code (group-decl, field-decl)} (see + * {@link KnownTagCodec}); a tag is present ONLY IF its group-decl bit is set in {@link + * #knownGroupMask} AND its field-decl bit is set in {@link #knownFieldMask}. Either bit clear ⟹ + * definitely absent ⟹ skip the scan. + * + *

+ * + * Superset semantics: bits are set on every add and NEVER cleared on remove (a stale bit only + * costs a scan, never a wrong answer), so correctness never depends on the coordinate→bit + * collision rate — only the fast-path hit rate does. With a single declaration group (today), the + * group bit is always set once anything is stored, so this degrades gracefully to exactly the + * tier-2 field bloom. A collision-minimizing coordinate assignment (the tag registry) later only + * raises the hit rate. + */ + private long knownGroupMask; + + private long knownFieldMask; + private static final int KNOWN_INIT_CAP = 12; // generous per-type max stopgap; exact per-type sizing comes with the tag registry @@ -1392,27 +1423,64 @@ private void ensureKnownCapacity() { } } + /** + * Tier-1 presence bit for {@code tagId}: {@code 1L << group-decl} (group-decl is 6 bits ≤ 63). + */ + private static long knownGroupBit(long tagId) { + return 1L << KnownTagCodec.groupDecl(tagId); + } + + /** + * Tier-2 presence bit for {@code tagId}: {@code field-decl} folded into one shared word ({@code + * field-decl & 63}). Crude to start; a collision-minimizing coordinate assignment (the tag + * registry) later only raises the hit rate — the scan stays authoritative. + */ + private static long knownFieldBit(long tagId) { + return 1L << (KnownTagCodec.fieldDecl(tagId) & 63); + } + + /** + * Whether {@code tagId} MAY be present in the dense store (both tiers say maybe), vs DEFINITELY + * absent (either tier's bit is clear). Group bit checked first so a fresh group short-circuits + * before touching the field bloom. + */ + private boolean knownMaybePresent(long tagId) { + return (this.knownGroupMask & knownGroupBit(tagId)) != 0 + && (this.knownFieldMask & knownFieldBit(tagId)) != 0; + } + /** * Stores a known tag's value densely (no {@link Entry} alloc). Overwrites in place when present * (returning the prior value materialized as an Entry, per the {@code Map} contract — usually - * discarded by {@code set}); otherwise appends, growing x2 as needed. + * discarded by {@code set}); otherwise appends, growing x2 as needed. The two-tier presence + * filter skips the {@link #knownIndexOf} scan when the tag is definitely absent (the common + * per-build case), so an append is O(1) instead of O(n). */ private Entry putKnownValue(long tagId, Object value) { - int i = this.knownIndexOf(tagId); - if (i >= 0) { - Object prior = this.knownValues[i]; - this.knownValues[i] = value; - return materializeKnown(tagId, prior); + long groupBit = knownGroupBit(tagId); + long fieldBit = knownFieldBit(tagId); + // maybe present only if BOTH tiers say so; either bit clear ⟹ definitely absent ⟹ append + if ((this.knownGroupMask & groupBit) != 0 && (this.knownFieldMask & fieldBit) != 0) { + int i = this.knownIndexOf(tagId); + if (i >= 0) { + Object prior = this.knownValues[i]; + this.knownValues[i] = value; + return materializeKnown(tagId, prior); + } + // filter false positive (coordinate collision) -> fall through to append } this.ensureKnownCapacity(); int slot = this.knownCount++; this.knownIds[slot] = tagId; this.knownValues[slot] = value; + this.knownGroupMask |= groupBit; + this.knownFieldMask |= fieldBit; return null; } /** Raw dense value for {@code tagId}, or {@code null} when absent (no Entry, no boxing). */ private Object knownRawValue(long tagId) { + if (!this.knownMaybePresent(tagId)) return null; // definitely absent, no scan int i = this.knownIndexOf(tagId); return i < 0 ? null : this.knownValues[i]; } @@ -1421,6 +1489,7 @@ private Object knownRawValue(long tagId) { * Removes a known tag from the dense store (swap-with-last), returning the prior Entry or null. */ private Entry removeKnown(long tagId) { + if (!this.knownMaybePresent(tagId)) return null; // definitely absent int i = this.knownIndexOf(tagId); if (i < 0) return null; Object prior = this.knownValues[i]; @@ -1429,6 +1498,8 @@ private Entry removeKnown(long tagId) { this.knownValues[i] = this.knownValues[last]; this.knownIds[last] = 0L; this.knownValues[last] = null; + // knownGroupMask/knownFieldMask intentionally NOT cleared: a stale-set bit only costs a scan; + // clearing could drop a bit still shared (via collision) by a present id -> false negative. return materializeKnown(tagId, prior); } @@ -1447,7 +1518,9 @@ private static Entry materializeKnown(long tagId, Object value) { private boolean parentDenseVisible(long tagId, TagMap fromAncestor) { String tag = null; // resolved lazily, only if a nearer level carries tombstones for (TagMap nearer = this; nearer != fromAncestor; nearer = nearer.parent) { - if (nearer.knownIndexOf(tagId) >= 0) return false; // shadowed by a nearer dense entry + // shadowed by a nearer dense entry — the two-tier filter prunes the scan when definitely + // absent, so a nearer level with disjoint groups never pays a scan here (read-through win) + if (nearer.knownMaybePresent(tagId) && nearer.knownIndexOf(tagId) >= 0) return false; if (nearer.removedFromParent != null) { if (tag == null) tag = KnownTagCodec.nameOf(tagId); if (nearer.removedFromParent.contains(tag)) return false; // tombstoned by a nearer level @@ -1907,6 +1980,8 @@ private void putAllIntoEmptyMap(TagMap that) { this.knownIds = Arrays.copyOf(that.knownIds, that.knownIds.length); this.knownValues = Arrays.copyOf(that.knownValues, that.knownValues.length); this.knownCount = that.knownCount; + this.knownGroupMask = that.knownGroupMask; + this.knownFieldMask = that.knownFieldMask; } } @@ -2294,6 +2369,8 @@ public void clear() { this.knownIds = null; this.knownValues = null; this.knownCount = 0; + this.knownGroupMask = 0L; + this.knownFieldMask = 0L; } public TagMap freeze() { diff --git a/internal-api/src/test/java/datadog/trace/api/KnownTagsTest.java b/internal-api/src/test/java/datadog/trace/api/KnownTagsTest.java index 11d01bc7ec8..aaffecf8e8c 100644 --- a/internal-api/src/test/java/datadog/trace/api/KnownTagsTest.java +++ b/internal-api/src/test/java/datadog/trace/api/KnownTagsTest.java @@ -11,49 +11,51 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; /** - * Parity test for the keyOf substrate (slice 1): the {@link KnownTags} registry + the {@link - * KnownTagCodec.Resolver} it registers. Verifies name ↔ id resolution without any dense store - * — {@code keyOf}/{@code nameOf} depend only on globalSerial + name, not on the (dormant) - * positional layout. + * Parity test for the keyOf substrate: the generated {@link KnownTags} registry + the {@link + * KnownTagCodec.Resolver} it registers. Verifies name ↔ id resolution and the intercepted / + * reserved / stored partitioning. {@code keyOf}/{@code nameOf} depend only on globalSerial + name, + * not on the (dormant) positional layout, so this is independent of the group/field-decl + * coordinates the tag registry assigns. */ class KnownTagsTest { - /** (name, id) pairs — the full registry. keyOf returns the id verbatim (incl. INTERCEPTED). */ + /** (name, id) pairs across the groups — keyOf returns the id verbatim (incl. INTERCEPTED). */ static Stream knownTags() { return Stream.of( - Arguments.of(Tags.ERROR, KnownTags.ERROR_ID), - Arguments.of(DDTags.PARENT_ID, KnownTags.PARENT_ID), - Arguments.of(DDTags.BASE_SERVICE, KnownTags.BASE_SERVICE_ID), - Arguments.of(Tags.VERSION, KnownTags.VERSION_ID), - Arguments.of(KnownTags.ENV, KnownTags.ENV_ID), - Arguments.of(DDTags.DJM_ENABLED, KnownTags.DJM_ENABLED_ID), - Arguments.of(DDTags.DSM_ENABLED, KnownTags.DSM_ENABLED_ID), - Arguments.of(DDTags.TRACER_HOST, KnownTags.TRACER_HOST_ID), - Arguments.of(DDTags.DD_INTEGRATION, KnownTags.INTEGRATION_ID), - Arguments.of(DDTags.DD_SVC_SRC, KnownTags.SVC_SRC_ID), - Arguments.of(Tags.PEER_SERVICE, KnownTags.PEER_SERVICE_ID), - Arguments.of(DDTags.PEER_SERVICE_REMAPPED_FROM, KnownTags.PEER_SERVICE_REMAPPED_FROM_ID), - Arguments.of(Tags.HTTP_METHOD, KnownTags.HTTP_METHOD_ID), - Arguments.of(Tags.HTTP_ROUTE, KnownTags.HTTP_ROUTE_ID), - Arguments.of(Tags.HTTP_URL, KnownTags.HTTP_URL_ID), - Arguments.of(Tags.PEER_HOSTNAME, KnownTags.PEER_HOSTNAME_ID), - Arguments.of(Tags.PEER_HOST_IPV4, KnownTags.PEER_HOST_IPV4_ID), - Arguments.of(Tags.PEER_HOST_IPV6, KnownTags.PEER_HOST_IPV6_ID), - Arguments.of(Tags.PEER_PORT, KnownTags.PEER_PORT_ID), - Arguments.of(Tags.COMPONENT, KnownTags.COMPONENT_ID), - Arguments.of(Tags.SPAN_KIND, KnownTags.SPAN_KIND_ID), - Arguments.of(DDTags.LANGUAGE_TAG_KEY, KnownTags.LANGUAGE_ID), - Arguments.of(Tags.DB_TYPE, KnownTags.DB_TYPE_ID), - Arguments.of(Tags.DB_INSTANCE, KnownTags.DB_INSTANCE_ID), - Arguments.of(Tags.DB_USER, KnownTags.DB_USER_ID), - Arguments.of(Tags.DB_OPERATION, KnownTags.DB_OPERATION_ID), - Arguments.of(Tags.DB_POOL_NAME, KnownTags.DB_POOL_NAME_ID)); + Arguments.of(Tags.ERROR, KnownTags.ERROR), + Arguments.of(DDTags.PARENT_ID, KnownTags.DD_PARENT_ID), + Arguments.of(DDTags.BASE_SERVICE, KnownTags.DD_BASE_SERVICE), + Arguments.of(Tags.VERSION, KnownTags.VERSION), + Arguments.of("env", KnownTags.ENV), + Arguments.of(DDTags.DJM_ENABLED, KnownTags.DD_DJM_ENABLED), + Arguments.of(DDTags.DSM_ENABLED, KnownTags.DD_DSM_ENABLED), + Arguments.of(DDTags.TRACER_HOST, KnownTags.DD_TRACER_HOST), + Arguments.of(DDTags.DD_INTEGRATION, KnownTags.DD_INTEGRATION), + Arguments.of(DDTags.DD_SVC_SRC, KnownTags.DD_SVC_SRC), + Arguments.of(Tags.PEER_SERVICE, KnownTags.PEER_SERVICE), + Arguments.of(DDTags.PEER_SERVICE_REMAPPED_FROM, KnownTags.DD_PEER_SERVICE_REMAPPED_FROM), + Arguments.of(Tags.HTTP_METHOD, KnownTags.HTTP_METHOD), + Arguments.of(Tags.HTTP_ROUTE, KnownTags.HTTP_ROUTE), + Arguments.of(Tags.HTTP_URL, KnownTags.HTTP_URL), + Arguments.of(Tags.PEER_HOSTNAME, KnownTags.PEER_HOSTNAME), + Arguments.of(Tags.PEER_HOST_IPV4, KnownTags.PEER_IPV4), + Arguments.of(Tags.PEER_HOST_IPV6, KnownTags.PEER_IPV6), + Arguments.of(Tags.PEER_PORT, KnownTags.PEER_PORT), + Arguments.of(Tags.COMPONENT, KnownTags.COMPONENT), + Arguments.of(Tags.SPAN_KIND, KnownTags.SPAN_KIND), + Arguments.of(DDTags.LANGUAGE_TAG_KEY, KnownTags.LANGUAGE), + Arguments.of(Tags.DB_TYPE, KnownTags.DB_TYPE), + Arguments.of(Tags.DB_INSTANCE, KnownTags.DB_INSTANCE), + Arguments.of(Tags.DB_USER, KnownTags.DB_USER), + Arguments.of(Tags.DB_OPERATION, KnownTags.DB_OPERATION), + Arguments.of(Tags.DB_POOL_NAME, KnownTags.DB_POOL_NAME)); } /** @@ -61,17 +63,22 @@ static Stream knownTags() { */ static Stream interceptedTags() { return Stream.of( - Arguments.of(KnownTags.ERROR_ID), - Arguments.of(KnownTags.PEER_SERVICE_ID), - Arguments.of(KnownTags.HTTP_METHOD_ID), - Arguments.of(KnownTags.HTTP_URL_ID), - Arguments.of(KnownTags.SPAN_KIND_ID)); + Arguments.of(KnownTags.ERROR), + Arguments.of(KnownTags.PEER_SERVICE), + Arguments.of(KnownTags.HTTP_METHOD), + Arguments.of(KnownTags.HTTP_URL), + Arguments.of(KnownTags.SPAN_KIND)); + } + + @BeforeAll + static void registerResolver() { + // Generated ids are compile-time constants (literal), so a constant reference is inlined and + // never triggers KnownTags.. init() forces class-load -> KnownTagCodec.register. + KnownTags.init(); } @Test - void resolverIsActiveOnceReferenced() { - // referencing any constant triggers KnownTags. -> KnownTagCodec.register - assertTrue(KnownTags.ERROR_ID != 0L); + void resolverIsActiveAfterInit() { assertTrue(KnownTagCodec.isActive()); assertEquals(KnownTags.SLOT_COUNT, KnownTagCodec.slotCount()); } @@ -88,13 +95,6 @@ void nameOfResolvesIdToName(String name, long id) { assertEquals(name, KnownTagCodec.nameOf(id), "nameOf(" + name + ")"); } - @ParameterizedTest - @MethodSource("knownTags") - void nameHashMatchesEntryHash(String name, long id) { - assertEquals( - (int) TagMap.Entry._hash(name), KnownTagCodec.nameHash(id), "nameHash(" + name + ")"); - } - @ParameterizedTest @MethodSource("interceptedTags") void interceptedTagsCarryFlag(long id) { @@ -125,18 +125,18 @@ void unknownNamesResolveToZero() { @Test void unknownIdsResolveToNullName() { assertNull(KnownTagCodec.nameOf(0L)); - assertNull(KnownTagCodec.nameOf(KnownTagCodec.tagId(9999, "made.up"))); + assertNull(KnownTagCodec.nameOf(KnownTagCodec.tagId(9999))); // serial with no assigned tag } @Test void errorIsReservedTheRestAreStored() { - assertTrue(KnownTagCodec.isReserved(KnownTags.ERROR_ID), "ERROR reserved"); - assertFalse(KnownTagCodec.isStored(KnownTags.ERROR_ID), "ERROR not stored"); + assertTrue(KnownTagCodec.isReserved(KnownTags.ERROR), "ERROR reserved"); + assertFalse(KnownTagCodec.isStored(KnownTags.ERROR), "ERROR not stored"); knownTags() .forEach( a -> { long id = (Long) a.get()[1]; - if (id != KnownTags.ERROR_ID) { + if (id != KnownTags.ERROR) { assertTrue(KnownTagCodec.isStored(id), "stored: " + a.get()[0]); assertFalse(KnownTagCodec.isReserved(id), "not reserved: " + a.get()[0]); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapDenseForkedTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapDenseForkedTest.java index a82742c3acd..70e41c47798 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapDenseForkedTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapDenseForkedTest.java @@ -35,8 +35,9 @@ class TagMapDenseForkedTest { @BeforeAll static void registerResolver() { - // referencing any KnownTags constant triggers its -> KnownTagCodec.register - assertTrue(KnownTags.BASE_SERVICE_ID != 0L); + // Generated ids are compile-time constants (literal), so a constant reference is inlined and + // never triggers KnownTags.. init() forces class-load -> KnownTagCodec.register. + KnownTags.init(); assertTrue(KnownTagCodec.isActive(), "resolver must be live for the dense store to engage"); assertTrue( KnownTagCodec.isStored(KnownTagCodec.keyOf(BASE_SERVICE)), "base_service routes dense"); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapDenseFuzzForkedTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapDenseFuzzForkedTest.java index a29795c542a..72e88ccd627 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapDenseFuzzForkedTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapDenseFuzzForkedTest.java @@ -58,7 +58,7 @@ enum Regime { public long keyOf(String name) { if (name.startsWith("known-")) { int n = Integer.parseInt(name.substring("known-".length())); - return KnownTagCodec.tagId(KnownTagCodec.FIRST_STORED_SERIAL + n, name); + return KnownTagCodec.tagId(KnownTagCodec.FIRST_STORED_SERIAL + n, n); } return 0L; }