packages feed

sectile-0.1.0.0: src/Data/Sectile/System/Linux/Disk.hs

-- |
-- Module        : Data.Sectile.System.Linux.Disk
-- Copyright     : Gautier DI FOLCO
-- License       : ISC
--
-- Maintainer    : Gautier DI FOLCO <foss@difolco.dev>
-- Stability     : Stable
-- Portability   : Portable
module Data.Sectile.System.Linux.Disk (disk) where

import qualified Data.Aeson as Aeson
import qualified Data.HashMap.Strict as HashMap
import Data.Sectile.System.Linux.Internal
import Data.Sectile.Types
import qualified Data.Text as T
import qualified System.Exit as Exit
import qualified System.Process as Process

-- | Display disk usage for a given mount point.
--
-- Uses 'System.Process' to call @df@.
--
-- Shows used, total, and percentage, e.g. @"\/" 450GiB (88%)@.
--
-- Example:
--
-- > import Data.Sectile
-- >
-- > diskSegment :: Segment IO
-- > diskSegment = disk "disk" "/"
-- > -- Renders e.g. "\/  1.5TiB / 1.8TiB (88%)"
disk :: Name -> FilePath -> Segment IO
disk name@(Name nameB) mountPoint =
  Segment $ do
    (exitCode, out, _) <- Process.readProcessWithExitCode "df" ["--output=size,used,avail,pcent", "-B1024", mountPoint] ""
    let (txt, bnds) = case exitCode of
          Exit.ExitSuccess ->
            case parseDiskUsage name (T.pack out) of
              Just (formatted, b) -> (formatted, b)
              Nothing -> (errMsg nameB, HashMap.empty)
          _ -> (errMsg nameB, HashMap.empty)
    pure $ do
      _ <- appendBindings bnds
      mkFormatted nameB "disk" txt [("MountPoint", T.pack mountPoint)]

-- | Parse disk usage from df --output=size,used,avail,pcent -B1024 output.
parseDiskUsage :: Name -> T.Text -> Maybe (T.Text, HashMap.HashMap T.Text Aeson.Value)
parseDiskUsage (Name nameB) content =
  let lns = T.lines content
   in case lns of
        (_ : dataLine : _) ->
          case T.words dataLine of
            (sizeT : usedT : availT : _pcentT : _) ->
              case (readDouble sizeT, readDouble usedT, readDouble availT) of
                (Just sizeKB, Just usedKB, Just availKB) ->
                  let size = sizeKB * 1024
                      used = usedKB * 1024
                      avail = availKB * 1024
                      bnds =
                        unitBindings "B" (Name (nameB <> ".total")) size
                          <> unitBindings "B" (Name (nameB <> ".used.total")) used
                          <> percentBindings (Name (nameB <> ".used")) (used / size)
                          <> unitBindings "B" (Name (nameB <> ".free.total")) avail
                          <> percentBindings (Name (nameB <> ".free")) (avail / size)
                      txt = formatKiB (round availKB) <> " (" <> T.pack (show (round (avail / size * 100) :: Int)) <> "%)"
                   in Just (txt, bnds)
                _ -> Nothing
            _ -> Nothing
        _ -> Nothing