2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 # Frequently used variable names:
28 # perf_data_string: Raw performance data as sent by the core, e.g "foo=17M;1;2;4;5"
29 # perf_data: Split performance data, e.g. [("foo", "17", "M", "1", "2", "4", "5")]
30 # translated_metrics: Completely parsed and translated into metrics, e.g. { "foo" : { "value" : 17.0, "unit" : { "render" : ... }, ... } }
31 # color: RGB color representation ala HTML, e.g. "#ffbbc3" or "#FFBBC3", len() is always 7!
32 # color_rgb: RGB color split into triple (r, g, b), where r,b,g in (0.0 .. 1.0)
33 # unit_name: The ID of a unit, e.g. "%"
34 # unit: The definition-dict of a unit like in unit_info
35 # graph_template: Template for a graph. Essentially a dict with the key "metrics"
46 import cmk
.utils
.render
47 import cmk
.utils
.plugin_registry
48 from cmk
.utils
.regex
import regex
50 import cmk
.gui
.utils
as utils
51 import cmk
.gui
.config
as config
52 import cmk
.gui
.sites
as sites
55 from cmk
.gui
.i18n
import _
56 from cmk
.gui
.globals import html
58 from cmk
.gui
.log
import logger
59 from cmk
.gui
.exceptions
import MKGeneralException
, MKUserError
, MKInternalError
61 # Needed for legacy (pre 1.6) plugins
62 from cmk
.gui
.plugins
.metrics
.utils
import ( # pylint: disable=unused-import
63 unit_info
, metric_info
, check_metrics
, perfometer_info
, graph_info
, scalar_colors
, KB
, MB
, GB
,
64 TB
, PB
, m
, K
, M
, G
, T
, P
, evaluate
, get_graph_range
, replace_expressions
,
65 generic_graph_template
, scale_symbols
, hsv_to_hexrgb
, render_color
, parse_color
,
66 parse_color_into_hexrgb
, render_color_icon
, darken_color
, get_palette_color_by_index
,
67 parse_perf_data
, perfvar_translation
, translate_metrics
, get_graph_templates
,
70 # .--Plugins-------------------------------------------------------------.
72 # | | _ \| |_ _ __ _(_)_ __ ___ |
73 # | | |_) | | | | |/ _` | | '_ \/ __| |
74 # | | __/| | |_| | (_| | | | | \__ \ |
75 # | |_| |_|\__,_|\__, |_|_| |_|___/ |
77 # +----------------------------------------------------------------------+
78 # | Typical code for loading Multisite plugins of this module |
79 # '----------------------------------------------------------------------'
80 # Datastructures and functions needed before plugins can be loaded
81 loaded_with_language
= False
84 def load_plugins(force
):
85 global loaded_with_language
86 if loaded_with_language
== cmk
.gui
.i18n
.get_current_language() and not force
:
89 utils
.load_web_plugins("metrics", globals())
93 fixup_perfometer_info()
94 loaded_with_language
= cmk
.gui
.i18n
.get_current_language()
97 def fixup_graph_info():
98 # create back link from each graph to its id.
99 for graph_id
, graph
in graph_info
.items():
100 graph
["id"] = graph_id
103 def fixup_unit_info():
104 # create back link from each unit to its id.
105 for unit_id
, unit
in unit_info
.items():
107 unit
.setdefault("description", unit
["title"])
110 def fixup_perfometer_info():
111 _convert_legacy_tuple_perfometers(perfometer_info
)
112 _precalculate_some_perfometer_caches(perfometer_info
)
115 # During implementation of the metric system the perfometers were first defined using
116 # tuples. This has been replaced with a dict based syntax. This function converts the
117 # old known formats from tuple to dict.
118 # All shipped perfometers have been converted to the dict format with 1.5.0i3.
119 # TODO: Remove this one day.
120 def _convert_legacy_tuple_perfometers(perfometers
):
121 for index
, perfometer
in reversed(list(enumerate(perfometers
))):
122 if isinstance(perfometer
, dict):
125 if not isinstance(perfometer
, tuple) or len(perfometer
) != 2:
126 raise MKGeneralException(_("Invalid perfometer declaration: %r") % perfometer
)
128 # Convert legacy tuple based perfometer
129 perfometer_type
, perfometer_args
= perfometer
[0], perfometer
[1]
130 if perfometer_type
in ("dual", "stacked"):
132 sub_performeters
= perfometer_args
[:]
133 _convert_legacy_tuple_perfometers(sub_performeters
)
135 perfometers
[index
] = {
136 "type": perfometer_type
,
137 "perfometers": sub_performeters
,
140 elif perfometer_type
== "linear" and len(perfometer_args
) == 3:
141 required
, total
, label
= perfometer_args
143 perfometers
[index
] = {
144 "type": perfometer_type
,
145 "segments": required
,
152 _("Could not convert perfometer to dict format: %r. Ignoring this one.") %
154 perfometers
.pop(index
)
157 def _precalculate_some_perfometer_caches(perfometers
):
158 for perfometer
in perfometers
:
159 # Precalculate the list of metric expressions of the perfometers
160 required_expressions
= _perfometer_expressions(perfometer
)
162 # And also precalculate the trivial metric names that can later be used to filter
163 # perfometers without the need to evaluate the expressions.
164 required_trivial_metric_names
= _required_trivial_metric_names(required_expressions
)
166 perfometer
["_required"] = required_expressions
167 perfometer
["_required_names"] = required_trivial_metric_names
170 def _perfometer_expressions(perfometer
):
171 """Returns all metric expressions of a perfometer
172 This is used for checking which perfometer can be displayed for a given service later.
176 if perfometer
["type"] == "linear":
177 required
+= perfometer
["segments"][:]
179 elif perfometer
["type"] == "logarithmic":
180 required
.append(perfometer
["metric"])
182 elif perfometer
["type"] in ("stacked", "dual"):
183 if "perfometers" not in perfometer
:
184 raise MKGeneralException(
185 _("Perfometers of type 'stacked' and 'dual' need "
186 "the element 'perfometers' (%r)") % perfometer
)
188 for sub_perfometer
in perfometer
["perfometers"]:
189 required
+= _perfometer_expressions(sub_perfometer
)
192 raise NotImplementedError(_("Invalid perfometer type: %s") % perfometer
["type"])
194 if "label" in perfometer
and perfometer
["label"] is not None:
195 required
.append(perfometer
["label"][0])
196 if "total" in perfometer
:
197 required
.append(perfometer
["total"])
202 def _required_trivial_metric_names(required_expressions
):
203 """Extract the trivial metric names from a list of expressions.
204 Ignores numeric parts. Returns None in case there is a non trivial
205 metric found. This means the trivial filtering can not be used.
207 required_metric_names
= set()
209 allowed_chars
= string
.ascii_letters
+ string
.digits
+ "_"
211 for entry
in required_expressions
:
212 if isinstance(entry
, six
.string_types
):
213 if any(char
not in allowed_chars
for char
in entry
):
214 # Found a non trivial metric expression. Totally skip this mechanism
217 required_metric_names
.add(entry
)
219 return required_metric_names
223 # .--Helpers-------------------------------------------------------------.
225 # | | | | | ___| |_ __ ___ _ __ ___ |
226 # | | |_| |/ _ \ | '_ \ / _ \ '__/ __| |
227 # | | _ | __/ | |_) | __/ | \__ \ |
228 # | |_| |_|\___|_| .__/ \___|_| |___/ |
230 # +----------------------------------------------------------------------+
231 # | Various helper functions |
232 # '----------------------------------------------------------------------'
233 # A few helper function to be used by the definitions
236 def metric_to_text(metric
, value
=None):
238 value
= metric
["value"]
239 return metric
["unit"]["render"](value
)
242 # aliases to be compatible to old plugins
243 physical_precision
= cmk
.utils
.render
.physical_precision
244 age_human_readable
= cmk
.utils
.render
.approx_age
247 # .--Evaluation----------------------------------------------------------.
249 # | | ____|_ ____ _| |_ _ __ _| |_(_) ___ _ __ |
250 # | | _| \ \ / / _` | | | | |/ _` | __| |/ _ \| '_ \ |
251 # | | |___ \ V / (_| | | |_| | (_| | |_| | (_) | | | | |
252 # | |_____| \_/ \__,_|_|\__,_|\__,_|\__|_|\___/|_| |_| |
254 # +----------------------------------------------------------------------+
255 # | Parsing of performance data into metrics, evaluation of expressions |
256 # '----------------------------------------------------------------------'
259 def translate_perf_data(perf_data_string
, check_command
=None):
260 perf_data
, check_command
= parse_perf_data(perf_data_string
, check_command
)
261 return translate_metrics(perf_data
, check_command
)
265 # .--Perf-O-Meters-------------------------------------------------------.
266 # | ____ __ ___ __ __ _ |
267 # | | _ \ ___ _ __ / _| / _ \ | \/ | ___| |_ ___ _ __ ___ |
268 # | | |_) / _ \ '__| |_ _____| | | |_____| |\/| |/ _ \ __/ _ \ '__/ __| |
269 # | | __/ __/ | | _|_____| |_| |_____| | | | __/ || __/ | \__ \ |
270 # | |_| \___|_| |_| \___/ |_| |_|\___|\__\___|_| |___/ |
272 # +----------------------------------------------------------------------+
273 # | Implementation of Perf-O-Meters |
274 # '----------------------------------------------------------------------'
277 class Perfometers(object):
278 def get_matching_perfometers(self
, translated_metrics
):
280 for perfometer
in perfometer_info
:
281 if self
._perfometer
_possible
(perfometer
, translated_metrics
):
282 perfometers
.append(perfometer
)
285 def _perfometer_possible(self
, perfometer
, translated_metrics
):
286 if self
._skip
_perfometer
_by
_trivial
_metrics
(perfometer
["_required_names"],
290 for req
in perfometer
["_required"]:
292 evaluate(req
, translated_metrics
)
296 if "condition" in perfometer
:
298 value
, _color
, _unit
= evaluate(perfometer
["condition"], translated_metrics
)
306 def _skip_perfometer_by_trivial_metrics(self
, required_metric_names
, translated_metrics
):
307 """Whether or not a perfometer can be skipped by simple metric name matching instead of expression evaluation
309 Performance optimization: Try to reduce the amount of perfometers to evaluate by
310 comparing the strings in the "required" metrics with the translated metrics.
311 We only look at the simple "requried expressions" that don't make use of formulas.
312 In case there is a formula, we can not skip the perfometer and have to evaluate
315 if required_metric_names
is None:
318 available_metric_names
= set(translated_metrics
.keys())
319 return not required_metric_names
.issubset(available_metric_names
)
322 class MetricometerRenderer(object):
323 __metaclass__
= abc
.ABCMeta
324 """Abstract base class for all metricometer renderers"""
328 raise NotImplementedError()
330 def __init__(self
, perfometer
, translated_metrics
):
331 super(MetricometerRenderer
, self
).__init
__()
332 self
._perfometer
= perfometer
333 self
._translated
_metrics
= translated_metrics
337 """Return a list of perfometer elements
339 Each element is represented by a 2 element tuple where the first element is
340 the width in px and the second element the hex color code of this element.
342 raise NotImplementedError()
345 """Returns the label to be shown on top of the rendered stack
347 When the perfometer type definition has a "label" element, this will be used.
348 Otherwise the perfometer type specific label of _get_type_label() will be used.
351 # "label" option in all Perf-O-Meters overrides automatic label
352 if "label" in self
._perfometer
:
353 if self
._perfometer
["label"] is None:
356 expr
, unit_name
= self
._perfometer
["label"]
357 value
, unit
, _color
= evaluate(expr
, self
._translated
_metrics
)
359 unit
= unit_info
[unit_name
]
360 return unit
["render"](value
)
362 return self
._get
_type
_label
()
365 def _get_type_label(self
):
366 """Returns the label for this perfometer type"""
367 raise NotImplementedError()
370 def get_sort_number(self
):
371 """Returns the number to sort this perfometer with compared to the other
372 performeters in the current performeter sort group"""
373 raise NotImplementedError()
376 class MetricometerRendererRegistry(cmk
.utils
.plugin_registry
.ClassRegistry
):
377 def plugin_base_class(self
):
378 return MetricometerRenderer
380 def plugin_name(self
, plugin_class
):
381 return plugin_class
.type_name()
383 def get_renderer(self
, perfometer
, translated_metrics
):
384 subclass
= self
[perfometer
["type"]]
385 return subclass(perfometer
, translated_metrics
)
388 renderer_registry
= MetricometerRendererRegistry()
391 @renderer_registry.register
392 class MetricometerRendererLogarithmic(MetricometerRenderer
):
397 def __init__(self
, perfometer
, translated_metrics
):
398 super(MetricometerRendererLogarithmic
, self
).__init
__(perfometer
, translated_metrics
)
400 if self
._perfometer
is not None and "metric" not in self
._perfometer
:
401 raise MKGeneralException(
402 _("Missing key \"metric\" in logarithmic perfometer: %r") % self
._perfometer
)
405 value
, _unit
, color
= evaluate(self
._perfometer
["metric"], self
._translated
_metrics
)
407 self
.get_stack_from_values(value
, self
._perfometer
["half_value"],
408 self
._perfometer
["exponent"], color
)
411 def _get_type_label(self
):
412 value
, unit
, _color
= evaluate(self
._perfometer
["metric"], self
._translated
_metrics
)
413 return unit
["render"](value
)
415 def get_sort_number(self
):
416 """Returns the number to sort this perfometer with compared to the other
417 performeters in the current performeter sort group"""
418 value
, _unit
, _color
= evaluate(self
._perfometer
["metric"], self
._translated
_metrics
)
421 def get_stack_from_values(self
, value
, half_value
, base
, color
):
422 # Negative values are printed like positive ones (e.g. time offset)
423 value
= abs(float(value
))
427 half_value
= float(half_value
)
428 h
= math
.log(half_value
, base
) # value to be displayed at 50%
429 pos
= 50 + 10.0 * (math
.log(value
, base
) - h
)
435 return [(pos
, color
), (100 - pos
, "#ffffff")]
438 @renderer_registry.register
439 class MetricometerRendererLinear(MetricometerRenderer
):
447 summed
= self
._get
_summed
_values
()
449 if "total" in self
._perfometer
:
450 total
, _unit
, _color
= evaluate(self
._perfometer
["total"], self
._translated
_metrics
)
455 entry
.append((100.0, "#ffffff"))
458 for ex
in self
._perfometer
["segments"]:
459 value
, _unit
, color
= evaluate(ex
, self
._translated
_metrics
)
460 entry
.append((100.0 * value
/ total
, color
))
462 # Paint rest only, if it is positive and larger than one promille
463 if total
- summed
> 0.001:
464 entry
.append((100.0 * (total
- summed
) / total
, "#ffffff"))
468 def _get_type_label(self
):
469 # Use unit of first metrics for output of sum. We assume that all
470 # stackes metrics have the same unit anyway
471 _value
, unit
, _color
= evaluate(self
._perfometer
["segments"][0], self
._translated
_metrics
)
472 return unit
["render"](self
._get
_summed
_values
())
474 def get_sort_number(self
):
475 """Use the first segment value for sorting"""
476 value
, _unit
, _color
= evaluate(self
._perfometer
["segments"][0], self
._translated
_metrics
)
479 def _get_summed_values(self
):
481 for ex
in self
._perfometer
["segments"]:
482 value
, _unit
, _color
= evaluate(ex
, self
._translated
_metrics
)
487 @renderer_registry.register
488 class MetricometerRendererStacked(MetricometerRenderer
):
495 for sub_perfometer
in self
._perfometer
["perfometers"]:
496 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
498 sub_stack
= renderer
.get_stack()
499 stack
.append(sub_stack
[0])
503 def _get_type_label(self
):
505 for sub_perfometer
in self
._perfometer
["perfometers"]:
506 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
508 sub_label
= renderer
.get_label()
510 sub_labels
.append(sub_label
)
515 return " / ".join(sub_labels
)
517 def get_sort_number(self
):
518 """Use the number of the first stack element."""
519 sub_perfometer
= self
._perfometer
["perfometers"][0]
520 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
521 return renderer
.get_sort_number()
524 @renderer_registry.register
525 class MetricometerRendererDual(MetricometerRenderer
):
530 def __init__(self
, perfometer
, translated_metrics
):
531 super(MetricometerRendererDual
, self
).__init
__(perfometer
, translated_metrics
)
533 if len(perfometer
["perfometers"]) != 2:
534 raise MKInternalError(
535 _("Perf-O-Meter of type 'dual' must contain exactly "
536 "two definitions, not %d") % len(perfometer
["perfometers"]))
540 for nr
, sub_perfometer
in enumerate(self
._perfometer
["perfometers"]):
541 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
543 sub_stack
= renderer
.get_stack()
544 if len(sub_stack
) != 1:
545 raise MKInternalError(
546 _("Perf-O-Meter of type 'dual' must only contain plain Perf-O-Meters"))
548 half_stack
= [(value
/ 2, color
) for (value
, color
) in sub_stack
[0]]
551 content
+= half_stack
555 def _get_type_label(self
):
557 for sub_perfometer
in self
._perfometer
["perfometers"]:
558 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
560 sub_label
= renderer
.get_label()
562 sub_labels
.append(sub_label
)
567 return " / ".join(sub_labels
)
569 def get_sort_number(self
):
570 """Sort by max(left, right)
572 E.g. for traffic graphs it seems to be useful to
573 make it sort by the maximum traffic independent of the direction.
575 sub_sort_numbers
= []
576 for sub_perfometer
in self
._perfometer
["perfometers"]:
577 renderer
= renderer_registry
.get_renderer(sub_perfometer
, self
._translated
_metrics
)
578 sub_sort_numbers
.append(renderer
.get_sort_number())
580 return max(*sub_sort_numbers
)
584 # .--Graphs--------------------------------------------------------------.
586 # | / ___|_ __ __ _ _ __ | |__ ___ |
587 # | | | _| '__/ _` | '_ \| '_ \/ __| |
588 # | | |_| | | | (_| | |_) | | | \__ \ |
589 # | \____|_| \__,_| .__/|_| |_|___/ |
591 # +----------------------------------------------------------------------+
592 # | Implementation of time graphs - basic code, not the rendering |
593 # | Rendering of the graphs is done by PNP4Nagios, we just create PHP |
594 # | templates for PNP here. |
595 # '----------------------------------------------------------------------'
598 # .--PNP Templates-------------------------------------------------------.
599 # | ____ _ _ ____ _____ _ _ |
600 # | | _ \| \ | | _ \ |_ _|__ _ __ ___ _ __ | | __ _| |_ ___ ___ |
601 # | | |_) | \| | |_) | | |/ _ \ '_ ` _ \| '_ \| |/ _` | __/ _ \/ __| |
602 # | | __/| |\ | __/ | | __/ | | | | | |_) | | (_| | || __/\__ \ |
603 # | |_| |_| \_|_| |_|\___|_| |_| |_| .__/|_|\__,_|\__\___||___/ |
605 # +----------------------------------------------------------------------+
606 # | Core for creating templates for PNP4Nagios from CMK graph defi- |
608 # '----------------------------------------------------------------------'
611 # Called with exactly one variable: the template ID. Example:
612 # "check_mk-kernel.util:guest,steal,system,user,wait".
613 @cmk.gui
.pages
.register("noauth:pnp_template")
614 def page_pnp_template():
616 template_id
= html
.request
.var("id")
618 check_command
, perf_var_string
= template_id
.split(":", 1)
619 perf_var_names
= perf_var_string
.split(",")
621 # Fake performance values in order to be able to find possible graphs
622 perf_data
= [(varname
, 1, "", 1, 1, 1, 1) for varname
in perf_var_names
]
623 translated_metrics
= translate_metrics(perf_data
, check_command
)
624 if not translated_metrics
:
625 return # check not supported
627 # Collect output in string. In case of an exception to not output
630 for graph_template
in get_graph_templates(translated_metrics
):
631 graph_code
= render_graph_pnp(graph_template
, translated_metrics
)
637 html
.write("An error occured:\n%s\n" % traceback
.format_exc())
640 # TODO: some_value.max not yet working
641 def render_graph_pnp(graph_template
, translated_metrics
):
643 vertical_label
= None
645 rrdgraph_commands
= ""
647 legend_precision
= graph_template
.get("legend_precision", 2)
648 legend_scale
= graph_template
.get("legend_scale", 1)
649 legend_scale_symbol
= scale_symbols
[legend_scale
]
651 # Define one RRD variable for each of the available metrics.
652 # Note: We need to use the original name, not the translated one.
653 for var_name
, metrics
in translated_metrics
.items():
654 rrd
= "$RRDBASE$_" + metrics
["orig_name"] + ".rrd"
655 scale
= metrics
["scale"]
656 unit
= metrics
["unit"]
659 rrdgraph_commands
+= "DEF:%s_UNSCALED=%s:1:MAX " % (var_name
, rrd
)
660 rrdgraph_commands
+= "CDEF:%s=%s_UNSCALED,%f,* " % (var_name
, var_name
, scale
)
663 rrdgraph_commands
+= "DEF:%s=%s:1:MAX " % (var_name
, rrd
)
666 rrdgraph_commands
+= "CDEF:%s_LEGSCALED=%s,%f,/ " % (var_name
, var_name
, legend_scale
)
668 # Prepare negative variants for upside-down graph
669 rrdgraph_commands
+= "CDEF:%s_NEG=%s,-1,* " % (var_name
, var_name
)
670 rrdgraph_commands
+= "CDEF:%s_LEGSCALED_NEG=%s_LEGSCALED,-1,* " % (var_name
, var_name
)
672 # Now add areas and lines to the graph
675 # Graph with upside down metrics? (e.g. for Disk IO)
676 have_upside_down
= False
678 # Compute width of the right column of the legend
680 for nr
, metric_definition
in enumerate(graph_template
["metrics"]):
681 if len(metric_definition
) >= 3:
682 title
= metric_definition
[2]
683 elif not "," in metric_definition
:
684 metric_name
= metric_definition
[0].split("#")[0]
685 mi
= translated_metrics
[metric_name
]
689 max_title_length
= max(max_title_length
, len(title
))
691 for nr
, metric_definition
in enumerate(graph_template
["metrics"]):
692 metric_name
= metric_definition
[0]
693 line_type
= metric_definition
[1] # "line", "area", "stack"
695 # Optional title, especially for derived values
696 if len(metric_definition
) >= 3:
697 title
= metric_definition
[2]
701 # Prefixed minus renders the metrics in negative direction
702 if line_type
[0] == '-':
703 have_upside_down
= True
705 upside_down_factor
= -1
706 line_type
= line_type
[1:]
707 upside_down_suffix
= "_NEG"
710 upside_down_factor
= 1
711 upside_down_suffix
= ""
713 if line_type
== "line":
716 elif line_type
== "area":
719 elif line_type
== "stack":
721 draw_stack
= ":STACK"
723 # User can specify alternative color using a suffixed #aabbcc
724 if '#' in metric_name
:
725 metric_name
, custom_color
= metric_name
.split("#", 1)
730 # Derived value with RBN syntax (evaluated by RRDTool!).
731 if "," in metric_name
:
732 # We evaluate just in order to get color and unit.
733 # TODO: beware of division by zero. All metrics are set to 1 here.
734 _value
, unit
, color
= evaluate(metric_name
, translated_metrics
)
736 if "@" in metric_name
:
737 expression
, _explicit_unit_name
= metric_name
.rsplit("@", 1) # isolate expression
739 expression
= metric_name
741 # Choose a unique name for the derived variable and compute it
742 commands
+= "CDEF:DERIVED%d=%s " % (nr
, expression
)
744 commands
+= "CDEF:DERIVED%d_NEG=DERIVED%d,-1,* " % (nr
, nr
)
746 metric_name
= "DERIVED%d" % nr
747 # Scaling and upsidedown handling for legend
748 commands
+= "CDEF:%s_LEGSCALED=%s,%f,/ " % (metric_name
, metric_name
, legend_scale
)
750 commands
+= "CDEF:%s_LEGSCALED%s=%s,%f,/ " % (
751 metric_name
, upside_down_suffix
, metric_name
, legend_scale
* upside_down_factor
)
754 mi
= translated_metrics
[metric_name
]
757 color
= parse_color_into_hexrgb(mi
["color"])
761 color
= "#" + custom_color
763 # Paint the graph itself
764 # TODO: Die Breite des Titels intelligent berechnen. Bei legend = "mirrored" muss man die
765 # Vefügbare Breite ermitteln und aufteilen auf alle Titel
766 right_pad
= " " * (max_title_length
- len(title
))
767 commands
+= "%s:%s%s%s:\"%s%s\"%s " % (draw_type
, metric_name
, upside_down_suffix
, color
,
768 title
.replace(":", "\\:"), right_pad
, draw_stack
)
769 if line_type
== "area":
770 commands
+= "LINE:%s%s%s " % (metric_name
, upside_down_suffix
,
771 render_color(darken_color(parse_color(color
), 0.2)))
773 unit_symbol
= unit
["symbol"]
774 if unit_symbol
== "%":
777 unit_symbol
= " " + unit_symbol
779 graph_metrics
.append((metric_name
, unit_symbol
, commands
))
781 # Use title and label of this metrics as default for the graph
782 if title
and not graph_title
:
784 if not vertical_label
:
785 vertical_label
= unit
["title"]
787 # Now create the rrdgraph commands for all metrics - according to the choosen layout
788 for metric_name
, unit_symbol
, commands
in graph_metrics
:
789 rrdgraph_commands
+= commands
790 legend_symbol
= unit_symbol
791 if unit_symbol
and unit_symbol
[0] == " ":
792 legend_symbol
= " %s%s" % (legend_scale_symbol
, unit_symbol
[1:])
793 for what
, what_title
in [("AVERAGE", _("average")), ("MAX", _("max")), ("LAST", _("last"))]:
794 rrdgraph_commands
+= "GPRINT:%%s_LEGSCALED:%%s:\"%%%%8.%dlf%%s %%s\" " % legend_precision
% \
795 (metric_name
, what
, legend_symbol
, what_title
)
796 rrdgraph_commands
+= "COMMENT:\"\\n\" "
798 # For graphs with both up and down, paint a gray rule at 0
800 rrdgraph_commands
+= "HRULE:0#c0c0c0 "
802 # Now compute the arguments for the command line of rrdgraph
803 rrdgraph_arguments
= ""
805 graph_title
= graph_template
.get("title", graph_title
)
806 vertical_label
= graph_template
.get("vertical_label", vertical_label
)
808 rrdgraph_arguments
+= " --vertical-label %s --title %s " % (cmk
.utils
.quote_shell_string(
809 vertical_label
or " "), cmk
.utils
.quote_shell_string(graph_title
))
811 min_value
, max_value
= get_graph_range(graph_template
, translated_metrics
)
812 if min_value
is not None and max_value
is not None:
813 rrdgraph_arguments
+= " -l %f -u %f" % (min_value
, max_value
)
815 rrdgraph_arguments
+= " -l 0"
817 return graph_title
+ "\n" + rrdgraph_arguments
+ "\n" + rrdgraph_commands
+ "\n"
821 # .--Hover-Graph---------------------------------------------------------.
823 # | | | | | _____ _____ _ __ / ___|_ __ __ _ _ __ | |__ |
824 # | | |_| |/ _ \ \ / / _ \ '__|____| | _| '__/ _` | '_ \| '_ \ |
825 # | | _ | (_) \ V / __/ | |_____| |_| | | | (_| | |_) | | | | |
826 # | |_| |_|\___/ \_/ \___|_| \____|_| \__,_| .__/|_| |_| |
828 # '----------------------------------------------------------------------'
831 def cmk_graphs_possible(site_id
=None):
833 return not config
.force_pnp_graphing \
834 and browser_supports_canvas() \
835 and site_is_running_cmc(site_id
)
840 # If site_id is None then we return True if at least
841 # one site is running CMC
842 def site_is_running_cmc(site_id
):
844 return sites
.state(site_id
, {}).get("program_version", "").startswith("Check_MK")
846 for status
in sites
.states().values():
847 if status
.get("program_version", "").startswith("Check_MK"):
852 def browser_supports_canvas():
853 user_agent
= html
.request
.user_agent
855 if 'MSIE' in user_agent
:
856 matches
= regex(r
'MSIE ([0-9]{1,}[\.0-9]{0,})').search(user_agent
)
858 ie_version
= float(matches
.group(1))
859 if ie_version
>= 9.0:
862 # Trying to deal with the IE compatiblity mode to detect the real IE version
863 matches
= regex(r
'Trident/([0-9]{1,}[\.0-9]{0,})').search(user_agent
)
865 trident_version
= float(matches
.group(1)) + 4
866 if trident_version
>= 9.0:
874 def get_graph_template_by_source(graph_templates
, source
):
875 graph_template
= None
876 for source_nr
, template
in enumerate(graph_templates
):
877 if source
== source_nr
+ 1:
878 graph_template
= template
880 return graph_template
883 # This page is called for the popup of the graph icon of hosts/services.
884 @cmk.gui
.pages
.register("host_service_graph_popup")
885 def page_host_service_graph_popup():
886 site_id
= html
.request
.var('site')
887 host_name
= html
.request
.var('host_name')
888 service_description
= html
.get_unicode_input('service')
890 # TODO: Refactor this to some OO based approach
891 if cmk_graphs_possible(site_id
):
892 import cmk
.gui
.cee
.plugins
.metrics
.graphs
as graphs
893 graphs
.host_service_graph_popup_cmk(site_id
, host_name
, service_description
)
895 host_service_graph_popup_pnp(site_id
, host_name
, service_description
)
898 def host_service_graph_popup_pnp(site
, host_name
, service_description
):
899 pnp_host
= cmk
.utils
.pnp_cleanup(host_name
)
900 pnp_svc
= cmk
.utils
.pnp_cleanup(service_description
)
901 url_prefix
= config
.site(site
)["url_prefix"]
904 url
= url_prefix
+ ("pnp4nagios/index.php?kohana_uri=/mobile/popup/%s/%s" % \
905 (html
.urlencode(pnp_host
), html
.urlencode(pnp_svc
)))
907 url
= url_prefix
+ ("pnp4nagios/index.php/popup?host=%s&srv=%s" % \
908 (html
.urlencode(pnp_host
), html
.urlencode(pnp_svc
)))
914 # .--Graph Dashlet-------------------------------------------------------.
915 # | ____ _ ____ _ _ _ |
916 # | / ___|_ __ __ _ _ __ | |__ | _ \ __ _ ___| |__ | | ___| |_ |
917 # | | | _| '__/ _` | '_ \| '_ \ | | | |/ _` / __| '_ \| |/ _ \ __| |
918 # | | |_| | | | (_| | |_) | | | | | |_| | (_| \__ \ | | | | __/ |_ |
919 # | \____|_| \__,_| .__/|_| |_| |____/ \__,_|___/_| |_|_|\___|\__| |
921 # +----------------------------------------------------------------------+
922 # | This page handler is called by graphs embedded in a dashboard. |
923 # '----------------------------------------------------------------------'
926 @cmk.gui
.pages
.register("graph_dashlet")
927 def page_graph_dashlet():
928 spec
= html
.request
.var("spec")
930 raise MKUserError("spec", _("Missing spec parameter"))
931 graph_identification
= json
.loads(html
.request
.var("spec"))
933 render
= html
.request
.var("render")
935 raise MKUserError("render", _("Missing render parameter"))
936 custom_graph_render_options
= json
.loads(html
.request
.var("render"))
938 # TODO: Refactor this to some OO based approach
939 if cmk_graphs_possible():
940 import cmk
.gui
.cee
.plugins
.metrics
.graphs
as graphs
941 graphs
.host_service_graph_dashlet_cmk(graph_identification
, custom_graph_render_options
)
942 elif graph_identification
[0] == "template":
943 host_service_graph_dashlet_pnp(graph_identification
)
945 html
.write(_("This graph can not be rendered."))
948 def host_service_graph_dashlet_pnp(graph_identification
):
949 site
= graph_identification
[1]["site"]
950 source
= int(graph_identification
[1]["graph_index"])
952 pnp_host
= cmk
.utils
.pnp_cleanup(graph_identification
[1]["host_name"])
953 pnp_svc
= cmk
.utils
.pnp_cleanup(graph_identification
[1]["service_description"])
954 url_prefix
= config
.site(site
)["url_prefix"]
956 pnp_theme
= html
.get_theme()
957 if pnp_theme
== "classic":
958 pnp_theme
= "multisite"
960 html
.write(url_prefix
+ "pnp4nagios/index.php/image?host=%s&srv=%s&source=%d&view=%s&theme=%s" % \
961 (html
.urlencode(pnp_host
), html
.urlencode(pnp_svc
), source
, html
.request
.var("timerange"), pnp_theme
))
965 # .--Metrics Table-------------------------------------------------------.
966 # | __ __ _ _ _____ _ _ |
967 # | | \/ | ___| |_ _ __(_) ___ ___ |_ _|_ _| |__ | | ___ |
968 # | | |\/| |/ _ \ __| '__| |/ __/ __| | |/ _` | '_ \| |/ _ \ |
969 # | | | | | __/ |_| | | | (__\__ \ | | (_| | |_) | | __/ |
970 # | |_| |_|\___|\__|_| |_|\___|___/ |_|\__,_|_.__/|_|\___| |
972 # +----------------------------------------------------------------------+
973 # | Renders a simple table with all metrics of a host or service |
974 # '----------------------------------------------------------------------'
977 def render_metrics_table(translated_metrics
, host_name
, service_description
):
978 output
= "<table class=metricstable>"
979 for metric_name
, metric
in sorted(
980 translated_metrics
.items(),
981 cmp=lambda a
, b
: cmp(a
[1]["title"], b
[1]["title"]),
984 output
+= "<td class=color>%s</td>" % render_color_icon(metric
["color"])
985 output
+= "<td>%s:</td>" % metric
["title"]
986 output
+= "<td class=value>%s</td>" % metric
["unit"]["render"](metric
["value"])
987 if cmk_graphs_possible():
989 output
+= html
.render_popup_trigger(
992 title
=_("Add this metric to a custom graph"),
993 cssclass
="iconbutton"),
994 ident
="add_metric_to_graph_" + host_name
+ ";" + service_description
,
995 what
="add_metric_to_custom_graph",
998 ("service", service_description
),
999 ("metric", metric_name
),
1003 output
+= "</table>"