(
name: string,
options?: {
readonly description?: string | undefined
readonly attributes?: Metric.Attributes | undefined
readonly boundaries?: ReadonlyArray<number>
}
): Histogram<Duration.Duration>Creates a timer metric, based on a Histogram, which keeps track of
durations in milliseconds.
Details
The unit of time will automatically be added to the metric as a tag (i.e.
"time_unit: milliseconds").
If options.boundaries is not provided, the boundaries will be computed
using Metric.exponentialBoundaries({ start: 0.5, factor: 2, count: 35 }).
Example (Recording durations with a timer)
import { Data, Duration, Effect, Metric } from "effect"
class TimerError extends Data.TaggedError("TimerError")<{
readonly operation: string
}> {}
// Create a timer metric to track API request durations
const apiRequestTimer = Metric.timer("api_request_duration", {
description: "Duration of API requests",
attributes: { service: "user-api" }
})
// Record a measured API operation duration
const apiOperation = Effect.gen(function*() {
const duration = Duration.millis(120)
yield* Metric.update(apiRequestTimer, duration)
const state = yield* Metric.value(apiRequestTimer)
console.log({
count: state.count,
min: state.min,
max: state.max,
sum: state.sum
}) // { count: 1, min: 120, max: 120, sum: 120 }
})export const const timer: (
name: string,
options?: {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
) => Histogram<Duration.Duration>
Creates a timer metric, based on a Histogram, which keeps track of
durations in milliseconds.
Details
The unit of time will automatically be added to the metric as a tag (i.e.
"time_unit: milliseconds").
If options.boundaries is not provided, the boundaries will be computed
using Metric.exponentialBoundaries({ start: 0.5, factor: 2, count: 35 }).
Example (Recording durations with a timer)
import { Data, Duration, Effect, Metric } from "effect"
class TimerError extends Data.TaggedError("TimerError")<{
readonly operation: string
}> {}
// Create a timer metric to track API request durations
const apiRequestTimer = Metric.timer("api_request_duration", {
description: "Duration of API requests",
attributes: { service: "user-api" }
})
// Record a measured API operation duration
const apiOperation = Effect.gen(function*() {
const duration = Duration.millis(120)
yield* Metric.update(apiRequestTimer, duration)
const state = yield* Metric.value(apiRequestTimer)
console.log({
count: state.count,
min: state.min,
max: state.max,
sum: state.sum
}) // { count: 1, min: 120, max: 120, sum: 120 }
})
timer = (name: stringname: string, options: | {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
| undefined
options?: {
readonly description?: string | undefineddescription?: string | undefined
readonly attributes?: Metric.Attributes | undefinedattributes?: Metric.type Metric<in Input, out State>.Attributes = Readonly<Record<string, string>> | readonly [string, string][]Union type for metric attributes that can be provided as either an object or array of tuples.
Example (Providing attributes in different formats)
import { Data, Effect, Metric } from "effect"
class AttributesError extends Data.TaggedError("AttributesError")<{
readonly operation: string
}> {}
const program = Effect.gen(function*() {
// Different ways to specify attributes
const attributesAsObject = {
service: "api",
environment: "production",
version: "1.2.3"
}
const attributesAsArray: ReadonlyArray<[string, string]> = [
["service", "api"],
["environment", "production"],
["version", "1.2.3"]
]
// Create metrics with different attribute formats
const requestCounter1 = Metric.counter("requests", {
description: "Total requests",
attributes: attributesAsObject // Using object format
})
const requestCounter2 = Metric.counter("requests", {
description: "Total requests",
attributes: attributesAsArray // Using array format
})
// Function to normalize attributes to object format
const normalizeAttributes = (
attrs: typeof attributesAsObject | ReadonlyArray<[string, string]>
) => {
if (Array.isArray(attrs)) {
return Object.fromEntries(attrs)
}
return attrs
}
// Add runtime attributes using withAttributes
const contextualCounter = Metric.withAttributes(requestCounter1, {
method: "GET",
endpoint: "/api/users"
})
// Update metrics with different attribute combinations
yield* Metric.update(contextualCounter, 1)
// Both formats result in the same internal representation
const normalizedObject = normalizeAttributes(attributesAsObject)
const normalizedArray = normalizeAttributes(attributesAsArray)
return {
attributeFormats: {
object: normalizedObject, // { service: "api", environment: "production", version: "1.2.3" }
array: normalizedArray, // { service: "api", environment: "production", version: "1.2.3" }
areEqual:
JSON.stringify(normalizedObject) === JSON.stringify(normalizedArray) // true
}
}
})
Attributes | undefined
readonly boundaries?: readonly number[] | undefinedboundaries?: interface ReadonlyArray<T>ReadonlyArray<number>
}): interface Histogram<Input>A Histogram metric that records observations in configurable buckets to analyze value distributions.
When to use
Use when histograms are ideal for measuring request durations, response sizes, and other continuous values
where you need to understand the distribution of values rather than just aggregates.
Example (Using histogram metrics)
import { Data, Effect, Metric } from "effect"
class HistogramInterfaceError
extends Data.TaggedError("HistogramInterfaceError")<{
readonly operation: string
}>
{}
const program = Effect.gen(function*() {
// Create histograms with different boundary strategies
const responseTimeHistogram: Metric.Histogram<number> = Metric.histogram(
"http_response_time_ms",
{
description: "HTTP response time distribution in milliseconds",
boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) // 0, 50, 100, ..., 950
}
)
const fileSizeHistogram: Metric.Histogram<number> = Metric.histogram(
"file_size_bytes",
{
description: "File size distribution in bytes",
boundaries: Metric.exponentialBoundaries({
start: 1,
factor: 2,
count: 10
}) // 1, 2, 4, 8, ..., 512
}
)
// Record observations (values get placed into appropriate buckets)
yield* Metric.update(responseTimeHistogram, 125) // Goes into 100-150ms bucket
yield* Metric.update(responseTimeHistogram, 75) // Goes into 50-100ms bucket
yield* Metric.update(responseTimeHistogram, 200) // Goes into 150-200ms bucket
yield* Metric.update(responseTimeHistogram, 45) // Goes into 0-50ms bucket
yield* Metric.update(fileSizeHistogram, 3) // Goes into 2-4 bytes bucket
yield* Metric.update(fileSizeHistogram, 15) // Goes into 8-16 bytes bucket
yield* Metric.update(fileSizeHistogram, 100) // Goes into 64-128 bytes bucket
// Read histogram state
const responseTimeState: Metric.HistogramState = yield* Metric.value(
responseTimeHistogram
)
const fileSizeState: Metric.HistogramState = yield* Metric.value(
fileSizeHistogram
)
// Histogram state contains:
// - buckets: Array of [boundary, cumulativeCount] pairs
// - count: total number of observations
// - min: smallest observed value
// - max: largest observed value
// - sum: sum of all observed values
return {
responseTime: {
totalRequests: responseTimeState.count, // 4
fastestRequest: responseTimeState.min, // 45
slowestRequest: responseTimeState.max, // 200
totalTime: responseTimeState.sum, // 445
averageTime: responseTimeState.sum / responseTimeState.count // 111.25
},
fileSize: {
totalFiles: fileSizeState.count, // 3
smallestFile: fileSizeState.min, // 3
largestFile: fileSizeState.max, // 100
totalBytes: fileSizeState.sum // 118
}
}
})
Histogram<import DurationDuration.type Duration.Duration = /*unresolved*/ anyDuration> => {
const const boundaries: readonly number[]boundaries = import PredicatePredicate.function isNotUndefined<readonly number[] | undefined>(input: readonly number[] | undefined): input is readonly number[]Checks whether a value is not undefined.
When to use
Use when you need a Predicate refinement that filters out undefined
while preserving other falsy values.
Details
Returns a refinement that excludes undefined.
Example (Filtering undefined values)
import { Predicate } from "effect"
const values = [1, undefined, 2]
const defined = values.filter(Predicate.isNotUndefined)
console.log(defined)
isNotUndefined(options: | {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
| undefined
options?.boundaries?: readonly number[] | undefinedboundaries)
? options: {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
options.boundaries?: readonly number[]boundaries
: const exponentialBoundaries: (options: {
readonly start: number
readonly factor: number
readonly count: number
}) => ReadonlyArray<number>
Creates histogram bucket boundaries with exponentially increasing values.
Details
Creates boundaries that grow exponentially, useful for metrics that span
multiple orders of magnitude. Each boundary is calculated as start * factor^i.
Example (Creating exponential boundaries)
import { Data, Effect, Metric } from "effect"
class BoundaryError extends Data.TaggedError("BoundaryError")<{
readonly operation: string
}> {}
// Create exponential boundaries for request size histogram
// Buckets: 0-1KB, 1-2KB, 2-4KB, 4-8KB, 8KB+
const sizeBoundaries = Metric.exponentialBoundaries({
start: 1, // Starting at 1KB
factor: 2, // Each boundary doubles the previous
count: 5 // Creates 4 boundaries + infinity
})
console.log(sizeBoundaries) // [1, 2, 4, 8, Infinity]
// Create a histogram for tracking request payload sizes
const requestSizeHistogram = Metric.histogram("request_size_kb", {
description: "Request payload size distribution in KB",
boundaries: sizeBoundaries
})
// For very wide ranges, use larger factors
const latencyBoundaries = Metric.exponentialBoundaries({
start: 0.1, // Start at 0.1ms
factor: 10, // Each boundary is 10x larger
count: 6 // Creates ranges: 0.1ms, 1ms, 10ms, 100ms, 1000ms+
})
const program = Effect.gen(function*() {
// Record different request sizes
yield* Metric.update(requestSizeHistogram, 1.5) // Goes in 1-2KB bucket
yield* Metric.update(requestSizeHistogram, 3.2) // Goes in 2-4KB bucket
yield* Metric.update(requestSizeHistogram, 12) // Goes in 8KB+ bucket
const value = yield* Metric.value(requestSizeHistogram)
return value
})
exponentialBoundaries({ start: numberstart: 0.5, factor: numberfactor: 2, count: numbercount: 35 })
const const attributes: Readonly<
Record<string, string>
>
attributes = function mergeAttributes(
self: Metric.Attributes | undefined,
other: Metric.Attributes | undefined
): Metric.AttributeSet
mergeAttributes(options: | {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
| undefined
options?.attributes?: Metric.Attributes | undefinedattributes, { time_unit: stringtime_unit: "milliseconds" })
const const metric: HistogramMetricconst metric: {
type: 'Histogram';
#boundaries: ReadonlyArray<number>;
createHooks: () => Metric.Hooks<number, HistogramState>;
Input: Contravariant<Input>;
State: Covariant<State>;
#metadataCache: WeakMap<Metric.Attributes, Metric.Metadata<number, HistogramState>>;
#metadata: Metric.Metadata<Input, State> | undefined;
id: string;
description: string | undefined;
attributes: Metric.AttributeSet | undefined;
valueUnsafe: (context: Context.Context<never>) => HistogramState;
modifyUnsafe: (input: number, context: Context.Context<never>) => void;
updateUnsafe: (input: number, context: Context.Context<never>) => void;
hook: (context: Context.Context<never>) => Metric.Hooks<number, HistogramState>;
getOrCreate: (context: Context.Context<never>, attributes: Metric.Attributes | undefined) => Metric.Metadata<number, HistogramState>;
pipe: () => unknown;
}
metric = new constructor HistogramMetric(id: string, options: {
readonly description?: string | undefined;
readonly attributes?: Metric.Attributes | undefined;
readonly boundaries: ReadonlyArray<number>;
}): HistogramMetric
HistogramMetric(name: stringname, { ...options: | {
readonly description?: string | undefined
readonly attributes?:
| Metric.Attributes
| undefined
readonly boundaries?: ReadonlyArray<number>
}
| undefined
options, boundaries: readonly number[]boundaries, attributes?: Metric.Attributes | undefinedattributes })
return const mapInput: {
<Input, Input2 extends Input>(
f: (
input: Input2,
context: Context.Context<never>
) => Input
): <State>(
self: Metric<Input, State>
) => Metric<Input2, State>
<Input, State, Input2>(
self: Metric<Input, State>,
f: (
input: Input2,
context: Context.Context<never>
) => Input
): Metric<Input2, State>
}
mapInput(const metric: HistogramMetricconst metric: {
type: 'Histogram';
#boundaries: ReadonlyArray<number>;
createHooks: () => Metric.Hooks<number, HistogramState>;
Input: Contravariant<Input>;
State: Covariant<State>;
#metadataCache: WeakMap<Metric.Attributes, Metric.Metadata<number, HistogramState>>;
#metadata: Metric.Metadata<Input, State> | undefined;
id: string;
description: string | undefined;
attributes: Metric.AttributeSet | undefined;
valueUnsafe: (context: Context.Context<never>) => HistogramState;
modifyUnsafe: (input: number, context: Context.Context<never>) => void;
updateUnsafe: (input: number, context: Context.Context<never>) => void;
hook: (context: Context.Context<never>) => Metric.Hooks<number, HistogramState>;
getOrCreate: (context: Context.Context<never>, attributes: Metric.Attributes | undefined) => Metric.Metadata<number, HistogramState>;
pipe: () => unknown;
}
metric, import DurationDuration.toMillis)
}