packages feed

eventlog-live-otelcol-0.7.0.0: src/GHC/Eventlog/Live/Otelcol/Options.hs

module GHC.Eventlog.Live.Otelcol.Options (
  Options (..),
  MyDebugOptions (..),
  ServiceName (..),
  OtlpExporterOptions (..),
  OtlpProtocol (..),
  options,
) where

import Data.Char (toLower)
import Data.Default (Default (..))
import Data.Functor (void)
import Data.List qualified as L
import Data.Text qualified as T
import Data.Version (showVersion)
import GHC.Debug.Stub.Compat (MyGhcDebugSocket, maybeMyGhcDebugSocketParser)
import GHC.Eventlog.Live.Data.Severity (Severity (..))
import GHC.Eventlog.Live.Options
import GHC.Eventlog.Live.Otelcol.Config (ServiceName (..))
import GHC.Eventlog.Live.Otelcol.Config qualified as C
import GHC.Eventlog.Live.Otelcol.Config.Default.Raw (defaultConfigJSONSchemaString, defaultConfigString)
import GHC.Eventlog.Live.Otelcol.Config.Types (Config)
import GHC.Eventlog.Live.Otelcol.Control (ControlOptions, controlOptionsParser)
import GHC.Eventlog.Live.Source.Core (EventlogSourceOptions (..))
import GHC.Eventlog.Socket.Compat (MyEventlogSocket (..), maybeMyEventlogSocketParser)
import GHC.RTS.Events (HeapProfBreakdown (..))
import Network.GRPC.Common qualified as G
import Options.Applicative qualified as O
import Options.Applicative.Compat qualified as OC
import Options.Applicative.Extra qualified as OE
import Options.Applicative.Help.Pretty qualified as OP
import Paths_eventlog_live_otelcol qualified as EventlogLive
import Text.ParserCombinators.ReadP (ReadP)
import Text.ParserCombinators.ReadP qualified as P

options :: O.ParserInfo Options
options =
  O.info
    ( optionsParser
        O.<**> defaultsPrinter
        O.<**> debugDefaultsPrinter
        O.<**> configJSONSchemaPrinter
        O.<**> OE.helperWith (O.long "help" <> O.help "Show this help text.")
        O.<**> OC.simpleVersioner (showVersion EventlogLive.version)
    )
    O.idm

data Options = Options
  { eventlogSourceOptions :: EventlogSourceOptions
  , eventlogSocketTimeoutS :: Double
  , eventlogSocketTimeoutExponent :: Double
  , eventlogFlushIntervalS :: Double
  , maybeEventlogLogFile :: Maybe FilePath
  , maybeHeapProfBreakdown :: Maybe HeapProfBreakdown
  , maybeServiceName :: Maybe ServiceName
  , maybeIpeDBPath :: Maybe FilePath
  , maybeCCDBPath :: Maybe FilePath
  , severityThreshold :: Severity
  , stats :: Bool
  , maybeConfigFile :: Maybe FilePath
  , otlpExporterOptions :: OtlpExporterOptions String
  , controlOptions :: ControlOptions
  , myDebugOptions :: MyDebugOptions
  }

optionsParser :: O.Parser Options
optionsParser =
  Options
    <$> eventlogSourceOptionsParser
    <*> eventlogSocketTimeoutSParser
    <*> eventlogSocketTimeoutExponentParser
    <*> eventlogFlushIntervalSParser
    <*> O.optional eventlogLogFileParser
    <*> O.optional heapProfBreakdownParser
    <*> O.optional serviceNameParser
    <*> O.optional ipeDBPathParser
    <*> O.optional ccDBPathParser
    <*> verbosityParser
    <*> statsParser
    <*> O.optional configFileParser
    <*> otlpExporterOptionsParser
    <*> controlOptionsParser
    <*> myDebugOptionsParser

--------------------------------------------------------------------------------
-- Configuration

configFileParser :: O.Parser FilePath
configFileParser =
  O.strOption
    ( O.long "config"
        <> O.metavar "FILE"
        <> O.help "The path to a detailed configuration file."
    )

defaultsPrinter :: O.Parser (a -> a)
defaultsPrinter =
  O.infoOption defaultConfigString . mconcat $
    [ O.long "print-defaults"
    , O.help "Print default configuration options."
    ]

configJSONSchemaPrinter :: O.Parser (a -> a)
configJSONSchemaPrinter =
  O.infoOption defaultConfigJSONSchemaString . mconcat $
    [ O.long "print-config-json-schema"
    , O.help "Print JSON Schema for configuration format."
    ]

debugDefaultsPrinter :: O.Parser (a -> a)
debugDefaultsPrinter =
  O.infoOption defaultConfigDebugString . mconcat $
    [ O.long "print-defaults-debug"
    , O.help "Print default configuration options using the parsed representation."
    , O.internal
    ]
 where
  defaultConfigDebugString =
    T.unpack . C.prettyConfig $ (def :: Config)

--------------------------------------------------------------------------------
-- Service Name

serviceNameParser :: O.Parser ServiceName
serviceNameParser =
  ServiceName
    <$> O.strOption
      ( O.long "service-name"
          <> O.metavar "STRING"
          <> O.help "The name of the profiled service."
      )

--------------------------------------------------------------------------------
-- InfoProv Tables

ipeDBPathParser :: O.Parser FilePath
ipeDBPathParser =
  O.strOption
    ( O.long "ipedb"
        <> O.metavar "FILE"
        <> O.help "The path to an IPE database."
    )

--------------------------------------------------------------------------------
-- CostCentre Tables

ccDBPathParser :: O.Parser FilePath
ccDBPathParser =
  O.strOption
    ( O.long "ccdb"
        <> O.metavar "FILE"
        <> O.help "The path a cost-centre database."
    )

--------------------------------------------------------------------------------
-- OpenTelemetry Collector configuration

data OtlpExporterOptions a = OtlpExporterOptions
  { otlpProtocol :: !OtlpProtocol
  , otlpEndpoint :: !a
  , otlpGrpcCertificateStore :: !(Maybe FilePath)
  , otlpGrpcSslKeyLog :: !(Maybe G.SslKeyLog)
  , otlpHttpHeaders :: !(Maybe [(String, String)])
  }
  deriving stock (Functor, Foldable, Traversable)

otlpExporterOptionsParser :: O.Parser (OtlpExporterOptions String)
otlpExporterOptionsParser =
  OC.parserOptionGroup "OTLP Exporter Options" $
    OtlpExporterOptions
      <$> otlpProtocolParser
      <*> otlpEndpointParser
      <*> O.optional otlpGrpcCertificateStoreParser
      <*> O.optional otlpGrpcSslKeyLogParser
      <*> O.optional otlpHttpHeadersParser

data OtlpProtocol
  = OtlpProtocolGrpc
  | OtlpProtocolHttpProtobuf
  deriving (Show)

otlpProtocolParser :: O.Parser OtlpProtocol
otlpProtocolParser =
  O.option (O.maybeReader readOtlpProtocol) . mconcat $
    [ O.long "otlp-protocol"
    , O.helpDoc . Just . OP.vcat . fmap OP.pretty $
        [ "The OTLP transport protocol to be used for all telemetry data (gRPC, HTTP/Protobuf)."
        , "Default value: gRPC"
        ]
    , O.value OtlpProtocolGrpc
    ]
 where
  readOtlpProtocol :: String -> Maybe OtlpProtocol
  readOtlpProtocol protocol =
    case map toLower protocol of
      "grpc" -> Just OtlpProtocolGrpc
      "http/protobuf" -> Just OtlpProtocolHttpProtobuf
      _ -> Nothing

otlpEndpointParser :: O.Parser String
otlpEndpointParser =
  O.strOption . mconcat $
    [ O.long "otlp-endpoint"
    , O.helpDoc . Just . OP.vcat . fmap OP.pretty $
        [ "The OTLP endpoint URL for all telemetry data, with an optionally-specified port number."
        , "Default value:"
        , "  gRPC: http://localhost:4317"
        , "  HTTP: http://localhost:4318"
        , "Example:"
        , "  gRPC: https://my-api-endpoint:443"
        , "  HTTP: http://my-api-endpoint/"
        ]
    ]

otlpGrpcCertificateStoreParser :: O.Parser FilePath
otlpGrpcCertificateStoreParser =
  O.strOption
    ( O.long "otlp-grpc-certificate-store"
        <> O.metavar "FILE"
        <> O.help "Store for certificate validation."
    )

otlpGrpcSslKeyLogParser :: O.Parser G.SslKeyLog
otlpGrpcSslKeyLogParser =
  O.asum
    [ G.SslKeyLogPath
        <$> O.strOption
          ( O.long "otlp-grpc-ssl-key-log"
              <> O.metavar "FILE"
              <> O.help "Use file to log SSL keys."
          )
    , O.flag'
        G.SslKeyLogFromEnv
        ( O.long "otlp-grpc-ssl-key-log-from-env"
            <> O.help "Use SSLKEYLOGFILE to log SSL keys."
        )
    ]

otlpHttpHeadersParser :: O.Parser [(String, String)]
otlpHttpHeadersParser =
  O.option (O.maybeReader readHeaders) . mconcat $
    [ O.long "otlp-http-headers"
    , O.help "A list of headers to apply to all outgoing data."
    ]

readHeaders :: String -> Maybe [(String, String)]
readHeaders = runReadP pHeaders
 where
  pHeaders :: ReadP [(String, String)]
  pHeaders = P.many (pHeader <* (void (P.char ',') P.<++ P.eof))

  pHeader :: ReadP (String, String)
  pHeader = (,) <$> P.munch1 (/= '=') <*> P.munch1 (/= ',')

--------------------------------------------------------------------------------
-- Debug Options

data MyDebugOptions = MyDebugOptions
  { maybeMyEventlogSocket :: Maybe MyEventlogSocket
  , maybeMyGhcDebugSocket :: Maybe MyGhcDebugSocket
  }

myDebugOptionsParser :: O.Parser MyDebugOptions
myDebugOptionsParser =
  OC.parserOptionGroup "Debug Options" $
    MyDebugOptions
      <$> maybeMyEventlogSocketParser
      <*> maybeMyGhcDebugSocketParser

--------------------------------------------------------------------------------
-- Internal helpers
--------------------------------------------------------------------------------

{- |
Internal helper.

Run a ReadP parser.
-}
runReadP :: ReadP a -> String -> Maybe a
runReadP p = fmap fst . L.find (null . snd) . P.readP_to_S p