Server yang paling berbahaya adalah yang terasa sehat. Disk terisi pelan-pelan oleh log yang dilupakan, memori bocor sedikit demi sedikit dari aplikasi yang tidak pernah direstart, dan load naik hanya pada jam sibuk — semuanya berjalan diam-diam sampai suatu pagi layanan menolak menyala dan kita baru tahu masalahnya sudah berbulan-bulan tumbuh. Membuka top sesekali tidak menolong, karena yang dibutuhkan bukan sekadar melihat, melainkan mencatat dan membandingkan secara berkala. Proyek ini membangun alat untuk kebiasaan itu: skrip pemantau sistem berbasis bash yang mengumpulkan load average, persentase memori, dan persentase disk per mount, membandingkannya dengan ambang waspada dan kritis dari berkas konfigurasi, lalu melaporkan status tiap metrik, menulis log terstruktur, dan mengirim alert setelah masalah bertahan beberapa kali pemeriksaan berturut-turut.
Skrip yang kita bangun bekerja dalam satu eksekusi:
- membaca load average 1 menit dari
/proc/loadavg, memori dari/proc/meminfo(MemTotaldanMemAvailable), dan disk dari keluarandf -Pyang bisa di-parse, - membandingkan tiap metrik dengan ambang waspada dan kritis yang dibaca dari berkas konfigurasi terpisah,
- mencetak laporan berbentuk tabel dengan status
OK,WASPADA, atauKRITISper metrik, plus satu baris kesimpulan, - menulis log terstruktur berstempel waktu ISO dan menggesernya saat melebihi batas ukuran (rotasi),
- mengirim alert ke stderr setelah satu metrik melewati ambang beberapa kali berturut-turut — hitungannya disimpan di berkas state, dan
- keluar dengan exit code agregat: 0 semua OK, 1 ada
WASPADA, 2 adaKRITIS— siap dibaca cron atau systemd timer.
Tampilan Program Akhir #
Sesi berikut menunjukkan hasil akhir proyek pada fixture — keadaan sistem yang dibekukan ke beberapa berkas tiruan — supaya outputnya bisa dibandingkan ulang. Stempel waktu log dikunci lewat MONITOR_STAMP, dan folder data tiruan diberikan lewat --data-dir; di penggunaan nyata keduanya tidak perlu:
$ MONITOR_STAMP=2026-01-15T09:00:00+07:00 bash src/monitor.sh --data-dir fixture
Metrik Nilai Ambang W/K Status
---------------------------------------
cpu-load 2.50 1.50/3.00 WASPADA
memori 84% 80%/90% WASPADA
disk:/ 48% 80%/90% OK
disk:/home 89% 80%/90% WASPADA
kesimpulan: WASPADA
$ echo $?
1
$ MONITOR_STAMP=2026-01-15T09:05:00+07:00 bash src/monitor.sh --data-dir fixture > /dev/null
$ MONITOR_STAMP=2026-01-15T09:10:00+07:00 bash src/monitor.sh --data-dir fixture
Metrik Nilai Ambang W/K Status
---------------------------------------
cpu-load 2.50 1.50/3.00 WASPADA
monitor.sh: ALERT cpu-load = 2.50 melewati ambang 3x berturut-turut
memori 84% 80%/90% WASPADA
monitor.sh: ALERT memori = 84% melewati ambang 3x berturut-turut
disk:/ 48% 80%/90% OK
disk:/home 89% 80%/90% WASPADA
monitor.sh: ALERT disk:/home = 89% melewati ambang 3x berturut-turut
kesimpulan: WASPADA
$ tail -n 8 monitor.log
2026-01-15T09:05:00+07:00 WASPADA disk:/home 89%
2026-01-15T09:10:00+07:00 WASPADA cpu-load 2.50
2026-01-15T09:10:00+07:00 ALERT cpu-load 2.50
2026-01-15T09:10:00+07:00 WASPADA memori 84%
2026-01-15T09:10:00+07:00 ALERT memori 84%
2026-01-15T09:10:00+07:00 OK disk:/ 48%
2026-01-15T09:10:00+07:00 WASPADA disk:/home 89%
2026-01-15T09:10:00+07:00 ALERT disk:/home 89%
$ cat monitor.state
cpu-load 3
memori 3
disk:/home 3
Dua eksekusi pertama hanya mencatat — dua kali WASPADA belum cukup untuk alarm. Eksekusi ketiga, ketika hitungan berturut-turut mencapai tiga, mengirim alert untuk setiap metrik yang bermasalah sekaligus mencatat baris ALERT di log. Inilah inti desain proyek: lencana sesaat tidak menakut-nakuti, tapi masalah yang bertahan tidak bisa lolos.
Tujuan Pembelajaran #
Setelah menyelesaikan proyek ini, kita mampu:
- mengambil metrik sistem langsung dari sumbernya —
/proc/loadavg,/proc/meminfo, dan keluarandf -P— denganawk, - menghitung persentase di dalam awk dan membulatkan hasilnya secara sadar,
- memisahkan ambang batas dari kode lewat berkas konfigurasi yang tervalidasi,
- memutuskan status per metrik dengan
casedan aritmetika bash, lalu meringkasnya menjadi exit code agregat, - menyusun laporan tabel yang rapi dengan
printfdan memilih warna ANSI hanya untuk terminal, - menulis log terstruktur berstempel ISO, merotasinya berdasarkan ukuran, dan
- memakai berkas state untuk menghitung kejadian berturut-turut sebelum alert dikirim.
Prasyarat #
Proyek ini merangkai kembali materi yang sudah dibahas, bukan memperkenalkan alat baru. Chapter yang paling sering dirujuk:
- CPU Monitoring
— membaca
/proc/loadavgdan memahami arti load average, - Memory Monitoring
—
MemFreevsMemAvailabledan cara membaca/proc/meminfo, - Functions — memecah skrip menjadi fungsi kecil dengan tanggung jawab tunggal.
Tahapan Pengerjaan #
Proyek dikerjakan dalam tiga bagian berurutan. Setiap bagian diakhiri dengan keadaan yang bisa dijalankan dan diamati:
- Bagian 1: Pengumpulan Data Sistem — membaca tiga sumber data dengan
awkdan membekukannya menjadi fixture yang bisa diuji ulang. - Bagian 2: Pelaporan Resource — memvalidasi ambang dari konfigurasi, menentukan status tiap metrik, dan mencetak laporan tabel dengan exit code agregat.
- Bagian 3: Logging dan Alert — menulis log terstruktur, menghitung kejadian berturut-turut di berkas state, mengirim alert, dan merotasi log.
Mulailah dari Bagian 1 karena tiga fungsi pengumpul data di sana menjadi fondasi untuk seluruh bagian berikutnya. Berkas sumber proyek (monitor.sh, monitor.conf, dan run-tests.sh) tersedia pada bagian Berkas Sumber di bawah halaman setiap bagian, sehingga Anda bisa membandingkan hasil pekerjaan dengan versi referensi kapan saja.
Kumpulkan metrik CPU, memori, dan disk dari /proc dan perintah sistem.
Buka → # Bagian 2: Pelaporan ResourceFormat laporan resource yang mudah dibaca lengkap dengan status ambang batas.
Buka → # Bagian 3: Logging dan AlertCatat hasil pemantauan ke file log dan kirim alert saat ambang terlampaui.
Buka →Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
src/monitor.sh
Unduh berkas#!/usr/bin/env bash
# monitor.sh — pemantau resource sederhana untuk admin kecil.
#
# Setiap eksekusi mengumpulkan tiga metrik — load average 1 menit,
# persentase memori terpakai, dan persentase disk terpakai per mount
# point dari konfigurasi — membandingkannya dengan ambang waspada dan
# kritis dari berkas konfigurasi, mencetak laporan berbentuk tabel,
# menulis log terstruktur, dan mengirim alert ke stderr setelah satu
# metrik melewati ambang beberapa kali berturut-turut. Berjalan sebagai
# user biasa — tidak butuh root.
#
# Pemakaian:
# bash monitor.sh [--data-dir DIR]
#
# --data-dir DIR baca loadavg, meminfo, dan df.txt dari DIR alih-
# alih dari /proc dan df sungguhan (untuk pengujian).
#
# Variabel lingkungan (berguna untuk pengujian):
# MONITOR_CONF berkas konfigurasi yang dibaca (default: monitor.conf)
# MONITOR_SOURCE sama dengan --data-dir (opsi menang bila keduanya diisi)
# MONITOR_STAMP stempel waktu ISO untuk log (default: waktu sekarang)
#
# Kode keluar:
# 0 semua metrik OK 1 ada metrik WASPADA
# 2 ada metrik KRITIS 3 konfigurasi atau sumber data tidak valid
set -u
CONF="${MONITOR_CONF:-monitor.conf}"
STAMP="${MONITOR_STAMP:-$(date +%Y-%m-%dT%H:%M:%S%:z)}"
data_dir="${MONITOR_SOURCE:-}"
usage() {
printf 'Pemakaian: bash monitor.sh [--data-dir DIR]\n'
printf '\n'
printf 'Memeriksa load average, memori, dan disk menurut ambang di\n'
printf 'monitor.conf, mencetak laporan, menulis log, dan mengirim\n'
printf 'alert setelah satu metrik melewati ambang beberapa kali\n'
printf 'berturut-turut.\n'
printf '\n'
printf ' --data-dir DIR baca data tiruan dari DIR (untuk pengujian)\n'
printf ' -h, --help tampilkan bantuan ini\n'
printf '\n'
printf 'Kode keluar: 0 semua OK, 1 ada WASPADA, 2 ada KRITIS,\n'
printf '3 konfigurasi atau sumber data tidak valid.\n'
}
while [ $# -gt 0 ]; do
case "$1" in
--data-dir)
if [ $# -lt 2 ]; then
printf 'monitor.sh: --data-dir membutuhkan argumen DIR\n' >&2
exit 3
fi
data_dir=$2
shift 2
;;
--data-dir=*)
data_dir=${1#--data-dir=}
shift
;;
-h|--help)
usage
exit 0
;;
*)
printf 'monitor.sh: argumen tidak dikenal: %s\n' "$1" >&2
exit 3
;;
esac
done
# angka_desimal V: sukses bila V bilangan desimal sederhana seperti
# "1.50" atau "3" — tanpa tanda, tanpa titik ganda, tanpa titik di ujung.
angka_desimal() {
case "$1" in
''|*[!0-9.]*) return 1 ;;
*.*.*) return 1 ;;
.*|*.|*.) return 1 ;;
esac
return 0
}
# angka_persen V: sukses bila V bilangan bulat 1 sampai 99.
angka_persen() {
case "$1" in
''|*[!0-9]*) return 1 ;;
esac
[ "$1" -ge 1 ] && [ "$1" -le 99 ]
}
# angka_bulat V: sukses bila V bilangan bulat 1 atau lebih.
angka_bulat() {
case "$1" in
''|*[!0-9]*|0) return 1 ;;
esac
return 0
}
# --- baca dan validasi konfigurasi ---------------------------------------
if [ ! -f "$CONF" ]; then
printf 'monitor.sh: berkas konfigurasi %s tidak ditemukan\n' "$CONF" >&2
exit 3
fi
# shellcheck source=/dev/null
. "$CONF"
gagal_konfig() {
printf 'monitor.sh: %s (periksa %s)\n' "$1" "$CONF" >&2
exit 3
}
angka_desimal "${LOAD_WARN:-}" || gagal_konfig 'LOAD_WARN harus angka desimal, mis. 1.50'
angka_desimal "${LOAD_KRITIS:-}" || gagal_konfig 'LOAD_KRITIS harus angka desimal, mis. 3.00'
angka_persen "${MEM_WARN_PCT:-}" || gagal_konfig 'MEM_WARN_PCT harus bilangan bulat 1-99'
angka_persen "${MEM_KRITIS_PCT:-}" || gagal_konfig 'MEM_KRITIS_PCT harus bilangan bulat 1-99'
angka_persen "${DISK_WARN_PCT:-}" || gagal_konfig 'DISK_WARN_PCT harus bilangan bulat 1-99'
angka_persen "${DISK_KRITIS_PCT:-}" || gagal_konfig 'DISK_KRITIS_PCT harus bilangan bulat 1-99'
angka_bulat "${ALERT_REPEAT:-}" || gagal_konfig 'ALERT_REPEAT harus bilangan bulat 1 atau lebih'
angka_bulat "${LOG_KEEP:-}" || gagal_konfig 'LOG_KEEP harus bilangan bulat 1 atau lebih'
angka_bulat "${LOG_MAX_BYTES:-}" || gagal_konfig 'LOG_MAX_BYTES harus bilangan bulat 1 atau lebih'
[ -n "${DISK_MOUNTS:-}" ] || gagal_konfig 'DISK_MOUNTS belum diisi'
[ -n "${LOG_FILE:-}" ] || gagal_konfig 'LOG_FILE belum diisi'
if ! awk -v a="$LOAD_WARN" -v b="$LOAD_KRITIS" 'BEGIN { exit (a < b) ? 0 : 1 }'; then
gagal_konfig 'LOAD_WARN harus lebih kecil dari LOAD_KRITIS'
fi
[ "${MEM_WARN_PCT}" -lt "${MEM_KRITIS_PCT}" ] ||
gagal_konfig 'MEM_WARN_PCT harus lebih kecil dari MEM_KRITIS_PCT'
[ "${DISK_WARN_PCT}" -lt "${DISK_KRITIS_PCT}" ] ||
gagal_konfig 'DISK_WARN_PCT harus lebih kecil dari DISK_KRITIS_PCT'
# --- sumber data: /proc sungguhan atau fixture pengujian ------------------
if [ -n "$data_dir" ]; then
loadavg_file="$data_dir/loadavg"
meminfo_file="$data_dir/meminfo"
df_file="$data_dir/df.txt"
for f in "$loadavg_file" "$meminfo_file" "$df_file"; do
if [ ! -r "$f" ]; then
printf 'monitor.sh: sumber data %s tidak terbaca\n' "$f" >&2
exit 3
fi
done
df_output=$(cat "$df_file")
else
loadavg_file=/proc/loadavg
meminfo_file=/proc/meminfo
df_output=$(df -P)
fi
# --- siapkan berkas log, state, dan rotasi --------------------------------
LOG_DIR=$(dirname "$LOG_FILE")
mkdir -p "$LOG_DIR" || exit 3
STATE_FILE="$LOG_DIR/monitor.state"
STATE_TMP="$STATE_FILE.tmp"
if [ -f "$LOG_FILE" ]; then
ukuran=$(stat -c %s "$LOG_FILE")
if [ "$ukuran" -gt "$LOG_MAX_BYTES" ]; then
i=$LOG_KEEP
while [ "$i" -gt 1 ]; do
[ -f "$LOG_FILE.$((i - 1))" ] &&
mv "$LOG_FILE.$((i - 1))" "$LOG_FILE.$i"
i=$((i - 1))
done
mv "$LOG_FILE" "$LOG_FILE.1"
fi
fi
# --- fungsi pengumpul data ------------------------------------------------
# ambil_load100: load average 1 menit dikali 100 (2.50 menjadi 250).
ambil_load100() {
awk '{ printf "%.0f", $1 * 100 }' "$loadavg_file"
}
# ke_ratus ANGKA: kalikan angka desimal dengan 100 menjadi bilangan bulat.
ke_ratus() {
awk -v v="$1" 'BEGIN { printf "%.0f", v * 100 }'
}
# ambil_mem100: persen memori terpakai. MemAvailable dipakai karena sudah
# memperhitungkan cache yang bisa direklamasi — bukan sekadar MemFree.
ambil_mem100() {
awk '
$1 == "MemTotal:" { total = $2 }
$1 == "MemAvailable:" { avail = $2; ada = 1 }
END { if (total > 0 && ada) printf "%.0f", (total - avail) * 100 / total }
' "$meminfo_file"
}
# ambil_disk100 MOUNT: persen terpakai untuk MOUNT dari output df.
ambil_disk100() {
printf '%s\n' "$df_output" |
awk -v mnt="$1" '
$6 == mnt { gsub(/%/, "", $5); print $5 + 0; exit }
'
}
# --- fungsi status ---------------------------------------------------------
# kode_status NILAI WASPADA KRITIS: 0=OK, 1=WASPADA, 2=KRITIS.
# Nilai dan kedua ambang harus bilangan bulat.
kode_status() {
if [ "$1" -ge "$3" ]; then
printf '2'
elif [ "$1" -ge "$2" ]; then
printf '1'
else
printf '0'
fi
}
# nama_status KODE: bentuk teks dari kode status.
nama_status() {
case "$1" in
1) printf 'WASPADA' ;;
2) printf 'KRITIS' ;;
*) printf 'OK' ;;
esac
}
# --- fungsi log, state, dan alert ------------------------------------------
# catat STATUS METRIK NILAI: tambah satu baris log terstruktur.
catat() {
printf '%s %-7s %-11s %s\n' "$STAMP" "$1" "$2" "$3" >> "$LOG_FILE"
}
# baca_hitung KUNCI: berapa kali berturut-turut KUNCI melewati ambang.
baca_hitung() {
[ -f "$STATE_FILE" ] || return 0
awk -v k="$1" '$1 == k { print $2; exit }' "$STATE_FILE"
}
# warna_status STATUS: teks status, diberi warna ANSI hanya bila stdout
# adalah terminal — output yang dialihkan ke berkas tetap polos.
warna_status() {
if [ -t 1 ]; then
case "$1" in
WASPADA) printf '\033[33m%s\033[0m' "$1" ;;
KRITIS) printf '\033[31m%s\033[0m' "$1" ;;
*) printf '%s' "$1" ;;
esac
else
printf '%s' "$1"
fi
}
# --- kumpulkan data --------------------------------------------------------
load_tampil=$(awk '{ print $1 }' "$loadavg_file")
load100=$(ambil_load100)
if [ -z "$load100" ]; then
printf 'monitor.sh: %s tidak berisi load average\n' "$loadavg_file" >&2
exit 3
fi
mem100=$(ambil_mem100)
if [ -z "$mem100" ]; then
printf 'monitor.sh: %s tidak berisi MemTotal/MemAvailable\n' "$meminfo_file" >&2
exit 3
fi
load_warn100=$(ke_ratus "$LOAD_WARN")
load_kritis100=$(ke_ratus "$LOAD_KRITIS")
# Mount yang tidak ada di data df ditolak di sini, sebelum laporan
# dicetak, supaya tabel tidak tercetak setengah jalan.
# shellcheck disable=SC2086 # daftar mount memang dipisah spasi
for mnt in $DISK_MOUNTS; do
if [ -z "$(ambil_disk100 "$mnt")" ]; then
printf 'monitor.sh: mount %s tidak ditemukan di data df (periksa DISK_MOUNTS)\n' "$mnt" >&2
exit 3
fi
done
# --- periksa tiap metrik: laporan + log + alert ----------------------------
terburuk=0
: > "$STATE_TMP"
# periksa METRIK TAMPIL NILAI WASPADA KRITIS LABEL_AMBANG
periksa() {
local metrik=$1 tampil=$2 nilai=$3 warn=$4 kritis=$5 label=$6
local kode status n
kode=$(kode_status "$nilai" "$warn" "$kritis")
status=$(nama_status "$kode")
if (( kode > terburuk )); then
terburuk=$kode
fi
printf '%-10s %7s %-12s %s\n' "$metrik" "$tampil" "$label" \
"$(warna_status "$status")"
catat "$status" "$metrik" "$tampil"
n=$(baca_hitung "$metrik")
n=${n:-0}
if [ "$status" != "OK" ]; then
n=$((n + 1))
printf '%s %s\n' "$metrik" "$n" >> "$STATE_TMP"
if [ "$n" -eq "$ALERT_REPEAT" ]; then
printf 'monitor.sh: ALERT %s = %s melewati ambang %sx berturut-turut\n' \
"$metrik" "$tampil" "$ALERT_REPEAT" >&2
catat ALERT "$metrik" "$tampil"
fi
fi
}
printf '%-10s %7s %-12s %s\n' 'Metrik' 'Nilai' 'Ambang W/K' 'Status'
printf '%.0s-' {1..39}; printf '\n'
periksa cpu-load "$load_tampil" "$load100" "$load_warn100" "$load_kritis100" \
"$LOAD_WARN/$LOAD_KRITIS"
periksa memori "${mem100}%" "$mem100" "$MEM_WARN_PCT" "$MEM_KRITIS_PCT" \
"${MEM_WARN_PCT}%/${MEM_KRITIS_PCT}%"
# shellcheck disable=SC2086 # daftar mount memang dipisah spasi
for mnt in $DISK_MOUNTS; do
disk100=$(ambil_disk100 "$mnt")
periksa "disk:$mnt" "${disk100}%" "$disk100" \
"$DISK_WARN_PCT" "$DISK_KRITIS_PCT" \
"${DISK_WARN_PCT}%/${DISK_KRITIS_PCT}%"
done
printf 'kesimpulan: %s\n' "$(nama_status "$terburuk")"
mv "$STATE_TMP" "$STATE_FILE"
exit "$terburuk"
tests/run-tests.sh
Unduh berkas#!/usr/bin/env bash
# run-tests.sh — test suite untuk monitor.sh.
# Suite menyiapkan sendiri folder kerja terisolasi (mktemp) berisi
# fixture /proc dan df yang dibekukan, menyalin src/monitor.sh dan
# config/monitor.conf, lalu menjalankan rangkaian pengujian dengan
# assertion sederhana. Semua eksekusi memakai MONITOR_STAMP tetap
# supaya isi log dan pesan bisa diramalkan.
# Jalankan dari mana saja: bash tests/run-tests.sh
set -u
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJ_DIR=$(dirname "$SCRIPT_DIR")
PASS=0
FAIL=0
# assert_eq <harapan> <nyata> <pesan>: bandingkan dua nilai.
assert_eq() {
if [ "$1" = "$2" ]; then
PASS=$((PASS + 1))
printf 'OK %s\n' "$3"
else
FAIL=$((FAIL + 1))
printf 'GAGAL %s: harapan %s, nyata %s\n' "$3" "$1" "$2"
fi
}
# --- siapkan folder kerja terisolasi ------------------------------------
WORK=$(mktemp -d)
cp "$PROJ_DIR/src/monitor.sh" "$PROJ_DIR/config/monitor.conf" "$WORK/"
cd "$WORK" || exit 1
# --- fixture: tiga keadaan sistem yang dibekukan ------------------------
# waspada: load 2.50, memori 84%, / 48%, /home 89%
mkdir -p waspada
printf '2.50 2.10 1.60 4/812 34567\n' > waspada/loadavg
cat > waspada/meminfo <<'EOF'
MemTotal: 8000000 kB
MemFree: 950000 kB
MemAvailable: 1280000 kB
Buffers: 210000 kB
Cached: 700000 kB
SwapCached: 0 kB
SwapTotal: 2000000 kB
SwapFree: 2000000 kB
EOF
cat > waspada/df.txt <<'EOF'
Filesystem 1024-blocks Used Available Capacity Mounted on
/dev/sda1 82043440 36919548 40942056 48% /
tmpfs 4042268 0 4042268 0% /dev/shm
/dev/sda2 412849860 348092000 43741860 89% /home
EOF
# sehat: load 0.40, memori 42%, / 48%, /home 51%
mkdir -p sehat
printf '0.40 0.35 0.30 1/512 21001\n' > sehat/loadavg
sed 's/1280000/4640000/' waspada/meminfo > sehat/meminfo
sed 's| 89% /home| 51% /home|' waspada/df.txt > sehat/df.txt
# kritis: load 4.10, memori 95%, / 48%, /home 91%
mkdir -p kritis
printf '4.10 3.20 2.10 9/640 55002\n' > kritis/loadavg
sed 's/1280000/400000/' waspada/meminfo > kritis/meminfo
sed 's| 89% /home| 91% /home|' waspada/df.txt > kritis/df.txt
# --- uji 1: parsing nilai dari fixture ----------------------------------
MONITOR_STAMP=2026-01-15T09:00:00+07:00 bash monitor.sh --data-dir waspada \
> laporan.txt
assert_eq 1 "$?" "eksekusi waspada keluar 1"
assert_eq 2.50 "$(awk '$1 == "cpu-load" { print $2 }' laporan.txt)" "load 1 menit terbaca 2.50"
assert_eq 84% "$(awk '$1 == "memori" { print $2 }' laporan.txt)" "memori terpakai 84 persen"
assert_eq 48% "$(awk '$1 == "disk:/" { print $2 }' laporan.txt)" "disk / terpakai 48 persen"
assert_eq 89% "$(awk '$1 == "disk:/home" { print $2 }' laporan.txt)" "disk /home terpakai 89 persen"
# --- uji 2: status per metrik pada tiga keadaan --------------------------
MONITOR_STAMP=2026-01-15T09:05:00+07:00 bash monitor.sh --data-dir sehat \
> sehat.txt
assert_eq 0 "$?" "eksekusi sehat keluar 0"
for m in cpu-load memori disk:/ disk:/home; do
assert_eq OK "$(awk -v m="$m" '$1 == m { print $NF }' sehat.txt)" "status $m OK saat sehat"
done
MONITOR_STAMP=2026-01-15T09:10:00+07:00 bash monitor.sh --data-dir kritis \
> kritis.txt
assert_eq 2 "$?" "eksekusi kritis keluar 2"
assert_eq KRITIS "$(awk '$1 == "cpu-load" { print $NF }' kritis.txt)" "status cpu-load KRITIS"
assert_eq KRITIS "$(awk '$1 == "memori" { print $NF }' kritis.txt)" "status memori KRITIS"
assert_eq OK "$(awk '$1 == "disk:/" { print $NF }' kritis.txt)" "status disk / tetap OK"
assert_eq KRITIS "$(awk '$1 == "disk:/home" { print $NF }' kritis.txt)" "status disk /home KRITIS"
# --- uji 3: laporan waspada memuat campuran status -----------------------
assert_eq WASPADA "$(awk '$1 == "cpu-load" { print $NF }' laporan.txt)" "status cpu-load WASPADA"
assert_eq OK "$(awk '$1 == "disk:/" { print $NF }' laporan.txt)" "status disk / OK saat waspada"
# --- uji 4: log terstruktur ditulis --------------------------------------
jumlah=$(wc -l < monitor.log)
assert_eq 12 "$jumlah" "tiga eksekusi menulis 12 baris log"
jumlah=$(grep -c '^2026-01-15T09:00:00+07:00' monitor.log)
assert_eq 4 "$jumlah" "satu eksekusi menulis 4 baris berstempel sama"
jumlah=$(grep -c ' ALERT ' monitor.log)
assert_eq 0 "$jumlah" "belum ada baris ALERT sebelum hitungan cukup"
# --- uji 5: alert terkirim setelah N kali berturut-turut ------------------
# Hitungan dimulai dari nol; tiga bentuk pemanggilan sumber data
# diuji bergantian pada empat eksekusi berikut.
rm -f monitor.state
: > err.txt
MONITOR_STAMP=2026-01-15T10:00:00+07:00 bash monitor.sh --data-dir waspada \
> /dev/null 2>> err.txt
assert_eq 1 "$?" "run 1 (--data-dir) keluar 1"
MONITOR_STAMP=2026-01-15T10:01:00+07:00 bash monitor.sh --data-dir=waspada \
> /dev/null 2>> err.txt
assert_eq 1 "$?" "run 2 (--data-dir=DIR) keluar 1"
MONITOR_SOURCE=waspada MONITOR_STAMP=2026-01-15T10:02:00+07:00 bash monitor.sh \
> /dev/null 2>> err.txt
assert_eq 1 "$?" "run 3 (MONITOR_SOURCE) keluar 1"
MONITOR_STAMP=2026-01-15T10:03:00+07:00 bash monitor.sh --data-dir waspada \
> /dev/null 2>> err.txt
assert_eq 1 "$?" "run 4 keluar 1 tanpa alert baru"
assert_eq 4 "$(awk '$1 == "cpu-load" { print $2 }' monitor.state)" "hitungan cpu-load naik sampai 4"
jumlah=$(grep -c ALERT err.txt)
assert_eq 3 "$jumlah" "alert terkirim tepat sekali per metrik"
grep -q '^monitor.sh: ALERT cpu-load = 2.50 melewati ambang 3x' err.txt
assert_eq 0 "$?" "isi alert menyebut metrik dan nilainya"
MONITOR_STAMP=2026-01-15T10:04:00+07:00 bash monitor.sh --data-dir sehat \
> /dev/null
assert_eq 0 "$?" "eksekusi sehat setelah deret waspada keluar 0"
[ ! -s monitor.state ]
assert_eq 0 "$?" "state dikosongkan saat semua metrik kembali OK"
# --- uji 6: rotasi log ----------------------------------------------------
cat > rotasi.conf <<'CONF'
LOAD_WARN="1.50"
LOAD_KRITIS="3.00"
MEM_WARN_PCT=80
MEM_KRITIS_PCT=90
DISK_WARN_PCT=80
DISK_KRITIS_PCT=90
DISK_MOUNTS="/ /home"
LOG_FILE="log/monitor.log"
LOG_MAX_BYTES=1
LOG_KEEP=3
ALERT_REPEAT=3
CONF
for i in 1 2 3; do
MONITOR_CONF=rotasi.conf MONITOR_STAMP=2026-01-15T11:00:00+07:00 \
bash monitor.sh --data-dir sehat > /dev/null 2>&1
done
[ -f log/monitor.log.1 ]
assert_eq 0 "$?" "rotasi menghasilkan monitor.log.1"
[ -f log/monitor.log.2 ]
assert_eq 0 "$?" "rotasi kedua menghasilkan monitor.log.2"
[ ! -f log/monitor.log.3 ]
assert_eq 0 "$?" "LOG_KEEP=3 menahan log.3"
jumlah=$(wc -l < log/monitor.log)
assert_eq 4 "$jumlah" "log aktif berisi 4 baris setelah rotasi"
# --- uji 7: konfigurasi dan sumber tidak valid keluar 3 -------------------
MONITOR_CONF=halang.conf bash monitor.sh --data-dir waspada > /dev/null 2>&1
assert_eq 3 "$?" "berkas konfigurasi hilang keluar 3"
bash monitor.sh --data-dir waspada --bogus > /dev/null 2>&1
assert_eq 3 "$?" "argumen tidak dikenal keluar 3"
cat > terbalik.conf <<'CONF'
LOAD_WARN="3.00"
LOAD_KRITIS="1.50"
MEM_WARN_PCT=80
MEM_KRITIS_PCT=90
DISK_WARN_PCT=80
DISK_KRITIS_PCT=90
DISK_MOUNTS="/ /home"
LOG_FILE="monitor.log"
LOG_MAX_BYTES=1048576
LOG_KEEP=3
ALERT_REPEAT=3
CONF
MONITOR_CONF=terbalik.conf bash monitor.sh --data-dir waspada > /dev/null 2>&1
assert_eq 3 "$?" "ambang waspada >= kritis ditolak"
sed 's/MEM_WARN_PCT=80/MEM_WARN_PCT=delapan/' monitor.conf > rusak.conf
MONITOR_CONF=rusak.conf bash monitor.sh --data-dir waspada > /dev/null 2>&1
assert_eq 3 "$?" "ambang bukan angka ditolak"
cat > salah-mount.conf <<'CONF'
LOAD_WARN="1.50"
LOAD_KRITIS="3.00"
MEM_WARN_PCT=80
MEM_KRITIS_PCT=90
DISK_WARN_PCT=80
DISK_KRITIS_PCT=90
DISK_MOUNTS="/ /data"
LOG_FILE="log2/monitor.log"
LOG_MAX_BYTES=1048576
LOG_KEEP=3
ALERT_REPEAT=3
CONF
MONITOR_CONF=salah-mount.conf bash monitor.sh --data-dir waspada > /dev/null 2>&1
assert_eq 3 "$?" "mount yang tidak ada di df ditolak"
mkdir -p rusak
printf '2.50 2.10 1.60 4/812 34567\n' > rusak/loadavg
printf 'MemTotal: 8000000 kB\n' > rusak/meminfo
cp waspada/df.txt rusak/df.txt
bash monitor.sh --data-dir rusak > /dev/null 2>&1
assert_eq 3 "$?" "meminfo tanpa MemAvailable ditolak"
# --- ringkasan --------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
printf 'PASSED: %d/%d test lulus\n' "$PASS" "$PASS"
else
printf 'FAILED: %d/%d test lulus\n' "$PASS" "$((PASS + FAIL))"
fi
cd / && rm -rf "$WORK"
[ "$FAIL" -eq 0 ]