Skip to content

Commit 4a28e2d

Browse files
committed
Related Metrics demo setup
1 parent 03c9155 commit 4a28e2d

File tree

8 files changed

+236
-2
lines changed

8 files changed

+236
-2
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
## Steps
2+
3+
### 1. Navigate to CloudWatch Application Signals Services
4+
5+
Go to CloudWatch → Application Signals (APM) → Services.
6+
7+
### 2. Search for billing-service-python
8+
9+
In the search field with placeholder text 'Filter services and resources by text, property or value', type 'billing-service-python' and press Enter.
10+
11+
**Constraints:**
12+
- You MUST ensure you press Enter.
13+
14+
### 3. Click on billing-service-python
15+
16+
Click the hyperlink 'billing-service-python'.
17+
18+
### 4. Click 'Service operation' tab
19+
20+
Click the 'Service operation' tab. Then, ensure the `GET ^summary/$` is selected if not already.
21+
22+
### 5. Click on a datapoint for GET /summary
23+
24+
Click on a datapoint in the GET /summary operation graph, PASS in 1 and 3 as a PARAMETERS.
25+
26+
**Constraints:**
27+
- You MUST pass in parameters 1 and 3
28+
29+
### 6. Click 'Correlate with Other Metrics'
30+
31+
In the diagnostic drawer on the right, click the 'Correlate with Other Metrics' button.
32+
33+
### 7. Check you are on Related metrics tab
34+
35+
Check that you are now on the 'Related metrics' tab.
36+
37+
### 8. Search for BillingSummaryCacheHitCount metric
38+
39+
In the search field with placeholder text 'Filter metrics by text, property, or value', type 'BillingSummaryCacheHitCount' and press Enter.
40+
41+
**Constraints:**
42+
- You MUST ensure you press Enter.
43+
44+
### 9. Select BillingSummaryCacheHitCount metric
45+
46+
In the metrics table, select the 'BillingSummaryCacheHitCount' metric to add it to the graph.
47+
48+
### 10. Search for BillingSummaryCacheMissCount metric
49+
50+
In the search field with placeholder text 'Filter metrics by text, property, or value', type 'BillingSummaryCacheMissCount' and press Enter.
51+
52+
**Constraints:**
53+
- You MUST ensure you press Enter.
54+
55+
### 11. Select BillingSummaryCacheMissCount metric
56+
57+
In the metrics table, select the 'BillingSummaryCacheMissCount' metric to add it to the graph.
58+
59+
### 12. Check correlation between metrics
60+
61+
Check that when latency spikes occur, BillingSummaryCacheHitCount decreases and BillingSummaryCacheMissCount increases at the same time periods.

data_test/test_cases/metrics_test_cases.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@
6060
}]
6161
},
6262
"notes": "Corresponds to PDF scenario 2"
63+
},
64+
{
65+
"test_case_id": "billing_summary_service_metric_availability_check",
66+
"description": "Verify Service Latency metric availability for billing summary API (Scenario 16)",
67+
"test_scenario": "Scenario 16",
68+
"metric_namespace": "ApplicationSignals",
69+
"metric_name": "Latency",
70+
"statistic": "p99",
71+
"dimensions": [
72+
{"Name": "Operation", "Value": "GET ^summary/$"},
73+
{"Name": "Environment", "Value": "ENVIRONMENT_NAME_PLACEHOLDER"},
74+
{"Name": "Service", "Value": "billing-service-python"}
75+
],
76+
"evaluation_period_minutes": 180,
77+
"threshold": {
78+
"comparison_operator": [{
79+
"operator": "GreaterThanOrEqualToThreshold",
80+
"threshold_value": 0
81+
}]
82+
},
83+
"notes": "Corresponds to PDF scenario 16"
6384
},
6485
{
6586
"test_case_id": "database_insight_dependency_metric_availability_check",

pet_clinic_billing_service/billing_service/views.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from rest_framework import viewsets, status
22
from rest_framework.response import Response
3-
from django.db.models import Subquery
3+
from django.db.models import Subquery, Count, Sum
4+
from django.utils import timezone
5+
from django.core.cache import cache
46
from .models import Billing,CheckList
57
from .serializers import BillingSerializer
68
from opentelemetry import trace
@@ -154,6 +156,42 @@ def log(self, data):
154156
# Don't raise the exception to avoid disrupting the main flow
155157

156158

159+
class SummaryViewSet(viewsets.ViewSet):
160+
def list(self, request, pk=None):
161+
span = trace.get_current_span()
162+
163+
# Always set request counter to 1
164+
span.set_attribute("billing_summary_request", 1)
165+
166+
# Set num_summaries based on current minute
167+
current_minute = timezone.now().minute
168+
num_summaries = 50 if current_minute % 5 == 0 else 2
169+
170+
# Use random cache key to simulate high cache miss rate
171+
cache_key = f'billing_summary_last_7_days_{random.randint(1, num_summaries)}'
172+
summary = cache.get(cache_key)
173+
174+
if summary is None:
175+
# Cache miss
176+
span.set_attribute("billing_summary_cache_hit", 0)
177+
178+
time.sleep(2)
179+
billings = Billing.objects.all()
180+
181+
summary = {
182+
'total_count': billings.count(),
183+
'total_amount': billings.aggregate(Sum('payment'))['payment__sum'] or 0,
184+
'period': 'all_time'
185+
}
186+
187+
cache.set(cache_key, summary, 300) # Cache for 5 minutes
188+
else:
189+
# Cache hit
190+
span.set_attribute("billing_summary_cache_hit", 1)
191+
192+
return Response(summary)
193+
194+
157195
class HealthViewSet(viewsets.ViewSet):
158196
def list(self, request):
159197
logger.info("HealthViewSet.list() called - Health check requested")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash
2+
3+
# Set variables
4+
LOG_GROUP_NAME="aws/spans"
5+
REGION=${AWS_REGION:-us-east-1}
6+
NAMESPACE="CustomBilling"
7+
8+
echo "Creating log group if it doesn't exist..."
9+
aws logs create-log-group --log-group-name "$LOG_GROUP_NAME" --region "$REGION" 2>/dev/null || echo "Log group already exists or creation failed"
10+
11+
echo "Creating CloudWatch metric filters for billing service custom metrics..."
12+
13+
# Create metric filter for billing summary requests
14+
cat > metric-filter.json << EOF
15+
{
16+
"logGroupName": "aws/spans",
17+
"filterName": "BillingSummaryRequests",
18+
"filterPattern": "{ $.attributes.['billing_summary_request'] = \"1\" }",
19+
"metricTransformations": [
20+
{
21+
"metricName": "BillingSummaryRequestCount",
22+
"metricNamespace": "$NAMESPACE",
23+
"metricValue": "1",
24+
"unit": "Count",
25+
"dimensions": {
26+
"Service": "$.attributes.['aws.local.service']",
27+
"Environment": "$.attributes.['aws.local.environment']",
28+
"Operation": "$.attributes.['aws.local.operation']"
29+
}
30+
}
31+
]
32+
}
33+
EOF
34+
35+
aws logs put-metric-filter --region $REGION --cli-input-json file://metric-filter.json || { rm -f metric-filter.json; exit 1; }
36+
37+
# Create metric filter for cache hits
38+
cat > metric-filter.json << EOF
39+
{
40+
"logGroupName": "aws/spans",
41+
"filterName": "BillingSummaryCacheHits",
42+
"filterPattern": "{ $.attributes.['billing_summary_cache_hit'] = \"1\" }",
43+
"metricTransformations": [
44+
{
45+
"metricName": "BillingSummaryCacheHitCount",
46+
"metricNamespace": "$NAMESPACE",
47+
"metricValue": "1",
48+
"unit": "Count",
49+
"dimensions": {
50+
"Service": "$.attributes.['aws.local.service']",
51+
"Environment": "$.attributes.['aws.local.environment']",
52+
"Operation": "$.attributes.['aws.local.operation']"
53+
}
54+
}
55+
]
56+
}
57+
EOF
58+
59+
aws logs put-metric-filter --region $REGION --cli-input-json file://metric-filter.json || { rm -f metric-filter.json; exit 1; }
60+
61+
# Create metric filter for cache misses
62+
cat > metric-filter.json << EOF
63+
{
64+
"logGroupName": "aws/spans",
65+
"filterName": "BillingSummaryCacheMisses",
66+
"filterPattern": "{ $.attributes.['billing_summary_cache_hit'] = \"0\" }",
67+
"metricTransformations": [
68+
{
69+
"metricName": "BillingSummaryCacheMissCount",
70+
"metricNamespace": "$NAMESPACE",
71+
"metricValue": "1",
72+
"unit": "Count",
73+
"dimensions": {
74+
"Service": "$.attributes.['aws.local.service']",
75+
"Environment": "$.attributes.['aws.local.environment']",
76+
"Operation": "$.attributes.['aws.local.operation']"
77+
}
78+
}
79+
]
80+
}
81+
EOF
82+
83+
aws logs put-metric-filter --region $REGION --cli-input-json file://metric-filter.json || { rm -f metric-filter.json; exit 1; }
84+
85+
rm -f metric-filter.json
86+
87+
echo "Metric filters created successfully!"
88+
echo "Metrics will appear in CloudWatch under the '$NAMESPACE' namespace:"
89+
echo "- BillingSummaryRequestCount"
90+
echo "- BillingSummaryCacheHitCount"
91+
echo "- BillingSummaryCacheMissCount"

pet_clinic_billing_service/pet_clinic_billing_service/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
from django.contrib import admin
1818
from django.urls import include, path
1919
from rest_framework.routers import DefaultRouter
20-
from billing_service.views import HealthViewSet, BillingViewSet
20+
from billing_service.views import HealthViewSet, BillingViewSet, SummaryViewSet
2121

2222
router = DefaultRouter()
2323
router.register('billings', BillingViewSet, basename='billings')
24+
router.register('summary', SummaryViewSet, basename='summary')
2425
router.register('health', HealthViewSet, basename='health')
2526
urlpatterns = [
2627
path("admin/", admin.site.urls),

spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/BillingServiceClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,11 @@ public Flux<BillingDetail> getBillings() {
4545
.bodyToFlux(BillingDetail.class);
4646
}
4747

48+
public Mono<Object> getBillingSummary() {
49+
return webClientBuilder.build().get()
50+
.uri("http://billing-service/summary/")
51+
.retrieve()
52+
.bodyToMono(Object.class);
53+
}
54+
4855
}

spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ public Flux<BillingDetail> getBillings() {
122122
return billingServiceClient.getBillings();
123123
}
124124

125+
@GetMapping(value = "billing/summary")
126+
public Mono<Object> getBillingSummary() {
127+
return billingServiceClient.getBillingSummary();
128+
}
129+
125130
@PostMapping(value = "insurance/pet-insurances")
126131
public Mono<Void> addPetInsurance(final @RequestBody PetInsurance petInsurance) {
127132
System.out.println(petInsurance.toString());

traffic-generator/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,13 @@ const lowTrafficBillingTask = cron.schedule('*/2 * * * *', () => {
251251
}, { scheduled: false });
252252

253253
lowTrafficBillingTask.start();
254+
255+
const lowTrafficBillingSummaryTask = cron.schedule('* * * * * *', () => {
256+
console.log('query billing summary every 1 second');
257+
axios.get(`${baseUrl}/api/billing/summary`, { timeout: 10000 })
258+
.catch(err => {
259+
console.error(`${baseUrl}/api/billing/summary, error: ` + (err.response ? err.response.data : err.toString()));
260+
}); // Catch and log errors
261+
}, { scheduled: false });
262+
263+
lowTrafficBillingSummaryTask.start();

0 commit comments

Comments
 (0)