-
Notifications
You must be signed in to change notification settings - Fork 12
[PROF-14883] Publish thread context attribute keys at process start #576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -438,7 +438,8 @@ Java_com_datadoghq_profiler_OTelContext_setProcessCtx0(JNIEnv *env, | |
| jstring runtime_id, | ||
| jstring service, | ||
| jstring version, | ||
| jstring tracer_version | ||
| jstring tracer_version, | ||
| jobjectArray attribute_keys | ||
| ) { | ||
| JniString env_str(env, env_data); | ||
| JniString hostname_str(env, hostname); | ||
|
|
@@ -449,6 +450,39 @@ Java_com_datadoghq_profiler_OTelContext_setProcessCtx0(JNIEnv *env, | |
|
|
||
| const char *host_name_attrs[] = {"host.name", hostname_str.c_str(), NULL}; | ||
|
|
||
| // Build the thread context attribute_key_map published alongside the process | ||
| // context: index 0 is the reserved datadog.local_root_span_id slot, followed by | ||
| // the caller-provided keys (clipped to DD_TAGS_CAPACITY) | ||
| int count = (attribute_keys != nullptr) ? env->GetArrayLength(attribute_keys) : 0; | ||
| int n = count < (int)DD_TAGS_CAPACITY ? count : (int)DD_TAGS_CAPACITY; | ||
| if (count > n) { | ||
| LOG_WARN("setProcessContext: %d attribute keys requested but capacity is %d; extra keys will be ignored", | ||
| count, (int)DD_TAGS_CAPACITY); | ||
| } | ||
|
|
||
| const char *key_ptrs[DD_TAGS_CAPACITY + 2]; // +1 reserved slot, +1 NULL terminator | ||
| JniString *jni_keys[DD_TAGS_CAPACITY]; | ||
| int built = 0; | ||
| key_ptrs[0] = "datadog.local_root_span_id"; | ||
| for (int i = 0; i < n; i++) { | ||
| jstring jstr = (jstring)env->GetObjectArrayElement(attribute_keys, i); | ||
| if (jstr == nullptr) { | ||
| // A null key would corrupt the index mapping; abort the publish. | ||
| for (int j = 0; j < built; j++) delete jni_keys[j]; | ||
| Log::warn("setProcessContext: null attribute key at index %d; skipping publish", i); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 LOW · consistency Two different logging APIs are used for Suggestion: Use one convention throughout |
||
| return; | ||
| } | ||
| jni_keys[built] = new JniString(env, jstr); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 LOW · robustness If Suggestion: After |
||
| key_ptrs[i + 1] = jni_keys[built]->c_str(); | ||
| built++; | ||
| } | ||
| key_ptrs[n + 1] = nullptr; | ||
|
|
||
| otel_thread_ctx_config_data thread_ctx_config = { | ||
| .schema_version = "tlsdesc_v1_dev", | ||
| .attribute_key_map = key_ptrs, | ||
| }; | ||
|
|
||
| otel_process_ctx_data data = { | ||
| .deployment_environment_name = env_str.c_str(), | ||
| .service_instance_id = runtime_id_str.c_str(), | ||
|
|
@@ -459,13 +493,15 @@ Java_com_datadoghq_profiler_OTelContext_setProcessCtx0(JNIEnv *env, | |
| .telemetry_sdk_name = "dd-trace-java", | ||
| .resource_attributes = host_name_attrs, | ||
| .extra_attributes = NULL, | ||
| .thread_ctx_config = NULL // Set later by ContextApi::registerAttributeKeys() when keys are known | ||
| .thread_ctx_config = &thread_ctx_config | ||
| }; | ||
|
|
||
| otel_process_ctx_result result = otel_process_ctx_publish(&data); | ||
| if (!result.success) { | ||
| Log::warn("Failed to publish process context: %s", result.error_message); | ||
| } | ||
|
|
||
| for (int i = 0; i < built; i++) delete jni_keys[i]; | ||
| } | ||
|
|
||
| extern "C" DLLEXPORT jobject JNICALL | ||
|
|
@@ -599,35 +635,6 @@ Java_com_datadoghq_profiler_ThreadContext_registerConstant0(JNIEnv* env, jclass | |
| return encoding == 0 ? -1 : (jint)encoding; | ||
| } | ||
|
|
||
| extern "C" DLLEXPORT void JNICALL | ||
| Java_com_datadoghq_profiler_OTelContext_registerAttributeKeys0(JNIEnv* env, jclass unused, jobjectArray keys) { | ||
| int count = (keys != nullptr) ? env->GetArrayLength(keys) : 0; | ||
| int n = count < (int)DD_TAGS_CAPACITY ? count : (int)DD_TAGS_CAPACITY; | ||
| if (count > n) { | ||
| LOG_WARN("registerAttributeKeys: %d keys requested but capacity is %d; extra keys will be ignored", | ||
| count, (int)DD_TAGS_CAPACITY); | ||
| } | ||
|
|
||
| const char* key_ptrs[DD_TAGS_CAPACITY]; | ||
| JniString* jni_strings[DD_TAGS_CAPACITY]; | ||
|
|
||
| for (int i = 0; i < n; i++) { | ||
| jstring jstr = (jstring)env->GetObjectArrayElement(keys, i); | ||
| if (jstr == nullptr) { | ||
| for (int j = 0; j < i; j++) delete jni_strings[j]; | ||
| return; | ||
| } | ||
| jni_strings[i] = new JniString(env, jstr); | ||
| key_ptrs[i] = jni_strings[i]->c_str(); | ||
| } | ||
|
|
||
| // Always call registerAttributeKeys even with n==0 so the reserved | ||
| // datadog.local_root_span_id key (index 0) is published in the process context. | ||
| ContextApi::registerAttributeKeys(key_ptrs, n); | ||
|
|
||
| for (int i = 0; i < n; i++) delete jni_strings[i]; | ||
| } | ||
|
|
||
| // ---- test and debug utilities | ||
| extern "C" DLLEXPORT void JNICALL | ||
| Java_com_datadoghq_profiler_JavaProfiler_testlog(JNIEnv* env, jclass unused, jstring msg) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,6 @@ | |
| #include "asyncSampleMutex.h" | ||
| #include "mallocTracer.h" | ||
| #include "context.h" | ||
| #include "context_api.h" | ||
| #include "guards.h" | ||
| #include "common.h" | ||
| #include "counters.h" | ||
|
|
@@ -1355,9 +1354,6 @@ Error Profiler::start(Arguments &args, bool reset) { | |
| JfrMetadata::reset(); | ||
| JfrMetadata::initialize(args._context_attributes); | ||
| _num_context_attributes = args._context_attributes.size(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM · completeness Removing Suggestion: Confirm the consolidation is intentional and ensure the production caller always invokes Also identified by the specialist reviewer (lens: |
||
| // Initialize the OTel thread context so external profilers can decode | ||
| // the per-thread context, including custom attributes | ||
| ContextApi::registerAttributeKeys(args._context_attributes); | ||
| error = _jfr.start(args, reset); | ||
|
ivoanjo marked this conversation as resolved.
|
||
| if (error) { | ||
| disableEngines(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,7 +145,7 @@ public OTelContext(String libLocation, String scratchDir, Consumer<Throwable> er | |
| * Reads the currently published OpenTelemetry process context, if any. | ||
| * | ||
| * <p>This method attempts to read back the process context that was previously | ||
| * published via {@link #setProcessContext(String, String, String, String, String, String)}. This is | ||
| * published via {@link #setProcessContext(String, String, String, String, String, String, String[])}. This is | ||
| * primarily useful for debugging and testing purposes. | ||
| * | ||
| * <p><b>Platform Support:</b> Currently only supported on Linux. On other | ||
|
|
@@ -193,7 +193,8 @@ public ProcessContext readProcessContext() { | |
| * "instance-12345", // runtime-id | ||
| * "my-service", // service | ||
| * "1.0.0", // version | ||
| * "3.5.0" // tracer-version | ||
| * "3.5.0", // tracer-version | ||
| * new String[] {"http.route", "db.system"} // thread-context attribute keys | ||
| * ); | ||
| * }</pre> | ||
| * | ||
|
|
@@ -215,58 +216,30 @@ public ProcessContext readProcessContext() { | |
| * @param tracerVersion the version of the tracer as defined by OpenTelemetry | ||
| * semantic conventions (telemetry.sdk.version). Must not be null. | ||
| * Examples: "3.5.0", "4.2.0" | ||
| * * | ||
| * @param attributeKeys the thread-context attribute key names whose per-thread | ||
| * values are recorded in the OTEP thread-local record (e.g. | ||
| * "http.route", "db.system"). Published in the process context's | ||
| * thread_ctx_config as the attribute_key_map, preceded by the | ||
| * reserved datadog.local_root_span_id slot. Must not be null | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 LOW · consistency The Javadoc states Suggestion: Either relax the Javadoc to document that Also identified by the specialist reviewer. |
||
| * (may be empty); keys beyond capacity are clipped. Order must | ||
| * match the indices used with | ||
| * {@link ThreadContext#setContextAttribute(int, String)}. | ||
| * | ||
| * @see <a href="https://opentelemetry.io/docs/specs/semconv/registry/attributes/service/">OpenTelemetry Service Attributes</a> | ||
| * @see <a href="https://opentelemetry.io/docs/specs/semconv/registry/attributes/deployment/">OpenTelemetry Deployment Attributes</a> | ||
| */ | ||
| public void setProcessContext(String env, String hostname, String runtimeId, String service, String version, String tracerVersion) { | ||
| public void setProcessContext(String env, String hostname, String runtimeId, String service, String version, String tracerVersion, String[] attributeKeys) { | ||
|
ivoanjo marked this conversation as resolved.
|
||
| if (!libraryLoadResult.succeeded) { | ||
| return; | ||
| } | ||
| try { | ||
| lock.writeLock().lock(); | ||
| setProcessCtx0(env, hostname, runtimeId, service, version, tracerVersion); | ||
| setProcessCtx0(env, hostname, runtimeId, service, version, tracerVersion, attributeKeys); | ||
| } finally { | ||
| lock.writeLock().unlock(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Registers attribute key names for the thread context. | ||
| * | ||
| * <p>These keys define the attribute_key_map in the process context's | ||
| * thread_ctx_config. In OTEL mode, attribute values set via | ||
| * {@link ThreadContext#setContextAttribute(int, String)} are encoded | ||
| * with the key index corresponding to position in this array. | ||
| * | ||
| * <p>Calling this method explicitly is <b>optional</b> when the profiler | ||
| * is started with the {@code attributes=...} argument (e.g. | ||
| * {@code execute("start,attributes=http.route;db.system,...")}); the | ||
| * native start path auto-registers those keys. | ||
| * | ||
| * <p>This method reads the currently published process context and | ||
| * republishes it with thread_ctx_config attached. It must therefore be | ||
| * called <b>after</b> | ||
| * {@link #setProcessContext(String, String, String, String, String, String)}; | ||
| * if no process context has been published yet, the registration is a | ||
| * no-op for the process-level attribute_key_map (per-thread | ||
| * setContextAttribute writes still work). Conversely, calling | ||
| * setProcessContext after this method drops the previously published | ||
| * thread_ctx_config — re-register the keys after each setProcessContext | ||
| * if you need them to persist. | ||
| * | ||
| * <p>Must be called before any calls to setContextAttribute. | ||
| * | ||
| * @param keys Attribute key names (e.g. "http.route", "db.system") | ||
| */ | ||
| public void registerAttributeKeys(String... keys) { | ||
| if (!libraryLoadResult.succeeded) { | ||
| return; | ||
| } | ||
| registerAttributeKeys0(keys); | ||
| } | ||
|
|
||
| private static native void setProcessCtx0(String env, String hostname, String runtimeId, String service, String version, String tracerVersion); | ||
| private static native void setProcessCtx0(String env, String hostname, String runtimeId, String service, String version, String tracerVersion, String[] attributeKeys); | ||
| private static native ProcessContext readProcessCtx0(); | ||
| private static native void registerAttributeKeys0(String[] keys); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 LOW · robustness
[GENERALIST]A single
nullelement inattributeKeysaborts the entiresetProcessContextpublish — the deployment environment, service name, runtime ID, and all other process-context fields are dropped, not just the bad key. Only aLog::warnis emitted. The Javadoc covers the case where the array itself is null but does not document that a null element aborts the whole publish.Suggestion: Either skip
nullelements rather than aborting the entire publish (e.g. treat them as a no-op slot), or document the abort-on-null-element contract explicitly inOTelContext.setProcessContextand add a test asserting the behaviour.