> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/beyond-apex/profiling/custom-marks.md).

# Custom Performance Marks

The high-level helpers (`timeBackendCall`, `timeUserInteraction`, `trackComponentLifecycle`, `trackComponentRender`) cover the common cases. When you need to time something that doesn't fit those moulds — a chunk of client-side data shaping, an animation, a debounce window, a delay between async steps — reach for raw performance marks.

The API mirrors the browser's `performance.mark()` pattern:

```javascript
this.triton.startPerformanceMark('product-search');
// ... do work ...
this.triton.endPerformanceMark('product-search');
```

`startPerformanceMark` stores a starting timestamp keyed by mark name and component instance. `endPerformanceMark` computes the duration, emits a `TYPE.PERFORMANCE` log entry, and clears the mark.

## When to Reach for Marks

Marks are the right tool when:

* You're measuring purely client-side work that has no backend round trip — e.g. parsing a CSV, computing a derived data structure, an animation timeline.
* You want to wrap *both sides* of an async boundary that the higher-level helpers don't model — e.g. starting a timer when a debounce fires, stopping it when the next user event arrives.
* You want a single duration record without the auto-generated start/complete pair that `timeBackendCall` produces.

If your work *is* an Apex call or a user-triggered flow, the higher-level helpers are almost always a better fit — they give you start/complete records and standard error handling for free.

## Example: Wrapping a Search Flow

```javascript
async handleSearch() {
    const searchTerm = this.searchInput.value;

    this.triton.startPerformanceMark('product-search');
    try {
        const results = await this.triton.timeBackendCall(
            'searchProducts',
            () => searchProducts({ searchTerm })
        ).execute();

        this.products = results;
    } finally {
        // Always pair the start with an end — even on failure.
        this.triton.endPerformanceMark('product-search');
    }
}
```

This pattern is handy when you want a single "user-perceived search latency" record (the mark) plus the more granular backend call timing inside it. The mark captures the whole stretch — including the time between the backend response and the UI being ready — while the backend call captures just the round trip.

{% hint style="info" %}
You can have multiple marks active at once. Names are independent: `startPerformanceMark('search')` and `startPerformanceMark('hydrate')` coexist happily and end independently.
{% endhint %}

## Per-Instance Isolation

Marks are stored per component instance, just like lifecycle and render counts. Two instances of the same component can each have an active `product-search` mark and neither will interfere with the other.

```javascript
export default class ProductSearch extends LightningElement {
    triton;

    connectedCallback() {
        this.triton = new Triton().bindToComponent('ProductSearch');
        this.triton.trackComponentLifecycle('connected');
    }

    async search() {
        // Each instance's mark is isolated.
        this.triton.startPerformanceMark('search-operation');

        const results = await this.triton.timeBackendCall(
            'searchProducts',
            () => searchProducts({ term: this.searchTerm })
        ).execute();

        this.triton.endPerformanceMark('search-operation');
        this.products = results;
    }
}
```

## What Gets Logged

Each `endPerformanceMark` call emits one log entry:

| Field      | Value                                       |
| ---------- | ------------------------------------------- |
| `type`     | `TYPE.PERFORMANCE`                          |
| `summary`  | `Performance: <componentName> - <markName>` |
| `details`  | `Duration: <ms>ms`                          |
| `duration` | the measured milliseconds                   |
| `action`   | the mark name                               |

No log is produced on `startPerformanceMark` — marks are silent until they're closed. If you call `endPerformanceMark` for a name that was never started, Triton emits a console warning and skips the log.

## Locker Service Compatibility

Mark timing uses the same `getCurrentTime()` helper described in [Component Lifecycle](/beyond-apex/profiling/component-lifecycle.md#locker-service-safe-timing): `performance.now()` when available, `Date.now()` otherwise. You get sub-millisecond precision where allowed, and you never need a fallback path in your code.

## Best Practices

* **Always pair `startPerformanceMark` with `endPerformanceMark`.** Wrap the end in `finally` so an exception in the middle doesn't leave a dangling mark.
* **Use descriptive, hyphenated names.** They show up directly in dashboards (`action` column).
* **Bind first.** Custom marks require `bindToComponent()` — without it you'll just get a console warning and no log.
* **Don't double up.** If you're already wrapping with `timeBackendCall` or `timeUserInteraction`, you usually don't need a custom mark on top — the helpers already record duration.
