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

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3from datetime import datetime 

4 

5from .core import BaseAction 

6from c7n.manager import resources 

7from c7n import utils 

8 

9 

10def average(numbers): 

11 return float(sum(numbers)) / max(len(numbers), 1) 

12 

13 

14def distinct_count(values): 

15 return float(len(set(values))) 

16 

17 

18METRIC_OPS = { 

19 'count': len, 

20 'distinct_count': distinct_count, 

21 'sum': sum, 

22 'average': average, 

23} 

24 

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] 

58 

59 

60class PutMetric(BaseAction): 

61 """Action to put metrics based on an expression into CloudWatch metrics 

62 

63 :example: 

64 

65 .. code-block:: yaml 

66 

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 

81 

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 } 

104 

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', []) 

113 

114 now = datetime.utcnow() 

115 

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) 

126 

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) 

133 

134 # for demo purposes 

135 # from math import sin, pi 

136 # value = sin((now.minute * 6 * 4 * pi) / 180) * ((now.hour + 1) * 4.0) 

137 

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 ] 

158 

159 client = utils.local_session( 

160 self.manager.session_factory).client('cloudwatch') 

161 client.put_metric_data(Namespace=ns, MetricData=metrics_data) 

162 

163 return resources 

164 

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) 

169 

170 

171resources.subscribe(PutMetric.register_resources)