Fuma83

system-stats

Aug 19th, 2025 (edited)
137
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.44 KB | None | 0 0
  1. #!/usr/bin/env -S bash
  2.  
  3. # A Bash script to monitor system stats and output them in JSON format.
  4. # This script is a conversion of ZigStat
  5.  
  6. # --- Configuration ---
  7. # Default sleep duration in seconds. Can be overridden by the first argument.
  8. SLEEP_DURATION=3
  9.  
  10. # --- Argument Parsing ---
  11. # Check if a command-line argument is provided for the sleep duration.
  12. if [[ -n "$1" ]]; then
  13. # Basic validation to ensure the argument is a number (integer or float).
  14. if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
  15. SLEEP_DURATION=$1
  16. else
  17. # Output to stderr if the format is invalid.
  18. echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
  19. fi
  20. fi
  21.  
  22. # --- Global Cache Variables ---
  23. # These variables will store the discovered CPU temperature sensor path and type
  24. # to avoid searching for it on every loop iteration.
  25. TEMP_SENSOR_PATH=""
  26. TEMP_SENSOR_TYPE=""
  27.  
  28. # --- Data Collection Functions ---
  29.  
  30. #
  31. # Gets memory usage in GB, MB, and as a percentage.
  32. #
  33. get_memory_info() {
  34. awk '
  35. /MemTotal/ {total=$2}
  36. /MemAvailable/ {available=$2}
  37. END {
  38. if (total > 0) {
  39. usage_kb = total - available
  40. usage_gb = usage_kb / 1000000
  41. usage_percent = (usage_kb / total) * 100
  42. printf "%.1f %.0f\n", usage_gb, usage_percent
  43. } else {
  44. # Fallback if /proc/meminfo is unreadable or empty.
  45. print "0.0 0 0"
  46. }
  47. }
  48. ' /proc/meminfo
  49. }
  50.  
  51. #
  52. # Gets the usage percentage of the root filesystem ("/").
  53. #
  54. get_disk_usage() {
  55. # df gets disk usage. --output=pcent shows only the percentage for the root path.
  56. # tail -1 gets the data line, and tr removes the '%' sign and whitespace.
  57. df --output=pcent / | tail -1 | tr -d ' %'
  58. }
  59.  
  60. #
  61. # Calculates current CPU usage over a short interval.
  62. #
  63. get_cpu_usage() {
  64. # Read all 10 CPU time fields to prevent errors on newer kernels.
  65. read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice </proc/stat
  66.  
  67. # Calculate previous total and idle times.
  68. local prev_total_idle=$((prev_idle + prev_iowait))
  69. local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
  70.  
  71. # Wait for a short period.
  72. sleep 0.05
  73.  
  74. # Read all 10 CPU time fields again for the second measurement.
  75. read -r cpu user nice system idle iowait irq softirq steal guest guest_nice </proc/stat
  76.  
  77. # Calculate new total and idle times.
  78. local total_idle=$((idle + iowait))
  79. local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
  80.  
  81. # Add a check to prevent division by zero if total hasn't changed.
  82. if ((total <= prev_total)); then
  83. echo "0.0"
  84. return
  85. fi
  86.  
  87. # Calculate the difference over the interval.
  88. local diff_total=$((total - prev_total))
  89. local diff_idle=$((total_idle - prev_total_idle))
  90.  
  91. # Use awk for floating-point calculation and print the percentage.
  92. awk -v total="$diff_total" -v idle="$diff_idle" '
  93. BEGIN {
  94. if (total > 0) {
  95. # Formula: 100 * (Total - Idle) / Total
  96. usage = 100 * (total - idle) / total
  97. printf "%.1f\n", usage
  98. } else {
  99. print "0.0"
  100. }
  101. }'
  102. }
  103.  
  104. #
  105. # Finds and returns the CPU temperature in degrees Celsius.
  106. # Caches the sensor path for efficiency.
  107. #
  108. get_cpu_temp() {
  109. # If the sensor path hasn't been found yet, search for it.
  110. if [[ -z "$TEMP_SENSOR_PATH" ]]; then
  111. for dir in /sys/class/hwmon/hwmon*; do
  112. # Check if the 'name' file exists and read it.
  113. if [[ -f "$dir/name" ]]; then
  114. local name
  115. name=$(<"$dir/name")
  116. # Check for supported sensor types.
  117. if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" ]]; then
  118.  
  119. TEMP_SENSOR_PATH=$dir
  120. TEMP_SENSOR_TYPE=$name
  121. break # Found it, no need to keep searching.
  122. fi
  123. fi
  124. done
  125. fi
  126.  
  127. # If after searching no sensor was found, return 0.
  128. if [[ -z "$TEMP_SENSOR_PATH" ]]; then
  129. echo 0
  130. return
  131. fi
  132.  
  133. # --- Get temp based on sensor type ---
  134. if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
  135. # For Intel 'coretemp', average all available temperature sensors.
  136. local total_temp=0
  137. local sensor_count=0
  138.  
  139. # Use a for loop with a glob to iterate over all temp input files.
  140. # This is more efficient than 'find' for this simple case.
  141. for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
  142. # The glob returns the pattern itself if no files match,
  143. # so we must check if the file actually exists.
  144. if [[ -f "$temp_file" ]]; then
  145. total_temp=$((total_temp + $(<"$temp_file")))
  146. sensor_count=$((sensor_count + 1))
  147. fi
  148. done
  149.  
  150. if ((sensor_count > 0)); then
  151. # Use awk for the final division to handle potential floating point numbers
  152. # and convert from millidegrees to integer degrees Celsius.
  153. awk -v total="$total_temp" -v count="$sensor_count" 'BEGIN { print int(total / count / 1000) }'
  154. else
  155. # If no sensor files were found, return 0.
  156. echo 0
  157. fi
  158.  
  159. elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
  160. # For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
  161. local tctl_input=""
  162. for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
  163. if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
  164. # The input file has the same name but with '_input' instead of '_label'.
  165. tctl_input="${label_file%_label}_input"
  166. break
  167. fi
  168. done
  169.  
  170. elif [[ "$TEMP_SENSOR_TYPE" == "zenpower" ]]; then
  171. # For zenpower, read the first available temp sensor
  172. for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
  173. if [[ -f "$temp_file" ]]; then
  174. local temp_value
  175. temp_value=$(cat "$temp_file" | tr -d '\n\r') # Remove any newlines
  176. echo "$((temp_value / 1000))"
  177. return
  178. fi
  179. done
  180. echo 0
  181.  
  182. if [[ -f "$tctl_input" ]]; then
  183. # Read the temperature and convert from millidegrees to degrees.
  184. echo "$(($(<"$tctl_input") / 1000))"
  185. else
  186. echo 0 # Fallback
  187. fi
  188. else
  189. echo 0 # Should not happen if cache logic is correct.
  190. fi
  191. }
  192.  
  193. # --- Main Loop ---
  194. # This loop runs indefinitely, gathering and printing stats.
  195. while true; do
  196. # Call the functions to gather all the data.
  197. # get_memory_info
  198. read -r mem_gb mem_per <<<"$(get_memory_info)"
  199.  
  200. # Command substitution captures the single output from the other functions.
  201. disk_per=$(get_disk_usage)
  202. cpu_usage=$(get_cpu_usage)
  203. cpu_temp=$(get_cpu_temp)
  204.  
  205. # Use printf to format the final JSON output string, adding the mem_mb key.
  206. printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s"}\n' \
  207. "$cpu_usage" \
  208. "$cpu_temp" \
  209. "$mem_gb" \
  210. "$mem_per" \
  211. "$disk_per"
  212.  
  213. # Wait for the specified duration before the next update.
  214. sleep "$SLEEP_DURATION"
  215. done
  216.  
Advertisement
Add Comment
Please, Sign In to add comment