def compute_graph_curves(metrics, rrd_data): curves = [] for metric_definition in metrics: expression = metric_definition["expression"] time_series = evaluate_time_series_expression(expression, rrd_data) if not time_series: continue multi = len(time_series) > 1 mirror_prefix = "-" if metric_definition["line_type"].startswith("-") else "" for i, ts in enumerate(time_series): title = metric_definition["title"] if ts.metadata.get('title') and multi: title += " - " + ts.metadata['title'] color = metric_definition.get("color", ts.metadata.get('color', "#000000")) if i % 2 == 1 and not (expression[0] == "transformation" and expression[1][0] == "forecast"): color = render_color(fade_color(parse_color(color), 0.3)) curves.append({ "line_type": mirror_prefix + ts.metadata.get('line_type', "") if multi else metric_definition["line_type"], "color": color, 'title': title, 'rrddata': ts }) return curves
def compute_graph_curves(metrics: Sequence[GraphMetric], rrd_data: RRDData) -> list[Curve]: curves = [] for metric_definition in metrics: expression = metric_definition["expression"] time_series = evaluate_time_series_expression(expression, rrd_data) if not time_series: continue multi = len(time_series) > 1 mirror_prefix = "-" if metric_definition["line_type"].startswith( "-") else "" for i, ts in enumerate(time_series): title = metric_definition["title"] if ts.metadata.get("title") and multi: title += " - " + ts.metadata["title"] color = metric_definition.get("color", ts.metadata.get("color", "#000000")) if i % 2 == 1 and not (expression[0] == "transformation" and expression[1][0] == "forecast"): color = render_color(fade_color(parse_color(color), 0.3)) curves.append( Curve({ "line_type": mirror_prefix + ts.metadata.get("line_type", "") if multi else metric_definition["line_type"], "color": color, "title": title, "rrddata": ts, })) return curves
def render_graph_pnp(graph_template, translated_metrics): graph_title = None vertical_label = None rrdgraph_commands = "" legend_precision = graph_template.get("legend_precision", 2) legend_scale = graph_template.get("legend_scale", 1) legend_scale_symbol = scale_symbols[legend_scale] # Define one RRD variable for each of the available metrics. # Note: We need to use the original name, not the translated one. for var_name, metrics in translated_metrics.items(): rrd = "$RRDBASE$_" + metrics["orig_name"] + ".rrd" scale = metrics["scale"] unit = metrics["unit"] if scale != 1.0: rrdgraph_commands += "DEF:%s_UNSCALED=%s:1:MAX " % (var_name, rrd) rrdgraph_commands += "CDEF:%s=%s_UNSCALED,%f,* " % ( var_name, var_name, scale) else: rrdgraph_commands += "DEF:%s=%s:1:MAX " % (var_name, rrd) # Scaling for legend rrdgraph_commands += "CDEF:%s_LEGSCALED=%s,%f,/ " % ( var_name, var_name, legend_scale) # Prepare negative variants for upside-down graph rrdgraph_commands += "CDEF:%s_NEG=%s,-1,* " % (var_name, var_name) rrdgraph_commands += "CDEF:%s_LEGSCALED_NEG=%s_LEGSCALED,-1,* " % ( var_name, var_name) # Now add areas and lines to the graph graph_metrics = [] # Graph with upside down metrics? (e.g. for Disk IO) have_upside_down = False # Compute width of the right column of the legend max_title_length = 0 for nr, metric_definition in enumerate(graph_template["metrics"]): if len(metric_definition) >= 3: title = metric_definition[2] elif not "," in metric_definition: metric_name = metric_definition[0].split("#")[0] mi = translated_metrics[metric_name] title = mi["title"] else: title = "" max_title_length = max(max_title_length, len(title)) for nr, metric_definition in enumerate(graph_template["metrics"]): metric_name = metric_definition[0] line_type = metric_definition[1] # "line", "area", "stack" # Optional title, especially for derived values if len(metric_definition) >= 3: title = metric_definition[2] else: title = "" # Prefixed minus renders the metrics in negative direction if line_type[0] == '-': have_upside_down = True upside_down = True upside_down_factor = -1 line_type = line_type[1:] upside_down_suffix = "_NEG" else: upside_down = False upside_down_factor = 1 upside_down_suffix = "" if line_type == "line": draw_type = "LINE" draw_stack = "" elif line_type == "area": draw_type = "AREA" draw_stack = "" elif line_type == "stack": draw_type = "AREA" draw_stack = ":STACK" # User can specify alternative color using a suffixed #aabbcc if '#' in metric_name: metric_name, custom_color = metric_name.split("#", 1) else: custom_color = None commands = "" # Derived value with RBN syntax (evaluated by RRDTool!). if "," in metric_name: # We evaluate just in order to get color and unit. # TODO: beware of division by zero. All metrics are set to 1 here. _value, unit, color = evaluate(metric_name, translated_metrics) if "@" in metric_name: expression, _explicit_unit_name = metric_name.rsplit( "@", 1) # isolate expression else: expression = metric_name # Choose a unique name for the derived variable and compute it commands += "CDEF:DERIVED%d=%s " % (nr, expression) if upside_down: commands += "CDEF:DERIVED%d_NEG=DERIVED%d,-1,* " % (nr, nr) metric_name = "DERIVED%d" % nr # Scaling and upsidedown handling for legend commands += "CDEF:%s_LEGSCALED=%s,%f,/ " % ( metric_name, metric_name, legend_scale) if upside_down: commands += "CDEF:%s_LEGSCALED%s=%s,%f,/ " % ( metric_name, upside_down_suffix, metric_name, legend_scale * upside_down_factor) else: mi = translated_metrics[metric_name] if not title: title = mi["title"] color = parse_color_into_hexrgb(mi["color"]) unit = mi["unit"] if custom_color: color = "#" + custom_color # Paint the graph itself # TODO: Die Breite des Titels intelligent berechnen. Bei legend = "mirrored" muss man die # Vefügbare Breite ermitteln und aufteilen auf alle Titel right_pad = " " * (max_title_length - len(title)) commands += "%s:%s%s%s:\"%s%s\"%s " % ( draw_type, metric_name, upside_down_suffix, color, title.replace(":", "\\:"), right_pad, draw_stack) if line_type == "area": commands += "LINE:%s%s%s " % ( metric_name, upside_down_suffix, render_color(darken_color(parse_color(color), 0.2))) unit_symbol = unit["symbol"] if unit_symbol == "%": unit_symbol = "%%" else: unit_symbol = " " + unit_symbol graph_metrics.append((metric_name, unit_symbol, commands)) # Use title and label of this metrics as default for the graph if title and not graph_title: graph_title = title if not vertical_label: vertical_label = unit["title"] # Now create the rrdgraph commands for all metrics - according to the choosen layout for metric_name, unit_symbol, commands in graph_metrics: rrdgraph_commands += commands legend_symbol = unit_symbol if unit_symbol and unit_symbol[0] == " ": legend_symbol = " %s%s" % (legend_scale_symbol, unit_symbol[1:]) if legend_symbol == " bits/s": # Use a literal '%s' so that GPRINT outputs values with the appropriate # SI magnitude (e.g. 123456 -> 123.456 k) legend_symbol = " %sbit/s" for what, what_title in [("AVERAGE", _("average")), ("MAX", _("max")), ("LAST", _("last"))]: rrdgraph_commands += "GPRINT:%s_LEGSCALED:%s:\"%%8.%dlf%s %s\" " % ( metric_name, what, legend_precision, legend_symbol, what_title, ) rrdgraph_commands += "COMMENT:\"\\n\" " # add horizontal rules for warn and crit scalars for scalar in graph_template.get("scalars", []): rrdgraph_commands += _scalar_value_command(scalar, translated_metrics) # For graphs with both up and down, paint a gray rule at 0 if have_upside_down: rrdgraph_commands += "HRULE:0#c0c0c0 " # Now compute the arguments for the command line of rrdgraph rrdgraph_arguments = "" graph_title = graph_template.get("title", graph_title) vertical_label = graph_template.get("vertical_label", vertical_label) rrdgraph_arguments += " --vertical-label %s --title %s " % ( cmk.utils.quote_shell_string( vertical_label or " "), cmk.utils.quote_shell_string(graph_title)) min_value, max_value = get_graph_range(graph_template, translated_metrics) if min_value is not None and max_value is not None: rrdgraph_arguments += " -l %f -u %f" % (min_value, max_value) else: rrdgraph_arguments += " -l 0" return graph_title + "\n" + rrdgraph_arguments + "\n" + rrdgraph_commands + "\n"
def render_graph_pdf( instance, graph_artwork, graph_data_range, graph_render_options, pos_left=None, pos_top=None, total_width=None, total_height=None, ): pdf_document = instance["document"] logger.debug(" Render graph %r", graph_artwork["definition"]["specification"]) if pos_left is None: # floating element pdf_document.margin(2.5) # Styling for PDF graphs. Note: We could make some of these # configurable font_size = graph_render_options["font_size"] mm_per_ex = mm_per_ex_by_render_options(graph_render_options) v_label_margin = 1.0 # mm t_label_margin = _graph_time_label_margin() left_border = _graph_vertical_axis_width(graph_render_options) left_margin = _graph_left_margin(graph_render_options) top_margin = _graph_top_margin(graph_render_options) right_margin = _graph_right_margin(graph_render_options) bottom_margin = _graph_bottom_margin(graph_render_options) axis_color = parse_color(graph_render_options["foreground_color"]) zero_rule_color = parse_color(graph_render_options["foreground_color"]) canvas_color = parse_color(graph_render_options["canvas_color"]) background_color = parse_color(graph_render_options["background_color"]) foreground_color = parse_color(graph_render_options["foreground_color"]) axis_over_width = _graph_axis_over_width(graph_render_options) color_gradient = graph_render_options["color_gradient"] / 100.0 curve_line_width = 0.1 # mm rule_line_width = 0.1 # mm label_line_width = 0.04 # mm v_line_color = tuple( map(parse_color, [graph_render_options["foreground_color"], "#a0a0a0", "#a0a0a0"])) v_line_dash = [None, [0.2, 0.4], None] t_line_color = tuple( map(parse_color, [graph_render_options["foreground_color"], "#a0a0a0", "#666666"])) t_line_dash = [None, [0.2, 0.2], None] legend_box_line_width = 0.1 pdf_document.save_state() pdf_document.set_font_size(font_size) legend_box_size = mm_per_ex title_height = graph_title_height(graph_render_options) legend_height = graph_legend_height(graph_artwork, graph_render_options) if pos_left is not None: # Absolute placement of graph height = total_height - title_height - legend_height width = total_width else: # Place graph in page flow width_ex, height_ex = graph_render_options["size"] width = width_ex * mm_per_ex height = height_ex * mm_per_ex left, top, width, total_height = pdf_document.add_canvas( width, height + title_height + legend_height, border_width=graph_render_options["border_width"], left_mm=pos_left, ) # From here width, height, total_height, left and top are in "mm". right = left + width - right_margin total_bottom = top - total_height bottom = top - height - title_height # Fill canvas with background color pdf_document.render_rect(left, total_bottom, width, total_height, fill_color=background_color) # Regular title (above graph area) if graph_render_options["show_title"] is True: pdf_document.render_aligned_text( left + right_margin, top - title_height, width, title_height, graph_artwork["title"], align="left", bold=True, color=foreground_color, ) # The following code is inspired by htdocs/js/graphs.js:render_graph(). Whenever # you change something there, the change should also be reflected here! bottom_border = _graph_bottom_border(graph_render_options) # Prepare position and translation of origin t_range_from = graph_artwork["time_axis"]["range"][0] t_range_to = graph_artwork["time_axis"]["range"][1] t_range = t_range_to - t_range_from t_mm = width - left_border - left_margin - right_margin t_mm_per_second = 1.0 * t_mm / t_range v_range_from = graph_artwork["vertical_axis"]["range"][0] v_range_to = graph_artwork["vertical_axis"]["range"][1] v_range = v_range_to - v_range_from v_mm = height - top_margin - bottom_border - bottom_margin v_mm_per_unit = 1.0 * v_mm / v_range t_orig = left + left_border + left_margin v_orig = bottom + bottom_border + bottom_margin v_axis_orig = v_range_from # paint graph background pdf_document.render_rect(t_orig, v_orig, t_mm, v_mm, fill_color=canvas_color) # Now transform the whole chooridate system to our real t and v choords # so if we paint something at (0, 0) it will correctly represent a # value of 0 and a time point of time_start. trans_t = lambda t: (t - t_range_from) * t_mm_per_second + t_orig trans_v = lambda v: v_orig + ((v - v_axis_orig) * v_mm_per_unit) trans = lambda t, v: (trans_t(t), trans_v(v)) # Paint curves pdf_document.save_state() pdf_document.add_clip_rect(t_orig, v_orig, t_mm, v_mm) step = graph_artwork["step"] / 2.0 for curve in graph_artwork["curves"]: if curve.get("dont_paint"): continue t = graph_artwork["start_time"] points = curve["points"] color = parse_color(curve["color"]) if curve["type"] == "area": prev_lower = None prev_upper = None gradient = ( t_orig, v_orig, t_orig, v_orig + v_mm, (darken_color(color, color_gradient), color, lighten_color(color, color_gradient)), (0.0, 0.5, 1.0), ) for lower, upper in points: if (lower is not None and upper is not None and prev_lower is not None and prev_upper is not None): pdf_document.begin_path() pdf_document.move_to( trans_t(t - step) - 0.01, trans_v(prev_lower)) pdf_document.line_to( trans_t(t - step) - 0.01, trans_v(prev_upper)) pdf_document.line_to(trans_t(t), trans_v(upper)) pdf_document.line_to(trans_t(t), trans_v(lower)) pdf_document.line_to( trans_t(t - step) - 0.01, trans_v(prev_lower)) pdf_document.close_path() pdf_document.fill_path(color, gradient=gradient) prev_lower = lower prev_upper = upper t += step else: # "line" last_value = None pdf_document.begin_path() for value in points: if value is not None: p = trans(t, value) if last_value is not None: pdf_document.line_to(p[0], p[1]) else: pdf_document.move_to(p[0], p[1]) last_value = value t += step pdf_document.stroke_path(color=color, width=curve_line_width) pdf_document.restore_state() # Remove clipping # Now we use these four dimensions for drawing into the canvas using render_... # functions from pdf. Note: top > bottom. # Clear areas where values have been painted out of range. This is # At top and bottom pdf_document.render_rect(t_orig, v_orig + v_mm, t_mm, top_margin, fill_color=background_color) pdf_document.render_rect(t_orig, bottom, t_mm, v_orig - bottom, fill_color=background_color) # Paint axes and a strong line at 0, if that is in the range pdf_document.render_line(t_orig, v_orig - axis_over_width, t_orig, v_orig + v_mm, color=axis_color) pdf_document.render_line(t_orig - axis_over_width, v_orig, right, v_orig, color=axis_color) if v_range_from <= 0 <= v_range_to: pdf_document.render_line(t_orig, trans_v(0), right, trans_v(0), color=zero_rule_color) # Show the inline title if graph_render_options["show_title"] == "inline": title_top = top - (mm_per_ex_by_render_options(graph_render_options) * 2) pdf_document.render_aligned_text( left, title_top, width, mm_per_ex_by_render_options(graph_render_options) * 2, graph_artwork["title"], align="center", bold=True, color=foreground_color, ) if graph_render_options["show_graph_time"]: title_top = top - (mm_per_ex_by_render_options(graph_render_options) * 2) pdf_document.render_aligned_text( left - right_margin, title_top, width, mm_per_ex_by_render_options(graph_render_options) * 2, graph_artwork["time_axis"]["title"], align="right", bold=True, color=foreground_color, ) # Paint the vertical axis if graph_render_options["show_vertical_axis"]: # Render optional vertical axis label vertical_axis_label = graph_artwork["vertical_axis"]["axis_label"] if vertical_axis_label: pdf_document.render_aligned_text( left + left_margin, top - title_height, left_border, title_height, vertical_axis_label, align="center", valign="middle", color=foreground_color, ) for position, label, line_width in graph_artwork["vertical_axis"][ "labels"]: if line_width > 0: pdf_document.render_line( t_orig, trans_v(position), right, trans_v(position), width=label_line_width, color=v_line_color[line_width], dashes=v_line_dash[line_width], ) if graph_render_options["show_vertical_axis"] and label: pdf_document.render_aligned_text( t_orig - v_label_margin - left_border, trans_v(position), left_border, mm_per_ex, label, align="right", valign="middle", color=foreground_color, ) # Paint time axis for position, label, line_width in graph_artwork["time_axis"]["labels"]: t_pos_mm = trans_t(position) if line_width > 0 and t_pos_mm > t_orig: pdf_document.render_line( t_pos_mm, v_orig, t_pos_mm, trans_v(v_range_to), width=label_line_width, color=t_line_color[line_width], dashes=t_line_dash[line_width], ) if graph_render_options["show_time_axis"] and label: pdf_document.render_aligned_text( t_pos_mm, v_orig - t_label_margin - mm_per_ex, 0, mm_per_ex, label, align="center", color=foreground_color, ) # Paint horizontal rules like warn and crit rules = graph_artwork["horizontal_rules"] for position, label, color_from_rule, title in rules: if v_range_from <= position <= v_range_to: pdf_document.render_line( t_orig, trans_v(position), right, trans_v(position), width=rule_line_width, color=parse_color(color_from_rule), ) # Paint legend if graph_render_options["show_legend"]: legend_lineskip = get_graph_legend_lineskip(graph_render_options) legend_top_margin = _graph_legend_top_margin() legend_top = bottom - legend_top_margin + bottom_margin legend_column_width = (width - left_margin - left_border - right_margin) / 7.0 def paint_legend_line(color, texts): l = t_orig if color: pdf_document.render_rect( l, legend_top + mm_per_ex * 0.2, legend_box_size, legend_box_size, fill_color=color, line_width=legend_box_line_width, ) for nr, text in enumerate(texts): if text: pdf_document.render_aligned_text( l + (color and nr == 0 and legend_box_size + 0.8 or 0), legend_top, legend_column_width, legend_lineskip, text, align=nr == 0 and "left" or "right", color=foreground_color, ) if nr == 0: l += legend_column_width * 3 else: l += legend_column_width scalars = [ ("min", _("Minimum")), ("max", _("Maximum")), ("average", _("Average")), ("last", _("Last")), ] scalars_legend_line: List[Optional[str]] = [None] paint_legend_line(None, scalars_legend_line + [x[1] for x in scalars]) pdf_document.render_line(t_orig, legend_top, t_orig + t_mm, legend_top) for curve in graph_artwork["curves"]: legend_top -= legend_lineskip texts = [str(curve["title"])] for scalar, title in scalars: texts.append(curve["scalars"][scalar][1]) paint_legend_line(parse_color(curve["color"]), texts) if graph_artwork["horizontal_rules"]: pdf_document.render_line(t_orig, legend_top, t_orig + t_mm, legend_top) for value, readable, color_from_artwork, title in graph_artwork[ "horizontal_rules"]: legend_top -= legend_lineskip paint_legend_line(parse_color(color_from_artwork), [title] + [None] * 3 + [readable]) if graph_artwork["definition"].get("is_forecast"): pin = trans_t(graph_artwork["requested_end_time"]) pdf_document.render_line(pin, v_orig, pin, trans_v(v_range_to), color=(0.0, 1.0, 0.0)) pdf_document.restore_state() if left is None: # floating element pdf_document.margin(2.5) logger.debug(" Finished rendering graph")
def render_graph_pdf( instance, graph_artwork: GraphArtwork, graph_data_range: GraphDataRange, graph_render_options: GraphRenderOptions, pos_left: Optional[SizeMM] = None, pos_top: Optional[SizeMM] = None, total_width: Optional[SizeMM] = None, total_height: Optional[SizeMM] = None, ) -> None: pdf_document = instance["document"] logger.debug(" Render graph %r", graph_artwork["definition"]["specification"]) if pos_left is None: # floating element pdf_document.margin(2.5) # Styling for PDF graphs. Note: We could make some of these # configurable font_size = graph_render_options["font_size"] mm_per_ex = mm_per_ex_by_render_options(graph_render_options) v_label_margin = 1.0 # mm t_label_margin = _graph_time_label_margin() left_border = _graph_vertical_axis_width(graph_render_options) left_margin = _graph_left_margin(graph_render_options) top_margin = _graph_top_margin(graph_render_options) right_margin = _graph_right_margin(graph_render_options) bottom_margin = _graph_bottom_margin(graph_render_options) axis_color = parse_color(graph_render_options["foreground_color"]) zero_rule_color = parse_color(graph_render_options["foreground_color"]) canvas_color = parse_color(graph_render_options["canvas_color"]) background_color = parse_color(graph_render_options["background_color"]) foreground_color = parse_color(graph_render_options["foreground_color"]) axis_over_width = _graph_axis_over_width(graph_render_options) color_gradient = graph_render_options["color_gradient"] / 100.0 curve_line_width = 0.1 # mm rule_line_width = 0.1 # mm label_line_width = 0.04 # mm v_line_color = tuple( map(parse_color, [graph_render_options["foreground_color"], "#a0a0a0", "#a0a0a0"]) ) v_line_dash = [None, [0.2, 0.4], None] t_line_color = tuple( map(parse_color, [graph_render_options["foreground_color"], "#a0a0a0", "#666666"]) ) t_line_dash = [None, [0.2, 0.2], None] legend_box_line_width = 0.1 pdf_document.save_state() pdf_document.set_font_size(font_size) legend_box_size = mm_per_ex title_height = graph_title_height(graph_render_options) legend_height = graph_legend_height(graph_artwork, graph_render_options) if pos_left is not None: # Absolute placement of graph assert pos_top is not None assert total_width is not None assert total_height is not None height = total_height - title_height - legend_height width = total_width else: # Place graph in page flow width_ex, height_ex = graph_render_options["size"] width = width_ex * mm_per_ex height = height_ex * mm_per_ex left, top, width, total_height = pdf_document.add_canvas( width, height + title_height + legend_height, border_width=graph_render_options["border_width"], left_mm=pos_left, ) # From here width, height, total_height, left and top are in "mm". right = left + width - right_margin total_bottom = top - total_height bottom = top - height - title_height # Fill canvas with background color pdf_document.render_rect(left, total_bottom, width, total_height, fill_color=background_color) # Regular title (above graph area) if graph_render_options["show_title"] is True: title_left_margin = left + right_margin if vertical_axis_label := graph_artwork.get("vertical_axis", {}).get("axis_label"): title_left_margin = left + left_border + left_margin pdf_document.render_aligned_text( title_left_margin, top - title_height, width, title_height, graph_artwork["title"], align="left", bold=True, color=foreground_color, )
# so if we paint something at (0, 0) it will correctly represent a # value of 0 and a time point of time_start. trans_t = lambda t: (t - t_range_from) * t_mm_per_second + t_orig trans_v = lambda v: v_orig + ((v - v_axis_orig) * v_mm_per_unit) trans = lambda t, v: (trans_t(t), trans_v(v)) # Paint curves pdf_document.save_state() pdf_document.add_clip_rect(t_orig, v_orig, t_mm, v_mm) step = graph_artwork["step"] // 2 for curve in graph_artwork["curves"]: if curve.get("dont_paint"): continue t = graph_artwork["start_time"] color = parse_color(curve["color"]) if curve["type"] == "area": curve = cast(LayoutedCurveArea, curve) points = curve["points"] prev_lower = None prev_upper = None gradient = ( t_orig, v_orig, t_orig, v_orig + v_mm, (darken_color(color, color_gradient), color, lighten_color(color, color_gradient)), (0.0, 0.5, 1.0), )
# value of 0 and a time point of time_start. trans_t = lambda t: (t - t_range_from) * t_mm_per_second + t_orig trans_v = lambda v: v_orig + ((v - v_axis_orig) * v_mm_per_unit) trans = lambda t, v: (trans_t(t), trans_v(v)) # Paint curves pdf_document.save_state() pdf_document.add_clip_rect(t_orig, v_orig, t_mm, v_mm) step = graph_artwork["step"] / 2.0 for curve in graph_artwork["curves"]: if curve.get("dont_paint"): continue t = graph_artwork["start_time"] points = curve["points"] color = parse_color(curve["color"]) if curve["type"] == "area": prev_lower = None prev_upper = None gradient = ( t_orig, v_orig, t_orig, v_orig + v_mm, (darken_color(color, color_gradient), color, lighten_color(color, color_gradient)), (0.0, 0.5, 1.0), )