{-# LANGUAGE OverloadedStrings #-}

{- |
Module      :  OpenTelemetry.Metric.Core
Copyright   :  (c) Ian Duncan, 2026
License     :  BSD-3
Description :  OpenTelemetry Metrics API
Stability   :  experimental

= Overview

This module defines the Metrics API for recording measurements in your
application. Libraries use this API to record metrics; the SDK provides
the actual aggregation and export.

Application authors should import @OpenTelemetry.Metric@ from the SDK
package instead, which re-exports everything here plus SDK initialization.

= Quick example

@
import OpenTelemetry.Metric.Core

main :: IO ()
main = do
  -- Obtain the global MeterProvider (set up by the SDK)
  mp <- getGlobalMeterProvider
  meter <- getMeter mp myInstrumentationLibrary

  -- Create instruments
  requestCounter <- meterCreateCounterInt64 meter "http.requests" "" Nothing defaultAdvisoryParameters
  latencyHist    <- meterCreateHistogram meter "http.request.duration" "ms" Nothing defaultAdvisoryParameters

  -- Record measurements
  counterAdd requestCounter 1 [("method", toAttribute "GET")]
  histogramRecord latencyHist 42.5 [("method", toAttribute "GET")]
@

= Instrument types

== Synchronous instruments

These are called inline with your application code:

* 'Counter' : monotonically increasing sum (e.g. request count, bytes sent).
  Use @counterAdd counter value attrs@.
* 'UpDownCounter' : sum that can increase or decrease (e.g. active connections,
  queue depth). Use @upDownCounterAdd counter value attrs@.
* 'Histogram' : distribution of values (e.g. request latency, payload size).
  Use @histogramRecord histogram value attrs@.
* 'Gauge' : point-in-time value (e.g. CPU temperature, memory usage).
  Use @gaugeRecord gauge value attrs@.

== Asynchronous (observable) instruments

These are read by callbacks during export:

* 'ObservableCounter' : monotonic sum read on demand (e.g. CPU time).
* 'ObservableUpDownCounter' : bidirectional sum read on demand.
* 'ObservableGauge' : snapshot value read on demand (e.g. disk free space).

Register a callback when creating the instrument:

@
obsGauge <- meterCreateObservableGaugeDouble meter
  "system.memory.usage" "By" Nothing defaultAdvisoryParameters
  (\result -> do
    memInfo <- getMemoryUsage
    observeDouble result memInfo [("state", toAttribute "used")]
  )
@

= No-op behavior

Before the SDK is installed, 'noopMeterProvider' is the global default.
All measurements are silently discarded. This makes it safe for libraries
to instrument unconditionally.

= Spec reference

<https://opentelemetry.io/docs/specs/otel/metrics/api/>
-}
module OpenTelemetry.Metric.Core (
  -- * Provider
  MeterProvider (..),
  getMeter,
  noopMeterProvider,
  noopMeter,
  shutdownMeterProvider,
  forceFlushMeterProvider,
  getGlobalMeterProvider,
  setGlobalMeterProvider,

  -- * Meter
  Meter (..),

  -- * Instruments
  InstrumentKind (..),
  HistogramAggregation (..),
  AdvisoryParameters (..),
  defaultAdvisoryParameters,
  Counter (..),
  UpDownCounter (..),
  Histogram (..),
  Gauge (..),
  ObservableResult (..),
  ObservableCallbackHandle (..),
  ObservableCounter (..),
  ObservableUpDownCounter (..),
  ObservableGauge (..),

  -- * Instrument name validation (for SDK implementers)
  InstrumentName.validateInstrumentName,
  InstrumentName.validateInstrumentUnit,
) where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.IORef (IORef, atomicWriteIORef, newIORef, readIORef)
import Data.Int (Int64)
import Data.Text (Text)
import OpenTelemetry.Internal.Common.Types (FlushResult (..), InstrumentationLibrary (..), ShutdownResult (..))
import OpenTelemetry.Internal.Metric.Types
import qualified OpenTelemetry.Metric.InstrumentName as InstrumentName
import System.IO.Unsafe (unsafePerformIO)


{- | Preferred accessor for obtaining a 'Meter' (spec: Get a Meter).

@since 0.0.1.0
-}
getMeter :: MeterProvider -> InstrumentationLibrary -> IO Meter
getMeter :: MeterProvider -> InstrumentationLibrary -> IO Meter
getMeter = MeterProvider -> InstrumentationLibrary -> IO Meter
meterProviderGetMeter


-- | @since 0.0.1.0
shutdownMeterProvider
  :: MeterProvider
  -> Maybe Int
  -- ^ Optional timeout in microseconds. @Nothing@ uses the SDK default (5s).
  -> IO ShutdownResult
shutdownMeterProvider :: MeterProvider -> Maybe Int -> IO ShutdownResult
shutdownMeterProvider = MeterProvider -> Maybe Int -> IO ShutdownResult
meterProviderShutdown


-- | @since 0.0.1.0
forceFlushMeterProvider
  :: MeterProvider
  -> Maybe Int
  -- ^ Optional timeout in microseconds. @Nothing@ uses the SDK default (5s).
  -> IO FlushResult
forceFlushMeterProvider :: MeterProvider -> Maybe Int -> IO FlushResult
forceFlushMeterProvider MeterProvider
mp = MeterProvider -> Maybe Int -> IO FlushResult
meterProviderForceFlush MeterProvider
mp


noopCounterI64 :: Counter Int64
noopCounterI64 :: Counter Int64
noopCounterI64 = (Int64 -> Attributes -> IO ()) -> IO Bool -> Counter Int64
forall a. (a -> Attributes -> IO ()) -> IO Bool -> Counter a
Counter (\Int64
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopCounterDbl :: Counter Double
noopCounterDbl :: Counter Double
noopCounterDbl = (Double -> Attributes -> IO ()) -> IO Bool -> Counter Double
forall a. (a -> Attributes -> IO ()) -> IO Bool -> Counter a
Counter (\Double
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopUpDownI64 :: UpDownCounter Int64
noopUpDownI64 :: UpDownCounter Int64
noopUpDownI64 = (Int64 -> Attributes -> IO ()) -> IO Bool -> UpDownCounter Int64
forall a. (a -> Attributes -> IO ()) -> IO Bool -> UpDownCounter a
UpDownCounter (\Int64
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopUpDownDbl :: UpDownCounter Double
noopUpDownDbl :: UpDownCounter Double
noopUpDownDbl = (Double -> Attributes -> IO ()) -> IO Bool -> UpDownCounter Double
forall a. (a -> Attributes -> IO ()) -> IO Bool -> UpDownCounter a
UpDownCounter (\Double
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopHistogram :: Histogram
noopHistogram :: Histogram
noopHistogram = (Double -> Attributes -> IO ()) -> IO Bool -> Histogram
Histogram (\Double
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopGaugeI64 :: Gauge Int64
noopGaugeI64 :: Gauge Int64
noopGaugeI64 = (Int64 -> Attributes -> IO ()) -> IO Bool -> Gauge Int64
forall a. (a -> Attributes -> IO ()) -> IO Bool -> Gauge a
Gauge (\Int64
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopGaugeDbl :: Gauge Double
noopGaugeDbl :: Gauge Double
noopGaugeDbl = (Double -> Attributes -> IO ()) -> IO Bool -> Gauge Double
forall a. (a -> Attributes -> IO ()) -> IO Bool -> Gauge a
Gauge (\Double
_ Attributes
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False)


noopReg :: IO ObservableCallbackHandle
noopReg :: IO ObservableCallbackHandle
noopReg = ObservableCallbackHandle -> IO ObservableCallbackHandle
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (IO () -> ObservableCallbackHandle
ObservableCallbackHandle (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()))


noopObservableCounter :: InstrumentationLibrary -> Text -> ObservableCounter a
noopObservableCounter :: forall a. InstrumentationLibrary -> Text -> ObservableCounter a
noopObservableCounter InstrumentationLibrary
scope Text
name =
  ObservableCounter
    { observableCounterRegisterCallback :: (ObservableResult a -> IO ()) -> IO ObservableCallbackHandle
observableCounterRegisterCallback = \ObservableResult a -> IO ()
_ -> IO ObservableCallbackHandle
noopReg
    , observableCounterInstrumentScope :: InstrumentationLibrary
observableCounterInstrumentScope = InstrumentationLibrary
scope
    , observableCounterInstrumentName :: Text
observableCounterInstrumentName = Text
name
    , observableCounterEnabled :: IO Bool
observableCounterEnabled = Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
    }


noopObservableUpDown :: InstrumentationLibrary -> Text -> ObservableUpDownCounter a
noopObservableUpDown :: forall a.
InstrumentationLibrary -> Text -> ObservableUpDownCounter a
noopObservableUpDown InstrumentationLibrary
scope Text
name =
  ObservableUpDownCounter
    { observableUpDownCounterRegisterCallback :: (ObservableResult a -> IO ()) -> IO ObservableCallbackHandle
observableUpDownCounterRegisterCallback = \ObservableResult a -> IO ()
_ -> IO ObservableCallbackHandle
noopReg
    , observableUpDownCounterInstrumentScope :: InstrumentationLibrary
observableUpDownCounterInstrumentScope = InstrumentationLibrary
scope
    , observableUpDownCounterInstrumentName :: Text
observableUpDownCounterInstrumentName = Text
name
    , observableUpDownCounterEnabled :: IO Bool
observableUpDownCounterEnabled = Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
    }


noopObservableGauge :: InstrumentationLibrary -> Text -> ObservableGauge a
noopObservableGauge :: forall a. InstrumentationLibrary -> Text -> ObservableGauge a
noopObservableGauge InstrumentationLibrary
scope Text
name =
  ObservableGauge
    { observableGaugeRegisterCallback :: (ObservableResult a -> IO ()) -> IO ObservableCallbackHandle
observableGaugeRegisterCallback = \ObservableResult a -> IO ()
_ -> IO ObservableCallbackHandle
noopReg
    , observableGaugeInstrumentScope :: InstrumentationLibrary
observableGaugeInstrumentScope = InstrumentationLibrary
scope
    , observableGaugeInstrumentName :: Text
observableGaugeInstrumentName = Text
name
    , observableGaugeEnabled :: IO Bool
observableGaugeEnabled = Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
    }


{- | A 'Meter' that records no telemetry (used by 'noopMeterProvider').

@since 0.0.1.0
-}
noopMeter :: InstrumentationLibrary -> Meter
noopMeter :: InstrumentationLibrary -> Meter
noopMeter InstrumentationLibrary
scope =
  Meter
    { meterInstrumentationScope :: InstrumentationLibrary
meterInstrumentationScope = InstrumentationLibrary
scope
    , meterCreateCounterInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (Counter Int64)
meterCreateCounterInt64 = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> Counter Int64 -> IO (Counter Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Counter Int64
noopCounterI64
    , meterCreateCounterDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (Counter Double)
meterCreateCounterDouble = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> Counter Double -> IO (Counter Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Counter Double
noopCounterDbl
    , meterCreateUpDownCounterInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (UpDownCounter Int64)
meterCreateUpDownCounterInt64 = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> UpDownCounter Int64 -> IO (UpDownCounter Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure UpDownCounter Int64
noopUpDownI64
    , meterCreateUpDownCounterDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (UpDownCounter Double)
meterCreateUpDownCounterDouble = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> UpDownCounter Double -> IO (UpDownCounter Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure UpDownCounter Double
noopUpDownDbl
    , meterCreateHistogram :: Text
-> Maybe Text -> Maybe Text -> AdvisoryParameters -> IO Histogram
meterCreateHistogram = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> Histogram -> IO Histogram
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Histogram
noopHistogram
    , meterCreateGaugeInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (Gauge Int64)
meterCreateGaugeInt64 = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> Gauge Int64 -> IO (Gauge Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Gauge Int64
noopGaugeI64
    , meterCreateGaugeDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> IO (Gauge Double)
meterCreateGaugeDouble = \Text
_ Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ -> Gauge Double -> IO (Gauge Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Gauge Double
noopGaugeDbl
    , meterCreateObservableCounterInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Int64 -> IO ()]
-> IO (ObservableCounter Int64)
meterCreateObservableCounterInt64 = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Int64 -> IO ()]
_ -> ObservableCounter Int64 -> IO (ObservableCounter Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableCounter Int64
forall a. InstrumentationLibrary -> Text -> ObservableCounter a
noopObservableCounter InstrumentationLibrary
scope Text
name)
    , meterCreateObservableCounterDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Double -> IO ()]
-> IO (ObservableCounter Double)
meterCreateObservableCounterDouble = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Double -> IO ()]
_ -> ObservableCounter Double -> IO (ObservableCounter Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableCounter Double
forall a. InstrumentationLibrary -> Text -> ObservableCounter a
noopObservableCounter InstrumentationLibrary
scope Text
name)
    , meterCreateObservableUpDownCounterInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Int64 -> IO ()]
-> IO (ObservableUpDownCounter Int64)
meterCreateObservableUpDownCounterInt64 = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Int64 -> IO ()]
_ -> ObservableUpDownCounter Int64 -> IO (ObservableUpDownCounter Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableUpDownCounter Int64
forall a.
InstrumentationLibrary -> Text -> ObservableUpDownCounter a
noopObservableUpDown InstrumentationLibrary
scope Text
name)
    , meterCreateObservableUpDownCounterDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Double -> IO ()]
-> IO (ObservableUpDownCounter Double)
meterCreateObservableUpDownCounterDouble = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Double -> IO ()]
_ -> ObservableUpDownCounter Double
-> IO (ObservableUpDownCounter Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableUpDownCounter Double
forall a.
InstrumentationLibrary -> Text -> ObservableUpDownCounter a
noopObservableUpDown InstrumentationLibrary
scope Text
name)
    , meterCreateObservableGaugeInt64 :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Int64 -> IO ()]
-> IO (ObservableGauge Int64)
meterCreateObservableGaugeInt64 = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Int64 -> IO ()]
_ -> ObservableGauge Int64 -> IO (ObservableGauge Int64)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableGauge Int64
forall a. InstrumentationLibrary -> Text -> ObservableGauge a
noopObservableGauge InstrumentationLibrary
scope Text
name)
    , meterCreateObservableGaugeDouble :: Text
-> Maybe Text
-> Maybe Text
-> AdvisoryParameters
-> [ObservableResult Double -> IO ()]
-> IO (ObservableGauge Double)
meterCreateObservableGaugeDouble = \Text
name Maybe Text
_ Maybe Text
_ AdvisoryParameters
_ [ObservableResult Double -> IO ()]
_ -> ObservableGauge Double -> IO (ObservableGauge Double)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Text -> ObservableGauge Double
forall a. InstrumentationLibrary -> Text -> ObservableGauge a
noopObservableGauge InstrumentationLibrary
scope Text
name)
    }


{- | No-op provider: safe for libraries; all measurements are discarded.

@since 0.0.1.0
-}
noopMeterProvider :: MeterProvider
noopMeterProvider :: MeterProvider
noopMeterProvider =
  MeterProvider
    { meterProviderGetMeter :: InstrumentationLibrary -> IO Meter
meterProviderGetMeter = \InstrumentationLibrary
scope -> Meter -> IO Meter
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InstrumentationLibrary -> Meter
noopMeter InstrumentationLibrary
scope)
    , meterProviderShutdown :: Maybe Int -> IO ShutdownResult
meterProviderShutdown = \Maybe Int
_ -> ShutdownResult -> IO ShutdownResult
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ShutdownResult
ShutdownSuccess
    , meterProviderForceFlush :: Maybe Int -> IO FlushResult
meterProviderForceFlush = \Maybe Int
_ -> FlushResult -> IO FlushResult
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure FlushResult
FlushSuccess
    }


globalMeterProvider :: IORef MeterProvider
{-# NOINLINE globalMeterProvider #-}
globalMeterProvider :: IORef MeterProvider
globalMeterProvider = IO (IORef MeterProvider) -> IORef MeterProvider
forall a. IO a -> a
unsafePerformIO (IO (IORef MeterProvider) -> IORef MeterProvider)
-> IO (IORef MeterProvider) -> IORef MeterProvider
forall a b. (a -> b) -> a -> b
$ MeterProvider -> IO (IORef MeterProvider)
forall a. a -> IO (IORef a)
newIORef MeterProvider
noopMeterProvider


-- | @since 0.0.1.0
getGlobalMeterProvider :: (MonadIO m) => m MeterProvider
getGlobalMeterProvider :: forall (m :: * -> *). MonadIO m => m MeterProvider
getGlobalMeterProvider = IO MeterProvider -> m MeterProvider
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO MeterProvider -> m MeterProvider)
-> IO MeterProvider -> m MeterProvider
forall a b. (a -> b) -> a -> b
$ IORef MeterProvider -> IO MeterProvider
forall a. IORef a -> IO a
readIORef IORef MeterProvider
globalMeterProvider


-- | @since 0.0.1.0
setGlobalMeterProvider :: (MonadIO m) => MeterProvider -> m ()
setGlobalMeterProvider :: forall (m :: * -> *). MonadIO m => MeterProvider -> m ()
setGlobalMeterProvider MeterProvider
p = IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ IORef MeterProvider -> MeterProvider -> IO ()
forall a. IORef a -> a -> IO ()
atomicWriteIORef IORef MeterProvider
globalMeterProvider MeterProvider
p