Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 381 additions & 0 deletions machine_report_macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
#!/bin/bash
# TR-100 Machine Report (macOS Compatible)
# Copyright © 2024, U.S. Graphics, LLC. BSD-3-Clause License.

# Global variables
MIN_NAME_LEN=5
MAX_NAME_LEN=13

MIN_DATA_LEN=20
MAX_DATA_LEN=32

BORDERS_AND_PADDING=7

# Basic configuration, change as needed
report_title="UNITED STATES GRAPHICS COMPANY"
last_login_ip_present=0
zfs_present=0
zfs_filesystem="zroot/ROOT/os"

# Utilities
max_length() {
local max_len=0
local len

for str in "$@"; do
len=${#str}
if (( len > max_len )); then
max_len=$len
fi
done

if [ $max_len -lt $MAX_DATA_LEN ]; then
printf '%s' "$max_len"
else
printf '%s' "$MAX_DATA_LEN"
fi
}

# All data strings must go here
set_current_len() {
CURRENT_LEN=$(max_length \
"$report_title" \
"$os_name" \
"$os_kernel" \
"$net_hostname" \
"$net_machine_ip" \
"$net_client_ip" \
"$net_current_user" \
"$cpu_model" \
"$cpu_cores_per_socket vCPU(s) / $cpu_sockets Socket(s)" \
"$cpu_hypervisor" \
"$cpu_freq GHz" \
"$cpu_1min_bar_graph" \
"$cpu_5min_bar_graph" \
"$cpu_15min_bar_graph" \
"$zfs_used_gb/$zfs_available_gb GB [$disk_percent%]" \
"$disk_bar_graph" \
"$zfs_health" \
"$root_used_gb/$root_total_gb GB [$disk_percent%]" \
"${mem_used_gb}/${mem_total_gb} GiB [${mem_percent}%]" \
"${mem_bar_graph}" \
"$last_login_time" \
"$last_login_ip" \
"$last_login_ip" \
"$sys_uptime" \
)
}

PRINT_HEADER() {
local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))

local top="┌"
local bottom="├"
for (( i = 0; i < length - 2; i++ )); do
top+="┬"
bottom+="┴"
done
top+="┐"
bottom+="┤"

printf '%s\n' "$top"
printf '%s\n' "$bottom"
}

PRINT_CENTERED_DATA() {
local max_len=$((CURRENT_LEN+MAX_NAME_LEN-BORDERS_AND_PADDING))
local text="$1"
local total_width=$((max_len + 12))

local text_len=${#text}
local padding_left=$(( (total_width - text_len) / 2 ))
local padding_right=$(( total_width - text_len - padding_left ))

printf "│%${padding_left}s%s%${padding_right}s│\n" "" "$text" ""
}

PRINT_DIVIDER() {
# either "top" or "bottom", no argument means middle divider
local side="$1"
case "$side" in
"top")
local left_symbol="├"
local middle_symbol="┬"
local right_symbol="┤"
;;
"bottom")
local left_symbol="└"
local middle_symbol="┴"
local right_symbol="┘"
;;
*)
local left_symbol="├"
local middle_symbol="┼"
local right_symbol="┤"
esac

local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))
local divider="$left_symbol"
for (( i = 0; i < length - 3; i++ )); do
divider+="─"
if [ "$i" -eq 14 ]; then
divider+="$middle_symbol"
fi
done
divider+="$right_symbol"
printf '%s\n' "$divider"
}

PRINT_DATA() {
local name="$1"
local data="$2"
local max_data_len=$CURRENT_LEN

# Pad name
local name_len=${#name}
if (( name_len < MIN_NAME_LEN )); then
name=$(printf "%-${MIN_NAME_LEN}s" "$name")
elif (( name_len > MAX_NAME_LEN )); then
name=$(echo "$name" | cut -c 1-$((MAX_NAME_LEN-3)))...
else
name=$(printf "%-${MAX_NAME_LEN}s" "$name")
fi

# Truncate or pad data
local data_len=${#data}
if (( data_len >= MAX_DATA_LEN || data_len == MAX_DATA_LEN-1 )); then
data=$(echo "$data" | cut -c 1-$((MAX_DATA_LEN-3-2)))...
else
data=$(printf "%-${max_data_len}s" "$data")
fi

printf "│ %-${MAX_NAME_LEN}s │ %s │\n" "$name" "$data"
}

PRINT_FOOTER() {
local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))
local footer="└"
for (( i = 0; i < length - 3; i++ )); do
footer+="─"
if [ "$i" -eq 14 ]; then
footer+="┴"
fi
done
footer+="┘"
printf '%s\n' "$footer"
}

bar_graph() {
local percent
local num_blocks
local width=$CURRENT_LEN
local graph=""
local used=$1
local total=$2

if (( total == 0 )); then
percent=0
else
percent=$(awk -v used="$used" -v total="$total" 'BEGIN { printf "%.2f", (used / total) * 100 }')
fi

num_blocks=$(awk -v percent="$percent" -v width="$width" 'BEGIN { printf "%d", (percent / 100) * width }')

for (( i = 0; i < num_blocks; i++ )); do
graph+="█"
done
for (( i = num_blocks; i < width; i++ )); do
graph+="░"
done
printf "%s" "${graph}"
}

get_ip_addr() {
# Initialize variables
ipv4_address=""
ipv6_address=""

# Check if ifconfig command exists
if command -v ifconfig &> /dev/null; then
# Try to get IPv4 address using ifconfig
ipv4_address=$(ifconfig | awk '
/^[a-z]/ {iface=$1}
iface != "lo0:" && iface !~ /^docker/ && /inet / && !/127\.0\.0\.1/ && !found_ipv4 {found_ipv4=1; print $2}')

# If IPv4 address not available, try IPv6 using ifconfig
if [ -z "$ipv4_address" ]; then
ipv6_address=$(ifconfig | awk '
/^[a-z]/ {iface=$1}
iface != "lo0:" && iface !~ /^docker/ && /inet6 / && !/::1/ && !found_ipv6 {found_ipv6=1; print $2}')
fi
fi

# If neither IPv4 nor IPv6 address is available, assign "No IP found"
if [ -z "$ipv4_address" ] && [ -z "$ipv6_address" ]; then
ip_address="No IP found"
else
# Prioritize IPv4 if available, otherwise use IPv6
ip_address="${ipv4_address:-$ipv6_address}"
fi

printf '%s' "$ip_address"
}

# Operating System Information (macOS)
os_name="macOS $(sw_vers -productVersion)"
os_kernel="$(uname -s) $(uname -r)"

# Network Information
net_current_user=$(whoami)
net_hostname=$(hostname)
if [ -z "$net_hostname" ]; then net_hostname="Not Defined"; fi

net_machine_ip=$(get_ip_addr)
net_client_ip=$(who am i | awk '{print $5}' | tr -d '()')
if [ -z "$net_client_ip" ]; then
net_client_ip="Not connected"
fi

# Get DNS from scutil
net_dns_ip=($(scutil --dns | grep 'nameserver\[[0-9]*\]' | awk '{print $3}' | sort -u))

# CPU Information (macOS)
cpu_model="$(sysctl -n machdep.cpu.brand_string | awk '{print $1 " " $2 " " $3 " " $4}')"
cpu_hypervisor="Bare Metal"

# Check if running in a VM
if sysctl -n machdep.cpu.features | grep -q "VMX\|SVM"; then
vm_check=$(system_profiler SPHardwareDataType | grep "Model Name" | grep -i "virtual\|vmware\|parallels")
if [ -n "$vm_check" ]; then
cpu_hypervisor="Virtual Machine"
fi
fi

cpu_cores="$(sysctl -n hw.ncpu)"
cpu_cores_per_socket="$cpu_cores"
cpu_sockets="1"
cpu_freq="$(sysctl -n hw.cpufrequency 2>/dev/null | awk '{ printf "%.2f", $1 / 1000000000 }')"
if [ -z "$cpu_freq" ]; then
cpu_freq="$(sysctl -n hw.cpufrequency_max 2>/dev/null | awk '{ printf "%.2f", $1 / 1000000000 }')"
fi
if [ -z "$cpu_freq" ]; then
cpu_freq="N/A"
fi

# Load averages
load_avg_1min=$(sysctl -n vm.loadavg | awk '{print $2}')
load_avg_5min=$(sysctl -n vm.loadavg | awk '{print $3}')
load_avg_15min=$(sysctl -n vm.loadavg | awk '{print $4}')

# Memory Information (macOS)
mem_total=$(sysctl -n hw.memsize)
mem_total_kb=$((mem_total / 1024))

# Get memory pressure and calculate used memory
vm_stat_output=$(vm_stat)
page_size=$(vm_stat | grep "page size" | awk '{print $8}')

pages_wired=$(echo "$vm_stat_output" | grep "Pages wired down" | awk '{print $4}' | tr -d '.')
pages_active=$(echo "$vm_stat_output" | grep "Pages active" | awk '{print $3}' | tr -d '.')
pages_compressed=$(echo "$vm_stat_output" | grep "Pages occupied by compressor" | awk '{print $5}' | tr -d '.')

mem_used_bytes=$(((pages_wired + pages_active + pages_compressed) * page_size))
mem_used_kb=$((mem_used_bytes / 1024))

mem_percent=$(awk -v used="$mem_used_kb" -v total="$mem_total_kb" 'BEGIN { printf "%.2f", (used / total) * 100 }')
mem_total_gb=$(echo "$mem_total_kb" | awk '{ printf "%.2f", $1 / (1024 * 1024) }')
mem_used_gb=$(echo "$mem_used_kb" | awk '{ printf "%.2f", $1 / (1024 * 1024) }')

# Disk Information (macOS)
root_partition="/"
disk_info=$(df -g "$root_partition" | awk 'NR==2 {print $2, $3}')
root_total_gb=$(echo "$disk_info" | awk '{printf "%.2f", $1}')
root_used_gb=$(echo "$disk_info" | awk '{printf "%.2f", $2}')
disk_percent=$(awk -v used="$root_used_gb" -v total="$root_total_gb" 'BEGIN { printf "%.2f", (used / total) * 100 }')

# Last login and Uptime
last_login=$(last -1 "$USER" | head -n 1)
last_login_ip=$(echo "$last_login" | awk '{print $3}')

# Check if last_login_ip is an IP address
if [[ "$last_login_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
last_login_ip_present=1
last_login_time=$(echo "$last_login" | awk '{print $4, $5, $6, $7}')
else
last_login_time=$(echo "$last_login" | awk '{print $4, $5, $6, $7}')
fi

# macOS uptime formatting
boot_time=$(sysctl -n kern.boottime | awk '{print $4}' | tr -d ',')
current_time=$(date +%s)
uptime_seconds=$((current_time - boot_time))
uptime_days=$((uptime_seconds / 86400))
uptime_hours=$(( (uptime_seconds % 86400) / 3600 ))
uptime_minutes=$(( (uptime_seconds % 3600) / 60 ))

sys_uptime=""
if [ $uptime_days -gt 0 ]; then
sys_uptime="${uptime_days}d "
fi
if [ $uptime_hours -gt 0 ]; then
sys_uptime="${sys_uptime}${uptime_hours}h "
fi
if [ $uptime_minutes -gt 0 ]; then
sys_uptime="${sys_uptime}${uptime_minutes}m"
fi
sys_uptime=$(echo "$sys_uptime" | sed 's/ $//')

# Set current length before graphs get calculated
set_current_len

# Create graphs
cpu_1min_bar_graph=$(bar_graph "$load_avg_1min" "$cpu_cores")
cpu_5min_bar_graph=$(bar_graph "$load_avg_5min" "$cpu_cores")
cpu_15min_bar_graph=$(bar_graph "$load_avg_15min" "$cpu_cores")

mem_bar_graph=$(bar_graph "$mem_used_kb" "$mem_total_kb")

disk_bar_graph=$(bar_graph "$root_used_gb" "$root_total_gb")

# Machine Report
PRINT_HEADER
PRINT_CENTERED_DATA "$report_title"
PRINT_CENTERED_DATA "TR-100 MACHINE REPORT"
PRINT_DIVIDER "top"
PRINT_DATA "OS" "$os_name"
PRINT_DATA "KERNEL" "$os_kernel"
PRINT_DIVIDER
PRINT_DATA "HOSTNAME" "$net_hostname"
PRINT_DATA "MACHINE IP" "$net_machine_ip"
PRINT_DATA "CLIENT IP" "$net_client_ip"

for dns_num in "${!net_dns_ip[@]}"; do
PRINT_DATA "DNS IP $(($dns_num + 1))" "${net_dns_ip[dns_num]}"
done

PRINT_DATA "USER" "$net_current_user"
PRINT_DIVIDER
PRINT_DATA "PROCESSOR" "$cpu_model"
PRINT_DATA "CORES" "$cpu_cores_per_socket vCPU(s) / $cpu_sockets Socket(s)"
PRINT_DATA "HYPERVISOR" "$cpu_hypervisor"
PRINT_DATA "CPU FREQ" "$cpu_freq GHz"
PRINT_DATA "LOAD 1m" "$cpu_1min_bar_graph"
PRINT_DATA "LOAD 5m" "$cpu_5min_bar_graph"
PRINT_DATA "LOAD 15m" "$cpu_15min_bar_graph"

PRINT_DIVIDER
PRINT_DATA "VOLUME" "$root_used_gb/$root_total_gb GB [$disk_percent%]"
PRINT_DATA "DISK USAGE" "$disk_bar_graph"

PRINT_DIVIDER
PRINT_DATA "MEMORY" "${mem_used_gb}/${mem_total_gb} GiB [${mem_percent}%]"
PRINT_DATA "USAGE" "${mem_bar_graph}"
PRINT_DIVIDER
PRINT_DATA "LAST LOGIN" "$last_login_time"

if [ $last_login_ip_present -eq 1 ]; then
PRINT_DATA "" "$last_login_ip"
fi

PRINT_DATA "UPTIME" "$sys_uptime"
PRINT_DIVIDER "bottom"