Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/conat/project/usage-info.ts
1453 views
1
/*
2
Provide info about a specific path, derived from the project-status stream.
3
E.g. cpu/ram usage by a Jupyter notebook kernel.
4
5
This starts measuring when a request comes in for a path and stop when
6
there is no request for a while.
7
*/
8
9
import { projectSubject } from "@cocalc/conat/names";
10
import { conat } from "@cocalc/conat/client";
11
import { getLogger } from "@cocalc/conat/client";
12
import { type UsageInfo } from "@cocalc/util/types/project-usage-info";
13
import TTL from "@isaacs/ttlcache";
14
15
const logger = getLogger("project:usage-info");
16
17
type InfoServer = any;
18
19
const SERVICE_NAME = "usage-info";
20
21
// we automatically stop computing data about a specific path after this amount of
22
// time elapses with no user requests. Users make a request every 2-3 seconds,
23
// and even it times out, everything starts again in 2-3 seconds. So this is fine.
24
const SERVER_TIMEOUT = 15000;
25
26
function getSubject({ project_id, compute_server_id }) {
27
return projectSubject({
28
project_id,
29
compute_server_id,
30
service: SERVICE_NAME,
31
});
32
}
33
34
interface Api {
35
get: (path) => Promise<UsageInfo | null>;
36
}
37
38
export async function get({
39
project_id,
40
compute_server_id = 0,
41
path,
42
}: {
43
project_id: string;
44
compute_server_id?: number;
45
path: string;
46
}) {
47
const c = await conat();
48
const subject = getSubject({ project_id, compute_server_id });
49
return await c.call(subject).get(path);
50
}
51
52
interface Options {
53
project_id: string;
54
compute_server_id: number;
55
createUsageInfoServer: Function;
56
}
57
58
export class UsageInfoService {
59
private service?;
60
private infoServers = new TTL<string, InfoServer>({
61
ttl: SERVER_TIMEOUT,
62
dispose: (server) => this.dispose(server),
63
});
64
private usage = new TTL<string, UsageInfo>({ ttl: 2 * SERVER_TIMEOUT });
65
66
constructor(private options: Options) {
67
this.createService();
68
}
69
70
private createService = async () => {
71
const subject = getSubject(this.options);
72
logger.debug("starting usage-info service", { subject });
73
const client = await conat();
74
this.service = await client.service<Api>(subject, {
75
get: this.get,
76
});
77
};
78
79
private get = async (path: string): Promise<UsageInfo | null> => {
80
if (!this.infoServers.has(path)) {
81
logger.debug("creating new usage server for ", { path });
82
const server = this.options.createUsageInfoServer(path);
83
this.infoServers.set(path, server);
84
server.on("usage", (usage) => {
85
// logger.debug("got new info", { path, usage });
86
this.usage.set(path, usage);
87
});
88
}
89
return this.usage.get(path) ?? null;
90
};
91
92
private dispose = (server) => {
93
server.close();
94
};
95
96
close = (): void => {
97
this.infoServers.clear();
98
this.usage.clear();
99
this.service?.close();
100
delete this.service;
101
};
102
}
103
104
export function createUsageInfoService(options: Options) {
105
return new UsageInfoService(options);
106
}
107
108