171 lines
6.3 KiB
Lua
171 lines
6.3 KiB
Lua
local obj = {}
|
|
obj.__index = obj
|
|
|
|
-- CONFIGURATION
|
|
local PROJECT_ID = "gen-lang-client-0842463959"
|
|
local PROJECT_NAME = "Google API Monitor"
|
|
local menu = hs.menubar.new()
|
|
local accessToken = nil
|
|
|
|
-- Data storage
|
|
local history = {}
|
|
local lastMetrics = {
|
|
requests = 0,
|
|
throughput = "0 B",
|
|
errors = 0,
|
|
latency = "0 ms",
|
|
quotaPercent = 0
|
|
}
|
|
|
|
-- Helper: Format bytes
|
|
local function formatBytes(bytes)
|
|
if not bytes or bytes == 0 then return "0 B" end
|
|
local units = {"B", "KB", "MB", "GB"}
|
|
local i = 1
|
|
while bytes > 1024 and i < #units do
|
|
bytes = bytes / 1024
|
|
i = i + 1
|
|
end
|
|
return string.format("%.2f %s", bytes, units[i])
|
|
end
|
|
|
|
-- Helper: ASCII Trend Graph (Traffic)
|
|
local function getTrendString()
|
|
if #history < 2 then return "▂ Loading..." end
|
|
local bars = {" ", "▂", "▃", "▄", "▅", "▆", "▇", "█"}
|
|
local maxVal = 0
|
|
for _, v in ipairs(history) do if v > maxVal then maxVal = v end end
|
|
local res = ""
|
|
for _, v in ipairs(history) do
|
|
local idx = 1
|
|
if maxVal > 0 then
|
|
idx = math.floor((v / maxVal) * 7) + 1
|
|
end
|
|
res = res .. bars[idx]
|
|
end
|
|
return res
|
|
end
|
|
|
|
-- Helper: Token Refresh
|
|
local function refreshAuth(callback)
|
|
local scriptPath = os.getenv("HOME") .. "/.hammerspoon/get_token.py"
|
|
hs.task.new("/usr/bin/python3", function(exitCode, stdOut, stdErr)
|
|
if exitCode == 0 then
|
|
accessToken = stdOut:gsub("%s+", "")
|
|
if callback then callback() end
|
|
else
|
|
print("Python Auth Error: " .. (stdErr or "Unknown"))
|
|
end
|
|
end, {scriptPath}):start()
|
|
end
|
|
|
|
-- Generic Fetch
|
|
local function fetchMetric(metricType, callback)
|
|
if not accessToken then return end
|
|
local now = os.date("!%Y-%m-%dT%H:%M:%SZ")
|
|
local start = os.date("!%Y-%m-%dT%H:%M:%SZ", os.time() - 3600)
|
|
local baseUrl = "https://monitoring.googleapis.com/v3/projects/" .. PROJECT_ID .. "/timeSeries"
|
|
|
|
local fullUrl = baseUrl .. "?" ..
|
|
"filter=" .. hs.http.encodeForQuery('metric.type="' .. metricType .. '"') ..
|
|
"&interval.startTime=" .. hs.http.encodeForQuery(start) ..
|
|
"&interval.endTime=" .. hs.http.encodeForQuery(now)
|
|
|
|
hs.http.asyncGet(fullUrl, {Authorization = "Bearer " .. accessToken}, function(status, body)
|
|
if status == 200 then
|
|
local data = hs.json.decode(body)
|
|
local total, count = 0, 0
|
|
if data and data.timeSeries then
|
|
for _, series in ipairs(data.timeSeries) do
|
|
for _, point in ipairs(series.points or {}) do
|
|
local val = point.value.int64Value or point.value.doubleValue or 0
|
|
total = total + tonumber(val)
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
callback(total, count)
|
|
elseif status == 401 or status == 403 then
|
|
accessToken = nil
|
|
end
|
|
end)
|
|
end
|
|
|
|
function obj.updateMenu()
|
|
local trend = getTrendString()
|
|
local qColor = {white=1}
|
|
if lastMetrics.quotaPercent >= 90 then qColor = {red=1}
|
|
elseif lastMetrics.quotaPercent >= 75 then qColor = {orange=1} end
|
|
|
|
menu:setMenu({
|
|
{ title = "Project: " .. PROJECT_NAME, disabled = true },
|
|
{ title = "Traffic Trend: " .. trend, disabled = true },
|
|
{ title = "-" },
|
|
{ title = "Quota Utilization: " .. lastMetrics.quotaPercent .. "%", font={color=qColor} },
|
|
{ title = "Total Requests (1h): " .. lastMetrics.requests },
|
|
{ title = "Average Latency: " .. lastMetrics.latency },
|
|
{ title = "Data Throughput: " .. lastMetrics.throughput },
|
|
{ title = "Errors (1h): " .. lastMetrics.errors, font = { color = (lastMetrics.errors > 0 and {red=1} or {white=1}) } },
|
|
{ title = "-" },
|
|
{ title = "Refresh Now", fn = function() obj.updateQuota() end },
|
|
{ title = "Open Google Console", fn = function() hs.urlevent.openURL("https://console.cloud.google.com/apis/dashboard?project=" .. PROJECT_ID) end }
|
|
})
|
|
end
|
|
|
|
function obj.updateQuota()
|
|
if not accessToken then
|
|
refreshAuth(function() obj.updateQuota() end)
|
|
return
|
|
end
|
|
|
|
-- 1. Fetch Traffic
|
|
fetchMetric("serviceruntime.googleapis.com/api/request_count", function(reqCount)
|
|
lastMetrics.requests = reqCount
|
|
|
|
-- Update history for trend bars
|
|
table.insert(history, reqCount)
|
|
if #history > 10 then table.remove(history, 1) end
|
|
|
|
-- 2. Fetch Quota Usage vs Limit
|
|
fetchMetric("serviceruntime.googleapis.com/quota/allocation/usage", function(usage)
|
|
fetchMetric("serviceruntime.googleapis.com/quota/limit", function(limit)
|
|
lastMetrics.quotaPercent = (limit > 0) and math.floor((usage / limit) * 100) or 0
|
|
|
|
-- 3. Fetch Throughput and Latency
|
|
fetchMetric("serviceruntime.googleapis.com/api/response_sizes", function(bytes)
|
|
lastMetrics.throughput = formatBytes(bytes)
|
|
fetchMetric("serviceruntime.googleapis.com/api/request_latencies", function(totalLat, count)
|
|
lastMetrics.latency = count > 0 and string.format("%.2f ms", (totalLat / count) * 1000) or "0 ms"
|
|
|
|
-- 4. Errors
|
|
local errFilter = 'serviceruntime.googleapis.com/api/request_count" AND metric.labels.response_code_class!="2xx'
|
|
fetchMetric(errFilter, function(errCount)
|
|
lastMetrics.errors = errCount
|
|
|
|
-- Title Icon Logic
|
|
local statusIcon = ""
|
|
if errCount > 0 then statusIcon = "🔴 "
|
|
elseif lastMetrics.quotaPercent >= 80 then statusIcon = "⚠️ "
|
|
end
|
|
|
|
menu:setTitle(statusIcon .. "G-API: " .. reqCount)
|
|
obj.updateMenu()
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function obj.init()
|
|
if menu then
|
|
menu:setTitle("G-API: ...")
|
|
obj.updateMenu()
|
|
-- Polling every 5 minutes
|
|
obj.timer = hs.timer.doEvery(300, function() obj.updateQuota() end)
|
|
obj.updateQuota()
|
|
end
|
|
end
|
|
|
|
return obj |