From 6d93afc40576ce70380c77840a93e1ff4004c758 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 13 May 2025 17:39:56 +0200 Subject: [PATCH 1/2] Add initial metric --- backend/open_webui/routers/auths.py | 3 ++ backend/open_webui/utils/telemetry/setup.py | 47 +++++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 309862ed5..55452c4e8 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -4,6 +4,7 @@ import time import datetime import logging from aiohttp import ClientSession +from open_webui.utils.telemetry import setup from open_webui.models.auths import ( AddUserForm, @@ -396,6 +397,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm): user = Auths.authenticate_user(form_data.email.lower(), form_data.password) if user: + if setup.login_counter is not None: + setup.login_counter.add(1, {"method": "regular"}) expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) expires_at = None diff --git a/backend/open_webui/utils/telemetry/setup.py b/backend/open_webui/utils/telemetry/setup.py index eb6a238c8..446ae7fbe 100644 --- a/backend/open_webui/utils/telemetry/setup.py +++ b/backend/open_webui/utils/telemetry/setup.py @@ -1,23 +1,54 @@ from fastapi import FastAPI -from opentelemetry import trace +from opentelemetry import trace, metrics from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import ( + PeriodicExportingMetricReader, +) from sqlalchemy import Engine from open_webui.utils.telemetry.exporters import LazyBatchSpanProcessor from open_webui.utils.telemetry.instrumentors import Instrumentor from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT +meter = None +login_counter = None +active_sessions_gauge = None + def setup(app: FastAPI, db_engine: Engine): + resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) + # set up trace - trace.set_tracer_provider( - TracerProvider( - resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) - ) - ) + trace_provider = TracerProvider(resource=resource) + trace.set_tracer_provider(trace_provider) # otlp export - exporter = OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT) - trace.get_tracer_provider().add_span_processor(LazyBatchSpanProcessor(exporter)) + span_exporter = OTLPSpanExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, insecure=True + ) + trace_provider.add_span_processor(LazyBatchSpanProcessor(span_exporter)) Instrumentor(app=app, db_engine=db_engine).instrument() + + # set up metrics + metric_exporter = OTLPMetricExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, insecure=True + ) + reader = PeriodicExportingMetricReader( + exporter=metric_exporter, export_interval_millis=5000 + ) + meter_provider = MeterProvider(resource=resource, metric_readers=[reader]) + metrics.set_meter_provider(meter_provider) + + global meter, login_counter, active_sessions_gauge + meter = metrics.get_meter(OTEL_SERVICE_NAME) + + login_counter = meter.create_counter( + "user_login_total", description="Total number of user logins" + ) + + active_sessions_gauge = meter.create_gauge( + "active.sessions", description="Number of currently active user sessions" + ) From d0973ab0606b6e4f2f6fe6ee03e86cee143fee1b Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 15 May 2025 11:30:58 +0200 Subject: [PATCH 2/2] Add dedicated class for metrics. Count logins and new chats --- backend/open_webui/routers/auths.py | 8 ++++-- backend/open_webui/routers/chats.py | 3 +++ backend/open_webui/utils/telemetry/metrics.py | 27 +++++++++++++++++++ backend/open_webui/utils/telemetry/setup.py | 16 ++--------- 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 backend/open_webui/utils/telemetry/metrics.py diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 55452c4e8..946983cc5 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -31,6 +31,7 @@ from open_webui.env import ( WEBUI_AUTH_SIGNOUT_REDIRECT_URL, SRC_LOG_LEVELS, ) +from open_webui.utils.telemetry import metrics from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.responses import RedirectResponse, Response from open_webui.config import OPENID_PROVIDER_URL, ENABLE_OAUTH_SIGNUP, ENABLE_LDAP @@ -300,6 +301,9 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm): user = Auths.authenticate_user_by_trusted_header(email) if user: + if metrics.telemetry_metrics is not None: + metrics.telemetry_metrics.track_user_login(user.id, user.email) + expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) expires_at = None if expires_delta: @@ -397,8 +401,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm): user = Auths.authenticate_user(form_data.email.lower(), form_data.password) if user: - if setup.login_counter is not None: - setup.login_counter.add(1, {"method": "regular"}) + if metrics.telemetry_metrics is not None: + metrics.telemetry_metrics.track_user_login(user.id, user.email) expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) expires_at = None diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 6f00dd4d7..e10dd0521 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -23,6 +23,7 @@ from pydantic import BaseModel from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.access_control import has_permission +from open_webui.utils.telemetry import metrics log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["MODELS"]) @@ -99,6 +100,8 @@ async def get_user_chat_list_by_user_id( async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)): try: chat = Chats.insert_new_chat(user.id, form_data) + if metrics.telemetry_metrics is not None: + metrics.telemetry_metrics.track_user_request(user.id) return ChatResponse(**chat.model_dump()) except Exception as e: log.exception(e) diff --git a/backend/open_webui/utils/telemetry/metrics.py b/backend/open_webui/utils/telemetry/metrics.py new file mode 100644 index 000000000..afb967a9d --- /dev/null +++ b/backend/open_webui/utils/telemetry/metrics.py @@ -0,0 +1,27 @@ +from typing import Optional + + +class TelemetryMetrics: + def __init__(self, meter): + self.login_counter = meter.create_counter( + "user_login_total", description="Total number of user logins" + ) + self.user_request_counter = meter.create_counter( + "user_request_total", description="Total number of user requests" + ) + + def track_user_login(self, user_id: str, email: str): + self.login_counter.add( + 1, {"method": "regular", "user_id": user_id, "email": email} + ) + + def track_user_request(self, user_id: str): + self.user_request_counter.add(1, {"user_id": user_id}) + + +telemetry_metrics: Optional[TelemetryMetrics] = None + + +def initialize_telemetry_metrics(meter): + global telemetry_metrics + telemetry_metrics = TelemetryMetrics(meter) diff --git a/backend/open_webui/utils/telemetry/setup.py b/backend/open_webui/utils/telemetry/setup.py index 446ae7fbe..144316e8a 100644 --- a/backend/open_webui/utils/telemetry/setup.py +++ b/backend/open_webui/utils/telemetry/setup.py @@ -1,4 +1,5 @@ from fastapi import FastAPI +from open_webui.utils.telemetry.metrics import initialize_telemetry_metrics from opentelemetry import trace, metrics from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter @@ -14,10 +15,6 @@ from open_webui.utils.telemetry.exporters import LazyBatchSpanProcessor from open_webui.utils.telemetry.instrumentors import Instrumentor from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT -meter = None -login_counter = None -active_sessions_gauge = None - def setup(app: FastAPI, db_engine: Engine): resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) @@ -41,14 +38,5 @@ def setup(app: FastAPI, db_engine: Engine): ) meter_provider = MeterProvider(resource=resource, metric_readers=[reader]) metrics.set_meter_provider(meter_provider) - - global meter, login_counter, active_sessions_gauge meter = metrics.get_meter(OTEL_SERVICE_NAME) - - login_counter = meter.create_counter( - "user_login_total", description="Total number of user logins" - ) - - active_sessions_gauge = meter.create_gauge( - "active.sessions", description="Number of currently active user sessions" - ) + initialize_telemetry_metrics(meter)