Skip to content
Open
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 not `static final`, not a `static final ClassValue<T>`, and not an instance field declared on a class annotated `@Singleton` (`datadog.perfcontract.Singleton`)*. 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 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
45 changes: 45 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,45 @@
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 class as having exactly one instance for the life of the process -- constructed once,
* reachable only through a single static field, DI-container registration, or other holder that
* itself guarantees there is no second instance.
*
* <p>This exists as the escape valve for {@link StaticLifetime}: a field can legitimately live on
* an instance, not as {@code static}, if that instance itself is guaranteed to be a process-wide
* singleton. Without this annotation, {@code @StaticLifetime} would have no way to accept a
* legitimate cache held on a singleton-scoped instance without also accepting one held on an
* ordinary, possibly-repeatedly-constructed instance -- which is exactly the defect shape it exists
* to catch.
*
* <p>This is a documentation-and-tooling marker; it changes no behavior.
*
* <p><b>v1 posture: trusted declaration, not independently verified.</b> This is the same stance
* the other perf-contract markers take toward {@code static final} itself -- declared, not proven.
* Verifying it for real (a single construction site, or an instance reachable only via one
* static/DI-registered path) is a call-site/construction-graph problem, out of scope for v1.
* Annotating a class that is, in fact, constructed more than once defeats every guarantee
* downstream checks (starting with {@link StaticLifetime}) build on top of this annotation -- apply
* it with the same care as any other unverified perf-contract claim.
*
* <p><b>On a class</b> ({@link ElementType#TYPE}): every instance field of this class may serve as
* the process-wide holder {@link StaticLifetime} requires, because the class itself is guaranteed
* to have at most one instance.
*
* <p><b>Checker contract.</b> This annotation has no violation condition of its own in v1 -- there
* is nothing to flag on the class itself, since the declaration is trusted rather than verified.
* Its only role in automated review is as an input fact to {@link StaticLifetime}'s checker: a
* field otherwise flagged by that check is accepted instead when its enclosing class carries this
* annotation. See {@link StaticLifetime}'s own Checker contract section for the full rule.
*/
@Documented
@PerfContract
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Singleton {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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 that must live for the whole program, not merely for the lifetime of its enclosing
* instance -- typically a cache or other expensive object whose entire value comes from being
* amortized across many uses, which a per-instance field can silently defeat if that instance
* itself doesn't live for the whole program.
*
* <p>The name deliberately echoes Rust's {@code 'static} lifetime rather than the Java keyword
* {@code static} itself: the property this enforces is "lives for the whole program." A {@link
* java.lang.ClassValue} is the motivating case for that distinction -- each per-{@code Class} value
* it computes lives for the whole program without itself being declared in a {@code static} field
* anywhere, because {@code ClassValue} does its own process-wide caching internally. The holder
* field that points at the {@code ClassValue} instance still needs to be {@code static final} --
* see the checker contract below -- it's only the individual per-{@code Class} values inside it
* that get their process-wide lifetime for free.
*
* <p>Motivating defect: a cache constructed per-request, as an instance field on a per-request
* object, instead of once as a {@code static} field -- so the cache was allocated and thrown away
* on every request and never actually amortized anything. It compiled, ran, and passed tests while
* quietly defeating the entire point of caching -- the same silent-failure shape as the other
* perf-contract markers.
*
* <p>This is a documentation-and-tooling marker; it changes no behavior. It exists to telegraph the
* constraint to readers and to give a future checker something to verify. The discipline it names
* is <b>not yet enforced</b>; hold to it by hand until the checker lands.
*
* <p><b>On a field</b> ({@link ElementType#FIELD}): the field's value must actually live for the
* whole program, not just for as long as its enclosing instance happens to.
*
* <p><b>Checker contract.</b> The rule below is written to be machine-checkable -- by a future
* static checker, or in the meantime by an AI reviewer -- without needing to read this class's
* prose above. This is a declaration-scan check: it looks at how the field is declared, not at
* whether it is, in practice, reused enough to be worth its cost, and it does not attempt to find
* unannotated per-instance caches -- it only guards fields that opt in.
*
* <ul>
* <li><b>Accepted (satisfies the contract):</b> a {@code static final} field of the cache /
* expensive-object type; a {@code static final} {@link java.lang.ClassValue}{@code <T>} used
* for a per-class one-shot computation; or a {@code final} instance field, on a class
* annotated {@link Singleton}, whose enclosing instance is thereby guaranteed to be
* process-wide. {@code final} is required in both the static and singleton-scoped-instance
* shapes: a reassignable field can be replaced with a fresh instance at any point, silently
* discarding everything amortized in the old one -- the same defeat this annotation exists to
* catch, just triggered by a write instead of by scope.
* <li><b>Trigger (violation, scope):</b> an instance field of the annotated type on a class that
* is <em>not</em> annotated {@link Singleton} -- even if, in practice, the class is only ever
* constructed once per logical "session". This check is declaration-local; it does not
* attempt to prove single construction dynamically, so a plain instance field never satisfies
* the contract on its own.
* <li><b>Trigger (violation, mutability):</b> a {@code static} field of the annotated type, or an
* instance field on a class annotated {@link Singleton}, that is not also {@code final} --
* reassignable, so nothing stops a fresh instance from silently replacing the amortized one
* at runtime, regardless of scope.
* <li><b>Violation example (scope):</b> {@code private final DDCache<K, V> cache =
* DDCaches.newFixedSizeCache(128);} as an instance field of a class constructed per-request,
* per-call, or otherwise more than once for the life of the process.
* <li><b>Violation example (mutability):</b> {@code private static DDCache<K, V> cache =
* DDCaches.newFixedSizeCache(128);} -- {@code static} but not {@code final}, so any code with
* write access can swap in a fresh, cold cache and discard everything amortized in the old
* one.
* <li><b>Compliant example (static):</b> {@code private static final DDCache<K, V> CACHE =
* DDCaches.newFixedSizeCache(128);}
* <li><b>Compliant example (singleton-scoped instance):</b> {@code @Singleton class Registry {
* private final DDCache<K, V> cache = DDCaches.newFixedSizeCache(128); }}
* <li><b>Out of scope (v1):</b> proving a cache is reused enough to be worth its allocation cost;
* catching a non-static cache that isn't annotated with this marker; a {@code final} field
* whose referent is itself internally mutable/replaceable (e.g. wraps its state in an {@code
* AtomicReference} it swaps) -- {@code final} is checked on the field, not transitively
* through the object graph. Widen this contract only once a real case proves it insufficient,
* rather than guessing ahead of one.
* </ul>
*/
@Documented
@PerfContract
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface StaticLifetime {}
Loading