UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

166 lines (147 loc) 5.63 kB
/* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { PostgreSQL } from "../types"; import { callback2 } from "smc-util/async-utils"; import { copy_with, len } from "smc-util/misc"; const TIMEOUT_S = 30; export async function number_of_running_projects_using_license( db: PostgreSQL, license_id: string ): Promise<number> { /* Do a query to count the number of projects that: (1) are running, (2) have the given license_id has a key in their site_license field with a nontrivial value. select project_id, site_license, state from projects where state#>>'{state}' in ('running', 'starting') and site_license#>>'{f3942ea1-ff3f-4d9f-937a-c5007babc693}'!='{}'; */ const query = `SELECT COUNT(*) FROM projects WHERE state#>>'{state}' = 'running' AND site_license#>>'{${license_id}}'!='{}'`; const x = await db.async_query({ query, timeout_s: TIMEOUT_S }); return parseInt(x.rows[0].count); } export async function number_of_projects_with_license_applied( db: PostgreSQL, license_id: string ): Promise<number> { /* Do a query to count the number of projects that have the given license_id has a key in their site_license field possibly with a trivial value. Basically, this is the number of projects somebody has set to use this license, whether or not they have successfully actually used it. select project_id, site_license, state from projects where state#>>'{state}' in ('running', 'starting') and site_license#>>'{f3942ea1-ff3f-4d9f-937a-c5007babc693}' IS NOT NULL; */ const query = `SELECT COUNT(*) FROM projects WHERE site_license#>>'{${license_id}}' IS NOT NULL`; const x = await db.async_query({ query, timeout_s: TIMEOUT_S }); return parseInt(x.rows[0].count); } /* Returns information about how licenses are being used across ALL running projects in the system right now. The following query excludes anything with site_license null or {}, due to how sql works: select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}'; We then just process the result in Javascript. It would be possible to make a more complicated query that does all the processing in the database, and returns less data as output, but that would be harder for me, so I leave that to others or later (since this query is not likely to be used too much). */ export async function site_license_usage_stats( db: PostgreSQL ): Promise<{ [license_id: string]: number }> { const query = "select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}'"; const result = await db.async_query({ query }); const usage: { [license_id: string]: number } = {}; for (let row of result.rows) { for (const license_id in row.site_license) { if (len(row.site_license[license_id]) > 0) { if (usage[license_id] == null) { usage[license_id] = 1; } else { usage[license_id] += 1; } } } } return usage; } function query_projects_using_site_license( license_id: string, cutoff?: Date ): { query: string; params: any[] } { const params: any[] = []; let query: string; if (cutoff) { query = `FROM projects, site_license_usage_log WHERE `; query += "projects.project_id = site_license_usage_log.project_id AND "; query += "site_license_usage_log.license_id = $1 AND"; query += "(site_license_usage_log.start >= $2 OR "; query += " site_license_usage_log.stop >= $2 OR "; query += " site_license_usage_log.stop IS NULL)"; params.push(license_id); params.push(cutoff); } else { // easier -- just directly query the projects table. query = `FROM projects`; query += ` WHERE state#>>'{state}' IN ('running', 'starting') AND site_license#>>'{${license_id}}'!='{}'`; } return { query, params }; } export async function projects_using_site_license( db: PostgreSQL, opts: { license_id: string; fields: string[]; // assumed sanitized by caller! cutoff?: Date; limit?: number; truncate?: number; } ): Promise<{ [field: string]: any }[]> { const query_fields = process_fields(opts.fields, opts.truncate); const { query, params } = query_projects_using_site_license( opts.license_id, opts.cutoff ); const select = `SELECT ${query_fields.join(",")} `; const x = await callback2(db._query.bind(db), { query: select + " " + query, limit: opts.limit, params, }); const v: { [field: string]: any }[] = []; for (const row of x.rows) { v.push(copy_with(row, opts.fields)); } return v; } function process_fields( fields: string[], truncate: number | undefined ): string[] { const v: string[] = []; for (let field of fields) { if (truncate && (field == "title" || field == "description")) { field = `left(projects.${field},${truncate}) as ${field}`; } else if (field == "project_id") { field = `distinct(projects.project_id)`; } else { field = `projects.${field}`; } v.push(field); } return v; } export async function number_of_projects_using_site_license( db: PostgreSQL, opts: { license_id: string; cutoff?: Date; } ): Promise<number> { const { query, params } = query_projects_using_site_license( opts.license_id, opts.cutoff ); const x = await db.async_query({ query: "SELECT COUNT(DISTINCT(projects.project_id)) " + query, params, timeout_s: TIMEOUT_S, }); return parseInt(x.rows[0].count); }