From 43a459d1042b37dcf2c60f2209fbf61fa205c6bb Mon Sep 17 00:00:00 2001 From: Stephen Collings Date: Tue, 26 Mar 2024 16:43:52 +0000 Subject: [PATCH] feat: Add metrics for start/end syncs --- lib/metrics.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++--- lib/settings.js | 17 +++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/lib/metrics.js b/lib/metrics.js index 3707dd3a..96d61523 100644 --- a/lib/metrics.js +++ b/lib/metrics.js @@ -1,6 +1,6 @@ const metrics = require("@operate-first/probot-metrics"); -const counter = metrics.useCounter({ +const actionCounter = metrics.useCounter({ name: 'num_of_actions_total', help: 'Total number of actions received', labelNames: ['repository', 'result', 'action', 'nop'], @@ -9,7 +9,7 @@ const counter = metrics.useCounter({ const meteredPlugin = async (plugin, fn) => { try { const result = await fn() - counter + actionCounter .labels({ repository: plugin.repo.repo, nop: plugin.nop, @@ -20,7 +20,7 @@ const meteredPlugin = async (plugin, fn) => { return result } catch (e) { console.dir(e) - counter + actionCounter .labels({ repository: plugin.repo.repo, result: "error", @@ -32,4 +32,78 @@ const meteredPlugin = async (plugin, fn) => { } } -module.exports = {meteredPlugin} +const syncStartCounter = metrics.useCounter({ + name: 'sync_start_count', + help: 'Number of sync start events', + labelNames: ['nop', 'type', 'name'] +}) + +const syncEndCounter = metrics.useCounter({ + name: 'sync_end_count', + help: 'Number of sync end events', + labelNames: ['nop', 'type', 'status', 'name'] +}) + +/** + * @typedef {"all" | "suborg" | "repo"} SyncType + */ + +/** + * Increase sync start counter + * + * @param {boolean} nop Is dry run on PR? + * @param {SyncType} type Type of sync + * @param {string | undefined} name Name of suborg or repo + * @returns + */ +const syncStart = (nop, type, name) => { + if (name) { + syncStartCounter.inc({ + nop, + type, + name + }) + return + } + + syncStartCounter.inc({ + nop, + type + }) +} + +/** + * Increase sync end counter + * + * @param {boolean} nop Is dry run on PR? + * @param {SyncType} type Type of sync + * @param {boolean} hasError At least one error occurred + * @param {boolean} hasException An exception potentially prevented syncing subsequent settings + * @param {string | undefined} name Name of suborg or repo + * @returns + */ +const syncEnd = (nop, type, hasException, hasError, name) => { + const status = hasException ? 'fail' : hasError ? 'error' : 'ok' + + if (name) { + syncEndCounter.inc({ + nop, + type, + status, + name + }) + return + } + + syncEndCounter.inc({ + nop, + type, + status + }) +} + +module.exports = { + meteredPlugin, + syncStart, + syncEnd +} diff --git a/lib/settings.js b/lib/settings.js index 8b7f4795..269479a1 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1,16 +1,18 @@ + const path = require('path') const { Eta } = require('eta') const Glob = require('./glob') const NopCommand = require('./nopcommand') const MergeDeep = require('./mergeDeep') const env = require('./env') -const { meteredPlugin } = require('./metrics') +const { meteredPlugin, syncStart, syncEnd } = require('./metrics') const CONFIG_PATH = env.CONFIG_PATH const eta = new Eta({ views: path.join(__dirname) }) const SCOPE = { ORG: 'org', REPO: 'repo' } // Determine if the setting is a org setting or repo setting class Settings { static async syncAll (nop, context, repo, config, ref) { + syncStart(nop, 'all') const settings = new Settings(nop, context, repo, config, ref) try { await settings.loadConfigs() @@ -18,34 +20,43 @@ class Settings { await settings.updateOrg() await settings.updateAll() await settings.handleResults() + syncEnd(nop, 'all', false, settings.hasError()) } catch (error) { + syncEnd(nop, 'all', true, true) settings.logError(error.message) await settings.handleResults() } } static async syncSubOrgs (nop, context, suborg, repo, config, ref) { + syncStart(nop, 'suborg', suborg) const settings = new Settings(nop, context, repo, config, ref, suborg) try { await settings.loadConfigs() await settings.updateAll() await settings.handleResults() + syncStart(nop, 'suborg', false, settings.hasError(), suborg) } catch (error) { + syncEnd(nop, 'suborg', true, true, suborg) settings.logError(error.message) await settings.handleResults() } } static async sync (nop, context, repo, config, ref) { + syncStart(nop, 'repo', repo?.repo) const settings = new Settings(nop, context, repo, config, ref) try { await settings.loadConfigs(repo) if (settings.isRestricted(repo.repo)) { + syncEnd(nop, 'repo', false, false, repo?.repo) return } await settings.updateRepos(repo) await settings.handleResults() + syncEnd(nop, 'repo', false, settings.hasError(), repo?.repo) } catch (error) { + syncEnd(nop, 'repo', true, true, repo?.repo) settings.logError(error.message) await settings.handleResults() } @@ -152,6 +163,10 @@ class Settings { }) } + hasError () { + return !!this.errors.length + } + async handleResults () { const { payload } = this.context