Skip to content

Conversation

@fredr
Copy link
Member

@fredr fredr commented Oct 30, 2025

Adds support for creating custom metrics in typescript apps. Depends on #2109

Inspired by the implementation in the go runtime.
Metrics needs to be defined within a service, and will automatically add the service label.

This implementation is backed by a SharedArrayBuffer, where each u64 slot is a metric value, created from js by the main thread. From js we do atomic operation to update values on this buffer, and from rust we do atomic load when collecting the metrics. The rust runtime is use to coordinate what slot should be used for a label set, so that we can support multiple workers updating the same metric.

@fredr fredr requested a review from eandre October 30, 2025 12:51
@fredr fredr self-assigned this Oct 30, 2025
@encore-cla
Copy link

encore-cla bot commented Oct 30, 2025

All committers have signed the CLA.

@fredr fredr force-pushed the fredr/ts-custom-metrics branch from f68e437 to 72f6462 Compare October 30, 2025 15:37
@fredr fredr force-pushed the fredr/ts-custom-metrics branch from 72f6462 to 1efb0bd Compare October 30, 2025 15:41
const metricsBuffer = __internalInitGlobalMetricsBuffer();
const extraWorkers = runtime.RT.numWorkerThreads() - 1;
if (extraWorkers > 0) {
log.debug(`Starting ${extraWorkers} worker threads`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended? Remove?

Comment on lines +24 to +26
* export const OrdersProcessed = new CounterGroup<Labels>("orders_processed");
*
* OrdersProcessed.with({ success: true }).increment();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* export const OrdersProcessed = new CounterGroup<Labels>("orders_processed");
*
* OrdersProcessed.with({ success: true }).increment();
* export const ordersProcessed = new CounterGroup<Labels>("orders_processed");
*
* ordersProcessed.with({ success: true }).increment();

Comment on lines +11 to +13
* export const OrdersProcessed = new Counter("orders_processed");
*
* OrdersProcessed.increment();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* export const OrdersProcessed = new Counter("orders_processed");
*
* OrdersProcessed.increment();
* export const ordersProcessed = new Counter("orders_processed");
*
* ordersProcessed.increment();

* Called during encores initialization
* @internal
*/
export function __internalSetGlobalMetricsBuffer(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export this here? Could we put it in an internal/ folder?

*
* Note: Number values in labels are converted to integers using Math.floor().
*/
with(labels: L): AtomicCounter | NoOpCounter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this return type? Or could we always return a Counter so the user doesn't need to care about the difference? For other resources we've separated the underlying implementation into an impl field so we could do like:

const impl = new NoOpCounterImpl();
return new Counter(impl);

or something like that. Maybe a bit tricky to do since it would require a private constructor?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants