///|
priv struct RpcCounters {
  mut calls : UInt64
  mut replies : UInt64
  mut request_bytes : UInt64
  mut response_bytes : UInt64
  mut failures : UInt64
}

///|
/// Payload counts exclude TCP and record-marker overhead. Requests count attempts;
/// a lost reply can follow a request the server has already executed.
pub(all) struct RpcStatistics {
  calls : UInt64
  replies : UInt64
  request_bytes : UInt64
  response_bytes : UInt64
  failures : UInt64
}

///|
pub fn Rpc::statistics(self : Rpc) -> RpcStatistics {
  let counters = self.counters
  {
    calls: counters.calls,
    replies: counters.replies,
    request_bytes: counters.request_bytes,
    response_bytes: counters.response_bytes,
    failures: counters.failures,
  }
}

///|
pub fn Client::statistics(self : Client) -> RpcStatistics {
  self.rpc.statistics()
}

///|
pub fn RpcStatistics::since(
  self : RpcStatistics,
  before : RpcStatistics,
) -> RpcStatistics raise {
  if self.calls < before.calls ||
    self.replies < before.replies ||
    self.request_bytes < before.request_bytes ||
    self.response_bytes < before.response_bytes ||
    self.failures < before.failures {
    raise InvalidArgument(
      "statistics baseline does not precede current counters",
    )
  }
  {
    calls: self.calls - before.calls,
    replies: self.replies - before.replies,
    request_bytes: self.request_bytes - before.request_bytes,
    response_bytes: self.response_bytes - before.response_bytes,
    failures: self.failures - before.failures,
  }
}

///|
pub(all) struct ReadBenchmark {
  rounds : Int
  bytes : UInt64
  elapsed_ms : Int64
  fastest_ms : Int64
  slowest_ms : Int64
  rpc : RpcStatistics
}

///|
pub fn ReadBenchmark::mebibytes_per_second(self : ReadBenchmark) -> Double {
  if self.elapsed_ms <= 0L {
    return 0.0
  }
  self.bytes.to_double() * 1000.0 / self.elapsed_ms.to_double() / 1048576.0
}

///|
/// Measures real reads through this connection, with no application-level cache.
/// Server/OS caches can affect results. Time is wall-clock milliseconds.
pub async fn Client::benchmark_read(
  self : Client,
  path : String,
  rounds? : Int = 3,
  max_bytes? : UInt64 = 1073741824UL,
) -> ReadBenchmark {
  if rounds < 1 || rounds > 1000 {
    raise InvalidArgument("benchmark rounds must be 1..1000")
  }
  let handle = self.resolve(path)
  let before = self.metadata(handle)
  if before.attributes.kind != 1U {
    raise InvalidArgument("benchmark requires a regular file")
  }
  let size = before.attributes.size
  if size > max_bytes / rounds.to_uint64() {
    raise InvalidArgument("benchmark exceeds total byte budget")
  }
  let counters = self.statistics()
  let started = @async.now()
  let mut fastest_ms = 9223372036854775807L
  let mut slowest_ms = 0L
  let mut bytes = 0UL
  for _ in 0.. slowest_ms {
      slowest_ms = duration
    }
  }
  let elapsed_ms = @async.now() - started
  if elapsed_ms < 0L {
    raise ProtocolError("clock moved backwards during benchmark")
  }
  {
    rounds,
    bytes,
    elapsed_ms,
    fastest_ms,
    slowest_ms,
    rpc: self.statistics().since(counters),
  }
}