dashboard.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. from constructs import Construct
  7. from aws_cdk import (
  8. CfnOutput,
  9. Duration,
  10. aws_cloudwatch as cloudwatch
  11. )
  12. from . import aws_metrics_constants
  13. from .layout_widget_construct import LayoutWidget
  14. from .aws_utils import resource_name_sanitizer
  15. class Dashboard:
  16. """
  17. Create the real time analytics CloudWatch dashboard for the AWSMetrics Gem.
  18. """
  19. def __init__(
  20. self,
  21. stack: Construct,
  22. input_stream_name: str,
  23. analytics_processing_lambda_name: str,
  24. application_name: str,
  25. delivery_stream_name: str = '',
  26. events_processing_lambda_name: str = '',
  27. ) -> None:
  28. self._dashboard_name = resource_name_sanitizer.sanitize_resource_name(
  29. f'{stack.stack_name}-Dashboard', 'cloudwatch_dashboard')
  30. self._dashboard = cloudwatch.Dashboard(
  31. stack,
  32. id="DashBoard",
  33. dashboard_name=self._dashboard_name,
  34. start=aws_metrics_constants.DASHBOARD_TIME_RANGE_START
  35. )
  36. self._dashboard.add_widgets(
  37. LayoutWidget(
  38. layout_description=aws_metrics_constants.DASHBOARD_GLOBAL_DESCRIPTION,
  39. widgets=[
  40. self._create_operational_health_layout(
  41. input_stream_name,
  42. delivery_stream_name,
  43. analytics_processing_lambda_name,
  44. events_processing_lambda_name),
  45. self._create_real_time_analytics_layout()
  46. ],
  47. max_width=aws_metrics_constants.DASHBOARD_MAX_WIDGET_WIDTH)
  48. )
  49. CfnOutput(
  50. stack,
  51. id='DashboardName',
  52. description='CloudWatch dashboard to monitor the operational health and real-time metrics',
  53. export_name=f"{application_name}:Dashboard",
  54. value=self._dashboard_name)
  55. def _create_operational_health_layout(
  56. self,
  57. input_stream_name: str,
  58. delivery_stream_name: str,
  59. analytics_processing_lambda_name: str,
  60. events_processing_lambda_name: str) -> LayoutWidget:
  61. """
  62. This layout contains operational health metrics during events ingestion and analytics processing.
  63. @param input_stream_name Name of the input Kinesis data stream.
  64. @param delivery_stream_name Name of the Kinesis Firehose delivery stream.
  65. @param analytics_processing_lambda_name Name of the analytics processing Lambda function.
  66. @param events_processing_lambda_name Name of the events processing Lambda function.
  67. @return Operational health layout widget.
  68. """
  69. operational_health_graph_widgets = list()
  70. event_ingestion_left_widgets = [
  71. cloudwatch.Metric(
  72. metric_name="IncomingRecords",
  73. label="Kinesis Incoming Records",
  74. namespace="AWS/Kinesis",
  75. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  76. statistic="Sum",
  77. dimensions_map={
  78. "StreamName": input_stream_name
  79. }
  80. )
  81. ]
  82. if delivery_stream_name:
  83. event_ingestion_left_widgets.append(
  84. cloudwatch.Metric(
  85. metric_name="DeliveryToS3.Records",
  86. label="Firehose Delivery To S3 Records",
  87. namespace="AWS/Firehose",
  88. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  89. statistic="Sum",
  90. dimensions_map={
  91. "DeliveryStreamName": delivery_stream_name
  92. }
  93. )
  94. )
  95. operational_health_graph_widgets.append(
  96. cloudwatch.GraphWidget(
  97. title="Events Ingestion",
  98. left=event_ingestion_left_widgets,
  99. live_data=True
  100. )
  101. )
  102. analytics_processing_lambda_errors_metrics, analytics_processing_lambda_error_rate_metrics = \
  103. self._get_lambda_operational_health_metrics(
  104. analytics_processing_lambda_name,
  105. "Analytics Processing Lambda"
  106. )
  107. lambda_processing_left_widgets = [analytics_processing_lambda_errors_metrics]
  108. lambda_processing_right_widgets = [analytics_processing_lambda_error_rate_metrics]
  109. if events_processing_lambda_name:
  110. events_processing_lambda_errors_metrics, events_processing_lambda_error_rate_metrics = \
  111. self._get_lambda_operational_health_metrics(
  112. events_processing_lambda_name,
  113. "Events Processing Lambda"
  114. )
  115. lambda_processing_left_widgets.append(events_processing_lambda_errors_metrics)
  116. lambda_processing_right_widgets.append(events_processing_lambda_error_rate_metrics)
  117. operational_health_graph_widgets.append(
  118. cloudwatch.GraphWidget(
  119. title="Lambda Processing",
  120. left=lambda_processing_left_widgets,
  121. right=lambda_processing_right_widgets,
  122. right_y_axis=cloudwatch.YAxisProps(
  123. show_units=False,
  124. min=0,
  125. max=100
  126. ),
  127. live_data=True,
  128. view=cloudwatch.GraphWidgetView.TIME_SERIES
  129. )
  130. )
  131. operational_health_layout = LayoutWidget(
  132. layout_description=aws_metrics_constants.DASHBOARD_OPERATIONAL_HEALTH_DESCRIPTION,
  133. widgets=operational_health_graph_widgets,
  134. max_width=aws_metrics_constants.DASHBOARD_MAX_WIDGET_WIDTH // 2)
  135. return operational_health_layout
  136. def _get_lambda_operational_health_metrics(self, function_name: str, metrics_label_prefix: str):
  137. """
  138. Get the errors and error rate metrics for the provided Lambda function.
  139. @param function_name Name of the Lambda function.
  140. @param metrics_label_prefix Prefix for the metrics Label. Metrics Label needs to be unique in a graph.
  141. @return Error and error rate metrics of the Lambda function.
  142. """
  143. lambda_errors_metrics = cloudwatch.Metric(
  144. metric_name='Errors',
  145. label=f'{metrics_label_prefix} Errors',
  146. namespace='AWS/Lambda',
  147. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  148. statistic='Sum',
  149. dimensions_map={
  150. 'FunctionName': function_name
  151. }
  152. )
  153. error_metrics_id = f'{metrics_label_prefix.replace(" ", "_")}_error'.lower()
  154. invocations_metrics_id = f'{metrics_label_prefix.replace(" ", "_")}_invocations'.lower()
  155. # Divide the Errors metric by the Invocations metric to get an error rate.
  156. lambda_error_rate_metrics = cloudwatch.MathExpression(
  157. expression=f'100 - 100 * {error_metrics_id} / MAX([{error_metrics_id}, {invocations_metrics_id}])',
  158. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  159. label=f'{metrics_label_prefix} Success Rate (%)',
  160. using_metrics={
  161. error_metrics_id: lambda_errors_metrics,
  162. invocations_metrics_id: cloudwatch.Metric(
  163. metric_name='Invocations',
  164. namespace='AWS/Lambda',
  165. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  166. statistic='Sum',
  167. dimensions_map={
  168. 'FunctionName': function_name
  169. }
  170. ),
  171. }
  172. )
  173. return lambda_errors_metrics, lambda_error_rate_metrics
  174. def _create_real_time_analytics_layout(self) -> LayoutWidget:
  175. """
  176. This layout contains real-time analytics metrics including login.
  177. """
  178. real_time_analytics_layout = LayoutWidget(
  179. layout_description=aws_metrics_constants.DASHBOARD_REAL_TIME_ANALYTICS_DESCRIPTION,
  180. widgets=[
  181. cloudwatch.GraphWidget(
  182. title="Logins",
  183. left=[
  184. cloudwatch.Metric(
  185. metric_name="TotalLogins",
  186. label="Logins",
  187. namespace="AWSMetrics",
  188. period=Duration.minutes(aws_metrics_constants.DASHBOARD_METRICS_TIME_PERIOD),
  189. statistic="Sum"
  190. )
  191. ],
  192. live_data=True
  193. )
  194. ],
  195. max_width=aws_metrics_constants.DASHBOARD_MAX_WIDGET_WIDTH // 2)
  196. return real_time_analytics_layout
  197. @property
  198. def dashboard_name(self) -> str:
  199. return self._dashboard_name