Skip to content

Conversation

mjameswh
Copy link
Contributor

@mjameswh mjameswh commented May 6, 2025

What was changed

Example usage:

From a workflow

import * as wf from `@temporalio/workflow`;

async function myWorkflow() {
  const myHistogramMetric = wf.metricMeter.createHistogram(
    'my-custom-histogram',
    'int', // or 'float'
    'ms', // Units (optional)
    'description' // (optional)
  );

  myHistogramMetric.record(50);
  myHistogramMetric.record(50, { tag1: 'tag-value' });
}

From an activity

import * as act from `@temporalio/activity`

const myCounterMetric = metricMeter.createCounter('activity-counter');

async function myActivity() {
  const myCounterMetric.add(1);
}

@mjameswh mjameswh changed the title feat(worker): Custom Metrics [DRAFT] feat(worker): Custom Metrics May 6, 2025
@mjameswh mjameswh force-pushed the emit-metrics-from-lang branch from 33af2ea to 4eba51d Compare May 16, 2025 21:01
@mjameswh mjameswh force-pushed the emit-metrics-from-lang branch from 4eba51d to 8271248 Compare May 16, 2025 21:07
@mjameswh mjameswh marked this pull request as ready for review May 16, 2025 21:07
@mjameswh mjameswh requested a review from a team as a code owner May 16, 2025 21:07
@mjameswh mjameswh changed the title [DRAFT] feat(worker): Custom Metrics feat(worker): Custom Metrics May 16, 2025
Copy link
Member

@Sushisource Sushisource left a comment

Choose a reason for hiding this comment

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

Looks really clean & thorough. I didn't do an incredibly close analysis, but overall it looks great and the tests have excellent coverage.

*
* @param tags Tags to append to existing tags.
*/
withTags(tags: MetricTags): MetricCounter;
Copy link
Member

Choose a reason for hiding this comment

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

Why not put withTags on Metric?

Copy link
Contributor Author

@mjameswh mjameswh May 18, 2025

Choose a reason for hiding this comment

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

What would be the return type of that function if defined on the parent class? Unfortunately, TS doesn't have anything like the Self type.

Choose a reason for hiding this comment

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

@mjameswh I noticed this while browsing through previous PRs to understand what work has been done / yet to be completed. (I'm trying to understand if we can update the SDK metrics to emit native histograms instead of classic ones with explicit buckets).

Couldn't help suggest though, if you want to add withTags to the base interface but ensure it returns the correct subtype, you could possibly use the "curiously recursive template pattern" (CRTP) pattern:

interface Metric<T extends Metric<T>> {
  // omitted 
  withTags(tags: MetricTags): T;
}

interface MetricCounter extends Metric<MetricCounter> {
  // omitted
  add(value: number, extraTags?: MetricTags): void;
}


const counter = {} as MetricCounter;
const counterWithTags = counter.withTags({"custom.label": "xyz"});
counterWithTags.add(5)  // type: MetricCounter

I was always quite fond of that in C++. Not sure if this is recommended in TS, or how generally extensible it is 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Curiously Recursive Template Pattern… That's a funny name 😄. I remember using that concept in several designs I did years ago, mostly in Java, but never heard the name of that pattern. Thanks for the reference!

Now, about this specific case… Yes, that would certainly work here. In fact, I had briefly consider that option following Sushisource's comment. But I had dismissed it because I don't think there sufficient win in this case to justify introducing a new idiom to the code base. I mean, that would simply save redefinition of the withTags methods exactly three times, on MetricCounter, MetricHistogram and MetricGauge, which isn't much, and I really can't think of any case where one would be likely to call withTags on a non-identified subtype of Metric.

But happy to revisit, now or at a later time, if anyone can think of an argument in favor of stronger generalization of these types. There should be nothing preventing retrofitting the existing types to that pattern, if ever justified.

@mjameswh mjameswh merged commit 1bcb339 into main May 21, 2025
25 of 27 checks passed
@mjameswh mjameswh deleted the emit-metrics-from-lang branch May 21, 2025 15:15
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.

[Feature Request] Add support for custom metrics
3 participants