smc-hub
Version:
CoCalc: Backend webserver component
166 lines (147 loc) • 5.63 kB
text/typescript
/*
* 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);
}