mgconky/devices/get_device_info.lua

450 lines
17 KiB
Lua

--[[ ###########################################################################################################################################
# This script file was created by Matt Grotke (mgrotke@gmail.com) for the "mgconky Conky Theme" project: https://github.com/mgrotke/mgconky
# This file is in the public domain.
# ----------------------------------------------------------------------------------------------------------------------------------------- ]]--
-- ########################################################## GET CPU MAKE AND MODEL ###########################################################
function conky_shorten_cpu_name(cpu_name)
-- Remove unnecessary parts like (R), (TM), "CPU", "@ XGHz", "Processor", and extra spaces
return (cpu_name
:gsub("%(R%)", "") -- Remove (R)
:gsub("%(TM%)", "") -- Remove (TM)
:gsub("CPU", "") -- Remove "CPU"
:gsub("Processor", "") -- Remove "Processor"
:gsub("@[%s%w%.]+", "") -- Remove "@ XGHz"
:gsub("with.*", "") -- Remove "with" and anything after the word "with"
:gsub("%s%s+", " ") -- Remove extra spaces
:gsub("^%s+", "") -- Trim leading spaces
:gsub("%s+$", "") -- Trim trailing spaces
)
end
-- Cache so we only run this once. The CPU will not change.
local conky_cpu_cached_output = nil
function conky_get_cpu_info()
if conky_cpu_cached_output then
return conky_cpu_cached_output
else
-- Open /proc/cpuinfo for reading
local file = io.open("/proc/cpuinfo", "r")
if not file then
conky_cpu_cached_output = "Failed to open /proc/cpuinfo"
return conky_cpu_cached_output
end
-- Read the contents of /proc/cpuinfo
local cpu_info = file:read("*all")
file:close()
-- Find the 'model name' line
for line in cpu_info:gmatch("[^\r\n]+") do
local model_name = line:match("^model name%s*:%s*(.+)")
if model_name then
conky_cpu_cached_output = conky_shorten_cpu_name(model_name)
return conky_cpu_cached_output
end
end
-- Return nil if no model name is found
conky_cpu_cached_output = "CPU model name not found"
return conky_cpu_cached_output
end
end
-- ########################################################## GET GPU MAKE AND MODEL ##########################################################
function conky_shorten_gpu_name(gpu_name)
return (gpu_name
:gsub("NVIDIA Corporation", "NVIDIA")
:gsub("Advanced Micro Devices, Inc%.", "AMD")
:gsub("AMD Corporation", "AMD")
:gsub("Intel Corporation", "Intel")
:gsub("ATI Technologies Inc%.", "ATI")
:gsub("Qualcomm Technologies, Inc%.", "Qualcomm")
:gsub("ARM Holdings", "ARM")
:gsub("Apple Inc%.", "Apple")
:gsub("Matrox Graphics, Inc%.", "Matrox")
:gsub("S3 Graphics Co%., Ltd%.", "S3 Graphics")
:gsub("VIA Technologies, Inc%.", "VIA")
:gsub("Imagination Technologies Ltd%.", "Imagination")
:gsub("SiS %(Silicon Integrated Systems%)", "SiS")
:gsub("XGI Technology Inc%.", "XGI")
:gsub("3dfx Interactive, Inc%.", "3dfx")
:gsub("Raspberry Pi Foundation", "Raspberry Pi")
:gsub("Broadcom Inc%.", "Broadcom")
:gsub("Lite Hash Rate", "LHR")
:gsub("Renoir", "") -- Remove "Renoir"
:gsub("Ryzen %d+/%d+ Mobile Series", "") -- Remove Ryzen details (for APUs)
:gsub("%(Ryzen.-%)", "") -- Remove Ryzen-related info in parentheses
:gsub("GA%d+%s%[", "") -- Remove chip identifier like "GA106 ["
:gsub("%[.*%]%s*", "") -- Remove chip code in brackets like "[1002:1636]"
:gsub("%((rev .-)%)", "") -- Remove revision info like "(rev a1)"
:gsub("%s+", " ") -- Normalize spaces
:gsub("%]%s*$", "") -- Remove trailing square bracket "]" if it exists
:match("^%s*(.-)%s*$") -- Trim leading/trailing spaces
)
end
-- Cache so we only run this once. The GPU will not change.
local conky_gpu_cached_output = nil
function conky_get_gpu_info()
if conky_gpu_cached_output then
return conky_gpu_cached_output
else
local handle = io.popen("lspci -nn")
if not handle then
conky_gpu_cached_output = "Failed to run lspci"
return conky_gpu_cached_output
end
-- Read the output of the command
local output = handle:read("*all")
handle:close()
-- Search for a VGA compatible controller or GPU entry
for line in output:gmatch("[^\r\n]+") do
if line:match("%[0300%]") then
-- Extract the relevant information after the colon
local gpu = line:match(": (.+)$")
if gpu then
conky_gpu_cached_output = conky_shorten_gpu_name(gpu)
return conky_gpu_cached_output
end
end
end
-- If no graphics card is found
conky_gpu_cached_output = "No graphics card found"
return conky_gpu_cached_output
end
end
-- ########################################################## GET DRIVES AND VOLUMES ###########################################################
-- Cache so we don't spam this every conky tick
local conky_drives_cache_duration = 300 -- Cache output for 5 minutes
local conky_drives_last_update_time = 0
local conky_drives_cached_output = ""
function conky_get_drives_and_volumes()
local current_time = os.time()
-- Check if cache is still valid
if current_time - conky_drives_last_update_time < conky_drives_cache_duration then
return conky_drives_cached_output
end
-- Start fresh output
local output = ""
-- Use lsblk to get all devices and their mount points
local handle = io.popen("lsblk -ln -o NAME,TYPE,MOUNTPOINT,PKNAME")
if not handle then
return "Failed to execute lsblk command\n"
end
local result = handle:read("*a")
handle:close()
-- Parse the lsblk output and group partitions by parent drive
local devices = {}
local parent_drive = nil
for line in (result or ""):gmatch("[^\r\n]+") do
local name, dtype, mount, pkname = line:match("^(%S+)%s+(%S+)%s*(%S*)%s*(%S*)$")
if dtype == "disk" then
parent_drive = name
devices[parent_drive] = { mountpoints = {} }
elseif dtype == "part" and pkname == parent_drive and mount and mount ~= "" then
-- Only consider partitions with a non-empty mount point
table.insert(devices[parent_drive].mountpoints, mount)
elseif dtype == "dm" and pkname and mount and mount ~= "" then
-- Associate device-mapper (dm) devices with their parent physical disk
devices[pkname] = devices[pkname] or { mountpoints = {} }
table.insert(devices[pkname].mountpoints, mount)
end
end
-- Generate the output, only including drives with active mount points
for drive, data in pairs(devices) do
if #data.mountpoints > 0 then
output = output .. '${voffset 0}${color0}${font Neuropolitical:size=8:bold}DRIVE${font Courier:size=9} /dev/' .. drive .. '${color} ${color1}${hr 2}${color}\n'
for _, mount in ipairs(data.mountpoints) do
-- Skip entries that are not real mountpoints like [SWAP]
if mount:sub(1, 1) ~= "/" then
goto continue
end
-- Skip common temporary mounts
if mount:match("^/sys/") then goto continue end
if mount:match("^/proc/") then goto continue end
if mount:match("^/dev/") then goto continue end
-- Skip mounts under /var/, but allow a mount at /var itself
if mount ~= "/var" and mount:match("^/var/") then goto continue end
-- Skip runtime mounts except removable media
if mount:match("^/run/") and not mount:match("^/run/media/") then goto continue end
-- Use df to get the size and used space for the mount
local handle = io.popen("df -h --output=target,size,used " .. mount .. " | tail -n 1")
local df_result = handle:read("*a")
handle:close()
-- Parse df output
local target, size, used = df_result:match("(%S+)%s+(%S+)%s+(%S+)")
-- Ensure mount still exists before emitting fs_bar
local test = io.open(target, "r")
if not test then goto continue end
test:close()
-- Create output lines for this mountpoint.
if target and size and used then
output = output .. '${voffset 2}' .. target .. ': ${alignr}${color3}' .. used .. 'B${color} of ${color3}' .. size .. 'B${color}\n'
output = output .. '${voffset -2}${color5}${fs_bar ' .. target .. '}${color}\n'
end
::continue::
end
output = output .. '\n' -- Add a blank line after the last mount point of the current drive
end
end
-- Cache and return the output
conky_drives_cached_output = output
conky_drives_last_update_time = current_time
return conky_drives_cached_output
end
-- Run and print the output (for standalone testing)
print(conky_get_drives_and_volumes())
-- ########################################################## GET VPN STATUS ###################################################################
-- Cache so we don't spam this every conky tick
local conky_vpn_cache = {
last_update_time = 0,
cache_duration = 15, -- Cache output for 15 seconds
cached_output = ""
}
function conky_get_vpn_status()
local current_time = os.time()
-- Update cache only if more than 30 seconds have passed
if current_time - conky_vpn_cache.last_update_time > conky_vpn_cache.cache_duration then
-- Run the `ip a` command and capture its output
local handle = io.popen("ip a")
if not handle then
return "${color7}Error: Failed to execute 'ip a'${color}"
end
local result = handle:read("*a")
handle:close()
-- Check for active VPN
local vpn_status = "Off"
local vpn_color = "${color7}" -- Red for Off
for line in result:gmatch("[^\r\n]+") do
-- Look for active POINTOPOINT interfaces
if line:find("POINTOPOINT") and line:find("UP") then
local device_name = line:match("^%d+: ([^:]+):")
if device_name then
vpn_status = "On (" .. device_name .. ")"
vpn_color = "${color6}" -- Blue for On
break
end
end
end
-- Update cache
conky_vpn_cache.cached_output = vpn_color .. vpn_status .. "${color}"
conky_vpn_cache.last_update_time = current_time
end
-- Return cached status
return conky_vpn_cache.cached_output
end
-- Test the function by printing the result
print(conky_get_vpn_status())
-- ########################################################## GET MEMORY USAGE #################################################################
-- Cache so we don't spam this every conky tick
local conky_memory_cache = {
last_update_time_swapstatus = 0,
last_update_time_memval = 0,
last_update_time_swapval = 0,
cache_duration = 15, -- Cache output for 1 minute
cached_output_swapstatus = "", -- Possible values: "swapenabled" | "swapdisabled"
cached_output_memval = "",
cached_output_swapval = ""
}
function conky_grep_memory(grep_filter)
-- Run the `free` command and capture its output
local handle = io.popen("free -h --si | grep " .. grep_filter)
if not handle then
return "Error: Failed to execute 'free'"
end
local result = handle:read("*a")
handle:close()
return result
end
function conky_get_memory_usage(grep_filter)
local current_time = os.time()
-- Is arg "sys" or "swap"
if grep_filter == "sys" then
-- Is cache stale?
if current_time - conky_memory_cache.last_update_time_memval > conky_memory_cache.cache_duration then
-- Extract memory usage details
local grep_result = conky_grep_memory("Mem")
local total, used = grep_result:match("Mem:%s+(%S+)%s+(%S+)")
if not total or not used then
conky_memory_cache.cached_output_memval = "Error: Failed to parse memory usage"
else
-- Format the output as "X of Y" with color formatting
conky_memory_cache.cached_output_memval = "${color3}" .. used .. "B${color} of ${color3}" .. total .. "B${color}"
end
-- Update cache time
conky_memory_cache.last_update_time_memval = current_time
-- Debug
--print("DEBUG: UPDATED MEM VAL (" .. conky_memory_cache.cached_output_memval .. ")")
end
-- return mem cache (the mem cache has been updated if the duration has passed)
return conky_memory_cache.cached_output_memval
else -- If arg is not "sys" we assume they are looking for swap
-- Is cache stale?
if current_time - conky_memory_cache.last_update_time_swapval > conky_memory_cache.cache_duration then
-- Extract swap usage details
local grep_result = conky_grep_memory("Swap")
local total, used = grep_result:match("Swap:%s+(%S+)%s+(%S+)")
if not total or not used then
conky_memory_cache.cached_output_swapval = "Error: Failed to parse swap usage"
else
-- Format the output as "X of Y" with color formatting
conky_memory_cache.cached_output_swapval = "${color3}" .. used .. "B${color} of ${color3}" .. total .. "B${color}"
end
-- Update cache time
conky_memory_cache.last_update_time_swapval = current_time
-- Debug
--print("DEBUG: UPDATED SWAP VAL (" .. conky_memory_cache.cached_output_swapval .. ")")
end
-- return swap cache (the swap cache has been updated if the duration has passed)
return conky_memory_cache.cached_output_swapval
end
end
function conky_check_swap_status()
local current_time = os.time()
-- Is cache stale?
if current_time - conky_memory_cache.last_update_time_swapstatus > conky_memory_cache.cache_duration then
local grep_result = conky_grep_memory("Swap")
local total = grep_result:match("Swap:%s+(%S+)")
if not total or total == "0B" or total == "0" then
conky_memory_cache.cached_output_swapstatus = "swapdisabled"
else
conky_memory_cache.cached_output_swapstatus = "swapenabled"
end
-- Update cache
conky_memory_cache.last_update_time_swapstatus = current_time
end
-- Debug
--print("DEBUG: UPDATED SWAP STATUS (" .. conky_memory_cache.cached_output_swapstatus .. ")")
-- Return cached output
return conky_memory_cache.cached_output_swapstatus
end
-- Test the function by printing the result
print("Initial Memory Check Function Outputs:")
print(" CHECK SWAP STATUS = " .. conky_check_swap_status())
print(" GET MEMORY USAGE = " .. conky_get_memory_usage("mem"))
print(" GET SWAP USAGE = " .. conky_get_memory_usage("swap"))
-- ####################################################### GET ACTIVE CONNECTIONS ##############################################################
function conky_get_key_with_highest_value(t)
local max_key = nil
local max_value = -math.huge -- Initialize with the smallest possible number
for key, value in pairs(t) do
if type(value) == "number" and value > max_value then
max_value = value
max_key = key
end
end
return max_key
end
function conky_get_connections()
local handle = io.popen("ss -eptH | grep ESTAB 2>/dev/null")
if not handle then return "Error: Unable to execute ss command" end
local process_names = {}
local process_counts = {}
-- Read each line of output
for line in handle:lines() do
-- Extract process name and PID using pattern matching
local pname, pid = line:match('users:%(%("([^"]+)",pid=(%d+)')
pid = tonumber(pid) -- Ensure pid is a number
if pname and pid then
-- Store process name by PID and count occurrences
process_names[pid] = pname
process_counts[pid] = (process_counts[pid] or 0) + 1
end
end
handle:close()
-- Header line
local tab1_offset = "${goto 145}"
local line_prefix = "${voffset 0}${color}${font StyleBats:size=10}h${font Courier:size=9}${voffset -1}${color3}"
local result = string.format("${color}Process%sPID${alignr}Num \n", tab1_offset)
-- Sort and format output
while true do
local max_key = conky_get_key_with_highest_value(process_counts)
if max_key == nil then
break -- Stop when the table is empty
end
result = result .. string.format("%s %s%s%d${alignr}%d \n", line_prefix, string.sub(process_names[max_key], 1, 16), tab1_offset, max_key, process_counts[max_key])
-- Remove the highest key from both tables
process_counts[max_key] = nil
process_names[max_key] = nil
end
return result
end