Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/actions/metric.py: 42%
43 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3from datetime import datetime
5from .core import BaseAction
6from c7n.manager import resources
7from c7n import utils
10def average(numbers):
11 return float(sum(numbers)) / max(len(numbers), 1)
14def distinct_count(values):
15 return float(len(set(values)))
18METRIC_OPS = {
19 'count': len,
20 'distinct_count': distinct_count,
21 'sum': sum,
22 'average': average,
23}
25METRIC_UNITS = [
26 # Time
27 'Seconds',
28 'Microseconds',
29 'Milliseconds',
30 # Bytes and Bits
31 'Bytes',
32 'Kilobytes',
33 'Megabytes',
34 'Gigabytes',
35 'Terabytes',
36 'Bits',
37 'Kilobits',
38 'Megabits',
39 'Gigabits',
40 'Terabits',
41 # Rates
42 'Bytes/Second',
43 'Kilobytes/Second',
44 'Megabytes/Second',
45 'Gigabytes/Second',
46 'Terabytes/Second',
47 'Bits/Second',
48 'Kilobits/Second',
49 'Megabits/Second',
50 'Gigabits/Second',
51 'Terabits/Second',
52 'Count/Second',
53 # Other Scalars
54 'Percent',
55 'Count',
56 'None'
57]
60class PutMetric(BaseAction):
61 """Action to put metrics based on an expression into CloudWatch metrics
63 :example:
65 .. code-block:: yaml
67 policies:
68 - name: track-attached-ebs
69 resource: ec2
70 comment: |
71 Put the count of the number of EBS attached disks to an instance
72 filters:
73 - Name: tracked-ec2-instance
74 actions:
75 - type: put-metric
76 key: Reservations[].Instances[].BlockDeviceMappings[].DeviceName
77 namespace: Usage Metrics
78 metric_name: Attached Disks
79 op: count
80 units: Count
82 op and units are optional and will default to simple Counts.
83 """
84 # permissions are typically lowercase servicename:TitleCaseActionName
85 permissions = {'cloudwatch:PutMetricData', }
86 schema_alias = True
87 schema = {
88 'type': 'object',
89 'required': ['type', 'key', 'namespace', 'metric_name'],
90 'additionalProperties': False,
91 'properties': {
92 'type': {'enum': ['put-metric', ]},
93 'key': {'type': 'string'}, # jmes path
94 'namespace': {'type': 'string'},
95 'metric_name': {'type': 'string'},
96 'dimensions': {
97 'type': 'array',
98 'items': {'type': 'object'},
99 },
100 'op': {'enum': list(METRIC_OPS.keys())},
101 'units': {'enum': METRIC_UNITS}
102 }
103 }
105 def process(self, resources):
106 ns = self.data['namespace']
107 metric_name = self.data['metric_name']
108 key_expression = self.data.get('key', 'Resources[]')
109 operation = self.data.get('op', 'count')
110 units = self.data.get('units', 'Count')
111 # dimensions are passed as a list of dicts
112 dimensions = self.data.get('dimensions', [])
114 now = datetime.utcnow()
116 # reduce the resources by the key expression, and apply the operation to derive the value
117 values = []
118 self.log.debug("searching for %s in %s", key_expression, resources)
119 try:
120 values = utils.jmespath_search("Resources[]." + key_expression,
121 {'Resources': resources})
122 # I had to wrap resourses in a dict like this in order to not have jmespath expressions
123 # start with [] in the yaml files. It fails to parse otherwise.
124 except TypeError as oops:
125 self.log.error(oops.message)
127 value = 0
128 try:
129 f = METRIC_OPS[operation]
130 value = f(values)
131 except KeyError:
132 self.log.error("Bad op for put-metric action: %s", operation)
134 # for demo purposes
135 # from math import sin, pi
136 # value = sin((now.minute * 6 * 4 * pi) / 180) * ((now.hour + 1) * 4.0)
138 metrics_data = [
139 {
140 'MetricName': metric_name,
141 'Dimensions': [{'Name': i[0], 'Value': i[1]}
142 for d in dimensions
143 for i in d.items()],
144 'Timestamp': now,
145 'Value': value,
146 # TODO: support an operation of 'stats' to include this
147 # structure instead of a single Value
148 # Value and StatisticValues are mutually exclusive.
149 # 'StatisticValues': {
150 # 'SampleCount': 1,
151 # 'Sum': 123.0,
152 # 'Minimum': 123.0,
153 # 'Maximum': 123.0
154 # },
155 'Unit': units,
156 },
157 ]
159 client = utils.local_session(
160 self.manager.session_factory).client('cloudwatch')
161 client.put_metric_data(Namespace=ns, MetricData=metrics_data)
163 return resources
165 @classmethod
166 def register_resources(cls, registry, resource_class):
167 if 'put-metric' not in resource_class.action_registry:
168 resource_class.action_registry.register('put-metric', PutMetric)
171resources.subscribe(PutMetric.register_resources)