packages feed

wasmedge-hs-0.17.1.0: examples/Stress/StatisticsCost.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.StatisticsCost
Description : Instruction counting and gas cost limit metering stress test
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Tests the WasmEdge statistics and metering subsystem:
  1. Instruction counting during execution
  2. Cost / gas tracking
  3. Instructions per second measurement
  4. Deterministic execution halting when a cost limit is exceeded
  5. Clearing and resetting statistics
-}
module Stress.StatisticsCost
  ( runStatisticsCostSuite
  , StatisticsMetrics (..)
  ) where

import Control.Exception (throwIO)
import Data.Either (isLeft)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import Stress.WasmBytecode (fibWasm)
import WasmEdge.Configure
  ( StatisticsOptions (..)
  , setStatisticsOptions
  , withConfigure
  )
import WasmEdge.Error (WasmEdgeError (..))
import WasmEdge.Statistics
  ( clearStatistics
  , getInstrCount
  , getInstrPerSecond
  , getTotalCost
  , setStatCostLimit
  )
import WasmEdge.Types (WasmVal (..))
import WasmEdge.VM

-- | Metrics recorded during the statistics and cost metering test
data StatisticsMetrics = StatisticsMetrics
  { smInstructionCount :: !Integer
  , smTotalCost :: !Integer
  , smInstrPerSec :: !Double
  , smCostLimitExhausted :: !Bool
  , smClearVerified :: !Bool
  , smDurationSec :: !Double
  }
  deriving stock (Show)

-- | Run the Statistics and Cost Metering suite.
runStatisticsCostSuite :: IO StatisticsMetrics
runStatisticsCostSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Instruction Counting & Gas Cost Metering"
  putStrLn "============================================================"

  t0 <- getTime Monotonic

  (instrs, cost, ips, limitTrapped, cleared) <- withConfigure $ \cfg -> do
    -- 1. Enable full statistics in configure
    setStatisticsOptions
      cfg
      StatisticsOptions
        { statInstructionCounting = True
        , statCostMeasuring = True
        , statTimeMeasuring = True
        }

    withVM (Just cfg) Nothing $ \vm -> do
      -- 2. Load fibWasm
      _ <- loadWasmBytes vm fibWasm
      _ <- validate vm
      _ <- instantiate vm

      -- Run fib(18) = 2584
      putStrLn "--> [1/3] Executing fib(18) with statistics instrumentation enabled..."
      execRes <- execute vm "fib" [WasmI32 18]
      case execRes of
        Right [WasmI32 2584] -> pure ()
        _ -> throwIO (userError "fib(18) returned unexpected result")

      stats <- getStatistics vm
      ic <- getInstrCount stats
      tc <- getTotalCost stats
      ipsRate <- getInstrPerSecond stats

      putStrLn $ printf "    Instructions executed: %d" ic
      putStrLn $ printf "    Total cost measured:   %d" tc
      putStrLn $ printf "    Rate:                  %.2f instructions/sec" ipsRate

      -- 3. Set a strict cost limit to test gas exhaustion
      putStrLn
        "--> [2/3] Setting cost limit to 500 units and executing fib(25) to trigger gas exhaustion..."
      setStatCostLimit stats 500
      exhaustRes <- execute vm "fib" [WasmI32 25]
      let trapped = isLeft exhaustRes
      case exhaustRes of
        Left (WasmEdgeError _cat code _msg) ->
          putStrLn $ printf "    Cost limit successfully triggered trap: code=0x%03X" code
        Right vals ->
          putStrLn $ "    Warning: execution unexpectedly succeeded: " ++ show vals

      -- 4. Clear statistics and verify reset
      putStrLn "--> [3/3] Clearing statistics context..."
      clearStatistics stats
      icAfter <- getInstrCount stats
      let resetOk = icAfter == 0
      putStrLn $ "    Counter reset verified: " ++ if resetOk then "PASSED" else "FAILED"

      pure (fromIntegral ic, fromIntegral tc, ipsRate, trapped, resetOk)

  t1 <- getTime Monotonic
  let durSec = fromIntegral (toNanoSecs (diffTimeSpec t1 t0)) / 1e9 :: Double
  printf "==> Statistics & Metering Suite PASSED in %.3f s\n\n" durSec

  pure $
    StatisticsMetrics
      { smInstructionCount = instrs
      , smTotalCost = cost
      , smInstrPerSec = ips
      , smCostLimitExhausted = limitTrapped
      , smClearVerified = cleared
      , smDurationSec = durSec
      }