Path: blob/master/src/packages/conat/monitor/usage.ts
1452 views
import json from "json-stable-stringify";1import { EventEmitter } from "events";2import type { JSONValue } from "@cocalc/util/types";3import { ConatError } from "@cocalc/conat/core/client";4import { getLogger } from "@cocalc/conat/client";56const logger = getLogger("monitor:usage");78interface Options {9resource: string;10maxPerUser?: number;11max?: number;12log?: (...args) => void;13}1415export class UsageMonitor extends EventEmitter {16private options: Options;17private total = 0;18private perUser: { [user: string]: number } = {};1920constructor(options: Options) {21super();22this.options = options;23logger.debug("creating usage monitor", this.options);24this.initLogging();25}2627stats = () => {28return { total: this.total, perUser: this.perUser };29};3031close = () => {32this.removeAllListeners();33this.perUser = {};34};3536private toJson = (user: JSONValue) => json(user) ?? "";3738private initLogging = () => {39const { log } = this.options;40if (log == null) {41return;42}43this.on("total", (total, limit) => {44log("usage", this.options.resource, { total, limit });45});46this.on("add", (user, count, limit) => {47log("usage", this.options.resource, "add", { user, count, limit });48});49this.on("delete", (user, count, limit) => {50log("usage", this.options.resource, "delete", { user, count, limit });51});52this.on("deny", (user, limit, type) => {53log("usage", this.options.resource, "not allowed due to hitting limit", {54type,55user,56limit,57});58});59};6061add = (user: JSONValue) => {62const u = this.toJson(user);63let count = this.perUser[u] ?? 0;64if (this.options.max && this.total >= this.options.max) {65this.emit("deny", user, this.options.max, "global");66throw new ConatError(67`There is a global limit of ${this.options.max} ${this.options.resource}. Please close browser tabs or files or come back later.`,68// http error code "429 Too Many Requests."69{ code: 429 },70);71}72if (this.options.maxPerUser && count >= this.options.maxPerUser) {73this.emit("deny", this.options.maxPerUser, "per-user");74throw new ConatError(75`There is a per user limit of ${this.options.maxPerUser} ${this.options.resource}. Please close browser tabs or files or come back later.`,76// http error code "429 Too Many Requests."77{ code: 429 },78);79}80this.total += 1;81count++;82this.perUser[u] = count;83this.emit("total", this.total, this.options.max);84this.emit("add", user, count, this.options.maxPerUser);85};8687delete = (user: JSONValue) => {88this.total -= 1;89const u = this.toJson(user);90let count = (this.perUser[u] ?? 0) - 1;91if (count <= 0) {92delete this.perUser[u];93} else {94this.perUser[u] = count;95}96this.emit("total", this.total);97this.emit("delete", user, count);98};99}100101102