diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 8963f7c141..dfc544c3ed 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -5,6 +5,8 @@ import { generateAppUserID, queryGlobalView, UNICODE_MAX, + DocumentType, + SEPARATOR, directCouchFind, } from "./db" import { BulkDocsResponse, User } from "@budibase/types" @@ -45,6 +47,16 @@ export const bulkGetGlobalUsersById = async ( return users } +export const getAllUserIds = async () => { + const db = getGlobalDB() + const startKey = `${DocumentType.USER}${SEPARATOR}` + const response = await db.allDocs({ + startkey: startKey, + endkey: `${startKey}${UNICODE_MAX}`, + }) + return response.rows.map(row => row.id) +} + export const bulkUpdateGlobalUsers = async (users: User[]) => { const db = getGlobalDB() return (await db.bulkDocs(users)) as BulkDocsResponse diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index a9959186a8..bcff78e861 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -115,6 +115,15 @@ ] } }, + "deploymentOutput": { + "value": { + "data": { + "_id": "ef12381f934b4f129675cdbb76eff3c2", + "status": "SUCCESS", + "appUrl": "/app-url" + } + } + }, "inputRow": { "value": { "_id": "ro_ta_5b1649e42a5b41dea4ef7742a36a7a70_e6dc7e38cf1343b2b56760265201cda4", @@ -413,6 +422,9 @@ } ] } + }, + "metrics": { + "value": "# HELP budibase_os_uptime Time in seconds that the host operating system has been up.\n# TYPE budibase_os_uptime counter\nbudibase_os_uptime 54958\n# HELP budibase_os_free_mem Bytes of memory free for usage on the host operating system.\n# TYPE budibase_os_free_mem gauge\nbudibase_os_free_mem 804507648\n# HELP budibase_os_total_mem Total bytes of memory on the host operating system.\n# TYPE budibase_os_total_mem gauge\nbudibase_os_total_mem 16742404096\n# HELP budibase_os_used_mem Total bytes of memory in use on the host operating system.\n# TYPE budibase_os_used_mem gauge\nbudibase_os_used_mem 15937896448\n# HELP budibase_os_load1 Host operating system load average.\n# TYPE budibase_os_load1 gauge\nbudibase_os_load1 1.91\n# HELP budibase_os_load5 Host operating system load average.\n# TYPE budibase_os_load5 gauge\nbudibase_os_load5 1.75\n# HELP budibase_os_load15 Host operating system load average.\n# TYPE budibase_os_load15 gauge\nbudibase_os_load15 1.56\n# HELP budibase_tenant_user_count The number of users created.\n# TYPE budibase_tenant_user_count gauge\nbudibase_tenant_user_count 1\n# HELP budibase_tenant_app_count The number of apps created by a user.\n# TYPE budibase_tenant_app_count gauge\nbudibase_tenant_app_count 2\n# HELP budibase_tenant_production_app_count The number of apps a user has published.\n# TYPE budibase_tenant_production_app_count gauge\nbudibase_tenant_production_app_count 1\n# HELP budibase_tenant_dev_app_count The number of apps a user has unpublished in development.\n# TYPE budibase_tenant_dev_app_count gauge\nbudibase_tenant_dev_app_count 1\n# HELP budibase_tenant_db_count The number of couchdb databases including global tables such as _users.\n# TYPE budibase_tenant_db_count gauge\nbudibase_tenant_db_count 3\n# HELP budibase_quota_usage_apps The number of apps created.\n# TYPE budibase_quota_usage_apps gauge\nbudibase_quota_usage_apps 1\n# HELP budibase_quota_limit_apps The limit on the number of apps that can be created.\n# TYPE budibase_quota_limit_apps gauge\nbudibase_quota_limit_apps 9007199254740991\n# HELP budibase_quota_usage_rows The number of database rows used from the quota.\n# TYPE budibase_quota_usage_rows gauge\nbudibase_quota_usage_rows 0\n# HELP budibase_quota_limit_rows The limit on the number of rows that can be created.\n# TYPE budibase_quota_limit_rows gauge\nbudibase_quota_limit_rows 9007199254740991\n# HELP budibase_quota_usage_plugins The number of plugins in use.\n# TYPE budibase_quota_usage_plugins gauge\nbudibase_quota_usage_plugins 0\n# HELP budibase_quota_limit_plugins The limit on the number of plugins that can be created.\n# TYPE budibase_quota_limit_plugins gauge\nbudibase_quota_limit_plugins 9007199254740991\n# HELP budibase_quota_usage_user_groups The number of user groups created.\n# TYPE budibase_quota_usage_user_groups gauge\nbudibase_quota_usage_user_groups 0\n# HELP budibase_quota_limit_user_groups The limit on the number of user groups that can be created.\n# TYPE budibase_quota_limit_user_groups gauge\nbudibase_quota_limit_user_groups 9007199254740991\n# HELP budibase_quota_usage_queries The number of queries used in the current month.\n# TYPE budibase_quota_usage_queries gauge\nbudibase_quota_usage_queries 0\n# HELP budibase_quota_limit_queries The limit on the number of queries for the current month.\n# TYPE budibase_quota_limit_queries gauge\nbudibase_quota_limit_queries 9007199254740991\n# HELP budibase_quota_usage_automations The number of automations used in the current month.\n# TYPE budibase_quota_usage_automations gauge\nbudibase_quota_usage_automations 0\n# HELP budibase_quota_limit_automations The limit on the number of automations that can be created.\n# TYPE budibase_quota_limit_automations gauge\nbudibase_quota_limit_automations 9007199254740991\n" } }, "securitySchemes": { @@ -2054,6 +2066,33 @@ } } }, + "/metrics": { + "get": { + "operationId": "metricsGet", + "summary": "Retrieve Budibase tenant metrics", + "description": "Output metrics in OpenMetrics format compatible with Prometheus", + "tags": [ + "metrics" + ], + "responses": { + "200": { + "description": "Returns tenant metrics.", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "examples": { + "metrics": { + "$ref": "#/components/examples/metrics" + } + } + } + } + } + } + } + }, "/queries/{queryId}": { "post": { "operationId": "queryExecute", diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index a3007b9639..a9daed6c76 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -85,6 +85,12 @@ components: updatedAt: 2022-02-22T13:00:54.035Z createdAt: 2022-02-11T18:02:26.961Z status: development + deploymentOutput: + value: + data: + _id: ef12381f934b4f129675cdbb76eff3c2 + status: SUCCESS + appUrl: /app-url inputRow: value: _id: ro_ta_5b1649e42a5b41dea4ef7742a36a7a70_e6dc7e38cf1343b2b56760265201cda4 @@ -290,6 +296,152 @@ components: name: Admin permissionId: admin inherits: POWER + metrics: + value: > + # HELP budibase_os_uptime Time in seconds that the host operating system + has been up. + + # TYPE budibase_os_uptime counter + + budibase_os_uptime 54958 + + # HELP budibase_os_free_mem Bytes of memory free for usage on the host operating system. + + # TYPE budibase_os_free_mem gauge + + budibase_os_free_mem 804507648 + + # HELP budibase_os_total_mem Total bytes of memory on the host operating system. + + # TYPE budibase_os_total_mem gauge + + budibase_os_total_mem 16742404096 + + # HELP budibase_os_used_mem Total bytes of memory in use on the host operating system. + + # TYPE budibase_os_used_mem gauge + + budibase_os_used_mem 15937896448 + + # HELP budibase_os_load1 Host operating system load average. + + # TYPE budibase_os_load1 gauge + + budibase_os_load1 1.91 + + # HELP budibase_os_load5 Host operating system load average. + + # TYPE budibase_os_load5 gauge + + budibase_os_load5 1.75 + + # HELP budibase_os_load15 Host operating system load average. + + # TYPE budibase_os_load15 gauge + + budibase_os_load15 1.56 + + # HELP budibase_tenant_user_count The number of users created. + + # TYPE budibase_tenant_user_count gauge + + budibase_tenant_user_count 1 + + # HELP budibase_tenant_app_count The number of apps created by a user. + + # TYPE budibase_tenant_app_count gauge + + budibase_tenant_app_count 2 + + # HELP budibase_tenant_production_app_count The number of apps a user has published. + + # TYPE budibase_tenant_production_app_count gauge + + budibase_tenant_production_app_count 1 + + # HELP budibase_tenant_dev_app_count The number of apps a user has unpublished in development. + + # TYPE budibase_tenant_dev_app_count gauge + + budibase_tenant_dev_app_count 1 + + # HELP budibase_tenant_db_count The number of couchdb databases including global tables such as _users. + + # TYPE budibase_tenant_db_count gauge + + budibase_tenant_db_count 3 + + # HELP budibase_quota_usage_apps The number of apps created. + + # TYPE budibase_quota_usage_apps gauge + + budibase_quota_usage_apps 1 + + # HELP budibase_quota_limit_apps The limit on the number of apps that can be created. + + # TYPE budibase_quota_limit_apps gauge + + budibase_quota_limit_apps 9007199254740991 + + # HELP budibase_quota_usage_rows The number of database rows used from the quota. + + # TYPE budibase_quota_usage_rows gauge + + budibase_quota_usage_rows 0 + + # HELP budibase_quota_limit_rows The limit on the number of rows that can be created. + + # TYPE budibase_quota_limit_rows gauge + + budibase_quota_limit_rows 9007199254740991 + + # HELP budibase_quota_usage_plugins The number of plugins in use. + + # TYPE budibase_quota_usage_plugins gauge + + budibase_quota_usage_plugins 0 + + # HELP budibase_quota_limit_plugins The limit on the number of plugins that can be created. + + # TYPE budibase_quota_limit_plugins gauge + + budibase_quota_limit_plugins 9007199254740991 + + # HELP budibase_quota_usage_user_groups The number of user groups created. + + # TYPE budibase_quota_usage_user_groups gauge + + budibase_quota_usage_user_groups 0 + + # HELP budibase_quota_limit_user_groups The limit on the number of user groups that can be created. + + # TYPE budibase_quota_limit_user_groups gauge + + budibase_quota_limit_user_groups 9007199254740991 + + # HELP budibase_quota_usage_queries The number of queries used in the current month. + + # TYPE budibase_quota_usage_queries gauge + + budibase_quota_usage_queries 0 + + # HELP budibase_quota_limit_queries The limit on the number of queries for the current month. + + # TYPE budibase_quota_limit_queries gauge + + budibase_quota_limit_queries 9007199254740991 + + # HELP budibase_quota_usage_automations The number of automations used in the current month. + + # TYPE budibase_quota_usage_automations gauge + + budibase_quota_usage_automations 0 + + # HELP budibase_quota_limit_automations The limit on the number of automations that can be created. + + # TYPE budibase_quota_limit_automations gauge + + budibase_quota_limit_automations 9007199254740991 securitySchemes: ApiKeyAuth: type: apiKey @@ -1531,6 +1683,23 @@ paths: examples: applications: $ref: "#/components/examples/applications" + /metrics: + get: + operationId: metricsGet + summary: Retrieve Budibase tenant metrics + description: Output metrics in OpenMetrics format compatible with Prometheus + tags: + - metrics + responses: + "200": + description: Returns tenant metrics. + content: + text/plain: + schema: + type: string + examples: + metrics: + $ref: "#/components/examples/metrics" "/queries/{queryId}": post: operationId: queryExecute diff --git a/packages/server/specs/resources/application.ts b/packages/server/specs/resources/application.ts index aa037da154..cd7a68c049 100644 --- a/packages/server/specs/resources/application.ts +++ b/packages/server/specs/resources/application.ts @@ -15,6 +15,12 @@ const application = { lockedBy: userResource.getExamples().user.value.user, } +const deployment = { + _id: "ef12381f934b4f129675cdbb76eff3c2", + status: "SUCCESS", + appUrl: "/app-url", +} + const base = { name: { description: "The name of the app.", @@ -108,6 +114,11 @@ export default new Resource() data: [application], }, }, + deploymentOutput: { + value: { + data: deployment, + }, + }, }) .setSchemas({ application: applicationSchema, diff --git a/packages/server/specs/resources/index.ts b/packages/server/specs/resources/index.ts index f1729f4541..6b8a1aa437 100644 --- a/packages/server/specs/resources/index.ts +++ b/packages/server/specs/resources/index.ts @@ -3,6 +3,7 @@ import row from "./row" import table from "./table" import query from "./query" import user from "./user" +import metrics from "./metrics" import misc from "./misc" export const examples = { @@ -12,6 +13,7 @@ export const examples = { ...query.getExamples(), ...user.getExamples(), ...misc.getExamples(), + ...metrics.getExamples(), } export const schemas = { diff --git a/packages/server/specs/resources/metrics.ts b/packages/server/specs/resources/metrics.ts new file mode 100644 index 0000000000..1dfbe101d8 --- /dev/null +++ b/packages/server/specs/resources/metrics.ts @@ -0,0 +1,81 @@ +import Resource from "./utils/Resource" + +const metricsResponse = + "# HELP budibase_os_uptime Time in seconds that the host operating system has been up.\n" + + "# TYPE budibase_os_uptime counter\n" + + "budibase_os_uptime 54958\n" + + "# HELP budibase_os_free_mem Bytes of memory free for usage on the host operating system.\n" + + "# TYPE budibase_os_free_mem gauge\n" + + "budibase_os_free_mem 804507648\n" + + "# HELP budibase_os_total_mem Total bytes of memory on the host operating system.\n" + + "# TYPE budibase_os_total_mem gauge\n" + + "budibase_os_total_mem 16742404096\n" + + "# HELP budibase_os_used_mem Total bytes of memory in use on the host operating system.\n" + + "# TYPE budibase_os_used_mem gauge\n" + + "budibase_os_used_mem 15937896448\n" + + "# HELP budibase_os_load1 Host operating system load average.\n" + + "# TYPE budibase_os_load1 gauge\n" + + "budibase_os_load1 1.91\n" + + "# HELP budibase_os_load5 Host operating system load average.\n" + + "# TYPE budibase_os_load5 gauge\n" + + "budibase_os_load5 1.75\n" + + "# HELP budibase_os_load15 Host operating system load average.\n" + + "# TYPE budibase_os_load15 gauge\n" + + "budibase_os_load15 1.56\n" + + "# HELP budibase_tenant_user_count The number of users created.\n" + + "# TYPE budibase_tenant_user_count gauge\n" + + "budibase_tenant_user_count 1\n" + + "# HELP budibase_tenant_app_count The number of apps created by a user.\n" + + "# TYPE budibase_tenant_app_count gauge\n" + + "budibase_tenant_app_count 2\n" + + "# HELP budibase_tenant_production_app_count The number of apps a user has published.\n" + + "# TYPE budibase_tenant_production_app_count gauge\n" + + "budibase_tenant_production_app_count 1\n" + + "# HELP budibase_tenant_dev_app_count The number of apps a user has unpublished in development.\n" + + "# TYPE budibase_tenant_dev_app_count gauge\n" + + "budibase_tenant_dev_app_count 1\n" + + "# HELP budibase_tenant_db_count The number of couchdb databases including global tables such as _users.\n" + + "# TYPE budibase_tenant_db_count gauge\n" + + "budibase_tenant_db_count 3\n" + + "# HELP budibase_quota_usage_apps The number of apps created.\n" + + "# TYPE budibase_quota_usage_apps gauge\n" + + "budibase_quota_usage_apps 1\n" + + "# HELP budibase_quota_limit_apps The limit on the number of apps that can be created.\n" + + "# TYPE budibase_quota_limit_apps gauge\n" + + "budibase_quota_limit_apps 9007199254740991\n" + + "# HELP budibase_quota_usage_rows The number of database rows used from the quota.\n" + + "# TYPE budibase_quota_usage_rows gauge\n" + + "budibase_quota_usage_rows 0\n" + + "# HELP budibase_quota_limit_rows The limit on the number of rows that can be created.\n" + + "# TYPE budibase_quota_limit_rows gauge\n" + + "budibase_quota_limit_rows 9007199254740991\n" + + "# HELP budibase_quota_usage_plugins The number of plugins in use.\n" + + "# TYPE budibase_quota_usage_plugins gauge\n" + + "budibase_quota_usage_plugins 0\n" + + "# HELP budibase_quota_limit_plugins The limit on the number of plugins that can be created.\n" + + "# TYPE budibase_quota_limit_plugins gauge\n" + + "budibase_quota_limit_plugins 9007199254740991\n" + + "# HELP budibase_quota_usage_user_groups The number of user groups created.\n" + + "# TYPE budibase_quota_usage_user_groups gauge\n" + + "budibase_quota_usage_user_groups 0\n" + + "# HELP budibase_quota_limit_user_groups The limit on the number of user groups that can be created.\n" + + "# TYPE budibase_quota_limit_user_groups gauge\n" + + "budibase_quota_limit_user_groups 9007199254740991\n" + + "# HELP budibase_quota_usage_queries The number of queries used in the current month.\n" + + "# TYPE budibase_quota_usage_queries gauge\n" + + "budibase_quota_usage_queries 0\n" + + "# HELP budibase_quota_limit_queries The limit on the number of queries for the current month.\n" + + "# TYPE budibase_quota_limit_queries gauge\n" + + "budibase_quota_limit_queries 9007199254740991\n" + + "# HELP budibase_quota_usage_automations The number of automations used in the current month.\n" + + "# TYPE budibase_quota_usage_automations gauge\n" + + "budibase_quota_usage_automations 0\n" + + "# HELP budibase_quota_limit_automations The limit on the number of automations that can be created.\n" + + "# TYPE budibase_quota_limit_automations gauge\n" + + "budibase_quota_limit_automations 9007199254740991\n" + +export default new Resource().setExamples({ + metrics: { + value: metricsResponse, + }, +}) diff --git a/packages/server/src/api/controllers/public/metrics.ts b/packages/server/src/api/controllers/public/metrics.ts new file mode 100644 index 0000000000..b6a8de3b7c --- /dev/null +++ b/packages/server/src/api/controllers/public/metrics.ts @@ -0,0 +1,251 @@ +import { Ctx } from "@budibase/types" +import { users as userCore, db as dbCore } from "@budibase/backend-core" +import { quotas, licensing } from "@budibase/pro" + +import os from "os" + +export async function fetch(ctx: Ctx) { + // *** OPERATING SYSTEM *** + const freeMem = os.freemem() + const totalMem = os.totalmem() + const usedMem = totalMem - freeMem + const uptime = os.uptime() + + // *** APPS *** + const allDatabases = await dbCore.getAllDbs() + const devAppIDs = await dbCore.getDevAppIDs() + const prodAppIDs = await dbCore.getProdAppIDs() + const allAppIds = await dbCore.getAllApps({ idsOnly: true }) + + // *** USERS *** + const usersObject = await userCore.getAllUserIds() + + // *** QUOTAS *** + const usage = await quotas.getQuotaUsage() + const license = await licensing.cache.getCachedLicense() + const appsQuotaUsage = usage.usageQuota.apps + const rowsQuotaUsage = usage.usageQuota.rows + const pluginsQuotaUsage = usage.usageQuota.plugins + const userGroupsQuotaUsage = usage.usageQuota.userGroups + const queryQuotaUsage = usage.monthly.current.queries + const automationsQuotaUsage = usage.monthly.current.automations + const appsQuotaLimit = license.quotas.usage.static.apps.value + const rowsQuotaLimit = license.quotas.usage.static.rows.value + const userGroupsQuotaLimit = license.quotas.usage.static.userGroups.value + const pluginsQuotaLimit = license.quotas.usage.static.plugins.value + const queryQuotaLimit = license.quotas.usage.monthly.queries.value + const automationsQuotaLimit = license.quotas.usage.monthly.automations.value + + // *** BUILD THE OUTPUT STRING *** + var outputString = "" + + // **** budibase_os_uptime **** + outputString += convertToOpenMetrics( + "budibase_os_uptime", + "Time in seconds that the host operating system has been up", + "counter", + uptime + ) + + // **** budibase_os_free_mem **** + outputString += convertToOpenMetrics( + "budibase_os_free_mem", + "Bytes of memory free for usage on the host operating system", + "gauge", + freeMem + ) + + // **** budibase_os_total_mem **** + outputString += convertToOpenMetrics( + "budibase_os_total_mem", + "Total bytes of memory on the host operating system", + "gauge", + totalMem + ) + + // **** budibase_os_used_mem **** + outputString += convertToOpenMetrics( + "budibase_os_used_mem", + "Total bytes of memory in use on the host operating system", + "gauge", + usedMem + ) + + // **** budibase_os_load1 **** + outputString += convertToOpenMetrics( + "budibase_os_load1", + "Host operating system load average", + "gauge", + os.loadavg()[0] + ) + + // **** budibase_os_load5 **** + outputString += convertToOpenMetrics( + "budibase_os_load5", + "Host operating system load average", + "gauge", + os.loadavg()[1] + ) + // **** budibase_os_load15 **** + outputString += convertToOpenMetrics( + "budibase_os_load15", + "Host operating system load average", + "gauge", + os.loadavg()[2] + ) + + // **** budibase_tenant_user_count **** + outputString += convertToOpenMetrics( + "budibase_tenant_user_count", + "The number of users created", + "gauge", + usersObject.length + ) + + // **** budibase_tenant_app_count **** + outputString += convertToOpenMetrics( + "budibase_tenant_app_count", + "The number of apps created by a user", + "gauge", + allAppIds.length + ) + + // **** budibase_tenant_production_app_count **** + outputString += convertToOpenMetrics( + "budibase_tenant_production_app_count", + "The number of apps a user has published", + "gauge", + prodAppIDs.length + ) + + // **** budibase_tenant_dev_app_count **** + outputString += convertToOpenMetrics( + "budibase_tenant_dev_app_count", + "The number of apps a user has unpublished in development", + "gauge", + devAppIDs.length + ) + + // **** budibase_tenant_db_count **** + outputString += convertToOpenMetrics( + "budibase_tenant_db_count", + "The number of couchdb databases including global tables such as _users", + "gauge", + allDatabases.length + ) + + // **** budibase_quota_usage_apps **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_apps", + "The number of apps created", + "gauge", + appsQuotaUsage + ) + + // **** budibase_quota_limit_apps **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_apps", + "The limit on the number of apps that can be created", + "gauge", + appsQuotaLimit == -1 ? Number.MAX_SAFE_INTEGER : appsQuotaLimit + ) + + // **** budibase_quota_usage_rows **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_rows", + "The number of database rows used from the quota", + "gauge", + rowsQuotaUsage + ) + + // **** budibase_quota_limit_rows **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_rows", + "The limit on the number of rows that can be created", + "gauge", + rowsQuotaLimit == -1 ? Number.MAX_SAFE_INTEGER : rowsQuotaLimit + ) + + // **** budibase_quota_usage_plugins **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_plugins", + "The number of plugins in use", + "gauge", + pluginsQuotaUsage + ) + + // **** budibase_quota_limit_plugins **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_plugins", + "The limit on the number of plugins that can be created", + "gauge", + pluginsQuotaLimit == -1 ? Number.MAX_SAFE_INTEGER : pluginsQuotaLimit + ) + + // **** budibase_quota_usage_user_groups **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_user_groups", + "The number of user groups created", + "gauge", + userGroupsQuotaUsage + ) + + // **** budibase_quota_limit_user_groups **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_user_groups", + "The limit on the number of user groups that can be created", + "gauge", + userGroupsQuotaLimit == -1 ? Number.MAX_SAFE_INTEGER : userGroupsQuotaLimit + ) + + // **** budibase_quota_usage_queries **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_queries", + "The number of queries used in the current month", + "gauge", + queryQuotaUsage + ) + + // **** budibase_quota_limit_queries **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_queries", + "The limit on the number of queries for the current month", + "gauge", + queryQuotaLimit == -1 ? Number.MAX_SAFE_INTEGER : queryQuotaLimit + ) + + // **** budibase_quota_usage_automations **** + outputString += convertToOpenMetrics( + "budibase_quota_usage_automations", + "The number of automations used in the current month", + "gauge", + automationsQuotaUsage + ) + + // **** budibase_quota_limit_automations **** + outputString += convertToOpenMetrics( + "budibase_quota_limit_automations", + "The limit on the number of automations that can be created", + "gauge", + automationsQuotaLimit == -1 + ? Number.MAX_SAFE_INTEGER + : automationsQuotaLimit + ) + ctx.body = outputString + ctx.set("Content-Type", "text/plain") +} + +export function convertToOpenMetrics( + metricName: string, + metricHelp: string, + metricType: string, + metricValue: number +) { + return `# HELP ${metricName} ${metricHelp}. +# TYPE ${metricName} ${metricType} +${metricName} ${metricValue}\n` +} + +export default { + fetch, +} diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts index 79e7731752..e2bce1e730 100644 --- a/packages/server/src/api/routes/public/index.ts +++ b/packages/server/src/api/routes/public/index.ts @@ -1,4 +1,5 @@ import appEndpoints from "./applications" +import metricEndpoints from "./metrics" import queryEndpoints from "./queries" import tableEndpoints from "./tables" import rowEndpoints from "./rows" @@ -12,7 +13,7 @@ import env from "../../../environment" // below imports don't have declaration files const Router = require("@koa/router") const { RateLimit, Stores } = require("koa2-ratelimit") -import { redis, permissions } from "@budibase/backend-core" +import { middleware, redis, permissions } from "@budibase/backend-core" const { PermissionType, PermissionLevel } = permissions const PREFIX = "/api/public/v1" @@ -91,6 +92,13 @@ function addToRouter(endpoints: any) { } } +function applyAdminRoutes(endpoints: any) { + addMiddleware(endpoints.read, middleware.builderOrAdmin) + addMiddleware(endpoints.write, middleware.builderOrAdmin) + addToRouter(endpoints.read) + addToRouter(endpoints.write) +} + function applyRoutes( endpoints: any, permType: string, @@ -119,6 +127,7 @@ function applyRoutes( addToRouter(endpoints.write) } +applyAdminRoutes(metricEndpoints) applyRoutes(appEndpoints, PermissionType.APP, "appId") applyRoutes(tableEndpoints, PermissionType.TABLE, "tableId") applyRoutes(userEndpoints, PermissionType.USER, "userId") diff --git a/packages/server/src/api/routes/public/metrics.ts b/packages/server/src/api/routes/public/metrics.ts new file mode 100644 index 0000000000..841da0e804 --- /dev/null +++ b/packages/server/src/api/routes/public/metrics.ts @@ -0,0 +1,28 @@ +import controller from "../../controllers/public/metrics" +import Endpoint from "./utils/Endpoint" + +const read = [] + +/** + * @openapi + * /metrics: + * get: + * operationId: metricsGet + * summary: Retrieve Budibase tenant metrics + * description: Output metrics in OpenMetrics format compatible with Prometheus + * tags: + * - metrics + * responses: + * 200: + * description: Returns tenant metrics. + * content: + * text/plain: + * schema: + * type: string + * examples: + * metrics: + * $ref: '#/components/examples/metrics' + */ +read.push(new Endpoint("get", "/metrics", controller.fetch)) + +export default { read } diff --git a/packages/server/src/api/routes/public/tests/metrics.spec.js b/packages/server/src/api/routes/public/tests/metrics.spec.js new file mode 100644 index 0000000000..8e226ec958 --- /dev/null +++ b/packages/server/src/api/routes/public/tests/metrics.spec.js @@ -0,0 +1,34 @@ +const setup = require("../../tests/utilities") + +jest.setTimeout(30000) + +describe("/metrics", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + // For some reason this cannot be a beforeAll or the test "should be able to update the user" fail + beforeEach(async () => { + await config.init() + }) + + describe("get", () => { + it("returns a list of metrics", async () => { + const res = await request + .get(`/api/public/v1/metrics`) + .set(config.defaultHeaders()) + .expect("Content-Type", /text\/plain/) + .expect(200) + expect(res.text).toContain("budibase_tenant_user_count") + }) + + it("endpoint should not be publicly exposed", async () => { + await request + .get(`/api/public/v1/metrics`) + .set(config.publicHeaders()) + .expect(403) + }) + }) + +}) diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index 8c44131e96..5ca4990647 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -22,6 +22,10 @@ export interface paths { /** Based on application properties (currently only name) search for applications. */ post: operations["appSearch"]; }; + "/metrics": { + /** Output metrics in OpenMetrics format compatible with Prometheus */ + get: operations["metricsGet"]; + }; "/queries/{queryId}": { /** Queries which have been created within a Budibase app can be executed using this, */ post: operations["queryExecute"]; @@ -844,6 +848,17 @@ export interface operations { }; }; }; + /** Output metrics in OpenMetrics format compatible with Prometheus */ + metricsGet: { + responses: { + /** Returns tenant metrics. */ + 200: { + content: { + "text/plain": string; + }; + }; + }; + }; /** Queries which have been created within a Budibase app can be executed using this, */ queryExecute: { parameters: {