Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/database/postgres/site-license/analytics.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { PostgreSQL } from "../types";6import { callback2 } from "@cocalc/util/async-utils";7import { copy_with, isValidUUID, len } from "@cocalc/util/misc";8const TIMEOUT_S = 30;910export function numberRunningQuery(license_id: string): string {11if (!isValidUUID(license_id)) {12// critical to check to avoid any possible SQL injection attack.13throw Error("invalid license_id");14}15// "... - 'status'" in the query, because there is always a status field (which is new)16// an applied license not providing upgrades is just an empty object.17return `18SELECT COUNT(*)::INT19FROM projects20WHERE state ->> 'state' = 'running'21AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`;22}2324export async function number_of_running_projects_using_license(25db: PostgreSQL,26license_id: string27): Promise<number> {28/* Do a query to count the number of projects that:29(1) are running,30(2) have the given license_id has a key in their site_license field with a nontrivial value.31(3) we have to ignore the "status" field, which is only information but not providing upgrades.32*/33const query = numberRunningQuery(license_id);34const x = await db.async_query({ query, timeout_s: TIMEOUT_S });35return parseInt(x.rows[0].count);36}3738export async function number_of_projects_with_license_applied(39db: PostgreSQL,40license_id: string41): Promise<number> {42/* Do a query to count the number of projects that have the given license_id has a key in their43site_license field possibly with a trivial value. Basically, this is the number of projects44somebody has set to use this license, whether or not they have successfully actually used it.4546select project_id, site_license, state from projects where state#>>'{state}' in ('running', 'starting') and site_license#>>'{f3942ea1-ff3f-4d9f-937a-c5007babc693}' IS NOT NULL;47*/4849const query = `SELECT COUNT(*) FROM projects WHERE site_license#>>'{${license_id}}' IS NOT NULL`;50const x = await db.async_query({ query, timeout_s: TIMEOUT_S });51return parseInt(x.rows[0].count);52}5354/* Returns information about how licenses are being used across ALL running projects55in the system right now.5657The following query excludes anything with site_license null or {}, due to how sql works:5859select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}';6061We then just process the result in Javascript. It would be possible to make a more complicated query that62does all the processing in the database, and returns less data as output, but that would be harder for me,63so I leave that to others or later (since this query is not likely to be used too much).64*/65export async function site_license_usage_stats(66db: PostgreSQL67): Promise<{ [license_id: string]: number }> {68const query =69"select site_license from projects where state#>>'{state}' in ('running', 'starting') and site_license!='{}'";70const result = await db.async_query({ query });71const usage: { [license_id: string]: number } = {};72for (let row of result.rows) {73for (const license_id in row.site_license) {74if (len(row.site_license[license_id]) > 0) {75if (usage[license_id] == null) {76usage[license_id] = 1;77} else {78usage[license_id] += 1;79}80}81}82}83return usage;84}8586export function query_projects_using_site_license(87license_id: string,88cutoff?: Date89): { query: string; params: any[] } {90const params: any[] = [];91let query: string;92if (cutoff) {93query = `FROM projects, site_license_usage_log WHERE `;94query += "projects.project_id = site_license_usage_log.project_id AND ";95query += "site_license_usage_log.license_id = $1 AND";96query += "(site_license_usage_log.start >= $2 OR ";97query += " site_license_usage_log.stop >= $2 OR ";98query += " site_license_usage_log.stop IS NULL)";99params.push(license_id);100params.push(cutoff);101} else {102// easier -- just directly query the projects table.103query = `104FROM projects105WHERE state#>>'{state}' IN ('running', 'starting')106AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`;107}108return { query, params };109}110111export async function projects_using_site_license(112db: PostgreSQL,113opts: {114license_id: string;115fields: string[]; // assumed sanitized by caller!116cutoff?: Date;117limit?: number;118truncate?: number;119}120): Promise<{ [field: string]: any }[]> {121const query_fields = process_fields(opts.fields, opts.truncate);122123const { query, params } = query_projects_using_site_license(124opts.license_id,125opts.cutoff126);127const select = `SELECT ${query_fields.join(",")} `;128const x = await callback2(db._query.bind(db), {129query: select + " " + query,130limit: opts.limit,131params,132});133const v: { [field: string]: any }[] = [];134for (const row of x.rows) {135v.push(copy_with(row, opts.fields));136}137return v;138}139140function process_fields(141fields: string[],142truncate: number | undefined143): string[] {144const v: string[] = [];145for (let field of fields) {146if (truncate && (field == "title" || field == "description")) {147field = `left(projects.${field},${truncate}) as ${field}`;148} else if (field == "project_id") {149field = `distinct(projects.project_id)`;150} else {151field = `projects.${field}`;152}153v.push(field);154}155return v;156}157158export async function number_of_projects_using_site_license(159db: PostgreSQL,160opts: {161license_id: string;162cutoff?: Date;163}164): Promise<number> {165const { query, params } = query_projects_using_site_license(166opts.license_id,167opts.cutoff168);169170const x = await db.async_query({171query: "SELECT COUNT(DISTINCT(projects.project_id))::INT " + query,172params,173timeout_s: TIMEOUT_S,174});175return parseInt(x.rows[0].count);176}177178179