Spaces:
Running
Running
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 +2 -1
- chart/env/prod.yaml +1 -0
- docs/source/_toctree.yml +2 -0
- docs/source/configuration/metrics.md +9 -0
- src/hooks.server.ts +3 -0
- src/lib/server/abortedGenerations.ts +2 -4
- src/lib/server/database.ts +3 -9
- src/lib/server/exitHandler.ts +41 -0
- src/lib/server/metrics.ts +21 -12
- src/lib/server/websearch/scrape/playwright.ts +2 -1
.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 |
-
|
|
|
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
|
45 |
-
|
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
|
43 |
-
|
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 |
-
|
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;
|