|
20 | 20 | """ |
21 | 21 |
|
22 | 22 | import datetime |
| 23 | +import json |
23 | 24 | import logging |
24 | 25 | import time |
25 | 26 | from typing import Any, Tuple |
|
28 | 29 | from perfkitbenchmarker import errors |
29 | 30 | from perfkitbenchmarker import provider_info |
30 | 31 | from perfkitbenchmarker import relational_db |
| 32 | +from perfkitbenchmarker import sample |
31 | 33 | from perfkitbenchmarker import sql_engine_utils |
32 | 34 | from perfkitbenchmarker import vm_util |
33 | 35 | from perfkitbenchmarker.providers import azure |
34 | 36 | from perfkitbenchmarker.providers.azure import azure_disk |
35 | 37 | from perfkitbenchmarker.providers.azure import azure_relational_db |
| 38 | +from perfkitbenchmarker.providers.azure import util |
36 | 39 |
|
37 | 40 | DEFAULT_DATABASE_NAME = 'database' |
38 | 41 |
|
@@ -70,6 +73,9 @@ class AzureFlexibleServer(azure_relational_db.AzureRelationalDb): |
70 | 73 | sql_engine_utils.FLEXIBLE_SERVER_POSTGRES, |
71 | 74 | sql_engine_utils.FLEXIBLE_SERVER_MYSQL, |
72 | 75 | ] |
| 76 | + # Metrics are processed in 5 minute batches according to |
| 77 | + # https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-monitoring. |
| 78 | + METRICS_COLLECTION_DELAY_SECONDS = 300 |
73 | 79 |
|
74 | 80 | def __init__(self, relational_db_spec: Any): |
75 | 81 | super().__init__(relational_db_spec) |
@@ -277,3 +283,122 @@ def _ApplyDbFlags(self) -> None: |
277 | 283 | ) |
278 | 284 |
|
279 | 285 | self._Reboot() |
| 286 | + |
| 287 | + def _GetResourceProvider(self) -> str: |
| 288 | + if self.spec.engine == sql_engine_utils.FLEXIBLE_SERVER_MYSQL: |
| 289 | + return 'Microsoft.DBforMySQL' |
| 290 | + elif self.spec.engine == sql_engine_utils.FLEXIBLE_SERVER_POSTGRES: |
| 291 | + return 'Microsoft.DBforPostgreSQL' |
| 292 | + else: |
| 293 | + raise NotImplementedError(f'Unsupported engine {self.spec.engine}') |
| 294 | + |
| 295 | + def _GetResourceId(self) -> str: |
| 296 | + return ( |
| 297 | + f'/subscriptions/{util.GetSubscriptionId()}/resourceGroups/' |
| 298 | + f'{self.resource_group.name}/providers/' |
| 299 | + f'{self._GetResourceProvider()}/flexibleServers/{self.instance_id}' |
| 300 | + ) |
| 301 | + |
| 302 | + def _GetMetricsToCollect(self) -> list[relational_db.MetricSpec]: |
| 303 | + """Returns a list of metrics to collect.""" |
| 304 | + # pyformat: disable |
| 305 | + if self.spec.engine == sql_engine_utils.FLEXIBLE_SERVER_MYSQL: |
| 306 | + return [ |
| 307 | + relational_db.MetricSpec('cpu_percent', 'cpu_utilization', '%', None), |
| 308 | + relational_db.MetricSpec('io_consumption_percent', 'io_consumption_percent', '%', None), |
| 309 | + relational_db.MetricSpec('storage_io_count', 'storage_io_count', 'iops', None), |
| 310 | + relational_db.MetricSpec('storage_used', 'disk_bytes_used', 'GB', lambda x: x / (1024 * 1024 * 1024)), |
| 311 | + ] |
| 312 | + else: |
| 313 | + return [ |
| 314 | + relational_db.MetricSpec('cpu_percent', 'cpu_utilization', '%', None), |
| 315 | + relational_db.MetricSpec('read_iops', 'disk_read_iops', 'iops', None), |
| 316 | + relational_db.MetricSpec('write_iops', 'disk_write_iops', 'iops', None), |
| 317 | + relational_db.MetricSpec('read_throughput', 'disk_read_throughput', 'MB/s', lambda x: x / (1024 * 1024)), |
| 318 | + relational_db.MetricSpec('write_throughput', 'disk_write_throughput', 'MB/s', lambda x: x / (1024 * 1024)), |
| 319 | + relational_db.MetricSpec('storage_used', 'disk_bytes_used', 'GB', lambda x: x / (1024 * 1024 * 1024)), |
| 320 | + ] |
| 321 | + # pyformat: enable |
| 322 | + |
| 323 | + @vm_util.Retry(poll_interval=60, max_retries=5, retryable_exceptions=KeyError) |
| 324 | + def _CollectProviderMetric( |
| 325 | + self, |
| 326 | + metric: relational_db.MetricSpec, |
| 327 | + start_time: datetime.datetime, |
| 328 | + end_time: datetime.datetime, |
| 329 | + collect_percentiles: bool = False, |
| 330 | + ) -> list[sample.Sample]: |
| 331 | + """Collects metrics from Azure Monitor.""" |
| 332 | + if end_time - start_time < datetime.timedelta(minutes=1): |
| 333 | + logging.warning( |
| 334 | + 'Not collecting metrics since end time %s is within 1 minute of start' |
| 335 | + ' time %s.', |
| 336 | + end_time, |
| 337 | + start_time, |
| 338 | + ) |
| 339 | + return [] |
| 340 | + metric_name = metric.provider_name |
| 341 | + logging.info( |
| 342 | + 'Collecting metric %s for instance %s', metric_name, self.instance_id |
| 343 | + ) |
| 344 | + cmd = [ |
| 345 | + azure.AZURE_PATH, |
| 346 | + 'monitor', |
| 347 | + 'metrics', |
| 348 | + 'list', |
| 349 | + '--resource', |
| 350 | + self._GetResourceId(), |
| 351 | + '--metric', |
| 352 | + metric_name, |
| 353 | + '--start-time', |
| 354 | + start_time.astimezone(datetime.timezone.utc).strftime( |
| 355 | + relational_db.METRICS_TIME_FORMAT |
| 356 | + ), |
| 357 | + '--end-time', |
| 358 | + end_time.astimezone(datetime.timezone.utc).strftime( |
| 359 | + relational_db.METRICS_TIME_FORMAT |
| 360 | + ), |
| 361 | + '--interval', |
| 362 | + 'pt1m', |
| 363 | + '--aggregation', |
| 364 | + 'Average', |
| 365 | + ] |
| 366 | + try: |
| 367 | + stdout, _ = vm_util.IssueRetryableCommand(cmd) |
| 368 | + except errors.VmUtil.IssueCommandError as e: |
| 369 | + logging.warning( |
| 370 | + 'Could not collect metric %s for instance %s: %s', |
| 371 | + metric.provider_name, |
| 372 | + self.instance_id, |
| 373 | + e, |
| 374 | + ) |
| 375 | + return [] |
| 376 | + response = json.loads(stdout) |
| 377 | + if ( |
| 378 | + not response |
| 379 | + or not response['value'] |
| 380 | + or not response['value'][0]['timeseries'] |
| 381 | + ): |
| 382 | + logging.warning('No timeseries for metric %s', metric_name) |
| 383 | + return [] |
| 384 | + |
| 385 | + datapoints = response['value'][0]['timeseries'][0]['data'] |
| 386 | + if not datapoints: |
| 387 | + logging.warning('No datapoints for metric %s', metric_name) |
| 388 | + return [] |
| 389 | + |
| 390 | + points = [] |
| 391 | + for dp in datapoints: |
| 392 | + if dp['average'] is None: |
| 393 | + continue |
| 394 | + value = dp['average'] |
| 395 | + if metric.conversion_func: |
| 396 | + value = metric.conversion_func(value) |
| 397 | + points.append(( |
| 398 | + datetime.datetime.fromisoformat(dp['timeStamp']), |
| 399 | + value, |
| 400 | + )) |
| 401 | + |
| 402 | + return self._CreateSamples( |
| 403 | + points, metric.sample_name, metric.unit, collect_percentiles |
| 404 | + ) |
0 commit comments