Liam Dyer nsarrazin HF Staff commited on
Commit
dc1fb76
·
unverified ·
1 Parent(s): b52520a

feat: allow disabling metrics server, consolidate exit handlers (#1201)

Browse files

* feat: allow disabling metrics server, consolidate exit handlers

* Make server off by default, enable in prods, update docs accordingly

---------

Co-authored-by: Nathan Sarrazin <[email protected]>

.env CHANGED
@@ -159,6 +159,7 @@ ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to
159
  USAGE_LIMITS=`{}`
160
 
161
  ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls
162
- METRICS_PORT=
 
163
  LOG_LEVEL=info
164
  BODY_SIZE_LIMIT=15728640
 
159
  USAGE_LIMITS=`{}`
160
 
161
  ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls
162
+ METRICS_ENABLED=false
163
+ METRICS_PORT=5565
164
  LOG_LEVEL=info
165
  BODY_SIZE_LIMIT=15728640
chart/env/prod.yaml CHANGED
@@ -35,6 +35,7 @@ envVars:
35
  EXPOSE_API: "true"
36
  METRICS_PORT: 5565
37
  LOG_LEVEL: "debug"
 
38
  MODELS: >
39
  [
40
  {
 
35
  EXPOSE_API: "true"
36
  METRICS_PORT: 5565
37
  LOG_LEVEL: "debug"
38
+ METRICS_ENABLED: "true"
39
  MODELS: >
40
  [
41
  {
docs/source/_toctree.yml CHANGED
@@ -20,6 +20,8 @@
20
  title: OpenID
21
  - local: configuration/web-search
22
  title: Web Search
 
 
23
  - local: configuration/embeddings
24
  title: Text Embedding Models
25
  - title: Models
 
20
  title: OpenID
21
  - local: configuration/web-search
22
  title: Web Search
23
+ - local: configuration/metrics
24
+ title: Metrics
25
  - local: configuration/embeddings
26
  title: Text Embedding Models
27
  - title: Models
docs/source/configuration/metrics.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Metrics
2
+
3
+ The server can expose prometheus metrics on port `5565` but is off by default. You may enable the metrics server with `METRICS_ENABLED=true` and change the port with `METRICS_PORT=1234`.
4
+
5
+ <Tip>
6
+
7
+ In development with `npm run dev`, the metrics server does not shutdown gracefully due to Sveltekit not providing hooks for restart. It's recommended to disable the metrics server in this case.
8
+
9
+ </Tip>
src/hooks.server.ts CHANGED
@@ -13,10 +13,13 @@ import { refreshAssistantsCounts } from "$lib/assistantStats/refresh-assistants-
13
  import { logger } from "$lib/server/logger";
14
  import { AbortedGenerations } from "$lib/server/abortedGenerations";
15
  import { MetricsServer } from "$lib/server/metrics";
 
16
  import { ObjectId } from "mongodb";
17
 
18
  // TODO: move this code on a started server hook, instead of using a "building" flag
19
  if (!building) {
 
 
20
  await checkAndRunMigrations();
21
  if (env.ENABLE_ASSISTANTS) {
22
  refreshAssistantsCounts();
 
13
  import { logger } from "$lib/server/logger";
14
  import { AbortedGenerations } from "$lib/server/abortedGenerations";
15
  import { MetricsServer } from "$lib/server/metrics";
16
+ import { initExitHandler } from "$lib/server/exitHandler";
17
  import { ObjectId } from "mongodb";
18
 
19
  // TODO: move this code on a started server hook, instead of using a "building" flag
20
  if (!building) {
21
+ initExitHandler();
22
+
23
  await checkAndRunMigrations();
24
  if (env.ENABLE_ASSISTANTS) {
25
  refreshAssistantsCounts();
src/lib/server/abortedGenerations.ts CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import { logger } from "$lib/server/logger";
4
  import { collections } from "$lib/server/database";
 
5
 
6
  export class AbortedGenerations {
7
  private static instance: AbortedGenerations;
@@ -10,10 +11,7 @@ export class AbortedGenerations {
10
 
11
  private constructor() {
12
  const interval = setInterval(this.updateList, 1000);
13
-
14
- process.on("SIGINT", () => {
15
- clearInterval(interval);
16
- });
17
  }
18
 
19
  public static getInstance(): AbortedGenerations {
 
2
 
3
  import { logger } from "$lib/server/logger";
4
  import { collections } from "$lib/server/database";
5
+ import { onExit } from "./exitHandler";
6
 
7
  export class AbortedGenerations {
8
  private static instance: AbortedGenerations;
 
11
 
12
  private constructor() {
13
  const interval = setInterval(this.updateList, 1000);
14
+ onExit(() => clearInterval(interval));
 
 
 
15
  }
16
 
17
  public static getInstance(): AbortedGenerations {
src/lib/server/database.ts CHANGED
@@ -15,6 +15,7 @@ import type { Semaphore } from "$lib/types/Semaphore";
15
  import type { AssistantStats } from "$lib/types/AssistantStats";
16
  import { logger } from "$lib/server/logger";
17
  import { building } from "$app/environment";
 
18
 
19
  export const CONVERSATION_STATS_COLLECTION = "conversations.stats";
20
 
@@ -41,15 +42,8 @@ export class Database {
41
  this.client.db(env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : ""));
42
  this.client.on("open", () => this.initDatabase());
43
 
44
- // Disconnect DB on process kill
45
- process.on("SIGINT", async () => {
46
- await this.client.close(true);
47
-
48
- // https://github.com/sveltejs/kit/issues/9540
49
- setTimeout(() => {
50
- process.exit(0);
51
- }, 100);
52
- });
53
  }
54
 
55
  public static getInstance(): Database {
 
15
  import type { AssistantStats } from "$lib/types/AssistantStats";
16
  import { logger } from "$lib/server/logger";
17
  import { building } from "$app/environment";
18
+ import { onExit } from "./exitHandler";
19
 
20
  export const CONVERSATION_STATS_COLLECTION = "conversations.stats";
21
 
 
42
  this.client.db(env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : ""));
43
  this.client.on("open", () => this.initDatabase());
44
 
45
+ // Disconnect DB on exit
46
+ onExit(() => this.client.close(true));
 
 
 
 
 
 
 
47
  }
48
 
49
  public static getInstance(): Database {
src/lib/server/exitHandler.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from "$lib/utils/randomUuid";
2
+ import { timeout } from "$lib/utils/timeout";
3
+ import { logger } from "./logger";
4
+
5
+ type ExitHandler = () => void | Promise<void>;
6
+ type ExitHandlerUnsubscribe = () => void;
7
+
8
+ const listeners = new Map<string, ExitHandler>();
9
+
10
+ export function onExit(cb: ExitHandler): ExitHandlerUnsubscribe {
11
+ const uuid = randomUUID();
12
+ listeners.set(uuid, cb);
13
+ return () => {
14
+ listeners.delete(uuid);
15
+ };
16
+ }
17
+
18
+ async function runExitHandler(handler: ExitHandler): Promise<void> {
19
+ return timeout(Promise.resolve().then(handler), 30_000).catch((err) => {
20
+ logger.error("Exit handler failed to run", err);
21
+ });
22
+ }
23
+
24
+ export function initExitHandler() {
25
+ let signalCount = 0;
26
+ const exitHandler = async () => {
27
+ signalCount++;
28
+ if (signalCount === 1) {
29
+ logger.info("Received signal... Exiting");
30
+ await Promise.all(Array.from(listeners.values()).map(runExitHandler));
31
+ logger.info("All exit handlers ran... Waiting for svelte server to exit");
32
+ }
33
+ if (signalCount === 3) {
34
+ logger.warn("Received 3 signals... Exiting immediately");
35
+ process.exit(1);
36
+ }
37
+ };
38
+
39
+ process.on("SIGINT", exitHandler);
40
+ process.on("SIGTERM", exitHandler);
41
+ }
src/lib/server/metrics.ts CHANGED
@@ -4,6 +4,8 @@ import { logger } from "$lib/server/logger";
4
  import { env } from "$env/dynamic/private";
5
  import type { Model } from "$lib/types/Model";
6
  import type { Tool } from "$lib/types/Tool";
 
 
7
 
8
  interface Metrics {
9
  model: {
@@ -37,11 +39,26 @@ export class MetricsServer {
37
 
38
  private constructor() {
39
  const app = express();
40
- const port = env.METRICS_PORT || "5565";
41
 
42
- const server = app.listen(port, () => {
43
- logger.info(`Metrics server listening on port ${port}`);
44
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  const register = new Registry();
47
  collectDefaultMetrics({ register });
@@ -160,14 +177,6 @@ export class MetricsServer {
160
  res.send(metrics);
161
  });
162
  });
163
-
164
- process.on("SIGINT", async () => {
165
- logger.info("Sigint received, disconnect metrics server ...");
166
- server.close(() => {
167
- logger.info("Server stopped ...");
168
- });
169
- process.exit();
170
- });
171
  }
172
 
173
  public static getInstance(): MetricsServer {
 
4
  import { env } from "$env/dynamic/private";
5
  import type { Model } from "$lib/types/Model";
6
  import type { Tool } from "$lib/types/Tool";
7
+ import { onExit } from "./exitHandler";
8
+ import { promisify } from "util";
9
 
10
  interface Metrics {
11
  model: {
 
39
 
40
  private constructor() {
41
  const app = express();
 
42
 
43
+ const port = Number(env.METRICS_PORT || "5565");
44
+ if (isNaN(port) || port < 0 || port > 65535) {
45
+ logger.warn(`Invalid value for METRICS_PORT: ${env.METRICS_PORT}`);
46
+ }
47
+
48
+ if (env.METRICS_ENABLED !== "false" && env.METRICS_ENABLED !== "true") {
49
+ logger.warn(`Invalid value for METRICS_ENABLED: ${env.METRICS_ENABLED}`);
50
+ }
51
+ if (env.METRICS_ENABLED === "true") {
52
+ const server = app.listen(port, () => {
53
+ logger.info(`Metrics server listening on port ${port}`);
54
+ });
55
+ const closeServer = promisify(server.close);
56
+ onExit(async () => {
57
+ logger.info("Disconnecting metrics server ...");
58
+ await closeServer();
59
+ logger.info("Server stopped ...");
60
+ });
61
+ }
62
 
63
  const register = new Registry();
64
  collectDefaultMetrics({ register });
 
177
  res.send(metrics);
178
  });
179
  });
 
 
 
 
 
 
 
 
180
  }
181
 
182
  public static getInstance(): MetricsServer {
src/lib/server/websearch/scrape/playwright.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
  import { PlaywrightBlocker } from "@cliqz/adblocker-playwright";
10
  import { env } from "$env/dynamic/private";
11
  import { logger } from "$lib/server/logger";
 
12
 
13
  const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch)
14
  .then((blker) => {
@@ -24,7 +25,7 @@ const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch)
24
  let browserSingleton: Promise<Browser> | undefined;
25
  async function getBrowser() {
26
  const browser = await chromium.launch({ headless: true });
27
- process.on("SIGINT", () => browser.close());
28
  browser.on("disconnected", () => {
29
  logger.warn("Browser closed");
30
  browserSingleton = undefined;
 
9
  import { PlaywrightBlocker } from "@cliqz/adblocker-playwright";
10
  import { env } from "$env/dynamic/private";
11
  import { logger } from "$lib/server/logger";
12
+ import { onExit } from "$lib/server/exitHandler";
13
 
14
  const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch)
15
  .then((blker) => {
 
25
  let browserSingleton: Promise<Browser> | undefined;
26
  async function getBrowser() {
27
  const browser = await chromium.launch({ headless: true });
28
+ onExit(() => browser.close());
29
  browser.on("disconnected", () => {
30
  logger.warn("Browser closed");
31
  browserSingleton = undefined;