Skip to content

Commit 58c1b2f

Browse files
authored
Merge pull request #35 from codemonkyu/feature/ebs-performance-monitoring-mcp
commit: add EBS Performance Monitoring MCP Server source code
2 parents c6ce28d + c258208 commit 58c1b2f

10 files changed

Lines changed: 2442 additions & 0 deletions

File tree

ebs-performance-monitoring-mcp/ebs-performance-monitoring-mcp/README.md

Lines changed: 362 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""AWS EBS Performance Monitoring MCP 서버"""
2+
3+
__version__ = "0.2.0"
4+
5+
from .models import (
6+
MetricDataPoint,
7+
MetricResult,
8+
IOPSResult,
9+
ThroughputResult,
10+
SnapshotInfo,
11+
AdvancedMetricsResult,
12+
PerformanceSummary,
13+
MultiVolumeResult,
14+
BottleneckAnalysis,
15+
)
16+
from .cloudwatch_client import CloudWatchClient, SUPPORTED_EBS_METRICS
17+
from .snapshot_calculator import SnapshotCalculator
18+
from .volume_client import VolumeClient, VolumeConfig
19+
from .ec2_client import EC2Client, EC2EbsBandwidth, VolumeAttachment
20+
from .performance_analyzer import PerformanceAnalyzer
21+
from .server import main
22+
23+
__all__ = [
24+
# Models
25+
"MetricDataPoint",
26+
"MetricResult",
27+
"IOPSResult",
28+
"ThroughputResult",
29+
"SnapshotInfo",
30+
"AdvancedMetricsResult",
31+
"PerformanceSummary",
32+
"MultiVolumeResult",
33+
"BottleneckAnalysis",
34+
# Clients
35+
"CloudWatchClient",
36+
"SUPPORTED_EBS_METRICS",
37+
"SnapshotCalculator",
38+
"VolumeClient",
39+
"VolumeConfig",
40+
"EC2Client",
41+
"EC2EbsBandwidth",
42+
"VolumeAttachment",
43+
# Analyzer
44+
"PerformanceAnalyzer",
45+
# Server
46+
"main",
47+
]
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""CloudWatch API Client
2+
3+
Client module for communicating with AWS CloudWatch API to retrieve EBS metric data.
4+
5+
Features:
6+
- Load AWS credentials from environment variables or AWS profile
7+
- Query metric data by metric name, volume ID, and time range
8+
- Support for EBS metric list
9+
- Support for statistic types (Average, Sum, Minimum, Maximum, SampleCount)
10+
"""
11+
12+
import asyncio
13+
from datetime import datetime
14+
from typing import Optional
15+
16+
import boto3
17+
from botocore.exceptions import ClientError
18+
19+
from .models import MetricDataPoint, MetricResult
20+
21+
22+
# Supported EBS CloudWatch metrics
23+
SUPPORTED_EBS_METRICS = [
24+
"VolumeReadOps",
25+
"VolumeWriteOps",
26+
"VolumeReadBytes",
27+
"VolumeWriteBytes",
28+
"VolumeTotalReadTime",
29+
"VolumeTotalWriteTime",
30+
"VolumeIdleTime",
31+
"VolumeQueueLength",
32+
"VolumeThroughputPercentage",
33+
"VolumeConsumedReadWriteOps",
34+
"BurstBalance",
35+
]
36+
37+
# Supported statistic types
38+
SUPPORTED_STATISTICS = [
39+
"Average",
40+
"Sum",
41+
"Minimum",
42+
"Maximum",
43+
"SampleCount",
44+
]
45+
46+
# CloudWatch namespace
47+
EBS_NAMESPACE = "AWS/EBS"
48+
49+
50+
class CloudWatchClient:
51+
"""AWS CloudWatch API Client
52+
53+
Client for querying CloudWatch metrics for EBS volumes.
54+
55+
Attributes:
56+
region: AWS region (uses default region if None)
57+
_client: boto3 CloudWatch client
58+
59+
Example:
60+
>>> client = CloudWatchClient(region="us-east-1")
61+
>>> result = await client.get_metric_statistics(
62+
... volume_id="vol-1234567890abcdef0",
63+
... metric_name="VolumeReadOps",
64+
... start_time=datetime(2024, 1, 1),
65+
... end_time=datetime(2024, 1, 2),
66+
... period=300,
67+
... statistics=["Average", "Maximum"]
68+
... )
69+
"""
70+
71+
def __init__(self, region: Optional[str] = None):
72+
"""Initialize boto3 CloudWatch client
73+
74+
Args:
75+
region: AWS region. Loads from environment variables or AWS profile if None
76+
"""
77+
self.region = region
78+
if region:
79+
self._client = boto3.client("cloudwatch", region_name=region)
80+
else:
81+
self._client = boto3.client("cloudwatch")
82+
83+
async def get_metric_statistics(
84+
self,
85+
volume_id: str,
86+
metric_name: str,
87+
start_time: datetime,
88+
end_time: datetime,
89+
period: int = 300,
90+
statistics: Optional[list[str]] = None
91+
) -> MetricResult:
92+
"""Query CloudWatch metric statistics
93+
94+
Retrieves CloudWatch metric statistics for the specified EBS volume.
95+
96+
Args:
97+
volume_id: EBS volume ID (e.g., vol-1234567890abcdef0)
98+
metric_name: CloudWatch metric name (e.g., VolumeReadOps)
99+
start_time: Query start time
100+
end_time: Query end time
101+
period: Metric collection interval in seconds (default: 300)
102+
statistics: List of statistic types to query (default: ["Average"])
103+
104+
Returns:
105+
MetricResult: Metric query result
106+
107+
Raises:
108+
ValueError: If unsupported metric name or statistic type
109+
ClientError: If AWS API call fails
110+
"""
111+
if statistics is None:
112+
statistics = ["Average"]
113+
114+
# Validate metric name
115+
if metric_name not in SUPPORTED_EBS_METRICS:
116+
raise ValueError(
117+
f"Unsupported metric name: {metric_name}. "
118+
f"Supported metrics: {', '.join(SUPPORTED_EBS_METRICS)}"
119+
)
120+
121+
# Validate statistic types
122+
for stat in statistics:
123+
if stat not in SUPPORTED_STATISTICS:
124+
raise ValueError(
125+
f"Unsupported statistic type: {stat}. "
126+
f"Supported statistics: {', '.join(SUPPORTED_STATISTICS)}"
127+
)
128+
129+
# CloudWatch API call (wrap synchronous call as async)
130+
loop = asyncio.get_event_loop()
131+
response = await loop.run_in_executor(
132+
None,
133+
lambda: self._client.get_metric_statistics(
134+
Namespace=EBS_NAMESPACE,
135+
MetricName=metric_name,
136+
Dimensions=[
137+
{
138+
"Name": "VolumeId",
139+
"Value": volume_id
140+
}
141+
],
142+
StartTime=start_time,
143+
EndTime=end_time,
144+
Period=period,
145+
Statistics=statistics
146+
)
147+
)
148+
149+
# Convert data points
150+
datapoints = []
151+
for dp in response.get("Datapoints", []):
152+
# Use first statistic value
153+
value = None
154+
for stat in statistics:
155+
if stat in dp:
156+
value = dp[stat]
157+
break
158+
159+
if value is not None:
160+
datapoints.append(
161+
MetricDataPoint(
162+
timestamp=dp["Timestamp"],
163+
value=value,
164+
unit=dp.get("Unit", "None")
165+
)
166+
)
167+
168+
# Sort by timestamp
169+
datapoints.sort(key=lambda x: x.timestamp)
170+
171+
# Calculate statistics
172+
average = None
173+
maximum = None
174+
minimum = None
175+
total_sum = None
176+
177+
if datapoints:
178+
values = [dp.value for dp in datapoints]
179+
if "Average" in statistics:
180+
average = sum(values) / len(values)
181+
if "Maximum" in statistics:
182+
maximum = max(values)
183+
if "Minimum" in statistics:
184+
minimum = min(values)
185+
if "Sum" in statistics:
186+
total_sum = sum(values)
187+
188+
return MetricResult(
189+
metric_name=metric_name,
190+
volume_id=volume_id,
191+
datapoints=datapoints,
192+
average=average,
193+
maximum=maximum,
194+
minimum=minimum,
195+
sum=total_sum
196+
)
197+
198+
def list_available_metrics(self) -> list[str]:
199+
"""Return list of available EBS metrics
200+
201+
Returns a list of all EBS metric names supported by CloudWatch.
202+
203+
Returns:
204+
list[str]: List of supported EBS metric names
205+
"""
206+
return SUPPORTED_EBS_METRICS.copy()

0 commit comments

Comments
 (0)