aboutsummaryrefslogtreecommitdiffstats
path: root/modules/desktop/eww/system-stats.lua
diff options
context:
space:
mode:
Diffstat (limited to 'modules/desktop/eww/system-stats.lua')
-rw-r--r--modules/desktop/eww/system-stats.lua826
1 files changed, 0 insertions, 826 deletions
diff --git a/modules/desktop/eww/system-stats.lua b/modules/desktop/eww/system-stats.lua
deleted file mode 100644
index 514647f..0000000
--- a/modules/desktop/eww/system-stats.lua
+++ /dev/null
@@ -1,826 +0,0 @@
-#!/usr/bin/env luajit
--- system-stats: emit {cpu, ram, net, sound} as JSON.
---
--- Single-threaded LuaJIT port of the original Python script. Instead of
--- OS threads, a single supervisor loop schedules the periodic cpu/ram and
--- sound refreshes and polls an inotifywait child for link-state changes.
-local cjson = require("cjson")
-local posix = require("posix")
-local unistd = require("posix.unistd")
-local gettimeofday = require("posix.sys.time").gettimeofday
-local poll = require("posix.poll").poll
-
-local NULL = cjson.null
-
-local function nonnull(v)
- if v == nil then
- return NULL
- end
- return v
-end
-
--- Throughput deltas between cpu/ram refreshes
-local net_last_time = nil
-local net_last_bytes = {}
-
--- ------------------------------------------------ time
-local function mono_sec()
- local g = gettimeofday()
- return g.tv_sec + g.tv_usec / 1e6
-end
-
--- ------------------------------------------------ process helpers
-local function sh(cmd)
- local h = io.popen(cmd)
- if not h then
- return nil
- end
- local out = h:read("*a")
- h:close()
- return out
-end
-
--- ------------------------------------------------ formatting
-local function format_rate(bytes_per_sec)
- if bytes_per_sec == nil or bytes_per_sec < 0 then
- return "0B"
- end
- local units = { "B", "K", "M", "G", "T" }
- local val = bytes_per_sec
- local unit_idx = 1
- while val >= 1024 and unit_idx < #units do
- val = val / 1024.0
- unit_idx = unit_idx + 1
- end
- local unit = units[unit_idx]
- if val >= 100 or unit == "B" then
- return string.format("%d%s", math.floor(val + 0.5), unit)
- end
- return string.format("%.1f%s", val, unit)
-end
-
-local function format_bytes(kib)
- if kib == nil or kib < 0 then
- return "0B"
- end
- local units = { "K", "M", "G", "T" }
- local val = kib
- local unit_idx = 1
- while val >= 1024 and unit_idx < #units do
- val = val / 1024.0
- unit_idx = unit_idx + 1
- end
- local unit = units[unit_idx]
- if val >= 100 or unit == "K" then
- return string.format("%d%s", math.floor(val + 0.5), unit)
- end
- return string.format("%.1f%s", val, unit)
-end
-
--- ------------------------------------------------ /proc readers
-local function read_cpu_times()
- local times = {}
- local f = io.open("/proc/stat")
- if not f then
- return times
- end
- for line in f:lines() do
- if not line:match("^cpu") then
- break
- end
- local parts = {}
- for w in line:gmatch("%S+") do
- parts[#parts + 1] = w
- end
- local label = parts[1]
- local total, idle = 0, 0
- for j = 2, #parts do
- local v = tonumber(parts[j]) or 0
- total = total + v
- if j == 4 or j == 5 then
- idle = idle + v
- end
- end
- times[label] = { idle = idle, total = total }
- end
- f:close()
- return times
-end
-
-local function get_cpu_freq_mhz()
- local count, sum, ok = 0, 0, false
- local f = io.open("/proc/cpuinfo")
- if not f then
- return nil
- end
- for line in f:lines() do
- local low = line:lower()
- if low:find("^cpu mhz") then
- local val = line:match(":%s*([%d%.]+)")
- if val then
- sum = sum + tonumber(val)
- count = count + 1
- ok = true
- end
- end
- end
- f:close()
- if not ok then
- return nil
- end
- return math.floor(sum / count + 0.5)
-end
-
-local function get_load_avg()
- local f = io.open("/proc/loadavg")
- if not f then
- return nil
- end
- local line = f:read("*l")
- f:close()
- if not line then
- return nil
- end
- local a, b, c = line:match("^([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)")
- if not a then
- return nil
- end
- return { tonumber(a), tonumber(b), tonumber(c) }
-end
-
-local function get_ram_speed_mhz()
- local out = sh("dmidecode -t 17 2>/dev/null")
- if not out then
- return nil
- end
- local speeds = {}
- for line in out:gmatch("[^\n]+") do
- local s = line:match("^%s*Configured Memory Speed:%s*(.-)%s*$")
- if s then
- local low = s:lower()
- if low ~= "unknown" and low ~= "no module installed" then
- local num = s:match("^(%d+)")
- if num then
- speeds[#speeds + 1] = tonumber(num)
- end
- end
- end
- end
- if #speeds == 0 then
- for line in out:gmatch("[^\n]+") do
- local s = line:match("^%s*Speed:%s*(.-)%s*$")
- if s then
- local low = s:lower()
- if low ~= "unknown" and low ~= "no module installed" then
- local num = s:match("^(%d+)")
- if num then
- speeds[#speeds + 1] = tonumber(num)
- end
- end
- end
- end
- end
- if #speeds == 0 then
- return nil
- end
- local mx = speeds[1]
- for j = 2, #speeds do
- if speeds[j] > mx then
- mx = speeds[j]
- end
- end
- return mx
-end
-
--- ------------------------------------------------ network helpers
-local function get_all_default_ifaces()
- local out = sh("ip -o route show default 2>/dev/null")
- if not out then
- return {}
- end
- local ifaces = {}
- local seen = {}
- for line in out:gmatch("[^\n]+") do
- local iface = line:match("dev (%S+)")
- if iface and not seen[iface] then
- seen[iface] = true
- ifaces[#ifaces + 1] = iface
- end
- end
- return ifaces
-end
-
-local function get_iface_ip(iface)
- if not iface or iface == "" then
- return nil
- end
- local out = sh("ip -o -4 addr show dev " .. iface .. " 2>/dev/null")
- if not out then
- return nil
- end
- local ip = out:match("inet (%d+%.%d+%.%d+%.%d+)")
- return ip
-end
-
-local function get_operstate(iface)
- local f = io.open("/sys/class/net/" .. iface .. "/operstate")
- if not f then
- return "down"
- end
- local s = f:read("*l")
- f:close()
- return (s and s:match("^%s*(.-)%s*$")) or "down"
-end
-
-local function get_iface_type(iface)
- if posix.stat("/sys/class/net/" .. iface .. "/wireless") then
- return "wifi"
- end
- local st = posix.stat("/sys/class/net/" .. iface .. "/bridge")
- if st and st.type == "directory" then
- return "bridge"
- end
- if posix.stat("/sys/class/net/" .. iface .. "/tun_flags") then
- return "tun"
- end
- return "ethernet"
-end
-
-local function get_speed_mbps(iface, iftype)
- if iftype == "ethernet" then
- local f = io.open("/sys/class/net/" .. iface .. "/speed")
- if f then
- local s = f:read("*l")
- f:close()
- local num = s and tonumber(s:match("^%s*(-?%d+)"))
- if num and num >= 0 then
- return num
- end
- end
- return nil
- end
- if iftype == "wifi" then
- local out = sh("iw dev " .. iface .. " link 2>/dev/null")
- if not out then
- return nil
- end
- for line in out:gmatch("[^\n]+") do
- local l = line:match("^%s*tx bitrate:%s*(.-)%s*$")
- if l then
- local num = tonumber(l:match("^([%d%.]+)"))
- if num then
- return math.floor(num + 0.5)
- end
- end
- end
- end
- return nil
-end
-
-local function get_iface_io_bytes(iface)
- if not iface or iface == "" then
- return nil, nil
- end
- local f = io.open("/proc/net/dev")
- if not f then
- return nil, nil
- end
- local rx, tx
- for line in f:lines() do
- local colon = line:find(":")
- if colon then
- local dev = line:sub(1, colon - 1):match("^%s*(.-)%s*$")
- if dev == iface then
- local fields = {}
- for w in line:sub(colon + 1):gmatch("%S+") do
- fields[#fields + 1] = w
- end
- rx = tonumber(fields[1]) or nil
- tx = tonumber(fields[9]) or nil
- break
- end
- end
- end
- f:close()
- return rx, tx
-end
-
--- ------------------------------------------------ tooltips
-local function build_cpu_tooltip(overall_pct, per_core_list, freq_mhz, load_avg)
- local lines = { string.format("CPU: %d%%", overall_pct) }
- if freq_mhz ~= nil then
- lines[#lines + 1] = string.format("Freq: %d MHz", freq_mhz)
- end
- if load_avg ~= nil then
- lines[#lines + 1] = string.format(
- "Load: %.2f %.2f %.2f",
- load_avg[1],
- load_avg[2],
- load_avg[3]
- )
- end
- for i, pct in ipairs(per_core_list) do
- lines[#lines + 1] = string.format("Core %d: %d%%", i - 1, pct)
- end
- return table.concat(lines, "\n")
-end
-
-local function build_ram_tooltip(ram_pct, used, total, available, swap_pct, swap_used, swap_total, speed_mhz)
- local lines = { string.format("RAM: %d%% (%s / %s)", ram_pct, used, total) }
- lines[#lines + 1] = "Available: " .. available
- lines[#lines + 1] = string.format("Swap: %d%% (%s / %s)", swap_pct, swap_used, swap_total)
- if speed_mhz ~= nil then
- lines[#lines + 1] = string.format("Speed: %d MHz", speed_mhz)
- else
- lines[#lines + 1] = "Speed: unknown"
- end
- return table.concat(lines, "\n")
-end
-
-local function build_sound_tooltip(percent, mute, sink)
- local vol = tostring(percent) .. "%"
- local lines = {}
- if mute then
- lines[#lines + 1] = string.format("Muted (%s)", vol)
- else
- lines[#lines + 1] = "Volume: " .. vol
- end
- if sink then
- lines[#lines + 1] = "Sink: " .. sink
- end
- return table.concat(lines, "\n")
-end
-
--- ------------------------------------------------ state collectors
--- get_all_default_ifaces (`ip route`), get_iface_ip (`ip addr`), and the
--- wifi branch of get_speed_mbps (`iw dev link`) each spawn a subprocess.
--- Those three rarely change second-to-second, so they're only re-probed
--- every NET_SLOW_REFRESH_INTERVAL instead of on every check_net_status()
--- call. operstate, iface type, ethernet speed, and rx/tx byte counters are
--- all plain sysfs/proc reads with no subprocess involved, so they stay
--- live on every call regardless -- rx/tx throughput in particular needs
--- to stay frequent to be useful.
-local NET_SLOW_REFRESH_INTERVAL = 15.0
-local net_slow_ifaces = nil
-local net_slow_ip = {}
-local net_slow_wifi_speed = {}
-local net_slow_expires_at = 0
-
-local function refresh_net_slow_cache(now)
- net_slow_ifaces = get_all_default_ifaces()
- local ip_cache, wifi_speed_cache = {}, {}
- for _, iface in ipairs(net_slow_ifaces) do
- ip_cache[iface] = get_iface_ip(iface)
- if get_iface_type(iface) == "wifi" then
- wifi_speed_cache[iface] = get_speed_mbps(iface, "wifi")
- end
- end
- net_slow_ip = ip_cache
- net_slow_wifi_speed = wifi_speed_cache
- net_slow_expires_at = now + NET_SLOW_REFRESH_INTERVAL
-end
-
--- Pass force=true to bypass the timer and re-probe immediately (used for
--- the initial call and whenever inotify reports an actual link change).
-local function check_net_status(force)
- local now = mono_sec()
- if force or net_slow_ifaces == nil or now >= net_slow_expires_at then
- refresh_net_slow_cache(now)
- end
-
- local ifaces = net_slow_ifaces
-
- if #ifaces == 0 then
- return { status = "down", tooltip = "Network Offline", routes = {} }
- end
-
- local routes = {}
- local any_up = false
- local any_limited = false
-
- for _, iface in ipairs(ifaces) do
- local oper = get_operstate(iface)
- if oper == "up" then
- any_up = true
- elseif oper == "dormant" then
- any_limited = true
- end
-
- local iftype = get_iface_type(iface)
- local ip_addr = net_slow_ip[iface]
- local speed
- if oper == "up" then
- if iftype == "wifi" then
- speed = net_slow_wifi_speed[iface]
- else
- -- Ethernet speed is a plain sysfs read (no subprocess), so it
- -- stays live every call rather than going through the slow cache.
- speed = get_speed_mbps(iface, iftype)
- end
- end
-
- local rx_bytes, tx_bytes = get_iface_io_bytes(iface)
- local rx_str, tx_str = nil, nil
-
- if oper ~= "down" and rx_bytes ~= nil and tx_bytes ~= nil then
- if net_last_time ~= nil and net_last_bytes[iface] then
- local dt = now - net_last_time
- if dt > 0 then
- local prev = net_last_bytes[iface]
- local rx_rate = math.max(0, (rx_bytes - prev[1]) / dt)
- local tx_rate = math.max(0, (tx_bytes - prev[2]) / dt)
- rx_str = format_rate(rx_rate)
- tx_str = format_rate(tx_rate)
- end
- end
- net_last_bytes[iface] = { rx_bytes, tx_bytes }
- else
- rx_str, tx_str = nil, nil
- end
-
- routes[#routes + 1] = {
- iface = iface,
- ip = nonnull(ip_addr),
- ["type"] = iftype,
- speed = nonnull(speed),
- rx = nonnull(rx_str),
- tx = nonnull(tx_str),
- }
- end
-
- net_last_time = now
-
- local overall_status
- if any_up then
- overall_status = "up"
- elseif any_limited then
- overall_status = "limited"
- else
- overall_status = "down"
- end
-
- local tooltip
- if overall_status == "down" or #routes == 0 then
- tooltip = "Network Offline"
- else
- local first = routes[1]
- local rx = first.rx
- local tx = first.tx
- if rx == NULL or rx == nil then
- rx = "-"
- end
- if tx == NULL or tx == nil then
- tx = "-"
- end
- tooltip = string.format("\u{2193}%s \u{2191}%s", rx, tx)
- for _, r in ipairs(routes) do
- local iface = r.iface or "unknown"
- local ip = r.ip
- if ip == NULL or ip == nil then
- ip = "no ip"
- end
- tooltip = tooltip .. "\n" .. iface .. ": " .. ip
- end
- end
-
- return { status = overall_status, tooltip = tooltip, routes = routes }
-end
-
-local function get_sound_state()
- local function pamixer_flag(flag)
- local out = sh("pamixer " .. flag .. " 2>/dev/null")
- if not out then
- return nil
- end
- return out:gsub("^%s+", ""):gsub("%s+$", "")
- end
-
- local percent, mute, sink = nil, nil, nil
-
- local pout = pamixer_flag("--get-volume")
- if pout then
- local num = tonumber(pout:match("^(%-?%d+)"))
- if num then
- percent = math.max(0, math.min(100, num))
- end
- end
-
- local mout = pamixer_flag("--get-mute")
- if mout ~= nil then
- mute = (mout:lower() == "true")
- end
-
- local sout = pamixer_flag("--get-default-sink")
- if sout then
- -- Output ends with: INDEX "name" "description"
- local names = {}
- for q in sout:gmatch('"([^"]*)"') do
- names[#names + 1] = q
- end
- if #names > 0 then
- sink = names[#names]
- else
- local last = sout:match("([^\n]+)$")
- sink = last or sout
- end
- end
-
- local vol = tostring(percent) .. "%"
- local supp = {}
- if mute then
- supp[#supp + 1] = string.format("Muted (%s)", vol)
- else
- supp[#supp + 1] = "Volume: " .. vol
- end
- if sink then
- supp[#supp + 1] = "Sink: " .. sink
- end
-
- return {
- percent = nonnull(percent),
- mute = mute or false,
- sink = nonnull(sink),
- tooltip = table.concat(supp, "\n"),
- }
-end
-
--- ------------------------------------------------ state + emit
-local state = {
- cpu = {
- percent = 0,
- per_core = {},
- freq_mhz = NULL,
- load_avg = NULL,
- tooltip = "CPU: 0%",
- },
- ram = {
- percent = 0,
- used = "0B",
- total = "0B",
- available = "0B",
- swap_percent = 0,
- swap_used = "0B",
- swap_total = "0B",
- speed_mhz = NULL,
- tooltip = "RAM: 0%",
- },
- net = {
- status = "down",
- tooltip = "Network Offline",
- routes = {},
- },
- sound = {
- percent = NULL,
- mute = false,
- sink = NULL,
- tooltip = "Volume: unknown",
- },
-}
-
-local function emit()
- io.write(cjson.encode(state) .. "\n")
- io.stdout:flush()
-end
-
-local CPU_INTERVAL = 2.0
-local SOUND_INTERVAL = 1.0
-local INOTIFY_RETRY_INTERVAL = 30.0 -- backoff between (re)spawn attempts
-
--- ------------------------------------------------ inotifywait child
--- Returns pid, read_fd on success, or nil, nil if the fork/exec setup
--- itself failed outright (pipe/fork syscall failure, not inotifywait
--- failing later).
-local function spawn_inotify()
- local r, w = posix.pipe()
- if not r or not w then
- return nil, nil
- end
- local pid = posix.fork()
- if pid == nil then
- posix.close(r)
- posix.close(w)
- return nil, nil
- end
- if pid == 0 then
- posix.dup2(w, unistd.STDOUT_FILENO)
- local nullfd = posix.open("/dev/null", posix.O_WRONLY)
- if nullfd then
- posix.dup2(nullfd, unistd.STDERR_FILENO)
- posix.close(nullfd)
- end
- posix.close(r)
- posix.close(w)
- local ok, err = posix.execp(
- "inotifywait",
- { "inotifywait", "-m", "-e", "modify", "/run/systemd/netif/state" }
- )
- if not ok then
- io.stderr:write("inotifywait: " .. tostring(err) .. "\n")
- os.exit(1)
- end
- end
- posix.close(w)
- return pid, r
-end
-
--- ------------------------------------------------ main loop
-local function main()
- local ram_speed_mhz = get_ram_speed_mhz()
-
- -- seed global state copy with all keys (null defaults)
- local prev_cpu_times = read_cpu_times()
-
- -- initial emission
- state.net = check_net_status(true)
- emit()
-
- -- net_fd is nil whenever we have no live inotifywait child to poll on.
- -- We (re)spawn opportunistically, but never more often than
- -- INOTIFY_RETRY_INTERVAL, so a persistently-failing child (missing
- -- binary, watched path doesn't exist, permissions, ...) can never turn
- -- into a tight loop -- it just falls back to picking up network
- -- changes on the regular CPU_INTERVAL cadence below instead.
- local inotify_pid = nil
- local net_fd = nil
- local next_inotify_attempt = 0 -- attempt immediately on first iteration
-
- local next_cpu = mono_sec() + CPU_INTERVAL
- local next_sound = mono_sec() + SOUND_INTERVAL
-
- while true do
- local now = mono_sec()
-
- if net_fd == nil and now >= next_inotify_attempt then
- inotify_pid, net_fd = spawn_inotify()
- -- Whether this attempt succeeded or not, don't try again for a
- -- while: a child that exits immediately (e.g. because the watched
- -- path doesn't exist) would otherwise cause an attempt-per-loop
- -- storm identical to the original bug.
- next_inotify_attempt = mono_sec() + INOTIFY_RETRY_INTERVAL
- end
-
- if now >= next_cpu then
- local cur_times = read_cpu_times()
- local overall_pct = 0
- local per_core = {}
- for label, cur in pairs(cur_times) do
- local prev = prev_cpu_times[label] or cur
- local d_idle = cur.idle - prev.idle
- local d_total = cur.total - prev.total
- local pct = 0
- if d_total > 0 then
- pct = math.floor((1 - d_idle / d_total) * 100 + 0.5)
- end
- if label == "cpu" then
- overall_pct = pct
- else
- per_core[tonumber(label:match("^cpu(.*)$")) or -1] = pct
- end
- end
- prev_cpu_times = cur_times
-
- local keys = {}
- for k in pairs(per_core) do
- keys[#keys + 1] = k
- end
- table.sort(keys)
- local per_core_list = {}
- for _, k in ipairs(keys) do
- per_core_list[#per_core_list + 1] = per_core[k]
- end
-
- local freq_mhz = get_cpu_freq_mhz()
- local load_avg = get_load_avg()
-
- local cpu_state = {
- percent = overall_pct,
- per_core = per_core_list,
- freq_mhz = nonnull(freq_mhz),
- load_avg = nonnull(load_avg),
- tooltip = build_cpu_tooltip(overall_pct, per_core_list, freq_mhz, load_avg),
- }
-
- -- RAM
- local meminfo = {}
- local f = io.open("/proc/meminfo")
- if f then
- for line in f:lines() do
- local k, v = line:match("^([^:]+):%s*(%d+)")
- if k and v then
- meminfo[k] = tonumber(v)
- end
- end
- f:close()
- end
-
- local total_kb = meminfo.MemTotal or 0
- local avail_kb = meminfo.MemAvailable or 0
- local used_kb = math.max(0, total_kb - avail_kb)
- local ram_pct = 0
- if total_kb > 0 then
- ram_pct = math.floor((1 - avail_kb / total_kb) * 100 + 0.5)
- end
-
- local swap_total_kb = meminfo.SwapTotal or 0
- local swap_free_kb = meminfo.SwapFree or 0
- local swap_used_kb = math.max(0, swap_total_kb - swap_free_kb)
- local swap_pct = 0
- if swap_total_kb > 0 then
- swap_pct = math.floor(swap_used_kb / swap_total_kb * 100 + 0.5)
- end
-
- local used_fmt = format_bytes(used_kb)
- local total_fmt = format_bytes(total_kb)
- local avail_fmt = format_bytes(avail_kb)
- local swap_used_fmt = format_bytes(swap_used_kb)
- local swap_total_fmt = format_bytes(swap_total_kb)
-
- local ram_state = {
- percent = ram_pct,
- used = used_fmt,
- total = total_fmt,
- available = avail_fmt,
- swap_percent = swap_pct,
- swap_used = swap_used_fmt,
- swap_total = swap_total_fmt,
- speed_mhz = nonnull(ram_speed_mhz),
- tooltip = build_ram_tooltip(
- ram_pct,
- used_fmt,
- total_fmt,
- avail_fmt,
- swap_pct,
- swap_used_fmt,
- swap_total_fmt,
- ram_speed_mhz
- ),
- }
-
- state.cpu = cpu_state
- state.ram = ram_state
- -- Net status is always refreshed here too, so even with inotify
- -- permanently unavailable we still pick up link changes within
- -- CPU_INTERVAL seconds.
- state.net = check_net_status()
- emit()
- next_cpu = mono_sec() + CPU_INTERVAL
- end
-
- if now >= next_sound then
- state.sound = get_sound_state()
- emit()
- next_sound = mono_sec() + SOUND_INTERVAL
- end
-
- -- Poll inotifywait for link-state changes (timeout in ms). If we
- -- have no live child right now, still sleep until the next
- -- scheduled piece of work (or the next respawn attempt) instead of
- -- busy-looping.
- local wake_at = math.min(next_cpu, next_sound)
- if net_fd == nil then
- wake_at = math.min(wake_at, next_inotify_attempt)
- end
- local until_next = wake_at - mono_sec()
- if until_next < 0 then
- until_next = 0
- end
- local timeout_ms = math.floor(until_next * 1000 + 0.5)
-
- local fds = {}
- if net_fd ~= nil then
- fds[net_fd] = { events = { IN = true } }
- end
-
- local nready = poll(fds, timeout_ms)
- if net_fd ~= nil and nready and nready > 0 then
- local revents = fds[net_fd].revents
- if revents and (revents.IN or revents.HUP or revents.ERR) then
- local data = unistd.read(net_fd, 8192)
- if data and #data > 0 then
- -- An actual link-state change: force a full re-probe rather
- -- than waiting out the slow-refresh timer, since this is
- -- exactly the moment the cached IP/speed could be stale.
- state.net = check_net_status(true)
- emit()
- else
- -- Empty read on a "ready" fd means the write end closed, i.e.
- -- the inotifywait child exited (crashed, watched path missing,
- -- binary not found, etc). Reap it, drop the fd, and don't
- -- touch it again until the next backed-off retry -- this is
- -- the fix for the tight busy-loop: without this branch, poll()
- -- would report this dead fd as "ready" forever, on every
- -- single iteration, with an effectively-zero timeout.
- unistd.close(net_fd)
- if inotify_pid then
- pcall(posix.wait, inotify_pid)
- end
- net_fd = nil
- inotify_pid = nil
- next_inotify_attempt = mono_sec() + INOTIFY_RETRY_INTERVAL
- end
- end
- end
- end
-end
-
-main()