@cocalc/database
Version:
CoCalc: code for working with our PostgreSQL database
142 lines (138 loc) • 6.49 kB
JavaScript
;
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.number_of_projects_using_site_license = exports.projects_using_site_license = exports.site_license_usage_stats = exports.number_of_projects_with_license_applied = exports.number_of_running_projects_using_license = exports.numberRunningQuery = void 0;
const async_utils_1 = require("@cocalc/util/async-utils");
const misc_1 = require("@cocalc/util/misc");
const TIMEOUT_S = 30;
function numberRunningQuery(license_id) {
if (!(0, misc_1.isValidUUID)(license_id)) {
// critical to check to avoid any possible SQL injection attack.
throw Error("invalid license_id");
}
// "... - 'status'" in the query, because there is always a status field (which is new)
// an applied license not providing upgrades is just an empty object.
return `
SELECT COUNT(*)::INT
FROM projects
WHERE state ->> 'state' = 'running'
AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`;
}
exports.numberRunningQuery = numberRunningQuery;
async function number_of_running_projects_using_license(db, license_id) {
/* 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.
(3) we have to ignore the "status" field, which is only information but not providing upgrades.
*/
const query = numberRunningQuery(license_id);
const x = await db.async_query({ query, timeout_s: TIMEOUT_S });
return parseInt(x.rows[0].count);
}
exports.number_of_running_projects_using_license = number_of_running_projects_using_license;
async function number_of_projects_with_license_applied(db, license_id) {
/* 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);
}
exports.number_of_projects_with_license_applied = number_of_projects_with_license_applied;
/* 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).
*/
async function site_license_usage_stats(db) {
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 = {};
for (let row of result.rows) {
for (const license_id in row.site_license) {
if ((0, misc_1.len)(row.site_license[license_id]) > 0) {
if (usage[license_id] == null) {
usage[license_id] = 1;
}
else {
usage[license_id] += 1;
}
}
}
}
return usage;
}
exports.site_license_usage_stats = site_license_usage_stats;
function query_projects_using_site_license(license_id, cutoff) {
const params = [];
let query;
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
WHERE state#>>'{state}' IN ('running', 'starting')
AND ((site_license -> '${license_id}') - 'status') != '{}'::JSONB`;
}
return { query, params };
}
async function projects_using_site_license(db, opts) {
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 (0, async_utils_1.callback2)(db._query.bind(db), {
query: select + " " + query,
limit: opts.limit,
params,
});
const v = [];
for (const row of x.rows) {
v.push((0, misc_1.copy_with)(row, opts.fields));
}
return v;
}
exports.projects_using_site_license = projects_using_site_license;
function process_fields(fields, truncate) {
const v = [];
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;
}
async function number_of_projects_using_site_license(db, opts) {
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))::INT " + query,
params,
timeout_s: TIMEOUT_S,
});
return parseInt(x.rows[0].count);
}
exports.number_of_projects_using_site_license = number_of_projects_using_site_license;
//# sourceMappingURL=analytics.js.map