ppad-censor-0.6.0: Anytime-valid sequential constant-time testing.
Copyright(c) 2026 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellSafe-Inferred
LanguageHaskell2010

Censor.Meter

Description

Measurement abstraction.

A Meter wraps an IO action and returns a Word64 reading. wallClock is available everywhere. On Linux, withCounter provides performance-counter meters via perf_event_open: the retired-instruction and retired-branch counts are deterministic properties of the executed code, immune to the frequency, cache, and scheduling noise that limits the wall clock on fast targets, while the other counters help attribute a difference to a channel (see Counter). withSplitMeter reads the cycle counter and the wall clock over the same execution, splitting wall time into the cycles the code took and the clock period they ran at.

Synopsis

Meter type

newtype Meter Source #

A measurement device. measure m k act runs act k times back to back and returns one reading for the batch: nanoseconds for wallClock, counted events for a withCounter meter. Each meter owns its inner loop, reading the clock or arming the counter once per batch.

Constructors

Meter 

Fields

Implementations

wallClock :: Meter Source #

Wall-clock meter: elapsed nanoseconds over the batch.

On Darwin, backed by clock_gettime_nsec_np(CLOCK_UPTIME_RAW), which resolves the hardware tick (~42 ns on Apple Silicon, so sub-microsecond targets need batching; see cfgBatch). Not GHC.Clock.getMonotonicTimeNSec, which macOS quantises to microseconds, putting CDF cut points on quantisation boundaries (see issues/handled/ISSUE2.md). Elsewhere, backed by GHC.Clock.getMonotonicTimeNSec.

Hardware performance-counter meters (Linux)

data Counter Source #

A hardware (or, for TaskClock, software) event to count.

With wallClock the counters form a ladder in which each adjacent gap isolates one cause of a class difference: instructions (code path) -> cycles (microarchitectural latency) -> ref-cycles (frequency) -> task-clock (kernel and interrupt time) -> wall (scheduling gaps).

Constructors

InstructionsRetired

PERF_COUNT_HW_INSTRUCTIONS; deterministic.

BranchesRetired

PERF_COUNT_HW_BRANCH_INSTRUCTIONS; deterministic.

Cycles

PERF_COUNT_HW_CPU_CYCLES; also sensitive to data-dependent latency.

RefCycles

PERF_COUNT_HW_REF_CPU_CYCLES: cycles at the constant reference rate, so a difference here with clean Cycles is a frequency effect. Not available on every microarchitecture.

TaskClock

PERF_COUNT_SW_TASK_CLOCK: on-CPU nanoseconds, including kernel time on the task's behalf. A software counter, so available in VMs, but counting kernel time needs kernel.perf_event_paranoid at most 1.

BranchMisses

PERF_COUNT_HW_BRANCH_MISSES: catches a secret-dependent branch direction when retired branch counts balance.

CacheMisses

PERF_COUNT_HW_CACHE_MISSES: attributes a cycles-level difference to memory access patterns.

Instances

Instances details
Show Counter Source # 
Instance details

Defined in Censor.Meter

Eq Counter Source # 
Instance details

Defined in Censor.Meter

Methods

(==) :: Counter -> Counter -> Bool #

(/=) :: Counter -> Counter -> Bool #

data MeterError Source #

Why a withCounter meter could not be opened.

Constructors

Unsupported

not a Linux build; no hardware-counter support

OpenFailed !Int

perf_event_open failed with this errno (commonly EACCES/EPERM when kernel.perf_event_paranoid is too high)

withCounter :: Counter -> (Meter -> IO a) -> IO a Source #

Acquire a performance-counter Meter for the duration of the callback, then release it. Throws MeterError if the counter cannot be opened.

Counters count user space only, which unprivileged processes may open at kernel.perf_event_paranoid 2 or lower; TaskClock deliberately includes kernel time and needs 1 or lower, failing with OpenFailed 13 otherwise rather than silently degrading to a user-only clock. A counter is bound to the thread that opened it, so the callback runs on one OS thread.

Wall time split into cycles and period (Linux)

newtype SplitMeter Source #

A meter reading two quantities over one execution of the batch: measureSplit m k act returns (cycles, nanoseconds). Wall time is cycles times the clock period, and a secret can move it through either factor: through cycles when the code's execution depends on it, through the period when the core's frequency does (data-dependent power under frequency scaling). Reading both on the same execution tells the two apart.

Constructors

SplitMeter 

Fields

withSplitMeter :: (SplitMeter -> IO a) -> IO a Source #

Acquire a SplitMeter for the duration of the callback, then release it. Throws MeterError if the cycle counter cannot be opened.

The wall clock is read inside the counted region, so both readings cover the batch and the clock reads; the overhead is the same in both classes. As with withCounter, the callback runs on one OS thread.