From 4e7627279825a5cba97186f1c56e588dbaeb1efd Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sat, 22 Jun 2024 14:10:06 +0000 Subject: [PATCH 1/3] otel --- src/fastapi_app/__init__.py | 4 ++++ src/pyproject.toml | 1 + 2 files changed, 5 insertions(+) diff --git a/src/fastapi_app/__init__.py b/src/fastapi_app/__init__.py index de1d0fc8..74dc8e2d 100644 --- a/src/fastapi_app/__init__.py +++ b/src/fastapi_app/__init__.py @@ -6,6 +6,7 @@ from dotenv import load_dotenv from environs import Env from fastapi import FastAPI +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from .globals import global_storage from .openai_clients import create_openai_chat_client, create_openai_embed_client @@ -63,6 +64,9 @@ def create_app(): app = FastAPI(docs_url="/docs", lifespan=lifespan) + if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"): + FastAPIInstrumentor.instrument_app(app) + from . import api_routes # noqa from . import frontend_routes # noqa diff --git a/src/pyproject.toml b/src/pyproject.toml index 1d753fec..9be27961 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "openai>=1.34.0,<2.0.0", "tiktoken>=0.7.0,<0.8.0", "openai-messages-token-helper>=0.1.5,<0.2.0", + "opentelemetry-instrumentation-fastapi>=0.46b0,<1.0.0", ] [build-system] From 4d8f3b271c167d12683a66e53e4e2266e4ed2e75 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 23 Jul 2024 18:39:09 +0000 Subject: [PATCH 2/3] Proper support for OpenTelemetry, dashboard, loadtesting --- .vscode/launch.json | 2 +- README.md | 13 +- docs/images/appinsights_trace.png | Bin 0 -> 64779 bytes docs/loadtesting.md | 38 + docs/monitoring.md | 17 + infra/backend-dashboard.bicep | 366 +++++ .../applicationinsights-dashboard.bicep | 1236 ----------------- infra/core/monitor/applicationinsights.bicep | 10 - infra/core/monitor/monitoring.bicep | 2 - infra/main.bicep | 12 +- locustfile.py | 46 + requirements-dev.txt | 1 + src/backend/fastapi_app/__init__.py | 18 +- src/backend/pyproject.toml | 5 +- 14 files changed, 510 insertions(+), 1256 deletions(-) create mode 100644 docs/images/appinsights_trace.png create mode 100644 docs/loadtesting.md create mode 100644 docs/monitoring.md create mode 100644 infra/backend-dashboard.bicep delete mode 100644 infra/core/monitor/applicationinsights-dashboard.bicep create mode 100644 locustfile.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 50b295df..aca97d0a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,7 @@ "request": "launch", "module": "uvicorn", "args": ["fastapi_app:create_app", "--factory", "--reload"], - "justMyCode": true + "justMyCode": false } ], "compounds": [ diff --git a/README.md b/README.md index c5146d96..0508987a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This project is designed for deployment to Azure using [the Azure Developer CLI] * [Local development](#local-development) * [Costs](#costs) * [Security guidelines](#security-guidelines) +* [Guidance](#guidance) * [Resources](#resources) ## Features @@ -176,12 +177,22 @@ You may try the [Azure pricing calculator](https://azure.microsoft.com/pricing/c * Azure PostgreSQL Flexible Server: Burstable Tier with 1 CPU core, 32GB storage. Pricing is hourly. [Pricing](https://azure.microsoft.com/pricing/details/postgresql/flexible-server/) * Azure Monitor: Pay-as-you-go tier. Costs based on data ingested. [Pricing](https://azure.microsoft.com/pricing/details/monitor/) -## Security Guidelines +## Security guidelines This template uses [Managed Identity](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview) for authenticating to the Azure services used (Azure OpenAI, Azure PostgreSQL Flexible Server). Additionally, we have added a [GitHub Action](https://github.com/microsoft/security-devops-action) that scans the infrastructure-as-code files and generates a report containing any detected issues. To ensure continued best practices in your own repository, we recommend that anyone creating solutions based on our templates ensure that the [Github secret scanning](https://docs.github.com/code-security/secret-scanning/about-secret-scanning) setting is enabled. +## Guidance + +Further documentation is available in the `docs/` folder: + +* [Deploying with existing resources](docs/deploy_existing.md) +* [Monitoring with Azure Monitor](docs/monitoring.md) +* [Load testing](docs/loadtesting.md) + +Please post in the issue tracker with any questions or issues. + ## Resources * [RAG chat with Azure AI Search + Python](https://github.com/Azure-Samples/azure-search-openai-demo/) diff --git a/docs/images/appinsights_trace.png b/docs/images/appinsights_trace.png new file mode 100644 index 0000000000000000000000000000000000000000..09256216fcaa42c51dfc123b0c4ec308830c584d GIT binary patch literal 64779 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%0DO6yZi9{Z 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 Performance" blade and navigate to any HTTP request to see the timing data. +To inspect the performance of chat requests, use the "Drill into Samples" button to see end-to-end traces of all the API calls made for any chat request: + +![Tracing screenshot](images/appinsights_trace.png) + +To see any exceptions and server errors, navigate to the "Investigate -> Failures" blade and use the filtering tools to locate a specific exception. You can see Python stack traces on the right-hand side. + +You can also see chart summaries on a dashboard by running the following command: + +```shell +azd monitor +``` diff --git a/infra/backend-dashboard.bicep b/infra/backend-dashboard.bicep new file mode 100644 index 00000000..62af08e7 --- /dev/null +++ b/infra/backend-dashboard.bicep @@ -0,0 +1,366 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/infra/core/monitor/applicationinsights-dashboard.bicep b/infra/core/monitor/applicationinsights-dashboard.bicep deleted file mode 100644 index d082e668..00000000 --- a/infra/core/monitor/applicationinsights-dashboard.bicep +++ /dev/null @@ -1,1236 +0,0 @@ -metadata description = 'Creates a dashboard for an Application Insights instance.' -param name string -param applicationInsightsName string -param location string = resourceGroup().location -param tags object = {} - -// 2020-09-01-preview because that is the latest valid version -resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { - name: name - location: location - tags: tags - properties: { - lenses: [ - { - order: 0 - parts: [ - { - position: { - x: 0 - y: 0 - colSpan: 2 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'id' - value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - { - name: 'Version' - value: '1.0' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' - asset: { - idInputName: 'id' - type: 'ApplicationInsights' - } - defaultMenuItemId: 'overview' - } - } - { - position: { - x: 2 - y: 0 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'Version' - value: '1.0' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - defaultMenuItemId: 'ProactiveDetection' - } - } - { - position: { - x: 3 - y: 0 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'ResourceId' - value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - } - } - { - position: { - x: 4 - y: 0 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'TimeContext' - value: { - durationMs: 86400000 - endTime: null - createdTime: '2018-05-04T01:20:33.345Z' - isInitialTime: true - grain: 1 - useDashboardTimeRange: false - } - } - { - name: 'Version' - value: '1.0' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - } - } - { - position: { - x: 5 - y: 0 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'TimeContext' - value: { - durationMs: 86400000 - endTime: null - createdTime: '2018-05-08T18:47:35.237Z' - isInitialTime: true - grain: 1 - useDashboardTimeRange: false - } - } - { - name: 'ConfigurationId' - value: '78ce933e-e864-4b05-a27b-71fd55a6afad' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - } - } - { - position: { - x: 0 - y: 1 - colSpan: 3 - rowSpan: 1 - } - metadata: { - inputs: [] - type: 'Extension/HubsExtension/PartType/MarkdownPart' - settings: { - content: { - settings: { - content: '# Usage' - title: '' - subtitle: '' - } - } - } - } - } - { - position: { - x: 3 - y: 1 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'TimeContext' - value: { - durationMs: 86400000 - endTime: null - createdTime: '2018-05-04T01:22:35.782Z' - isInitialTime: true - grain: 1 - useDashboardTimeRange: false - } - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - } - } - { - position: { - x: 4 - y: 1 - colSpan: 3 - rowSpan: 1 - } - metadata: { - inputs: [] - type: 'Extension/HubsExtension/PartType/MarkdownPart' - settings: { - content: { - settings: { - content: '# Reliability' - title: '' - subtitle: '' - } - } - } - } - } - { - position: { - x: 7 - y: 1 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ResourceId' - value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - { - name: 'DataModel' - value: { - version: '1.0.0' - timeContext: { - durationMs: 86400000 - createdTime: '2018-05-04T23:42:40.072Z' - isInitialTime: false - grain: 1 - useDashboardTimeRange: false - } - } - isOptional: true - } - { - name: 'ConfigurationId' - value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' - isAdapter: true - asset: { - idInputName: 'ResourceId' - type: 'ApplicationInsights' - } - defaultMenuItemId: 'failures' - } - } - { - position: { - x: 8 - y: 1 - colSpan: 3 - rowSpan: 1 - } - metadata: { - inputs: [] - type: 'Extension/HubsExtension/PartType/MarkdownPart' - settings: { - content: { - settings: { - content: '# Responsiveness\r\n' - title: '' - subtitle: '' - } - } - } - } - } - { - position: { - x: 11 - y: 1 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ResourceId' - value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - { - name: 'DataModel' - value: { - version: '1.0.0' - timeContext: { - durationMs: 86400000 - createdTime: '2018-05-04T23:43:37.804Z' - isInitialTime: false - grain: 1 - useDashboardTimeRange: false - } - } - isOptional: true - } - { - name: 'ConfigurationId' - value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' - isAdapter: true - asset: { - idInputName: 'ResourceId' - type: 'ApplicationInsights' - } - defaultMenuItemId: 'performance' - } - } - { - position: { - x: 12 - y: 1 - colSpan: 3 - rowSpan: 1 - } - metadata: { - inputs: [] - type: 'Extension/HubsExtension/PartType/MarkdownPart' - settings: { - content: { - settings: { - content: '# Browser' - title: '' - subtitle: '' - } - } - } - } - } - { - position: { - x: 15 - y: 1 - colSpan: 1 - rowSpan: 1 - } - metadata: { - inputs: [ - { - name: 'ComponentId' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'MetricsExplorerJsonDefinitionId' - value: 'BrowserPerformanceTimelineMetrics' - } - { - name: 'TimeContext' - value: { - durationMs: 86400000 - createdTime: '2018-05-08T12:16:27.534Z' - isInitialTime: false - grain: 1 - useDashboardTimeRange: false - } - } - { - name: 'CurrentFilter' - value: { - eventTypes: [ - 4 - 1 - 3 - 5 - 2 - 6 - 13 - ] - typeFacets: {} - isPermissive: false - } - } - { - name: 'id' - value: { - Name: applicationInsights.name - SubscriptionId: subscription().subscriptionId - ResourceGroup: resourceGroup().name - } - } - { - name: 'Version' - value: '1.0' - } - ] - #disable-next-line BCP036 - type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' - asset: { - idInputName: 'ComponentId' - type: 'ApplicationInsights' - } - defaultMenuItemId: 'browser' - } - } - { - position: { - x: 0 - y: 2 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'sessions/count' - aggregationType: 5 - namespace: 'microsoft.insights/components/kusto' - metricVisualization: { - displayName: 'Sessions' - color: '#47BDF5' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'users/count' - aggregationType: 5 - namespace: 'microsoft.insights/components/kusto' - metricVisualization: { - displayName: 'Users' - color: '#7E58FF' - } - } - ] - title: 'Unique sessions and users' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - openBladeOnClick: { - openBlade: true - destinationBlade: { - extensionName: 'HubsExtension' - bladeName: 'ResourceMenuBlade' - parameters: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - menuid: 'segmentationUsers' - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 4 - y: 2 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'requests/failed' - aggregationType: 7 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Failed requests' - color: '#EC008C' - } - } - ] - title: 'Failed requests' - visualization: { - chartType: 3 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - openBladeOnClick: { - openBlade: true - destinationBlade: { - extensionName: 'HubsExtension' - bladeName: 'ResourceMenuBlade' - parameters: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - menuid: 'failures' - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 8 - y: 2 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'requests/duration' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Server response time' - color: '#00BCF2' - } - } - ] - title: 'Server response time' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - openBladeOnClick: { - openBlade: true - destinationBlade: { - extensionName: 'HubsExtension' - bladeName: 'ResourceMenuBlade' - parameters: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - menuid: 'performance' - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 12 - y: 2 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'browserTimings/networkDuration' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Page load network connect time' - color: '#7E58FF' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'browserTimings/processingDuration' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Client processing time' - color: '#44F1C8' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'browserTimings/sendDuration' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Send request time' - color: '#EB9371' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'browserTimings/receiveDuration' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Receiving response time' - color: '#0672F1' - } - } - ] - title: 'Average page load time breakdown' - visualization: { - chartType: 3 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 0 - y: 5 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'availabilityResults/availabilityPercentage' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Availability' - color: '#47BDF5' - } - } - ] - title: 'Average availability' - visualization: { - chartType: 3 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - openBladeOnClick: { - openBlade: true - destinationBlade: { - extensionName: 'HubsExtension' - bladeName: 'ResourceMenuBlade' - parameters: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - menuid: 'availability' - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 4 - y: 5 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'exceptions/server' - aggregationType: 7 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Server exceptions' - color: '#47BDF5' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'dependencies/failed' - aggregationType: 7 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Dependency failures' - color: '#7E58FF' - } - } - ] - title: 'Server exceptions and Dependency failures' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 8 - y: 5 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'performanceCounters/processorCpuPercentage' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Processor time' - color: '#47BDF5' - } - } - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'performanceCounters/processCpuPercentage' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Process CPU' - color: '#7E58FF' - } - } - ] - title: 'Average processor and process CPU utilization' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 12 - y: 5 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'exceptions/browser' - aggregationType: 7 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Browser exceptions' - color: '#47BDF5' - } - } - ] - title: 'Browser exceptions' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 0 - y: 8 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'availabilityResults/count' - aggregationType: 7 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Availability test results count' - color: '#47BDF5' - } - } - ] - title: 'Availability test results count' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 4 - y: 8 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'performanceCounters/processIOBytesPerSecond' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Process IO rate' - color: '#47BDF5' - } - } - ] - title: 'Average process I/O rate' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - { - position: { - x: 8 - y: 8 - colSpan: 4 - rowSpan: 3 - } - metadata: { - inputs: [ - { - name: 'options' - value: { - chart: { - metrics: [ - { - resourceMetadata: { - id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' - } - name: 'performanceCounters/memoryAvailableBytes' - aggregationType: 4 - namespace: 'microsoft.insights/components' - metricVisualization: { - displayName: 'Available memory' - color: '#47BDF5' - } - } - ] - title: 'Average available memory' - visualization: { - chartType: 2 - legendVisualization: { - isVisible: true - position: 2 - hideSubtitle: false - } - axisVisualization: { - x: { - isVisible: true - axisType: 2 - } - y: { - isVisible: true - axisType: 1 - } - } - } - } - } - } - { - name: 'sharedTimeRange' - isOptional: true - } - ] - #disable-next-line BCP036 - type: 'Extension/HubsExtension/PartType/MonitorChartPart' - settings: {} - } - } - ] - } - ] - } -} - -resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { - name: applicationInsightsName -} diff --git a/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep index 850e9fe1..dd91a405 100644 --- a/infra/core/monitor/applicationinsights.bicep +++ b/infra/core/monitor/applicationinsights.bicep @@ -1,6 +1,5 @@ metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' param name string -param dashboardName string = '' param location string = resourceGroup().location param tags object = {} param logAnalyticsWorkspaceId string @@ -16,15 +15,6 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { } } -module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { - name: 'application-insights-dashboard' - params: { - name: dashboardName - location: location - applicationInsightsName: applicationInsights.name - } -} - output connectionString string = applicationInsights.properties.ConnectionString output id string = applicationInsights.id output instrumentationKey string = applicationInsights.properties.InstrumentationKey diff --git a/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep index 74761258..a12c083a 100644 --- a/infra/core/monitor/monitoring.bicep +++ b/infra/core/monitor/monitoring.bicep @@ -1,7 +1,6 @@ metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' param logAnalyticsName string param applicationInsightsName string -param applicationInsightsDashboardName string = '' param location string = resourceGroup().location param tags object = {} @@ -20,7 +19,6 @@ module applicationInsights 'applicationinsights.bicep' = { name: applicationInsightsName location: location tags: tags - dashboardName: applicationInsightsDashboardName logAnalyticsWorkspaceId: logAnalytics.outputs.id } } diff --git a/infra/main.bicep b/infra/main.bicep index f0e3f32b..37a6d79f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -143,12 +143,22 @@ module monitoring 'core/monitor/monitoring.bicep' = { params: { location: location tags: tags - applicationInsightsDashboardName: '${prefix}-appinsights-dashboard' applicationInsightsName: '${prefix}-appinsights' logAnalyticsName: '${take(prefix, 50)}-loganalytics' // Max 63 chars } } + +module applicationInsightsDashboard 'backend-dashboard.bicep' = { + name: 'application-insights-dashboard' + scope: resourceGroup + params: { + name: '${prefix}-appinsights-dashboard' + location: location + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + // Container apps host (including container registry) module containerApps 'core/host/container-apps.bicep' = { name: 'container-apps' diff --git a/locustfile.py b/locustfile.py new file mode 100644 index 00000000..c1c91f5b --- /dev/null +++ b/locustfile.py @@ -0,0 +1,46 @@ +import random +import time + +from locust import HttpUser, between, task + + +class ChatUser(HttpUser): + wait_time = between(5, 20) + + @task + def ask_question(self): + self.client.get("/") + time.sleep(5) + self.client.post( + "/chat", + json={ + "messages": [ + { + "content": random.choice( + ["Best shoe for hiking?", "Climbing shoe cheaper than $30?", "Waterproof camping gear?"] + ), + "role": "user", + } + ], + "context": { + "overrides": {"use_advanced_flow": True, "top": 3, "retrieval_mode": "hybrid", "temperature": 0.3} + }, + }, + ) + time.sleep(5) + self.client.post( + "/chat", + json={ + "messages": [ + {"content": "Best shoe for hiking?", "role": "user"}, + { + "content": "For the best shoe for hiking, I recommend the Trailblaze Steel-Blue Hiking Shoes.", + "role": "assistant", + }, + {"content": "any other options?", "role": "user"}, + ], + "context": { + "overrides": {"use_advanced_flow": True, "top": 3, "retrieval_mode": "hybrid", "temperature": 0.3} + }, + }, + ) diff --git a/requirements-dev.txt b/requirements-dev.txt index bfe6862b..722434db 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ pytest pytest-cov pytest-asyncio mypy +locust diff --git a/src/backend/fastapi_app/__init__.py b/src/backend/fastapi_app/__init__.py index 78c19264..201bedcf 100644 --- a/src/backend/fastapi_app/__init__.py +++ b/src/backend/fastapi_app/__init__.py @@ -4,9 +4,12 @@ from contextlib import asynccontextmanager from typing import TypedDict +import fastapi +from azure.monitor.opentelemetry import configure_azure_monitor from dotenv import load_dotenv -from fastapi import FastAPI from openai import AsyncAzureOpenAI, AsyncOpenAI +from opentelemetry.instrumentation.openai import OpenAIInstrumentor +from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from fastapi_app.dependencies import ( @@ -29,14 +32,14 @@ class State(TypedDict): @asynccontextmanager -async def lifespan(app: FastAPI) -> AsyncIterator[State]: +async def lifespan(app: fastapi.FastAPI) -> AsyncIterator[State]: context = await common_parameters() azure_credential = await get_azure_credentials() engine = await create_postgres_engine_from_env(azure_credential) sessionmaker = await create_async_sessionmaker(engine) chat_client = await create_openai_chat_client(azure_credential) embed_client = await create_openai_embed_client(azure_credential) - + SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine) yield {"sessionmaker": sessionmaker, "context": context, "chat_client": chat_client, "embed_client": embed_client} await engine.dispose() @@ -48,8 +51,15 @@ def create_app(testing: bool = False): if not testing: load_dotenv(override=True) logging.basicConfig(level=logging.INFO) + logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) + + if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"): + logger.info("Configuring Azure Monitor") + configure_azure_monitor(logger_name="ragapp") + # OpenAI SDK requests use httpx and are thus not auto-instrumented: + OpenAIInstrumentor().instrument() - app = FastAPI(docs_url="/docs", lifespan=lifespan) + app = fastapi.FastAPI(docs_url="/docs", lifespan=lifespan) from fastapi_app.routes import api_routes, frontend_routes diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 9be27961..0e62fdc7 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -14,7 +14,10 @@ dependencies = [ "openai>=1.34.0,<2.0.0", "tiktoken>=0.7.0,<0.8.0", "openai-messages-token-helper>=0.1.5,<0.2.0", - "opentelemetry-instrumentation-fastapi>=0.46b0,<1.0.0", + "azure-monitor-opentelemetry>=1.6.0,<2.0.0", + "opentelemetry-instrumentation-sqlalchemy>=0.46b0,<1.0.0", + "opentelemetry-instrumentation-aiohttp-client>=0.46b0,<1.0.0", + "opentelemetry-instrumentation-openai>=0.25.6,<1.0.0", ] [build-system] From 1f68cb7eaa2e1d1a95ddb55a30110459f96adbea Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 23 Jul 2024 19:26:20 +0000 Subject: [PATCH 3/3] Tweaks --- .vscode/launch.json | 2 +- docs/images/locust_loadtest.png | Bin 0 -> 38486 bytes docs/loadtesting.md | 4 ++-- src/backend/fastapi_app/__init__.py | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 docs/images/locust_loadtest.png diff --git a/.vscode/launch.json b/.vscode/launch.json index aca97d0a..50b295df 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,7 @@ "request": "launch", "module": "uvicorn", "args": ["fastapi_app:create_app", "--factory", "--reload"], - "justMyCode": false + "justMyCode": true } ], "compounds": [ diff --git a/docs/images/locust_loadtest.png b/docs/images/locust_loadtest.png new file mode 100644 index 0000000000000000000000000000000000000000..47352e9ac5796a7dd5aad0c23cad54568d850334 GIT binary patch literal 38486 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 AsyncIterator[State]: sessionmaker = await create_async_sessionmaker(engine) chat_client = await create_openai_chat_client(azure_credential) embed_client = await create_openai_embed_client(azure_credential) - SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine) + if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"): + SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine) yield {"sessionmaker": sessionmaker, "context": context, "chat_client": chat_client, "embed_client": embed_client} await engine.dispose() @@ -51,6 +52,7 @@ def create_app(testing: bool = False): if not testing: load_dotenv(override=True) logging.basicConfig(level=logging.INFO) + # Turn off particularly noisy INFO level logs from Azure Core SDK: logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):