Skip to content

Commit e21bb95

Browse files
feat: add costing-entra-appid sample with Azure Workbook dashboard (#145)
1 parent 1a28c20 commit e21bb95

34 files changed

Lines changed: 2626 additions & 435 deletions

.github/copilot-instructions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Structure:
172172
- Standard library imports (time, json, tempfile, requests, pathlib, datetime)
173173
- `utils`, `apimtypes`, `console`, `azure_resources` (including `az`, `get_infra_rg_name`, `get_account_info`)
174174
2. USER CONFIGURATION section:
175-
- `rg_location`: Azure region (default: 'eastus2')
175+
- `rg_location`: Azure region (default: `Region.EAST_US_2`)
176176
- `index`: Deployment index for resource naming (default: 1)
177177
- `deployment`: Selected infrastructure type (reference INFRASTRUCTURE enum options)
178178
- `api_prefix`: Prefix for APIs to avoid naming collisions
@@ -407,6 +407,7 @@ Check `docs/README.md` for local preview instructions and styling notes. The pag
407407
- Existing cells must keep a unique `metadata.id` value.
408408
- New cells do not need a `metadata.id` value unless an editor or tool assigns one.
409409
- Keep notebook JSON logically structured and valid. Do not emit partial notebook fragments when a full notebook document is required.
410+
- Place **all** `import` statements at the top of every code cell, before any other code. Never nest imports inside `if` / `else` / `try` blocks within a cell. Ruff's `PLC0415` does not flag imports inside module-level conditionals, so this must be enforced manually.
410411
- When describing notebook changes to users, refer to cells by visible cell number (Cell 1, Cell 2, etc.), not by internal cell IDs.
411412

412413
### Presentation Instructions

.github/python.instructions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This ensures all code changes comply with the project's linting standards from t
2020
- Use explicit imports (avoid `from module import *`), especially in notebooks, to prevent `F403/F405`.
2121
- Keep lines within the configured length limit (see `pyproject.toml`), and wrap long strings or calls.
2222
- Avoid f-strings without placeholders (e.g., `F541`).
23+
- **Ruff gap:** `PLC0415` (`import-outside-toplevel`) only flags imports inside functions and classes. It does **not** flag imports inside module-level `if` / `else` / `try` blocks. Ruff will not catch those, so the top-of-file import rule below must be enforced manually.
2324

2425
## Goals
2526

@@ -35,7 +36,7 @@ This ensures all code changes comply with the project's linting standards from t
3536
## Style and Conventions
3637

3738
- Prefer Python 3.12+ features unless otherwise required.
38-
- Keep all imports at the top of the file.
39+
- Keep **all** imports at the top of the file. Do not place `import` statements inside `if` / `else` / `try` blocks or inside functions. Hoist them even when only one branch uses the module. Ruff `PLC0415` will catch function-scope imports but will **not** catch imports inside module-level conditional blocks, so apply this rule manually.
3940
- Use type hints and concise docstrings (PEP 257).
4041
- Use 4-space indentation and PEP 8 conventions.
4142
- Surround an equal sign by a space on each side.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ It's quick and easy to get started!
6767
| [AuthX][sample-authx] | Authentication and role-based authorization in a mock HR API. | All infrastructures |
6868
| [AuthX Pro][sample-authx-pro] | Authentication and role-based authorization in a mock product with multiple APIs and policy fragments. | All infrastructures |
6969
| [Azure Maps][sample-azure-maps] | Proxying calls to Azure Maps with APIM policies. | All infrastructures |
70-
| [Costing][sample-costing] | Track and allocate API costs per business unit using APIM subscriptions, Log Analytics, and Cost Management. | All infrastructures |
70+
| [Costing][sample-costing] | Track and allocate API costs per business unit using APIM subscriptions, Entra ID application tracking, and AI Gateway token/PTU tracking via Log Analytics and Cost Management. | All infrastructures |
7171
| [Egress Control][sample-egress-control] | Control APIM outbound internet traffic by routing it through a Network Virtual Appliance (NVA) in a hub/spoke topology. | appgw-apim, appgw-apim-pe |
7272
| [General][sample-general] | Basic demo of APIM sample setup and policy usage. | All infrastructures |
7373
| [Load Balancing][sample-load-balancing] | Priority and weighted load balancing across backends. | apim-aca, afd-apim-pe |

assets/APIM-Samples-Slide-Deck.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ <h4>Azure Maps</h4>
11181118
</div>
11191119
<div class="arch-card">
11201120
<h4>Costing &amp; Showback</h4>
1121-
<p>Track API costs per business unit via subscriptions &amp; Log Analytics.</p>
1121+
<p>Track API costs per business unit via subscriptions, Entra ID apps &amp; AI Gateway tokens.</p>
11221122
</div>
11231123
<div class="arch-card">
11241124
<h4>Credential Manager</h4>

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ <h3>Azure Maps</h3>
446446

447447
<a class="sample-card" href="https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/costing" target="_blank" rel="noopener">
448448
<h3>Costing</h3>
449-
<p>Track and allocate API costs per business unit using subscriptions, Log Analytics, and Cost Management.</p>
449+
<p>Track and allocate API costs per business unit using subscriptions, Entra ID application tracking, and AI Gateway token/PTU tracking via Log Analytics and Cost Management.</p>
450450
<span class="infra-tag">All infrastructures</span>
451451
</a>
452452

pyproject.toml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ exclude = ["*.ipynb"]
3737

3838
[tool.ruff.lint]
3939
select = [
40-
"E", # pycodestyle errors
41-
"W", # pycodestyle warnings
42-
"F", # Pyflakes
43-
"PLC", # Pylint convention
44-
"PLE", # Pylint error
45-
"PLR", # Pylint refactoring
46-
"PLW", # Pylint warning
47-
"Q", # flake8-quotes
40+
"E", # pycodestyle errors
41+
"W", # pycodestyle warnings
42+
"F", # Pyflakes
43+
"PLC", # Pylint convention
44+
"PLC0415", # import-outside-toplevel (explicit; enforce imports at top of file/cell)
45+
"PLE", # Pylint error
46+
"PLR", # Pylint refactoring
47+
"PLW", # Pylint warning
48+
"Q", # flake8-quotes
4849
]
4950
ignore = [
5051
"PLR0911", # Too many return statements

samples/costing/README.md

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Samples: APIM Costing & Showback
22

3-
This sample demonstrates how to track and allocate API costs using Azure API Management with Azure Monitor, Application Insights, Log Analytics, and Cost Management. This setup enables organizations to determine the cost of API consumption per business unit, department, or application.
3+
This sample demonstrates how to track and allocate API costs using Azure API Management with Azure Monitor, Application Insights, Log Analytics, and Cost Management. It supports three complementary approaches: **subscription-based** tracking (using APIM subscription keys), **Entra ID application** tracking (using the `emit-metric` policy with JWT `appid` claims), and **AI Gateway token/PTU** tracking (using the `emit-metric` policy to capture per-client token consumption when APIM acts as an AI Gateway). All approaches share a single Azure Monitor Workbook with tabbed views.
44

55
⚙️ **Supported infrastructures**: All infrastructures (or bring your own existing APIM deployment)
66

@@ -9,10 +9,13 @@ This sample demonstrates how to track and allocate API costs using Azure API Man
99
## 🎯 Objectives
1010

1111
1. **Track API usage by caller** - Use APIM subscription keys to identify business units, departments, or applications
12-
2. **Capture request metrics** - Log subscriptionId, apiName, operationName, and status codes
13-
3. **Aggregate cost data** - Combine API usage metrics with Azure Cost Management data
14-
4. **Visualize showback data** - Create Azure Monitor Workbooks to display cost allocation by caller
15-
5. **Enable cost governance** - Establish patterns for consistent tagging and naming conventions
12+
2. **Track API usage by Entra ID application** - Use the `emit-metric` policy to extract `appid`/`azp` JWT claims and emit per-caller custom metrics
13+
3. **Capture request metrics** - Log subscriptionId, apiName, operationName, and status codes
14+
4. **Aggregate cost data** - Combine API usage metrics with Azure Cost Management data
15+
5. **Visualize showback data** - Create Azure Monitor Workbooks with tabbed views for both approaches
16+
6. **Enable cost governance** - Establish patterns for consistent tagging and naming conventions
17+
7. **Enable budget alerts** - Create scheduled query alerts when callers exceed configurable thresholds
18+
8. **Track AI token consumption per client** - When APIM is used as an AI Gateway, capture prompt, completion, and total token usage per calling application, enabling per-client cost attribution for PTU or pay-as-you-go OpenAI deployments
1619

1720
## ✅ Prerequisites
1821

@@ -50,46 +53,35 @@ Users who only need to **view** the deployed Azure Monitor Workbook (not deploy
5053
5154
## ⚙️ Configuration
5255

53-
### Important: Sample Index
56+
### Deployment Index
5457

55-
The `create.ipynb` notebook passes a **`sampleIndex` parameter** to the Bicep template. This parameter ensures unique resource naming when deploying multiple instances of this sample. The notebook automatically provides this value; you only need to verify it matches your deployment scenario:
58+
The `create.ipynb` notebook passes an **`index` parameter** to the Bicep template. This parameter ensures unique resource naming when deploying multiple instances of this sample. The notebook automatically provides this value; you only need to verify it matches your deployment scenario:
5659

5760
```python
58-
sample_index = 2 # Increment this for multiple sample deployments
61+
index = 1 # Match your infrastructure deployment index
5962
```
6063

61-
This index is used in resource names (e.g., `appi-cost-2-xxxx`, `log-cost-2-xxxx`) to avoid naming conflicts when running multiple instances of the sample.
64+
This index is used in resource names (e.g., `appi-cost-1-xxxx`, `log-cost-1-xxxx`) to avoid naming conflicts.
6265

63-
### Option A: Use a repository infrastructure (recommended)
66+
### Running the Sample
6467

6568
1. Navigate to the desired [infrastructure](../../infrastructure/) folder (e.g., [simple-apim](../../infrastructure/simple-apim/)) and follow its README.md to deploy.
6669
2. Open `create.ipynb` and set:
6770
```python
68-
infrastructure = INFRASTRUCTURE.SIMPLE_APIM # Match your deployed infra
69-
index = 1 # Match your infra index
70-
sample_index = 1 # Increment for multiple sample deployments
71+
deployment = INFRASTRUCTURE.SIMPLE_APIM # Match your deployed infra
72+
index = 1 # Match your infra index
7173
```
7274
3. Run All Cells.
7375

74-
### Option B: Bring your own existing APIM
75-
76-
You can use any existing Azure API Management instance. The sample only adds diagnostic settings and sample resources to your APIM - it does **not** modify your existing APIs or policies.
77-
78-
1. Open `create.ipynb` and **uncomment** the two lines in the User Configuration section:
79-
```python
80-
existing_rg_name = 'your-resource-group-name'
81-
existing_apim_name = 'your-apim-service-name'
82-
```
83-
2. Set the correct Azure subscription: `az account set -s <subscription-id>`
84-
3. Run All Cells.
85-
8676
**What the sample deploys into your resource group:**
8777
- Application Insights instance
8878
- Log Analytics Workspace
8979
- Storage Account (for cost exports)
9080
- Diagnostic Settings on your APIM (routes gateway logs to Log Analytics)
9181
- Azure Monitor Workbook
92-
- A sample API (`cost-tracking-api`) with 5 business unit subscriptions
82+
- Sample APIs with 4 business unit subscriptions
83+
- Entra ID tracking API with `emit-metric` policy (optional)
84+
- AI Gateway token tracking API with `emit-metric` policy (optional)
9385

9486
**What it does NOT touch:**
9587
- Your existing APIs, policies, or subscriptions
@@ -109,6 +101,19 @@ Organizations often need to allocate the cost of shared API Management infrastru
109101

110102
This sample focuses on **producing cost data**, not implementing billing processes. You determine costs; how you use that information (showback reports, chargeback, budgeting) is a separate business decision.
111103

104+
### Three Tracking Approaches
105+
106+
| Aspect | Subscription-Based | Entra ID Application | AI Gateway Token/PTU |
107+
|---|---|---|---|
108+
| **Caller identification** | APIM subscription key (`ApimSubscriptionId`) | JWT `appid`/`azp` claim | JWT `appid`/`azp` claim |
109+
| **Data source** | `ApiManagementGatewayLogs` in Log Analytics | `customMetrics` in Application Insights | `customMetrics` in Application Insights |
110+
| **Tracking mechanism** | Built-in APIM logging | `emit-metric` policy | `emit-metric` policy (outbound response parsing) |
111+
| **Metric name** | N/A (built-in logs) | `caller-requests` | `caller-tokens` |
112+
| **Cost Management export** | Yes (storage account) | No (metrics-based) | No (metrics-based) |
113+
| **Best for** | Dedicated subscriptions per BU | OAuth client-credentials flows, shared subscriptions | AI Gateway scenarios (Azure OpenAI, PTU capacity planning) |
114+
115+
All three approaches are deployed together. Toggle `enable_entraid_tracking` and `enable_token_tracking` in the notebook to include or exclude each flow.
116+
112117
## 🛩️ Lab Components
113118

114119
This lab deploys and configures:
@@ -118,15 +123,14 @@ This lab deploys and configures:
118123
- **Storage Account** - Receives Azure Cost Management exports
119124
- **Cost Management Export** - Automated export of cost data (configurable frequency)
120125
- **Diagnostic Settings** - Links APIM to Log Analytics with `logAnalyticsDestinationType: Dedicated` for resource-specific tables
121-
- **Sample API & Subscriptions** - 5 subscriptions representing different business units
122-
- **Azure Monitor Workbook** - Pre-built dashboard with:
123-
- Cost allocation table (base + variable cost per BU)
124-
- Base vs variable cost stacked bar chart
125-
- Cost breakdown by API
126-
- Request count and distribution charts
127-
- Success/error rate analysis
128-
- Response code distribution
129-
- **Live Pricing Integration** - Auto-detects your APIM SKU and fetches current pricing from the [Azure Retail Prices API](https://learn.microsoft.com/rest/api/cost-management/retail-prices/azure-retail-prices)
126+
- **Sample API & Subscriptions** - 4 subscriptions representing different business units
127+
- **Entra ID Tracking API** (optional) - A second API with the `emit-metric` policy that extracts `appid` from JWT tokens and emits `caller-requests` custom metrics
128+
- **AI Gateway Token Tracking API** (optional) - A third API with the `emit-metric` policy that parses Azure OpenAI response bodies to extract `prompt_tokens`, `completion_tokens`, and `total_tokens`, emitting `caller-tokens` custom metrics with `CallerId`, `TokenType`, and `Model` dimensions
129+
- **Azure Monitor Workbook** - Pre-built tabbed dashboard with:
130+
- **Subscription-Based Costing tab**: Cost allocation table (base + variable cost per BU), base vs variable cost stacked bar chart, cost breakdown by API, request count and distribution charts, success/error rate analysis, response code distribution, business unit drill-down
131+
- **Entra ID Application Costing tab**: Usage by caller ID (bar chart + table), cost allocation by caller (table + pie chart), hourly request trend by caller
132+
- **AI Gateway Token/PTU tab**: Token consumption by client (prompt vs completion bar chart), token cost allocation table with configurable per-1K-token rates, token/cost distribution pie charts, hourly token trend with PTU capacity threshold line, prompt vs completion area chart, model breakdown table
133+
- **SKU-Based Pricing** - Automatically derives base monthly cost, overage rate, and included request allowance from the deployed APIM SKU using built-in pricing data (sourced from the [Azure API Management pricing page](https://azure.microsoft.com/pricing/details/api-management/), March 2026)
130134
- **Budget Alerts** (optional) - Per-BU scheduled query alerts when request thresholds are exceeded
131135

132136
### Cost Allocation Model
@@ -153,10 +157,10 @@ This lab deploys and configures:
153157

154158
After running the notebook, you will have:
155159

156-
1. **Application Insights** showing real-time API requests
160+
1. **Application Insights** showing real-time API requests and `caller-requests` custom metrics (Entra ID)
157161
2. **Log Analytics** with queryable `ApiManagementGatewayLogs` (resource-specific table)
158162
3. **Storage Account** receiving cost export data
159-
4. **Azure Monitor Workbook** displaying cost allocation and usage analytics
163+
4. **Azure Monitor Workbook** with tabbed views for both subscription-based and Entra ID cost allocation
160164
5. **Portal links** printed in the notebook's final cell for quick access
161165

162166
### Cost Management Export
@@ -181,6 +185,30 @@ The deployed workbook provides a comprehensive view of API cost allocation and u
181185

182186
![Dashboard - Response Code Analysis](screenshots/Dashboard-05.png)
183187

188+
![Dashboard - Drill-Down Details](screenshots/Dashboard-06.png)
189+
190+
### Entra ID Application Costing Tab
191+
192+
The Entra ID tab shows cost attribution by calling application, using the `emit-metric` policy's `caller-requests` custom metric.
193+
194+
![Entra ID - Usage by Caller ID](screenshots/EntraID-01.png)
195+
196+
![Entra ID - Cost Allocation](screenshots/EntraID-02.png)
197+
198+
![Entra ID - Request Trend](screenshots/EntraID-03.png)
199+
200+
### AI Gateway Token/PTU Tab
201+
202+
The AI Gateway tab shows per-client token consumption and estimated costs when APIM is used as an AI Gateway in front of Azure OpenAI or other LLM backends. It uses the `emit-metric` policy's `caller-tokens` custom metric with `CallerId`, `TokenType` (prompt/completion/total), and `Model` dimensions.
203+
204+
![AI Gateway - Token Consumption by Client](screenshots/AIGateway-01.png)
205+
206+
![AI Gateway - Token Cost Allocation](screenshots/AIGateway-02.png)
207+
208+
![AI Gateway - Token Trends & PTU Utilization](screenshots/AIGateway-03.png)
209+
210+
![AI Gateway - Model & Caller Breakdown](screenshots/AIGateway-04.png)
211+
184212
## 🧹 Clean Up
185213

186214
To remove all resources created by this sample, open and run `clean-up.ipynb`. This deletes:
@@ -199,6 +227,11 @@ To remove all resources created by this sample, open and run `clean-up.ipynb`. T
199227
- [Log Analytics Kusto Query Language](https://learn.microsoft.com/azure/data-explorer/kusto/query/)
200228
- [Azure Monitor Workbooks](https://learn.microsoft.com/azure/azure-monitor/visualize/workbooks-overview)
201229
- [APIM Diagnostic Settings](https://learn.microsoft.com/azure/api-management/api-management-howto-use-azure-monitor)
230+
- [APIM emit-metric policy](https://learn.microsoft.com/azure/api-management/emit-metric-policy)
231+
- [Application Insights custom metrics](https://learn.microsoft.com/azure/azure-monitor/essentials/metrics-custom-overview)
232+
- [Microsoft Entra ID application model](https://learn.microsoft.com/entra/identity-platform/application-model)
233+
- [Azure OpenAI usage and token metrics](https://learn.microsoft.com/azure/ai-services/openai/how-to/monitoring)
234+
- [PTU provisioned throughput concepts](https://learn.microsoft.com/azure/ai-services/openai/concepts/provisioned-throughput)
202235

203236
[infrastructure-architectures]: ../../README.md#infrastructure-architectures
204237
[infrastructure-folder]: ../../infrastructure/

0 commit comments

Comments
 (0)