Skip to content

Commit

Permalink
feat(samplers): Add deterministic sampler (#17)
Browse files Browse the repository at this point in the history
## Short description of the changes

Adds a deterministic sampler modeled off the deterministic sampler in
honeycombs other distros.

## How to verify that this has the expected result

unit tests
  • Loading branch information
martin308 authored Oct 16, 2024
1 parent 16fdb64 commit 2afcc5c
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
60 changes: 60 additions & 0 deletions Sources/Honeycomb/HoneycombDeterministicSampler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk

class HoneycombDeterministicSampler: Sampler {
private let inner: Sampler
private let rate: [String: AttributeValue]

init(sampleRate: Int) {
var inner: Sampler

switch sampleRate {
case Int.min..<1:
inner = Samplers.alwaysOff
case 1:
inner = Samplers.alwaysOn
default:
inner = Samplers.traceIdRatio(ratio: 1.0 / Double(sampleRate))
}

self.inner = inner
self.rate = ["SampleRate": AttributeValue(sampleRate)]
}

func shouldSample(
parentContext: SpanContext?,
traceId: TraceId,
name: String,
kind: SpanKind,
attributes: [String: AttributeValue],
parentLinks: [SpanData.Link]
) -> any Decision {
var result = self.inner.shouldSample(
parentContext: parentContext,
traceId: traceId,
name: name,
kind: kind,
attributes: attributes,
parentLinks: parentLinks
)

if result.isSampled {
let attrs = result.attributes.merging(
rate,
uniquingKeysWith: { (_, new) in new }
)
result = HoneycombDecision(isSampled: result.isSampled, attributes: attrs)
}

return result
}

var description: String = "DeterministicSampler"
}

private struct HoneycombDecision: Decision {
let isSampled: Bool

let attributes: [String: AttributeValue]
}
53 changes: 53 additions & 0 deletions Tests/Honeycomb/HoneycombDeterministicSamplerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import OpenTelemetryApi
import OpenTelemetrySdk
import XCTest

@testable import Honeycomb

class HoneycombDeterministicSamplerTests: XCTestCase {
func testSampler() {
let testCases = [
(rate: 0, sampled: false),
(rate: 1, sampled: true),
(rate: 10, sampled: true),
(rate: 100, sampled: true),
]

// static trace id to ensure the inner traceIdRatio
// sampler always samples.
let traceID = TraceId.init(idHi: 10, idLo: 10)
let spanID = SpanId.random()
let parentContext = SpanContext.create(
traceId: traceID,
spanId: spanID,
traceFlags: TraceFlags.init(),
traceState: TraceState.init()
)

for (rate, sampled) in testCases {
XCTContext.runActivity(
named: "",
block: { activity in
let sampler = HoneycombDeterministicSampler(sampleRate: rate)
let result = sampler.shouldSample(
parentContext: parentContext,
traceId: traceID,
name: "test",
kind: SpanKind.client,
attributes: [:],
parentLinks: []
)
XCTAssertEqual(result.isSampled, sampled)

if sampled {
guard let r = result.attributes["SampleRate"] else {
XCTFail("sample rate attribute not found")
return
}
XCTAssertEqual(AttributeValue.int(rate), r)
}
}
)
}
}
}

0 comments on commit 2afcc5c

Please sign in to comment.