#!/bin/bash

#####################################################################
# Copyright © 2019 - 2020 Tino Reichardt
#####################################################################
#
# Benchmarking neomutt header compression, results will be send to
# these two files:
# 1) log_init.md    -> times needed for first start
# 2) log_reload.md  -> times needed for reload
#
# The output files are markdown and can be used for evaluating the best
# header compression type for your system.
#
# ctime: 2019-11-17 /TR
# mtime: 2020-03-24 /TR
#####################################################################

function error() {
  echo "$*"
  exit 111
}

NEOMUTT=`which neomutt`
GNUTIME=`which time`
CWD=`pwd`

# testing maildir, just create an symlink
MAILDIR="$CWD/Maildir/"
CACHEDIR="$CWD/cache"

test -z $NEOMUTT && error "Sorry, neomutt not found :/"
test -z $GNUTIME && error "Sorry, I need the GNU time command :/"
test ! -d $MAILDIR/cur && error "Sorry, I can handle only Maildir/ files."

# how much tests should be done, the average is used
TIMES_INIT="3"
TIMES_RELOAD="9"

# backends with built in compression (header_cache_compress=yes)
M1=(qdbm kyotocabinet rocksdb tokyocabinet)
#M1=(tokyocabinet)

# these backends should be tested with header_cache_compress=no
M2=(bdb gdbm kyotocabinet lmdb qdbm tdb rocksdb tokyocabinet)
#M2=(rocksdb)

# generic compression methods, should be used via:
#  header_cache_compress=no
#  header_cache_compress_method=XYZ
METHODS=("none" "lz4" "zstd" "zlib")

function getcol() {
  ROW="$1"
  cat "$LOG" | awk "{print \$$ROW}" | xargs
}

function getavg() {
  ACCURACY="$1"
  shift
  echo "$ACCURACY k 0 $* $(printf "%.s+" $(seq 1 $TIMES)) $TIMES / p" | dc
}

function test_neomutt() {
  MODE="$1"
  TIMES="$2"

  LOG="$HCACHE_FILE-$MODE.log"
  echo -n > "$LOG"
  for i in `seq 1 $TIMES`; do
    [ "$MODE" = "init" ] && rm -rf "$HCACHE_FILE.db"
    $GNUTIME --format="%e %U %S %M %F %R %O" \
    --append --output="$LOG" $NEOMUTT -F "$CWD/muttrc" > /dev/null
  done

  # 1 %e Elapsed real time (in seconds).
  # 2 %S Total number of CPU-seconds that the process spent in kernel mode.
  # 3 %U Total number of CPU-seconds that the process spent in user mode.
  # 4 %M Maximum resident set size of the process during its lifetime, in Kbytes.
  # 5 %F Number of major page faults that occurred while the process was running.
  # 6 %R Number of minor, or recoverable, page faults.
  # 7 %O Number of filesystem outputs by the process.
  e=`getavg 3 "$(getcol 1)"|sed -e 's/^\./0\./g'`
  S=`getavg 3 "$(getcol 2)"|sed -e 's/^\./0\./g'`
  U=`getavg 3 "$(getcol 3)"|sed -e 's/^\./0\./g'`
  M=`getavg 0 "$(getcol 4)"`
  F=`getavg 0 "$(getcol 5)"`
  R=`getavg 0 "$(getcol 6)"`
  O=`getavg 0 "$(getcol 7)"`

  # wait some moment, so the stat data are correct
  sync
  L=`du -s "$HCACHE_FILE.db"| cut -f1`
  echo "du -s $HCACHE_FILE.db -> size=$L time=$e"
  echo "$e | $S | $U | $M | $L | $HCACHE_METHOD | $HCACHE_COMPRESS | $CM$WD $CL | $F + $R | $O" >> log_$MODE.md
}

function doit() {
  HCACHE_METHOD="$1"
  HCACHE_COMPRESS="$2"
  CM="$3"
  CL="$4"
  WD=""

  if [ ! -z $CM ]; then
    HCACHE_COMPRESS_METHOD="set header_cache_compress_method = $CM"
  else
    HCACHE_COMPRESS_METHOD=""
    CM="none"
  fi

  if [ $CM = "none" ]; then
    HCACHE_COMPRESS_METHOD=""
    HCACHE_COMPRESS_LEVEL=""
    CL=""
  fi

  if [ ! -z $CL ]; then
    HCACHE_COMPRESS_LEVEL="set header_cache_compress_level = $CL"
  else
    HCACHE_COMPRESS_LEVEL=""
  fi

  HCACHE_FILE="$CACHEDIR/hcache-$HCACHE_COMPRESS-$HCACHE_METHOD-$CM"

  cat > "$CWD/muttrc" << EOF

# set debug_level = 2
# set debug_file = neolog

set read_inc = 0
set write_inc = 0

set folder = $MAILDIR
set spoolfile = $MAILDIR

set maildir_header_cache_verify = no

set header_cache = $HCACHE_FILE.db
set header_cache_backend = $HCACHE_METHOD

# set header_cache_compress = yes | no
set header_cache_compress = $HCACHE_COMPRESS

# set header_cache_compress_method = zstd | lz4 | zlib
$HCACHE_COMPRESS_METHOD

#set header_cache_compress_level = 1
$HCACHE_COMPRESS_LEVEL

folder-hook . exec exit
EOF

  test_neomutt "init"   "$TIMES_INIT"
  test_neomutt "reload" "$TIMES_RELOAD"
}

X="Real | User | Sys | MaxMem | DBSize | Backend | Builtin Compress | Generic Compress | PageFaults | FS Outputs
-|-|-|-|-|-|-|-|-|-"
echo "$X" > log_init.md
echo "$X" > log_reload.md
mkdir -p "$CACHEDIR"

# header_cache_compress = yes
for hm in ${M1[@]}; do
  # dbms | yes | method="" | level=""
  doit "$hm" "yes" "" ""
done

#exit

# header_cache_compress = no
for hm in ${M2[@]}; do
  for cm in ${METHODS[@]}; do
    # dbms | no | method | level
    doit "$hm" "no" "$cm" "1"
  done
done
