Initial commit of Hammerspoon config
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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
|
||||
Reference in New Issue
Block a user