keel-0.1.0.0: src/Keel/Doctor.hs
-- | @keel doctor@: probe every keel capability on this machine and say
-- exactly what resolved, what did not, and the one command that fixes
-- each gap. Pure diagnosis — nothing is downloaded or modified.
module Keel.Doctor
( CapStatus (..)
, CapabilityReport (..)
, diagnose
, renderReports
, allAvailable
) where
import Keel.Dyn (capLibrary, libraryPath)
import Keel.Linalg
( BackendError (..)
, backendConfig
, closeBackend
, openBackend
)
import Keel.Onnx (OnnxError (..), loadOnnxRuntime, ortVersion)
-- | Outcome of probing one capability.
data CapStatus
= Available
-- ^ Resolved and answered a version probe.
| Missing
-- ^ Nothing found by the search policy; installable.
| Broken
-- ^ Something was found but is unusable (wrong build, too old,
-- symbols absent).
deriving (Eq, Show)
-- | One line of the doctor's report.
data CapabilityReport = CapabilityReport
{ capName :: String
, capStatus :: CapStatus
, capDetail :: String
-- ^ Version\/config\/path on success; the reason otherwise.
, capFix :: Maybe String
-- ^ The one command that fixes it, when there is one.
}
deriving (Eq, Show)
-- | Probe everything. Never throws; each probe folds its failure into
-- the report.
diagnose :: IO [CapabilityReport]
diagnose =
sequence
[ pure dynReport
, pure abiReport
, blasReport
, onnxReport
]
dynReport :: CapabilityReport
dynReport =
CapabilityReport
"keel-dyn"
Available
"pure Haskell over the OS loader; no native dependency"
Nothing
abiReport :: CapabilityReport
abiReport =
CapabilityReport
"keel-abi"
Available
"frozen C ABI structs, hand-laid-out; no native dependency"
Nothing
blasFix :: Maybe String
blasFix = Just "keel setup blas (or point KEEL_OPENBLAS at a stock LP64 libopenblas)"
blasReport :: IO CapabilityReport
blasReport = do
r <- openBackend
case r of
Right be -> do
let detail = backendConfig be <> " @ " <> libraryPath (capLibrary be)
closeBackend be
pure (CapabilityReport "keel-linalg (OpenBLAS)" Available detail Nothing)
Left err ->
pure $ case err of
BackendNotFound _ ->
CapabilityReport "keel-linalg (OpenBLAS)" Missing
"no OpenBLAS found via KEEL_OPENBLAS, the keel data dir, or the system search path"
blasFix
BackendNotOpenBLAS p ->
CapabilityReport "keel-linalg (OpenBLAS)" Broken
("library at " <> p <> " exports no openblas_get_config; only OpenBLAS is supported")
blasFix
BackendILP64 cfg ->
CapabilityReport "keel-linalg (OpenBLAS)" Broken
("ILP64 build refused (would corrupt silently): " <> cfg)
blasFix
BackendMissingSymbol e ->
CapabilityReport "keel-linalg (OpenBLAS)" Broken
("required symbol absent (symbol-renamed or LAPACKE-less build): " <> show e)
blasFix
onnxFix :: Maybe String
onnxFix = Just "keel setup onnx (or point KEEL_ONNXRUNTIME at the official onnxruntime library)"
onnxReport :: IO CapabilityReport
onnxReport = do
r <- loadOnnxRuntime
case r of
-- deliberately not closed: onnxruntime owns thread pools and
-- unloading at process end is the safe path
Right ort ->
pure
( CapabilityReport "keel-onnx (ONNX Runtime)" Available
("ONNX Runtime " <> ortVersion ort <> " @ " <> libraryPath (capLibrary ort))
Nothing
)
Left err ->
pure $ case err of
OnnxRuntimeNotFound _ ->
CapabilityReport "keel-onnx (ONNX Runtime)" Missing
"no ONNX Runtime found via KEEL_ONNXRUNTIME, the keel data dir, or the system search path"
onnxFix
OnnxApiUnsupported v ->
CapabilityReport "keel-onnx (ONNX Runtime)" Broken
("a runtime was found but is older than C API version " <> show v
<> " (a stray old onnxruntime on PATH shadows the good one)")
onnxFix
other ->
CapabilityReport "keel-onnx (ONNX Runtime)" Broken (show other) onnxFix
-- | @True@ when every probed capability is 'Available'.
allAvailable :: [CapabilityReport] -> Bool
allAvailable = all ((== Available) . capStatus)
-- | Plain-text rendering, one capability per block.
renderReports :: [CapabilityReport] -> String
renderReports reports = unlines (concatMap block reports)
where
block r =
(tag (capStatus r) <> " " <> pad (capName r) <> " " <> capDetail r)
: case (capStatus r, capFix r) of
(Available, _) -> []
(_, Just fix) -> [" fix: " <> fix]
_ -> []
tag Available = "[ok] "
tag Missing = "[MISSING]"
tag Broken = "[BROKEN] "
width = maximum (map (length . capName) reports)
pad s = s <> replicate (width - length s) ' '