Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Support a description field in MetricKey #458

Merged
merged 1 commit into from
Jun 21, 2023
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: 5 additions & 0 deletions .changeset/red-deers-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/io": patch
---

support a description field in MetricKey
15 changes: 9 additions & 6 deletions src/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const contramap: {
* @since 1.0.0
* @category constructors
*/
export const counter: (name: string) => Metric.Counter<number> = internal.counter
export const counter: (name: string, description?: string) => Metric.Counter<number> = internal.counter

/**
* A string histogram metric, which keeps track of the counts of different
Expand All @@ -166,7 +166,7 @@ export const counter: (name: string) => Metric.Counter<number> = internal.counte
* @since 1.0.0
* @category constructors
*/
export const frequency: (name: string) => Metric.Frequency<string> = internal.frequency
export const frequency: (name: string, description?: string) => Metric.Frequency<string> = internal.frequency

/**
* Returns a new metric that is powered by this one, but which accepts updates
Expand Down Expand Up @@ -196,7 +196,7 @@ export const fromMetricKey: <Type extends MetricKeyType.MetricKeyType<any, any>>
* @since 1.0.0
* @category constructors
*/
export const gauge: (name: string) => Metric.Gauge<number> = internal.gauge
export const gauge: (name: string, description?: string) => Metric.Gauge<number> = internal.gauge

/**
* A numeric histogram metric, which keeps track of the count of numbers that
Expand All @@ -207,7 +207,8 @@ export const gauge: (name: string) => Metric.Gauge<number> = internal.gauge
*/
export const histogram: (
name: string,
boundaries: MetricBoundaries.MetricBoundaries
boundaries: MetricBoundaries.MetricBoundaries,
description?: string
) => Metric<MetricKeyType.MetricKeyType.Histogram, number, MetricState.MetricState.Histogram> = internal.histogram

/**
Expand Down Expand Up @@ -290,7 +291,8 @@ export const summary: (
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
quantiles: Chunk.Chunk<number>,
description?: string
) => Metric.Summary<number> = internal.summary

/**
Expand All @@ -302,7 +304,8 @@ export const summaryTimestamp: (
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
quantiles: Chunk.Chunk<number>,
description?: string
) => Metric.Summary<readonly [value: number, timestamp: number]> = internal.summaryTimestamp

/**
Expand Down
24 changes: 15 additions & 9 deletions src/Metric/Key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type * as Chunk from "@effect/data/Chunk"
import type * as Duration from "@effect/data/Duration"
import type * as Equal from "@effect/data/Equal"
import type * as HashSet from "@effect/data/HashSet"
import type * as Option from "@effect/data/Option"
import * as internal from "@effect/io/internal_effect_untraced/metric/key"
import type * as MetricBoundaries from "@effect/io/Metric/Boundaries"
import type * as MetricKeyType from "@effect/io/Metric/KeyType"
Expand All @@ -25,9 +26,9 @@ export type MetricKeyTypeId = typeof MetricKeyTypeId
/**
* A `MetricKey` is a unique key associated with each metric. The key is based
* on a combination of the metric type, the name and tags associated with the
* metric, and any other information to describe a metric, such as the
* boundaries of a histogram. In this way, it is impossible to ever create
* different metrics with conflicting keys.
* metric, an optional description of the key, and any other information to
* describe a metric, such as the boundaries of a histogram. In this way, it is
* impossible to ever create different metrics with conflicting keys.
*
* @since 1.0.0
* @category models
Expand All @@ -37,6 +38,7 @@ export interface MetricKey<Type extends MetricKeyType.MetricKeyType<any, any>>
{
readonly name: string
readonly keyType: Type
readonly description: Option.Option<string>
readonly tags: HashSet.HashSet<MetricLabel.MetricLabel>
}

Expand Down Expand Up @@ -104,7 +106,7 @@ export const isMetricKey: (u: unknown) => u is MetricKey<MetricKeyType.MetricKey
* @since 1.0.0
* @category constructors
*/
export const counter: (name: string) => MetricKey.Counter = internal.counter
export const counter: (name: string, description?: string) => MetricKey.Counter = internal.counter

/**
* Creates a metric key for a categorical frequency table, with the specified
Expand All @@ -113,24 +115,27 @@ export const counter: (name: string) => MetricKey.Counter = internal.counter
* @since 1.0.0
* @category constructors
*/
export const frequency: (name: string) => MetricKey.Frequency = internal.frequency
export const frequency: (name: string, description?: string) => MetricKey.Frequency = internal.frequency

/**
* Creates a metric key for a gauge, with the specified name.
*
* @since 1.0.0
* @category constructors
*/
export const gauge: (name: string) => MetricKey.Gauge = internal.gauge
export const gauge: (name: string, description?: string) => MetricKey.Gauge = internal.gauge

/**
* Creates a metric key for a histogram, with the specified name and boundaries.
*
* @since 1.0.0
* @category constructors
*/
export const histogram: (name: string, boundaries: MetricBoundaries.MetricBoundaries) => MetricKey.Histogram =
internal.histogram
export const histogram: (
name: string,
boundaries: MetricBoundaries.MetricBoundaries,
description?: string
) => MetricKey.Histogram = internal.histogram

/**
* Creates a metric key for a summary, with the specified name, maxAge,
Expand All @@ -144,7 +149,8 @@ export const summary: (
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
quantiles: Chunk.Chunk<number>,
description?: string
) => MetricKey.Summary = internal.summary

/**
Expand Down
23 changes: 14 additions & 9 deletions src/internal_effect_untraced/metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ export const contramap = Debug.untracedDual<
))

/** @internal */
export const counter = (name: string): Metric.Metric.Counter<number> => fromMetricKey(metricKey.counter(name))
export const counter = (name: string, description?: string): Metric.Metric.Counter<number> =>
fromMetricKey(metricKey.counter(name, description))

/** @internal */
export const frequency = (name: string): Metric.Metric.Frequency<string> => fromMetricKey(metricKey.frequency(name))
export const frequency = (name: string, description?: string): Metric.Metric.Frequency<string> =>
fromMetricKey(metricKey.frequency(name, description))

/** @internal */
export const withConstantInput = Debug.untracedDual<
Expand Down Expand Up @@ -116,11 +118,12 @@ export const fromMetricKey = <Type extends MetricKeyType.MetricKeyType<any, any>
}

/** @internal */
export const gauge = (name: string): Metric.Metric.Gauge<number> => fromMetricKey(metricKey.gauge(name))
export const gauge = (name: string, description?: string): Metric.Metric.Gauge<number> =>
fromMetricKey(metricKey.gauge(name, description))

/** @internal */
export const histogram = (name: string, boundaries: MetricBoundaries.MetricBoundaries) =>
fromMetricKey(metricKey.histogram(name, boundaries))
export const histogram = (name: string, boundaries: MetricBoundaries.MetricBoundaries, description?: string) =>
fromMetricKey(metricKey.histogram(name, boundaries, description))

/* @internal */
export const increment = Debug.methodWithTrace((trace) =>
Expand Down Expand Up @@ -183,18 +186,20 @@ export const summary = (
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
): Metric.Metric.Summary<number> => withNow(summaryTimestamp(name, maxAge, maxSize, error, quantiles))
quantiles: Chunk.Chunk<number>,
description?: string
): Metric.Metric.Summary<number> => withNow(summaryTimestamp(name, maxAge, maxSize, error, quantiles, description))

/** @internal */
export const summaryTimestamp = (
name: string,
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
quantiles: Chunk.Chunk<number>,
description?: string
): Metric.Metric.Summary<readonly [value: number, timestamp: number]> =>
fromMetricKey(metricKey.summary(name, maxAge, maxSize, error, quantiles))
fromMetricKey(metricKey.summary(name, maxAge, maxSize, error, quantiles, description))

/** @internal */
export const tagged = dual<
Expand Down
50 changes: 29 additions & 21 deletions src/internal_effect_untraced/metric/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Equal from "@effect/data/Equal"
import { dual, pipe } from "@effect/data/Function"
import * as Hash from "@effect/data/Hash"
import * as HashSet from "@effect/data/HashSet"
import * as Option from "@effect/data/Option"
import * as metricKeyType from "@effect/io/internal_effect_untraced/metric/keyType"
import * as metricLabel from "@effect/io/internal_effect_untraced/metric/label"
import type * as MetricBoundaries from "@effect/io/Metric/Boundaries"
Expand All @@ -30,61 +31,68 @@ class MetricKeyImpl<Type extends MetricKeyType.MetricKeyType<any, any>> implemen
constructor(
readonly name: string,
readonly keyType: Type,
readonly description: Option.Option<string>,
readonly tags: HashSet.HashSet<MetricLabel.MetricLabel> = HashSet.empty()
) {}
[Hash.symbol](): number {
return pipe(
Hash.hash(this.name),
Hash.combine(Hash.hash(this.keyType)),
Hash.combine(Hash.hash(this.description)),
Hash.combine(Hash.hash(this.tags))
)
}
[Equal.symbol](u: unknown): boolean {
return isMetricKey(u) &&
this.name === u.name &&
Equal.equals(this.keyType, u.keyType) &&
Equal.equals(this.description, u.description) &&
Equal.equals(this.tags, u.tags)
}
}

/** @internal */
export const isMetricKey = (u: unknown): u is MetricKey.MetricKey<MetricKeyType.MetricKeyType<unknown, unknown>> => {
return typeof u === "object" && u != null && MetricKeyTypeId in u
}
export const isMetricKey = (u: unknown): u is MetricKey.MetricKey<MetricKeyType.MetricKeyType<unknown, unknown>> =>
typeof u === "object" && u != null && MetricKeyTypeId in u

/** @internal */
export const counter = (name: string): MetricKey.MetricKey.Counter => {
return new MetricKeyImpl(name, metricKeyType.counter)
}
export const counter = (name: string, description?: string): MetricKey.MetricKey.Counter =>
new MetricKeyImpl(name, metricKeyType.counter, Option.fromNullable(description))

/** @internal */
export const frequency = (name: string): MetricKey.MetricKey.Frequency => {
return new MetricKeyImpl(name, metricKeyType.frequency)
}
export const frequency = (name: string, description?: string): MetricKey.MetricKey.Frequency =>
new MetricKeyImpl(name, metricKeyType.frequency, Option.fromNullable(description))

/** @internal */
export const gauge = (name: string): MetricKey.MetricKey.Gauge => {
return new MetricKeyImpl(name, metricKeyType.gauge)
}
export const gauge = (name: string, description?: string): MetricKey.MetricKey.Gauge =>
new MetricKeyImpl(name, metricKeyType.gauge, Option.fromNullable(description))

/** @internal */
export const histogram = (
name: string,
boundaries: MetricBoundaries.MetricBoundaries
): MetricKey.MetricKey.Histogram => {
return new MetricKeyImpl(name, metricKeyType.histogram(boundaries))
}
boundaries: MetricBoundaries.MetricBoundaries,
description?: string
): MetricKey.MetricKey.Histogram =>
new MetricKeyImpl(
name,
metricKeyType.histogram(boundaries),
Option.fromNullable(description)
)

/** @internal */
export const summary = (
name: string,
maxAge: Duration.Duration,
maxSize: number,
error: number,
quantiles: Chunk.Chunk<number>
): MetricKey.MetricKey.Summary => {
return new MetricKeyImpl(name, metricKeyType.summary(maxAge, maxSize, error, quantiles))
}
quantiles: Chunk.Chunk<number>,
description?: string
): MetricKey.MetricKey.Summary =>
new MetricKeyImpl(
name,
metricKeyType.summary(maxAge, maxSize, error, quantiles),
Option.fromNullable(description)
)

/** @internal */
export const tagged = dual<
Expand Down Expand Up @@ -128,4 +136,4 @@ export const taggedWithLabelSet = dual<
>(2, (self, extraTags) =>
HashSet.size(extraTags) === 0
? self
: new MetricKeyImpl(self.name, self.keyType, pipe(self.tags, HashSet.union(extraTags))))
: new MetricKeyImpl(self.name, self.keyType, self.description, pipe(self.tags, HashSet.union(extraTags))))
42 changes: 42 additions & 0 deletions test/Metric.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import * as Chunk from "@effect/data/Chunk"
import * as Duration from "@effect/data/Duration"
import * as Equal from "@effect/data/Equal"
import { pipe } from "@effect/data/Function"
import * as HashMap from "@effect/data/HashMap"
import * as HashSet from "@effect/data/HashSet"
import * as Option from "@effect/data/Option"
import * as ReadonlyArray from "@effect/data/ReadonlyArray"
import * as Clock from "@effect/io/Clock"
import * as Effect from "@effect/io/Effect"
import * as Fiber from "@effect/io/Fiber"
import * as Metric from "@effect/io/Metric"
import * as MetricBoundaries from "@effect/io/Metric/Boundaries"
import * as MetricKey from "@effect/io/Metric/Key"
import * as MetricLabel from "@effect/io/Metric/Label"
import * as PollingMetric from "@effect/io/Metric/Polling"
import * as MetricState from "@effect/io/Metric/State"
Expand Down Expand Up @@ -537,4 +541,42 @@ describe.concurrent("Metric", () => {
assert.strictEqual(result2.value, gaugeIncrement2 * pollingCount)
}))
})

it.effect("with a description", () =>
Effect.gen(function*(_) {
const name = "counterName"
const counter1 = Metric.counter(name)
const counter2 = Metric.counter(name, "description1")
const counter3 = Metric.counter(name, "description2")

yield* _(Metric.update(counter1, 1))
yield* _(Metric.update(counter2, 1))
yield* _(Metric.update(counter3, 1))

const result1 = yield* _(Metric.value(counter1))
const result2 = yield* _(Metric.value(counter2))
const result3 = yield* _(Metric.value(counter3))

const snapshot = yield* _(Metric.snapshot())
const values = Array.from(snapshot)
const pair1 = yield* _(
ReadonlyArray.findFirst(values, (key) => Equal.equals(key.metricKey, MetricKey.counter(name)))
)
const pair2 = yield* _(
ReadonlyArray.findFirst(values, (key) => Equal.equals(key.metricKey, MetricKey.counter(name, "description1")))
)
const pair3 = yield* _(
ReadonlyArray.findFirst(values, (key) => Equal.equals(key.metricKey, MetricKey.counter(name, "description2")))
)

expect(Equal.equals(result1, MetricState.counter(1))).toBe(true)
expect(Equal.equals(result2, MetricState.counter(1))).toBe(true)
expect(Equal.equals(result3, MetricState.counter(1))).toBe(true)
expect(Equal.equals(pair1.metricState, MetricState.counter(1))).toBe(true)
expect(Option.isNone(pair1.metricKey.description)).toBe(true)
expect(Equal.equals(pair2.metricState, MetricState.counter(1))).toBe(true)
expect(Equal.equals(pair2.metricKey, MetricKey.counter(name, "description1"))).toBe(true)
expect(Equal.equals(pair3.metricState, MetricState.counter(1))).toBe(true)
expect(Equal.equals(pair3.metricKey, MetricKey.counter(name, "description2"))).toBe(true)
}))
})