Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .agents/skills/perf-review/references/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ Format: **pattern** — *expensive when (the interprocedural condition to trace)
6. **FFI / native-boundary crossing on a hot path** *(central to the shared-core effort)* — *a native crossing per-span/per-item (not batched), or transporting strings/objects rather than primitives/IDs* — flag-with-confidence (boundary cost is mechanism-determined; runtime-specific pinning → addendum) — SEV-1/2 (SEV-1 if it blocks/pins under concurrency) — fix: batch (one per flush, not per item); transport interned IDs not strings; keep crossings off the hot/concurrency path.
7. **Escape / allocation-elision defeated** *(Java/Go/.NET/V8 all have a version)* — *a refactor makes a previously-local object escape (stored, returned, captured by a closure, passed to a virtual/non-inlined call) → silent heap allocation on a hot path* — **flag-as-measure** ("may now escape and allocate; verify with an allocation profiler") — SEV-2/3 — fix: keep it local; avoid the escaping store/capture.
8. **`@NoEscape` field-storage violation** — *a field (instance or static, directly or as a generic type argument) declared with an `@NoEscape`-annotated type (`datadog.trace.api.function.NoEscape`), **or** initialized directly from a call to an `@NoEscape`-annotated method — with no comment at the declaration justifying the retention*. The type form marks a concrete type (e.g. `SubSequence`); the method form exists for a return value whose concrete type can't itself carry the annotation (an anonymous class or lambda implementing a JDK interface). The annotation's own javadoc carries a self-contained "Checker contract" section (trigger / not-a-trigger / violation example / compliant example, both forms) written so this can be checked from the diff alone, with no other context needed. The underlying rule is "should", not "must" (RFC-2119 sense): a trigger is a presumptive finding, not an automatic failure — compliant if the declaration carries either a `// Retained on purpose: <reason>`-style comment, or `@SuppressPerfContract(value = NoEscape.class, reason = "...")` (`datadog.perfcontract.SuppressPerfContract` — the generic exemption mechanism shared by every perf-contract marker, not `@NoEscape`-specific), or a named canned-exception annotation itself meta-annotated `@SuppressPerfContract` with `NoEscape.class` among its `value` (e.g. a hypothetical `@Borrowed`) — treat the meta-annotation's own `reason` as satisfying the requirement. Don't flag a `SuppressPerfContract`/canned-exception use as non-compliant just for being terse; that mirrors the annotation's own Checker contract. **flag-with-confidence** — SEV-2/3 (SEV-1 if the annotated type/method shares backing storage with something large, per its own javadoc). Current wearers: `SubSequence`, `Maybe` (type form; see J7 below for `SubSequence`'s specific retention-vs-transient discriminator). **No lint enforces this yet — the AI reviewer is the only check, so this stays here (not under deterministic-lint candidates below) until a checker lands and it can migrate down.**
9. **`@StaticLifetime` field-lifetime violation** — *a field annotated `@StaticLifetime` (`datadog.perfcontract.StaticLifetime`) that is neither `static final` nor a `final` instance field declared on a class annotated `@Singleton` (`datadog.perfcontract.Singleton`); a shared `ClassValue` holder follows the same rule*. Declaration-scan only, same shape as the `@NoEscape` field-storage check above — it does not attempt to prove a field is actually reused enough to be worth caching, and it does not catch an unannotated per-instance cache; it only guards fields that opt in. `@Singleton`, applied to a class, is a trusted, unverified declaration (v1 posture — the same stance the other perf-contract markers take toward `static final` itself) that the class has exactly one process-wide instance; declaring it satisfies `@StaticLifetime` for that class's `final` instance fields. **flag-with-confidence** — SEV-2/3, motivated by a real production defect (a per-request-constructed cache that never actually amortized anything — see the annotation's own javadoc for the full trigger/accepted-form table). Refines the "per-call cache / expensive-object creation that should be static/once" deterministic-lint candidate below by giving that pattern's opted-in subset an explicit annotation and trigger; the unannotated general case is still that line's job. **No lint enforces this yet — the AI reviewer is the only check, so this stays here (not under deterministic-lint candidates below) until a checker lands and it can migrate down.**

## Deterministic-lint candidates (DON'T spend AI budget — make these real lints)
Fixed-signature, mechanically checkable:
- per-call cache / regex / expensive-object creation that should be static/once
- per-call cache / regex / expensive-object creation that should be static/once (the `@StaticLifetime`-annotated subset is checked above; this line covers the still-unannotated general case)
- boxing in specific hot APIs
- using a string-API where an id-API exists on a hot decorator
- the existing convention rules (e.g. don't extract one-shot instrumentation methods to constants)
- *(grows as patterns prove mechanically checkable — migrate them off the AI as they stabilize; the `@NoEscape` field-storage check above belongs here once its checker lands)*
- *(grows as patterns prove mechanically checkable — migrate them off the AI as they stabilize; the `@NoEscape` and `@StaticLifetime` field-storage checks above belong here once their checkers land)*

## Java addendum (JVM-specific — mechanism authored with JIT-developer authority; **calibrate production-priority against your own escalation history**)
Refines the universal checks with JVM mechanics. Quarantined here, for the Java audience that has the substrate.
Expand Down
26 changes: 26 additions & 0 deletions internal-api/src/main/java/datadog/perfcontract/Singleton.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package datadog.perfcontract;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Declares that a class has one instance for the life of the process. All construction paths must
* preserve that guarantee; a static holder or DI registration alone does not prevent another
* instance from being created.
*
* <p>This marker changes no runtime behavior. Tools trust the declaration without checking
* construction sites. If the class is instantiated more than once, they may accept caches that are
* rebuilt with each instance.
*
* <p><b>Checker contract.</b> This annotation has no violation rule of its own. It allows {@link
* StaticLifetime}'s checker to accept {@code final} instance fields declared on the annotated
* class.
*/
Comment thread
dougqh marked this conversation as resolved.
@Documented
@PerfContract
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Singleton {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package datadog.perfcontract;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a field whose value must be retained for the life of the program to share its setup cost
* across uses. A cache rebuilt with each request repeats that cost.
*
* <p>The name echoes Rust's {@code 'static} lifetime rather than the Java keyword {@code static}:
* the property is "lives for the whole program," which a {@code final} field on a {@link Singleton}
* satisfies without being {@code static}.
*
* <p>This marker changes no runtime behavior. The declaration rules below guide AI review; no
* static checker currently enforces them.
*
* <p><b>Checker contract.</b> Inspect only fields annotated {@code @StaticLifetime}:
*
* <ul>
* <li><b>Accepted:</b> a {@code static final} field, or a {@code final} instance field declared
* on a class annotated {@link Singleton}. The singleton declaration is trusted, not verified.
* <li><b>Violation:</b> any other annotated field. This includes fields without {@code final} and
* instance fields on classes without {@code @Singleton}, even if a class is constructed only
* once per session.
* <li><b>Out of scope:</b> unannotated fields, how often a value is reused, and replacement of
* state inside the referenced object. The check does not follow references through the object
* graph.
* </ul>
*
* <p>Requiring {@code final} prevents reassignment from discarding cached state and repeating
* setup. It does not prevent the referenced object from changing its own state.
*
* <p>A shared {@link java.lang.ClassValue} satisfies the same field rules. Its values are cached
* per class; computation may be repeated under races or after {@link
* java.lang.ClassValue#remove(Class) remove}. This contract covers the shared holder, not the
* lifetime of each cached value.
*
* <p>Violation examples: an instance cache on a class without {@code @Singleton}, or a static cache
* without {@code final}.
*
* <pre>
* &#64;StaticLifetime
* private final DDCache&lt;String, String&gt; instanceCache = DDCaches.newFixedSizeCache(128);
*
* &#64;StaticLifetime
* private static DDCache&lt;String, String&gt; staticCache = DDCaches.newFixedSizeCache(128);
* </pre>
*
* <p>Compliant examples:
*
* <pre>
* &#64;StaticLifetime
* private static final DDCache&lt;String, String&gt; CACHE = DDCaches.newFixedSizeCache(128);
*
* &#64;Singleton class Registry {
* &#64;StaticLifetime
* private final DDCache&lt;String, String&gt; cache = DDCaches.newFixedSizeCache(128);
* }
* </pre>
*/
Comment thread
dougqh marked this conversation as resolved.
@Documented
@PerfContract
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface StaticLifetime {}
Loading