diff --git a/memory_chrom.sh b/memory_chrom.sh new file mode 100644 index 0000000..53ef6b4 --- /dev/null +++ b/memory_chrom.sh @@ -0,0 +1,249 @@ +#!/bin/bash + +# This script monitors the total memory usage of all Chrome processes +# and displays an ASCII line chart of the usage over the last 2 hours, +# ensuring the chart always fits within the terminal width. + +# Configuration +INTERVAL_SECONDS=5 # How often to sample memory usage (in seconds) +DURATION_HOURS=2 # How many hours of data to display on the chart +DATA_FILE="/tmp/chrome_memory_data.txt" # Temporary file to store historical data +CHART_HEIGHT=20 # Number of lines for the chart's vertical height (excluding labels) + +# --- Script Initialization --- + +# Clear the terminal when the script starts +tput clear + +# Ensure the temporary data file exists. If it doesn't, touch creates it. +touch "$DATA_FILE" + +# --- Functions --- + +# Function to get the total memory usage of all Chrome processes in MB. +# It sums the Resident Set Size (RSS) for all processes named 'chrome'. +get_chrome_memory_usage() { + # Find all process IDs (PIDs) for processes named 'chrome'. + # '-d,' makes pgrep output PIDs separated by commas, suitable for 'ps -p'. + local pids=$(pgrep -d, chrome) + + # If no Chrome processes are found, return 0. + if [ -z "$pids" ]; then + echo "0" + return + fi + + # Sum the RSS (Resident Set Size) in KB for all identified Chrome PIDs. + # 'ps -p "$pids" -o rss=' outputs only the RSS value in KB for each PID. + # '2>/dev/null' suppresses errors if some PIDs no longer exist. + # 'awk' sums all the RSS values. + local total_rss_kb=$(ps -p "$pids" -o rss= 2>/dev/null | awk '{s+=$1} END {print s}') + + # If no memory is reported (e.g., processes died between pgrep and ps), return 0. + if [ -z "$total_rss_kb" ]; then + echo "0" + return + fi + + # Convert KB to MB (1024 KB = 1 MB) using 'bc' for floating-point arithmetic. + local total_rss_mb=$(echo "scale=2; $total_rss_kb / 1024" | bc) + echo "$total_rss_mb" +} + +# Function to clean old data from the temporary file. +# It removes any entries older than the specified DURATION_HOURS +# and also ensures the number of data points does not exceed terminal width. +clean_old_data() { + local current_timestamp=$(date +%s) # Get current Unix timestamp + # Calculate the cutoff timestamp: current time minus the duration in seconds. + local cutoff_timestamp=$((current_timestamp - DURATION_HOURS * 3600)) # 3600 seconds in an hour + + # First, filter data based on time. + awk -F':' -v cutoff="$cutoff_timestamp" '$1 >= cutoff' "$DATA_FILE" > "${DATA_FILE}.tmp" && \ + mv "${DATA_FILE}.tmp" "$DATA_FILE" + + # Now, ensure the number of data points does not exceed terminal width. + # Get current terminal columns. + local current_columns=$(tput cols) + # Estimate the width taken by the Y-axis label and padding. + # " 999.9 MB |" is roughly 14 characters. + local y_axis_label_width=14 + # Calculate maximum available columns for the chart data points. + local max_chart_columns=$((current_columns - y_axis_label_width - 2)) # -2 for extra padding/border + + # Ensure a minimum number of columns to avoid negative values or very small charts. + if [ "$max_chart_columns" -lt 10 ]; then + max_chart_columns=10 # Ensure at least 10 columns for readability + fi + + # Read the data again after time-based cleaning. + local filtered_data=() + while IFS=':' read -r timestamp mem_mb; do + filtered_data+=("$timestamp:$mem_mb") + done < "$DATA_FILE" + + local num_filtered_points=${#filtered_data[@]} + + # If the number of data points exceeds the maximum chart columns, + # keep only the latest 'max_chart_columns' data points. + if [ "$num_filtered_points" -gt "$max_chart_columns" ]; then + local start_index=$((num_filtered_points - max_chart_columns)) + printf "%s\n" "${filtered_data[@]:$start_index:$max_chart_columns}" > "$DATA_FILE" + fi +} + +# Function to draw the ASCII line chart in the terminal. +draw_chart() { + local data=() # Array to store memory usage values + local timestamps=() # Array to store corresponding timestamps + local max_mem=0 # Maximum memory usage found in the current data + local min_mem=9999999 # Minimum memory usage found (initialized high) + + # Read data from the file into the arrays and find min/max memory. + while IFS=':' read -r timestamp mem_mb; do + timestamps+=("$timestamp") + data+=("$mem_mb") + # Update max_mem if current value is higher. + if (( $(echo "$mem_mb > $max_mem" | bc -l) )); then + max_mem="$mem_mb" + fi + # Update min_mem if current value is lower. + if (( $(echo "$mem_mb < $min_mem" | bc -l) )); then + min_mem="$mem_mb" + fi + done < "$DATA_FILE" + + local num_data_points=${#data[@]} # Total number of data points to chart + + # If no data is available, print a message and exit the function. + if [ "$num_data_points" -eq 0 ]; then + echo "No data to display yet. Waiting for Chrome memory usage..." + return + fi + + # Adjust max_mem and min_mem for better chart scaling and padding. + # Ensure a minimum range if memory usage is very low to prevent flat charts. + if (( $(echo "$max_mem < 10" | bc -l) )); then + max_mem=10 + fi + # If min_mem is very close to max_mem, expand the range slightly for visibility. + if (( $(echo "$max_mem - $min_mem < 5" | bc -l) )); then + local avg_mem=$(echo "scale=2; ($max_mem + $min_mem) / 2" | bc) + max_mem=$(echo "scale=2; $avg_mem + 2.5" | bc) + min_mem=$(echo "scale=2; $avg_mem - 2.5" | bc) + # Ensure min_mem doesn't go below zero. + if (( $(echo "$min_mem < 0" | bc -l) )); then min_mem=0; fi + fi + + # Calculate the total memory range for scaling. + local mem_range=$(echo "scale=2; $max_mem - $min_mem" | bc) + # Prevent division by zero if all memory values are identical. + if (( $(echo "$mem_range == 0" | bc -l) )); then + mem_range=1 + fi + + echo "Chrome Memory Usage (last ${DURATION_HOURS} hours)" + echo "--------------------------------------------------" + + # Array to store the scaled vertical position (row index) for each data point. + local scaled_positions=() + for (( i = 0; i < num_data_points; i++ )); do + local current_mem="${data[i]}" + # Scale the current memory value to a position within the CHART_HEIGHT (0 to CHART_HEIGHT). + local scaled_pos=$(echo "scale=0; ($current_mem - $min_mem) * $CHART_HEIGHT / $mem_range" | bc) + scaled_positions+=("$scaled_pos") + done + + # Draw the chart rows from top to bottom (highest memory level to lowest). + for (( h = CHART_HEIGHT; h >= 0; h-- )); do + local line="" + # Calculate the memory level corresponding to the current row 'h'. + local mem_level_label=$(echo "scale=1; $min_mem + ($mem_range * $h / $CHART_HEIGHT)" | bc) + # Print the Y-axis label (memory level). + LC_NUMERIC="C" printf "%6.1f MB |" "$mem_level_label" + + # Iterate through each data point to draw the chart line. + for (( i = 0; i < num_data_points; i++ )); do + # If the scaled position of the data point matches the current row 'h', print an asterisk. + if [ "${scaled_positions[i]}" -eq "$h" ]; then + line+="*" + else + line+=" " # Otherwise, print a space. + fi + done + echo "$line" + done + + # Draw the X-axis line. + printf "%6s +%s\n" " " "$(printf -- "-%.0s" $(seq 1 "$num_data_points"))" + + # Draw X-axis labels (time). + printf "%6s " " " # Indent for the labels. + local first_timestamp=${timestamps[0]} + local last_timestamp=${timestamps[num_data_points-1]} + + local start_time_str=$(date -d "@$first_timestamp" +%H:%M) # Format first timestamp + local end_time_str=$(date -d "@$last_timestamp" +%H:%M) # Format last timestamp + + local mid_time_str="" + local mid_len=0 + + # Only show a middle label if there's enough horizontal space. + if [ "$num_data_points" -gt 20 ]; then + local mid_timestamp_index=$((num_data_points / 2)) + mid_time_str=$(date -d "@${timestamps[$mid_timestamp_index]}" +%H:%M) + mid_len=${#mid_time_str} + fi + + local start_len=${#start_time_str} + local end_len=${#end_time_str} + local total_label_width=$((start_len + mid_len + end_len)) + + # Distribute labels based on available space. + if [ "$total_label_width" -le "$num_data_points" ]; then + # Enough space for all three labels (start, middle, end). + local padding_before_mid=$(( (num_data_points - total_label_width) / 2 )) + local padding_after_mid=$(( num_data_points - total_label_width - padding_before_mid )) + + printf "%s%*s%s%*s%s\n" \ + "$start_time_str" \ + "$padding_before_mid" " " \ + "$mid_time_str" \ + "$padding_after_mid" " " \ + "$end_time_str" + else + # Not enough space, just show start and end labels. + local padding_between=$((num_data_points - start_len - end_len)) + if [ "$padding_between" -lt 0 ]; then padding_between=0; fi # Ensure padding is not negative. + printf "%s%*s%s\n" "$start_time_str" "$padding_between" " " "$end_time_str" + fi + + echo "" # Empty line for spacing. + echo "Current Chrome Memory: $(get_chrome_memory_usage) MB" + echo "Press Ctrl+C to exit." +} + +# --- Main Loop --- + +# This loop runs indefinitely until interrupted (e.g., by Ctrl+C). +while true; do + tput clear # Clear the terminal screen before redrawing the chart. + + # 1. Get current Chrome memory usage. + current_mem=$(get_chrome_memory_usage) + current_timestamp=$(date +%s) + + # 2. Append the current timestamp and memory usage to the data file. + echo "$current_timestamp:$current_mem" >> "$DATA_FILE" + + # 3. Clean up old data from the file to keep only the last DURATION_HOURS + # and ensure it fits the terminal width. + clean_old_data + + # 4. Draw the ASCII chart based on the current data. + draw_chart + + # 5. Wait for the specified interval before the next update. + sleep "$INTERVAL_SECONDS" +done +