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