From a28d2b65c774b5c6d96a6e28ef9e368a35cf5bf9 Mon Sep 17 00:00:00 2001 From: 7-Zark-7 <1085655+zx8086@users.noreply.github.com> Date: Sun, 2 Feb 2025 17:21:22 +0100 Subject: [PATCH] Enhance DNS Prefetch - 1st Phase --- hooks.server.ts | 20 ++++++ src/hooks.server.ts | 4 ++ src/lib/config/dnsConfig.ts | 69 ++++++++++++++++++++ src/lib/context/tracker.ts | 44 ++++--------- src/lib/utils/dnsUtils.ts | 83 ++++++++++++++++++++++++ src/otlp/MonitoredOTLPExporter.ts | 15 +++-- src/routes/api/health-check/+page.svelte | 12 ++-- src/routes/api/health-check/+server.ts | 10 +++ 8 files changed, 215 insertions(+), 42 deletions(-) create mode 100644 src/lib/config/dnsConfig.ts create mode 100644 src/lib/utils/dnsUtils.ts diff --git a/hooks.server.ts b/hooks.server.ts index e55cd10..467fa4c 100644 --- a/hooks.server.ts +++ b/hooks.server.ts @@ -2,6 +2,7 @@ import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; +import { dns } from "bun"; // Define paths that should be public const PUBLIC_PATHS = [ @@ -53,5 +54,24 @@ export const handle: Handle = async ({ event, resolve }) => { response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + const prefetchAuthenticatedEndpoints = () => { + const endpoints = [ + new URL(Bun.env.GRAPHQL_ENDPOINT || '').hostname, + new URL(Bun.env.API_BASE_URL || '').hostname + ]; + + endpoints.forEach(hostname => { + try { + dns.prefetch(hostname); + } catch (error) { + console.warn(`Auth endpoints DNS prefetch failed for ${hostname}:`, error); + } + }); + }; + + if (authCookie) { + prefetchAuthenticatedEndpoints(); + } + return response; }; \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts index b30a546..3c1768e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,10 @@ import type { Handle } from '@sveltejs/kit'; +import { prefetchDnsForCategories } from "$lib/config/dnsConfig"; export const handle: Handle = async ({ event, resolve }) => { + // Prefetch DNS for CDN and API endpoints on startup + await prefetchDnsForCategories(['cdn', 'api']); + const response = await resolve(event); // Add security headers (excluding CSP which is handled by svelte.config.js) diff --git a/src/lib/config/dnsConfig.ts b/src/lib/config/dnsConfig.ts new file mode 100644 index 0000000..3e9167a --- /dev/null +++ b/src/lib/config/dnsConfig.ts @@ -0,0 +1,69 @@ +import { backendConfig } from "$backendConfig"; +import { frontendConfig } from "$frontendConfig"; +import { safeDnsPrefetch } from "$lib/utils/dnsUtils"; + +// Helper to safely extract hostname from URL string +const getHostname = (url: string): string | null => { + try { + return new URL(url).hostname; + } catch { + return null; + } +}; + +// Define DNS prefetch targets by category +export const dnsPrefetchTargets = { + // API Endpoints + api: [ + getHostname(backendConfig.application.GRAPHQL_ENDPOINT), + getHostname(backendConfig.capella.API_BASE_URL), + 'api.openai.com', + 'api.pinecone.io' + ], + + // Monitoring & APM + monitoring: [ + getHostname(frontendConfig.elasticApm.SERVER_URL), + getHostname(frontendConfig.openreplay.INGEST_POINT), + getHostname(backendConfig.openTelemetry.TRACES_ENDPOINT), + getHostname(backendConfig.openTelemetry.METRICS_ENDPOINT), + getHostname(backendConfig.openTelemetry.LOGS_ENDPOINT) + ], + + // Authentication + auth: [ + 'login.microsoftonline.com' + ], + + // Content Delivery + cdn: [ + 'd2bgp0ri487o97.cloudfront.net' + ] +} as const; + +// Helper to get all unique, valid hostnames for a given category +export function getDnsPrefetchTargets(categories: (keyof typeof dnsPrefetchTargets)[] = Object.keys(dnsPrefetchTargets) as any): string[] { + const hostnames = categories + .flatMap(category => dnsPrefetchTargets[category]) + .filter((hostname): hostname is string => + hostname !== null && + hostname !== undefined && + hostname !== '' + ); + + // Remove duplicates and localhost + return [...new Set(hostnames)] + .filter(hostname => + !hostname.includes('localhost') && + !hostname.includes('127.0.0.1') + ); +} + +// Helper to prefetch DNS for specific categories +export async function prefetchDnsForCategories(categories: (keyof typeof dnsPrefetchTargets)[]): Promise { + const targets = getDnsPrefetchTargets(categories); + await safeDnsPrefetch(targets); +} + +// Export the type for use in other files +export type DnsPrefetchCategory = keyof typeof dnsPrefetchTargets; \ No newline at end of file diff --git a/src/lib/context/tracker.ts b/src/lib/context/tracker.ts index 9f3aa65..701e82e 100644 --- a/src/lib/context/tracker.ts +++ b/src/lib/context/tracker.ts @@ -115,60 +115,40 @@ export async function initTracker() { setupAPMIntegration(trackerInstance); } - // Add tracker assist plugin with enhanced user identification + // Update Assist plugin configuration with more robust options trackerInstance.use(trackerAssist({ callConfirm: "Would you like to start a support call?", controlConfirm: "Would you like to allow support to control your screen?", onCallStart: () => { console.log("🎥 Support call started"); - toast.info("Support call started", { + toast.success("Support call started", { description: "You are now connected to a support session", - duration: Infinity + duration: 5000 }); return () => { console.log("📞 Support call ended"); - toast.info("Support call ended", { - description: "Your support session has ended", - duration: Infinity - }); + toast.info("Support call ended"); }; }, onRemoteControlStart: () => { console.log("🖱️ Remote control started"); - toast.info("Remote control active", { + toast.warning("Remote control active", { description: "Support agent now has control of your screen", - duration: Infinity + duration: 5000 }); return () => { console.log("🔒 Remote control ended"); - toast.info("Remote control ended", { - description: "Support agent no longer has control of your screen", - duration: Infinity - }); + toast.info("Remote control ended"); }; }, onAgentConnect: (agentInfo: any = {}) => { - const user = get(userAccount); - if (user?.email && trackerInstance) { - // Comprehensive user identification for assist - identifyUser(user.email, { - name: user.name || user.email, - email: user.email, - }); - } - - const { email = '', name = '', query = '' } = agentInfo; - console.log("👋 Agent connected:", { email, name, query }); - toast.info("Support agent connected", { - description: `${name} (${email}) has joined the session`, - duration: Infinity + const { email = '', name = '' } = agentInfo; + console.log("👋 Agent connected:", { email, name }); + toast.success(`Support agent ${name} connected`, { + duration: 5000 }); return () => { - console.log("👋 Agent disconnected"); - toast.info("Support agent disconnected", { - description: "The support agent has left the session", - duration: Infinity - }); + toast.info("Support agent disconnected"); }; } })); diff --git a/src/lib/utils/dnsUtils.ts b/src/lib/utils/dnsUtils.ts new file mode 100644 index 0000000..a1fabd4 --- /dev/null +++ b/src/lib/utils/dnsUtils.ts @@ -0,0 +1,83 @@ +import { log, warn } from "$utils/unifiedLogger"; + +// Type definition for Bun's DNS cache stats +interface DnsCacheStats { + size: number; + cacheHitsCompleted: number; + cacheHitsInflight: number; + cacheMisses: number; + errors: number; + totalCount: number; +} + +let bunDns: { + prefetch: (hostname: string) => void; + getCacheStats: () => DnsCacheStats; +} | null = null; + +// Initialize Bun DNS if available +try { + // Using dynamic import to avoid issues in non-Bun environments + if (process.versions?.bun) { + import('bun').then(bun => { + bunDns = bun.dns; + }).catch(err => { + warn('Failed to initialize Bun DNS:', err); + }); + } +} catch (error) { + warn('Bun DNS initialization error:', error); +} + +// Safe DNS prefetch function that works in both Bun and non-Bun environments +export async function safeDnsPrefetch(hostnames: string[]): Promise { + if (!bunDns) { + log('DNS prefetch skipped - Bun DNS not available'); + return; + } + + for (const hostname of hostnames) { + try { + bunDns.prefetch(hostname); + log(`DNS prefetch successful for ${hostname}`); + } catch (error) { + warn(`DNS prefetch failed for ${hostname}:`, error); + } + } +} + +// Get DNS cache stats safely +export function getDnsCacheStats(): DnsCacheStats | null { + if (!bunDns) { + return null; + } + + try { + return bunDns.getCacheStats(); + } catch (error) { + warn('Failed to get DNS cache stats:', error); + return null; + } +} + +// Log DNS cache effectiveness +export function logDnsCacheEffectiveness(): void { + const stats = getDnsCacheStats(); + if (!stats) { + log('DNS cache stats not available'); + return; + } + + const hitRate = stats.totalCount > 0 + ? (stats.cacheHitsCompleted / stats.totalCount) * 100 + : 0; + + log('DNS Cache Effectiveness:', { + hitRate: `${hitRate.toFixed(2)}%`, + hits: stats.cacheHitsCompleted, + misses: stats.cacheMisses, + totalQueries: stats.totalCount, + cacheSize: stats.size, + errors: stats.errors + }); +} \ No newline at end of file diff --git a/src/otlp/MonitoredOTLPExporter.ts b/src/otlp/MonitoredOTLPExporter.ts index ab9bcc5..3e701f6 100644 --- a/src/otlp/MonitoredOTLPExporter.ts +++ b/src/otlp/MonitoredOTLPExporter.ts @@ -8,6 +8,8 @@ import type { ExportResult } from "@opentelemetry/core"; import { backendConfig } from "../backend-config"; // Changed from $backendConfig import { log, warn, err, debug } from "../utils/browserLogger"; // Changed from $utils/browserLogger import { otlpConfig } from "./otlpConfig"; +import { safeDnsPrefetch, getDnsCacheStats } from "$lib/utils/dnsUtils"; +import { getDnsPrefetchTargets } from "$lib/config/dnsConfig"; // Import Bun DNS with type checking let bunDns: any; @@ -102,12 +104,17 @@ export abstract class MonitoredOTLPExporter { private async initializeDNSPrefetch(): Promise { try { - const initialStats = bunDns.getCacheStats(); - debug("Initial DNS cache stats:", initialStats); + const initialStats = getDnsCacheStats(); + if (initialStats) { + debug("Initial DNS cache stats:", initialStats); + } - bunDns.prefetch(this.hostName); + // Add the current hostname to monitoring targets + const targets = [...getDnsPrefetchTargets(['monitoring']), this.hostName]; + await safeDnsPrefetch(targets); + this.dnsPrefetchInitiated = true; - debug(`DNS prefetch initiated for ${this.hostName} (port ${this.port})`); + debug(`DNS prefetch initiated for ${targets.join(', ')}`); await this.verifyDNSPrefetch(); } catch (error) { diff --git a/src/routes/api/health-check/+page.svelte b/src/routes/api/health-check/+page.svelte index 877112a..50b6a43 100644 --- a/src/routes/api/health-check/+page.svelte +++ b/src/routes/api/health-check/+page.svelte @@ -98,9 +98,14 @@
-

Health Check Status

+

Health Check

+

+ {checkType === "Simple" + ? "Simple check tests the SQL Database, Internal API & GraphQL endpoint." + : "Detailed check covering all dependencies."} +

-

- {checkType === "Simple" - ? "Simple check tests the SQL Database, Internal API & GraphQL endpoint." - : "Detailed check including all API endpoints."} -

{#if loading} diff --git a/src/routes/api/health-check/+server.ts b/src/routes/api/health-check/+server.ts index 02777a0..bba8928 100644 --- a/src/routes/api/health-check/+server.ts +++ b/src/routes/api/health-check/+server.ts @@ -19,6 +19,9 @@ import { import type { RequestEvent } from '@sveltejs/kit'; import { OpenAI } from "openai"; import { Pinecone } from "@pinecone-database/pinecone"; +import { dns } from "bun"; +import { safeDnsPrefetch } from "$lib/utils/dnsUtils"; +import { prefetchDnsForCategories } from "$lib/config/dnsConfig"; const INDIVIDUAL_CHECK_TIMEOUT = 15000; // 15 seconds timeout for most checks const CAPELLA_API_TIMEOUT = 30000; // 30 seconds timeout for Capella API @@ -436,6 +439,10 @@ async function checkPineconeEndpoint(fetch: typeof global.fetch): Promise checkTracesEndpoint(fetch) }, ].sort((a, b) => a.name.localeCompare(b.name)); + // Call before health checks + await prefetchHealthCheckEndpoints(); + // Run all checks in parallel but handle each independently await Promise.all((isSimpleCheck ? simpleChecks : detailedChecks) .map(async ({ name, check }) => {