PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/checks.d/wmi_check.py

https://gitlab.com/meetly/dd-agent
Python | 285 lines | 268 code | 8 blank | 9 comment | 0 complexity | a2eb8eaa8702a924431ce6a39a359e45 MD5 | raw file
  1. # stdlib
  2. from collections import namedtuple
  3. # project
  4. from checks import AgentCheck
  5. from checks.libs.wmi.sampler import WMISampler
  6. WMIMetric = namedtuple('WMIMetric', ['name', 'value', 'tags'])
  7. class InvalidWMIQuery(Exception):
  8. """
  9. Invalid WMI Query.
  10. """
  11. pass
  12. class MissingTagBy(Exception):
  13. """
  14. WMI query returned multiple rows but no `tag_by` value was given.
  15. """
  16. pass
  17. class TagQueryUniquenessFailure(Exception):
  18. """
  19. 'Tagging query' did not return or returned multiple results.
  20. """
  21. pass
  22. class WMICheck(AgentCheck):
  23. """
  24. WMI check.
  25. Windows only.
  26. """
  27. def __init__(self, name, init_config, agentConfig, instances):
  28. AgentCheck.__init__(self, name, init_config, agentConfig, instances)
  29. self.wmi_samplers = {}
  30. self.wmi_props = {}
  31. def check(self, instance):
  32. """
  33. Fetch WMI metrics.
  34. """
  35. # Connection information
  36. host = instance.get('host', "localhost")
  37. namespace = instance.get('namespace', "root\\cimv2")
  38. username = instance.get('username', "")
  39. password = instance.get('password', "")
  40. # WMI instance
  41. wmi_class = instance.get('class')
  42. metrics = instance.get('metrics')
  43. filters = instance.get('filters')
  44. tag_by = instance.get('tag_by', "").lower()
  45. tag_queries = instance.get('tag_queries', [])
  46. constant_tags = instance.get('constant_tags')
  47. # Create or retrieve an existing WMISampler
  48. instance_key = self._get_instance_key(host, namespace, wmi_class)
  49. metric_name_and_type_by_property, properties = \
  50. self._get_wmi_properties(instance_key, metrics, tag_queries)
  51. wmi_sampler = self._get_wmi_sampler(
  52. instance_key,
  53. wmi_class, properties,
  54. filters=filters,
  55. host=host, namespace=namespace,
  56. username=username, password=password
  57. )
  58. # Sample, extract & submit metrics
  59. wmi_sampler.sample()
  60. metrics = self._extract_metrics(wmi_sampler, tag_by, tag_queries, constant_tags)
  61. self._submit_metrics(metrics, metric_name_and_type_by_property)
  62. def _format_tag_query(self, sampler, wmi_obj, tag_query):
  63. """
  64. Format `tag_query` or raise on incorrect parameters.
  65. """
  66. try:
  67. link_source_property = int(wmi_obj[tag_query[0]])
  68. target_class = tag_query[1]
  69. link_target_class_property = tag_query[2]
  70. target_property = tag_query[3]
  71. except IndexError:
  72. self.log.error(
  73. u"Wrong `tag_queries` parameter format. "
  74. "Please refer to the configuration file for more information.")
  75. raise
  76. except TypeError:
  77. self.log.error(
  78. u"Incorrect 'link source property' in `tag_queries` parameter:"
  79. " `{wmi_property}` is not a property of `{wmi_class}`".format(
  80. wmi_property=tag_query[0],
  81. wmi_class=sampler.class_name,
  82. )
  83. )
  84. raise
  85. return target_class, target_property, [{link_target_class_property: link_source_property}]
  86. def _raise_on_invalid_tag_query_result(self, sampler, wmi_obj, tag_query):
  87. """
  88. """
  89. target_property = sampler.property_names[0]
  90. target_class = sampler.class_name
  91. if len(sampler) != 1:
  92. message = "no result was returned"
  93. if len(sampler):
  94. message = "multiple results returned (one expected)"
  95. self.log.warning(
  96. u"Failed to extract a tag from `tag_queries` parameter: {reason}."
  97. " wmi_object={wmi_obj} - query={tag_query}".format(
  98. reason=message,
  99. wmi_obj=wmi_obj, tag_query=tag_query,
  100. )
  101. )
  102. raise TagQueryUniquenessFailure
  103. if sampler[0][target_property] is None:
  104. self.log.error(
  105. u"Incorrect 'target property' in `tag_queries` parameter:"
  106. " `{wmi_property}` is not a property of `{wmi_class}`".format(
  107. wmi_property=target_property,
  108. wmi_class=target_class,
  109. )
  110. )
  111. raise TypeError
  112. def _get_tag_query_tag(self, sampler, wmi_obj, tag_query):
  113. """
  114. Design a query based on the given WMIObject to extract a tag.
  115. Returns: tag or TagQueryUniquenessFailure exception.
  116. """
  117. self.log.debug(
  118. u"`tag_queries` parameter found."
  119. " wmi_object={wmi_obj} - query={tag_query}".format(
  120. wmi_obj=wmi_obj, tag_query=tag_query,
  121. )
  122. )
  123. # Extract query information
  124. target_class, target_property, filters = \
  125. self._format_tag_query(sampler, wmi_obj, tag_query)
  126. # Create a specific sampler
  127. connection = sampler.get_connection()
  128. tag_query_sampler = WMISampler(
  129. self.log,
  130. target_class, [target_property],
  131. filters=filters,
  132. **connection
  133. )
  134. tag_query_sampler.sample()
  135. # Extract tag
  136. self._raise_on_invalid_tag_query_result(tag_query_sampler, wmi_obj, tag_query)
  137. link_value = str(tag_query_sampler[0][target_property]).lower()
  138. tag = "{tag_name}:{tag_value}".format(
  139. tag_name=target_property.lower(),
  140. tag_value="_".join(link_value.split())
  141. )
  142. self.log.debug(u"Extracted `tag_queries` tag: '{tag}'".format(tag=tag))
  143. return tag
  144. def _extract_metrics(self, wmi_sampler, tag_by, tag_queries, constant_tags):
  145. """
  146. Extract and tag metrics from the WMISampler.
  147. Raise when multiple WMIObject were returned by the sampler with no `tag_by` specified.
  148. Returns: List of WMIMetric
  149. ```
  150. [
  151. WMIMetric("freemegabytes", 19742, ["name:_total"]),
  152. WMIMetric("avgdiskbytesperwrite", 1536, ["name:c:"]),
  153. ]
  154. ```
  155. """
  156. if len(wmi_sampler) > 1 and not tag_by:
  157. raise MissingTagBy(
  158. u"WMI query returned multiple rows but no `tag_by` value was given."
  159. " class={wmi_class} - properties={wmi_properties} - filters={filters}".format(
  160. wmi_class=wmi_sampler.class_name, wmi_properties=wmi_sampler.property_names,
  161. filters=wmi_sampler.filters,
  162. )
  163. )
  164. metrics = []
  165. for wmi_obj in wmi_sampler:
  166. tags = list(constant_tags) if constant_tags else []
  167. # Tag with `tag_queries` parameter
  168. for query in tag_queries:
  169. try:
  170. tags.append(self._get_tag_query_tag(wmi_sampler, wmi_obj, query))
  171. except TagQueryUniquenessFailure:
  172. continue
  173. for wmi_property, wmi_value in wmi_obj.iteritems():
  174. # Tag with `tag_by` parameter
  175. if wmi_property == tag_by:
  176. tag_value = str(wmi_value).lower()
  177. if tag_queries and tag_value.find("#") > 0:
  178. tag_value = tag_value[:tag_value.find("#")]
  179. tags.append(
  180. "{name}:{value}".format(
  181. name=tag_by.lower(), value=tag_value
  182. )
  183. )
  184. continue
  185. try:
  186. metrics.append(WMIMetric(wmi_property, float(wmi_value), tags))
  187. except ValueError:
  188. self.log.warning(u"When extracting metrics with WMI, found a non digit value"
  189. " for property '{0}'.".format(wmi_property))
  190. continue
  191. except TypeError:
  192. self.log.warning(u"When extracting metrics with WMI, found a missing property"
  193. " '{0}'".format(wmi_property))
  194. continue
  195. return metrics
  196. def _submit_metrics(self, metrics, metric_name_and_type_by_property):
  197. """
  198. Resolve metric names and types and submit it.
  199. """
  200. for metric in metrics:
  201. if metric.name not in metric_name_and_type_by_property:
  202. # Only report the metrics that were specified in the configration
  203. # Ignore added properties like 'Timestamp_Sys100NS', `Frequency_Sys100NS`, etc ...
  204. continue
  205. metric_name, metric_type = metric_name_and_type_by_property[metric.name]
  206. try:
  207. func = getattr(self, metric_type)
  208. except AttributeError:
  209. raise Exception(u"Invalid metric type: {0}".format(metric_type))
  210. func(metric_name, metric.value, metric.tags)
  211. def _get_instance_key(self, host, namespace, wmi_class):
  212. """
  213. Return an index key for a given instance. Usefull for caching.
  214. """
  215. return "{host}:{namespace}:{wmi_class}".format(
  216. host=host, namespace=namespace, wmi_class=wmi_class,
  217. )
  218. def _get_wmi_sampler(self, instance_key, wmi_class, properties, **kwargs):
  219. """
  220. Create and cache a WMISampler for the given (class, properties)
  221. """
  222. if instance_key not in self.wmi_samplers:
  223. wmi_sampler = WMISampler(self.log, wmi_class, properties, **kwargs)
  224. self.wmi_samplers[instance_key] = wmi_sampler
  225. return self.wmi_samplers[instance_key]
  226. def _get_wmi_properties(self, instance_key, metrics, tag_queries):
  227. """
  228. Create and cache a (metric name, metric type) by WMI property map and a property list.
  229. """
  230. if instance_key not in self.wmi_props:
  231. metric_name_by_property = dict(
  232. (wmi_property.lower(), (metric_name, metric_type))
  233. for wmi_property, metric_name, metric_type in metrics
  234. )
  235. properties = map(lambda x: x[0], metrics + tag_queries)
  236. self.wmi_props[instance_key] = (metric_name_by_property, properties)
  237. return self.wmi_props[instance_key]