Path: blob/master/src/packages/conat/project/usage-info.ts
1453 views
/*1Provide info about a specific path, derived from the project-status stream.2E.g. cpu/ram usage by a Jupyter notebook kernel.34This starts measuring when a request comes in for a path and stop when5there is no request for a while.6*/78import { projectSubject } from "@cocalc/conat/names";9import { conat } from "@cocalc/conat/client";10import { getLogger } from "@cocalc/conat/client";11import { type UsageInfo } from "@cocalc/util/types/project-usage-info";12import TTL from "@isaacs/ttlcache";1314const logger = getLogger("project:usage-info");1516type InfoServer = any;1718const SERVICE_NAME = "usage-info";1920// we automatically stop computing data about a specific path after this amount of21// time elapses with no user requests. Users make a request every 2-3 seconds,22// and even it times out, everything starts again in 2-3 seconds. So this is fine.23const SERVER_TIMEOUT = 15000;2425function getSubject({ project_id, compute_server_id }) {26return projectSubject({27project_id,28compute_server_id,29service: SERVICE_NAME,30});31}3233interface Api {34get: (path) => Promise<UsageInfo | null>;35}3637export async function get({38project_id,39compute_server_id = 0,40path,41}: {42project_id: string;43compute_server_id?: number;44path: string;45}) {46const c = await conat();47const subject = getSubject({ project_id, compute_server_id });48return await c.call(subject).get(path);49}5051interface Options {52project_id: string;53compute_server_id: number;54createUsageInfoServer: Function;55}5657export class UsageInfoService {58private service?;59private infoServers = new TTL<string, InfoServer>({60ttl: SERVER_TIMEOUT,61dispose: (server) => this.dispose(server),62});63private usage = new TTL<string, UsageInfo>({ ttl: 2 * SERVER_TIMEOUT });6465constructor(private options: Options) {66this.createService();67}6869private createService = async () => {70const subject = getSubject(this.options);71logger.debug("starting usage-info service", { subject });72const client = await conat();73this.service = await client.service<Api>(subject, {74get: this.get,75});76};7778private get = async (path: string): Promise<UsageInfo | null> => {79if (!this.infoServers.has(path)) {80logger.debug("creating new usage server for ", { path });81const server = this.options.createUsageInfoServer(path);82this.infoServers.set(path, server);83server.on("usage", (usage) => {84// logger.debug("got new info", { path, usage });85this.usage.set(path, usage);86});87}88return this.usage.get(path) ?? null;89};9091private dispose = (server) => {92server.close();93};9495close = (): void => {96this.infoServers.clear();97this.usage.clear();98this.service?.close();99delete this.service;100};101}102103export function createUsageInfoService(options: Options) {104return new UsageInfoService(options);105}106107108