Last active 1 month ago

monitor_disk_stats.sh Raw
1
2#!/bin/bash
3
4# Script to check comprehensive disk statistics
5# Author: Alex@portalZINE.de
6# Date: 25.11.2025
7
8#####################################
9# CONFIGURATION
10#####################################
11DEFAULT_DEVICE="/dev/sda1" # Default device to monitor
12
13# Ntfy Configuration
14NTFY_URL="https://your-ntfy-server.com/topic"
15# Leave the ":", when using a token
16NTFY_TOKEN=":your_token_here"
17# NTFY_TOKEN="user:password"
18NTFY_TITLE="Whatever Title You Want"
19#####################################
20
21# Initialize output variable
22OUTPUT=""
23
24# Function to add to output
25add_output() {
26 OUTPUT="${OUTPUT}${1}\n"
27}
28
29# Function to display usage
30usage() {
31 cat << EOF
32Usage: $(basename "$0") [OPTIONS] [DEVICE]
33
34Check comprehensive disk statistics for a device.
35
36OPTIONS:
37 -h, --help Show this help message
38 -d, --device DEVICE Specify device to check (e.g., /dev/sda1)
39 -q, --quiet Quiet mode - minimal output
40 -v, --verbose Verbose mode - show all available stats
41 -s, --section NAME Show only specific section(s) (comma-separated)
42 -l, --list-sections List all available sections and exit
43 -n, --ntfy Send notification via ntfy (no console output)
44 --ntfy-always Send ntfy notification AND show console output
45
46AVAILABLE SECTIONS:
47 all Show all sections (default)
48 basic Basic disk usage (size, used, available)
49 inodes Inode usage statistics
50 filesystem Filesystem information (type, blocks)
51 mount Mount options
52 device Device information (lsblk output)
53 io I/O statistics (reads, writes, sectors)
54 analysis Usage analysis and warnings
55 consumers Top space consumers (largest directories)
56
57ARGUMENTS:
58 DEVICE Device to check (default: $DEFAULT_DEVICE)
59
60NTFY CONFIGURATION:
61 Configure ntfy settings at the top of the script:
62 - NTFY_URL Ntfy server URL
63 - NTFY_TOKEN Authentication token
64 - NTFY_TITLE Notification title
65
66EXAMPLES:
67 $(basename "$0") # Check default device ($DEFAULT_DEVICE)
68 $(basename "$0") /dev/sdb1 # Check /dev/sdb1
69 $(basename "$0") -d /dev/nvme0n1p1 # Check NVMe device
70 $(basename "$0") --quiet /dev/sda2 # Quiet mode for /dev/sda2
71 $(basename "$0") -s basic # Show only basic usage
72 $(basename "$0") -s basic,analysis # Show basic usage and analysis
73 $(basename "$0") -s io,consumers /dev/sdb1 # Show I/O stats and top consumers
74 $(basename "$0") --list-sections # List all available sections
75 $(basename "$0") --ntfy # Send notification only (no output)
76 $(basename "$0") --ntfy-always # Send notification AND show output
77
78EOF
79 exit 0
80}
81
82# Function to list all available sections
83list_sections() {
84 cat << EOF
85Available Sections:
86===================
87
88 all - Show all sections (default behavior)
89 basic - Basic disk usage information
90 • Filesystem name
91 • Total size
92 • Used space
93 • Available space
94 • Usage percentage
95 • Mount point
96
97 inodes - Inode usage statistics
98 • Total inodes
99 • Used inodes
100 • Free inodes
101 • Inode usage percentage
102
103 filesystem - Filesystem information
104 • Filesystem type (ext4, xfs, etc.)
105 • Block size
106 • Total blocks
107 • Free blocks
108 • Available blocks
109
110 mount - Mount options
111 • Full mount command output
112 • Mount flags (rw, relatime, etc.)
113
114 device - Device information
115 • Device name
116 • Size
117 • Type
118 • Filesystem type
119 • Mount point
120 • Label
121 • UUID
122
123 io - I/O statistics
124 • Read operations
125 • Write operations
126 • Sectors read/written
127 • Read/write time
128 • I/O operations in progress
129 • Extended iostat (verbose mode only)
130
131 analysis - Usage analysis
132 • Disk space status (OK/WARNING/CRITICAL)
133 • Inode status (OK/WARNING/CRITICAL)
134 • Threshold-based alerts
135
136 consumers - Top space consumers
137 • Largest directories on the partition
138 • Top 10 (normal) or top 20 (verbose)
139 • Human-readable sizes
140
141Usage Examples:
142 $(basename "$0") -s basic # Just the essentials
143 $(basename "$0") -s basic,analysis # Quick health check
144 $(basename "$0") -s io # I/O performance only
145 $(basename "$0") -s all # Everything (same as no -s flag)
146
147EOF
148 exit 0
149}
150
151# Parse command line arguments
152DEVICE=""
153QUIET_MODE=false
154VERBOSE_MODE=false
155SECTIONS=""
156NTFY_MODE=false
157NTFY_ALWAYS=false
158
159while [[ $# -gt 0 ]]; do
160 case $1 in
161 -h|--help)
162 usage
163 ;;
164 -l|--list-sections)
165 list_sections
166 ;;
167 -d|--device)
168 DEVICE="$2"
169 shift 2
170 ;;
171 -q|--quiet)
172 QUIET_MODE=true
173 shift
174 ;;
175 -v|--verbose)
176 VERBOSE_MODE=true
177 shift
178 ;;
179 -s|--section)
180 SECTIONS="$2"
181 shift 2
182 ;;
183 -n|--ntfy)
184 NTFY_MODE=true
185 shift
186 ;;
187 --ntfy-always)
188 NTFY_ALWAYS=true
189 shift
190 ;;
191 -*)
192 echo "Error: Unknown option: $1"
193 echo "Use -h or --help for usage information"
194 exit 1
195 ;;
196 *)
197 # Assume it's a device path
198 if [ -z "$DEVICE" ]; then
199 DEVICE="$1"
200 else
201 echo "Error: Multiple devices specified"
202 exit 1
203 fi
204 shift
205 ;;
206 esac
207done
208
209# Use default device if none specified
210if [ -z "$DEVICE" ]; then
211 DEVICE="$DEFAULT_DEVICE"
212fi
213
214# Function to check if a section should be displayed
215should_show_section() {
216 local section=$1
217
218 # If no sections specified, show all
219 if [ -z "$SECTIONS" ]; then
220 return 0
221 fi
222
223 # If 'all' is specified, show everything
224 if [[ ",$SECTIONS," == *",all,"* ]]; then
225 return 0
226 fi
227
228 # Check if section is in the comma-separated list
229 if [[ ",$SECTIONS," == *",$section,"* ]]; then
230 return 0
231 fi
232
233 return 1
234}
235
236add_output "========================================"
237add_output "Disk Statistics Report for $DEVICE"
238add_output "========================================"
239if [ "$QUIET_MODE" = false ]; then
240 add_output "Report generated: $(date '+%Y-%m-%d %H:%M:%S')"
241fi
242add_output ""
243
244# Check if device exists
245if [ ! -b "$DEVICE" ]; then
246 echo "Error: $DEVICE does not exist or is not a block device"
247 exit 1
248fi
249
250# Get mount point
251MOUNT_POINT=$(df "$DEVICE" 2>/dev/null | awk 'NR==2 {print $6}')
252
253if [ -z "$MOUNT_POINT" ]; then
254 echo "Error: $DEVICE is not mounted"
255 exit 1
256fi
257
258# Quiet mode - show only essential info
259if [ "$QUIET_MODE" = true ]; then
260 QUIET_INFO=$(df -h "$DEVICE" | awk 'NR==2 {
261 print "Device: " $1
262 print "Size: " $2
263 print "Used: " $3
264 print "Available: " $4
265 print "Usage: " $5
266 print "Mount: " $6
267 }')
268 add_output "$QUIET_INFO"
269
270 USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//')
271 if [ "$USAGE_PERCENT" -ge 90 ]; then
272 add_output "Status: ⚠️ CRITICAL (${USAGE_PERCENT}%)"
273 elif [ "$USAGE_PERCENT" -ge 80 ]; then
274 add_output "Status: ⚠️ WARNING (${USAGE_PERCENT}%)"
275 else
276 add_output "Status: ✓ OK (${USAGE_PERCENT}%)"
277 fi
278
279 # Print all output at once
280 echo -e "$OUTPUT"
281 exit 0
282fi
283
284if should_show_section "basic"; then
285 add_output "========== BASIC DISK USAGE =========="
286 BASIC_USAGE=$(df -h "$DEVICE" | awk 'NR==2 {
287 print " Filesystem: " $1
288 print " Total Size: " $2
289 print " Used: " $3
290 print " Available: " $4
291 print " Usage: " $5
292 print " Mount Point: " $6
293 }')
294 add_output "$BASIC_USAGE"
295 add_output ""
296fi
297
298if should_show_section "inodes"; then
299 add_output "========== INODE USAGE =========="
300 INODE_USAGE=$(df -hi "$DEVICE" | awk 'NR==2 {
301 print " Total Inodes: " $2
302 print " Used Inodes: " $3
303 print " Free Inodes: " $4
304 print " Inode Usage: " $5
305 }')
306 add_output "$INODE_USAGE"
307 add_output ""
308fi
309
310if should_show_section "filesystem"; then
311 add_output "========== FILESYSTEM INFORMATION =========="
312 # Filesystem type
313 FS_TYPE=$(df -T "$DEVICE" | awk 'NR==2 {print $2}')
314 add_output " Filesystem Type: $FS_TYPE"
315
316 # Block size information
317 BLOCK_SIZE=$(stat -f -c %s "$MOUNT_POINT" 2>/dev/null)
318 if [ -n "$BLOCK_SIZE" ]; then
319 add_output " Block Size: $BLOCK_SIZE bytes"
320 fi
321
322 # Total blocks
323 TOTAL_BLOCKS=$(stat -f -c %b "$MOUNT_POINT" 2>/dev/null)
324 FREE_BLOCKS=$(stat -f -c %f "$MOUNT_POINT" 2>/dev/null)
325 AVAIL_BLOCKS=$(stat -f -c %a "$MOUNT_POINT" 2>/dev/null)
326
327 if [ -n "$TOTAL_BLOCKS" ]; then
328 add_output " Total Blocks: $TOTAL_BLOCKS"
329 add_output " Free Blocks: $FREE_BLOCKS"
330 add_output " Available: $AVAIL_BLOCKS"
331 fi
332 add_output ""
333fi
334
335if should_show_section "mount"; then
336 add_output "========== MOUNT OPTIONS =========="
337 MOUNT_INFO=$(mount | grep "$DEVICE")
338 if [ -n "$MOUNT_INFO" ]; then
339 add_output " $MOUNT_INFO"
340 fi
341 add_output ""
342fi
343
344if should_show_section "device"; then
345 add_output "========== DISK DEVICE INFO =========="
346 if command -v lsblk &> /dev/null; then
347 LSBLK_INFO=$(lsblk "$DEVICE" -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL,UUID 2>/dev/null | sed 's/^/ /')
348 add_output "$LSBLK_INFO"
349 fi
350 add_output ""
351fi
352
353if should_show_section "io"; then
354 add_output "========== I/O STATISTICS =========="
355 if [ -f /proc/diskstats ]; then
356 # Extract device name without /dev/ prefix
357 DEVICE_NAME=$(basename "$DEVICE")
358 # Extract stats for the device
359 DISK_STATS=$(grep " $DEVICE_NAME " /proc/diskstats)
360 if [ -n "$DISK_STATS" ]; then
361 IO_STATS=$(echo "$DISK_STATS" | awk '{
362 print " Device: " $3
363 print " Reads: " $4
364 print " Reads Merged: " $5
365 print " Sectors Read: " $6
366 print " Read Time (ms): " $7
367 print " Writes: " $8
368 print " Writes Merged: " $9
369 print " Sectors Written: " $10
370 print " Write Time (ms): " $11
371 print " I/Os in Progress:" $12
372 print " I/O Time (ms): " $13
373 }')
374 add_output "$IO_STATS"
375 fi
376 fi
377
378 # iostat if available - show in verbose mode or if explicitly installed
379 if command -v iostat &> /dev/null && [ "$VERBOSE_MODE" = true ]; then
380 add_output ""
381 add_output " Extended I/O Stats:"
382 IOSTAT_INFO=$(iostat -x "$DEVICE" 1 1 2>/dev/null | tail -n +4 | sed 's/^/ /')
383 add_output "$IOSTAT_INFO"
384 fi
385 add_output ""
386fi
387
388if should_show_section "analysis"; then
389 add_output "========== USAGE ANALYSIS =========="
390
391 # Get usage percentage as a number
392 USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//')
393 INODE_PERCENT=$(df -i "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//')
394
395 # Disk space warning
396 add_output " Disk Space Status:"
397 if [ "$USAGE_PERCENT" -ge 90 ]; then
398 add_output " ⚠️ CRITICAL: Disk usage is critically high (${USAGE_PERCENT}%)!"
399 elif [ "$USAGE_PERCENT" -ge 80 ]; then
400 add_output " ⚠️ WARNING: Disk usage is high (${USAGE_PERCENT}%)"
401 elif [ "$USAGE_PERCENT" -ge 70 ]; then
402 add_output " ⚡ NOTICE: Disk usage is moderate (${USAGE_PERCENT}%)"
403 else
404 add_output " ✓ OK: Disk usage is within normal range (${USAGE_PERCENT}%)"
405 fi
406
407 # Inode warning
408 add_output ""
409 add_output " Inode Status:"
410 if [ "$INODE_PERCENT" -ge 90 ]; then
411 add_output " ⚠️ CRITICAL: Inode usage is critically high (${INODE_PERCENT}%)!"
412 elif [ "$INODE_PERCENT" -ge 80 ]; then
413 add_output " ⚠️ WARNING: Inode usage is high (${INODE_PERCENT}%)"
414 else
415 add_output " ✓ OK: Inode usage is within normal range (${INODE_PERCENT}%)"
416 fi
417 add_output ""
418fi
419
420if should_show_section "consumers"; then
421 add_output "========== TOP SPACE CONSUMERS =========="
422 if [ -d "$MOUNT_POINT" ]; then
423 add_output " Largest directories in $MOUNT_POINT:"
424 if [ "$VERBOSE_MODE" = true ]; then
425 # Show top 20 in verbose mode
426 TOP_DIRS=$(du -h "$MOUNT_POINT" --max-depth=1 2>/dev/null | sort -rh | head -20 | sed 's/^/ /')
427 else
428 # Show top 10 in normal mode
429 TOP_DIRS=$(du -h "$MOUNT_POINT" --max-depth=1 2>/dev/null | sort -rh | head -10 | sed 's/^/ /')
430 fi
431 add_output "$TOP_DIRS"
432 fi
433 add_output ""
434fi
435
436add_output "========================================"
437add_output "Report completed: $(date '+%Y-%m-%d %H:%M:%S')"
438add_output "========================================"
439
440# Function to send ntfy notification
441send_ntfy() {
442 local message="$1"
443 local priority="${2:-default}"
444 local tags="${3:-}"
445
446 if [ -z "$NTFY_URL" ] || [ -z "$NTFY_TOKEN" ]; then
447 echo "Error: Ntfy configuration is incomplete"
448 return 1
449 fi
450
451 local curl_args=(-u "$NTFY_TOKEN")
452
453 if [ -n "$NTFY_TITLE" ]; then
454 curl_args+=(-H "Title: $NTFY_TITLE")
455 fi
456
457 if [ -n "$priority" ] && [ "$priority" != "default" ]; then
458 curl_args+=(-H "Priority: $priority")
459 fi
460
461 if [ -n "$tags" ]; then
462 curl_args+=(-H "Tags: $tags")
463 fi
464
465 curl_args+=(-d "$message" "$NTFY_URL")
466
467 curl -s "${curl_args[@]}" > /dev/null
468 return $?
469}
470
471# Prepare notification if ntfy is enabled
472if [ "$NTFY_MODE" = true ] || [ "$NTFY_ALWAYS" = true ]; then
473 # Get key metrics for determining priority
474 USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//')
475 INODE_PERCENT=$(df -i "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//')
476
477 # Determine priority and tags based on usage
478 PRIORITY="default"
479 TAGS="white_check_mark"
480
481 if [ "$USAGE_PERCENT" -ge 90 ] || [ "$INODE_PERCENT" -ge 90 ]; then
482 PRIORITY="urgent"
483 TAGS="warning,skull"
484 elif [ "$USAGE_PERCENT" -ge 80 ] || [ "$INODE_PERCENT" -ge 80 ]; then
485 PRIORITY="high"
486 TAGS="warning"
487 elif [ "$USAGE_PERCENT" -ge 70 ] || [ "$INODE_PERCENT" -ge 70 ]; then
488 PRIORITY="default"
489 TAGS="zap"
490 fi
491
492 # Send the full output as notification
493 NTFY_MESSAGE=$(echo -e "$OUTPUT")
494
495 # Send notification
496 if send_ntfy "$NTFY_MESSAGE" "$PRIORITY" "$TAGS"; then
497 if [ "$NTFY_MODE" = true ]; then
498 echo "Notification sent successfully to ntfy"
499 exit 0
500 fi
501 else
502 echo "Error: Failed to send ntfy notification"
503 if [ "$NTFY_MODE" = true ]; then
504 exit 1
505 fi
506 fi
507fi
508
509# Print all output at once at the end (unless ntfy-only mode)
510if [ "$NTFY_MODE" = false ]; then
511 echo -e "$OUTPUT"
512fi
513