# Copyright (c) 2013, 2020, Oracle and/or its affiliates. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is also distributed with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, as # designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have included with MySQL. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU General Public License, version 2.0, for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from workbench.graphics.canvas import Figure, ImageFigure, RectangleShapeFigure, TextFigure from mforms import Color, ControlBackgroundColor, TextColor, TextBackgroundColor import re import math #from grt import log_error #_this_file = os.path.basename(__file__) def scale_value(value): if value >= 1024*1024*1024*1024: unit = "T" value /= 1024*1024*1024*1024 elif value >= 1024*1024*1024: unit = "G" value /= 1024*1024*1024 elif value >= 1024*1024: unit = "M" value /= 1024*1024 elif value >= 1024: unit = "K" value /= 1024 else: value = int(round(value)) unit = "" return value, unit class DBTimeLineGraph(Figure): def __init__(self, calc, vaxis_format="%s", ndatasets=1, scale_unit=True, auto_scale_vaxis=True): Figure.__init__(self) self.calc = calc self._vaxis_format = vaxis_format self._ndatasets = ndatasets self._points = [[] for i in range(ndatasets)] self._width = 160 self._height = 120 self._vertical_dividers = 4 self._auto_scale_vaxis = auto_scale_vaxis self._scale = 100 self._scale_unit = scale_unit self._detail_line_pos = None # how many seconds each pixel represents self._seconds_per_hpixel = 1 self._colors = [(0, 0, 0) for i in range(ndatasets)] def set_max_value(self, value): self._scale = value def set_main_color(self, colors): assert self._ndatasets == 1 or (type(colors) is list and len(colors) == self._ndatasets) if self._ndatasets == 1 and type(colors) is not list: self._colors = [colors] else: self._colors = colors def format_vaxis_value(self, value): if self._auto_scale_vaxis: v, u = scale_value(value) if not u: format = self._vaxis_format i = format.find("%") format = format[:i]+re.sub(r"^%(\.|[0-9])*f", "%i", format[i:]) return format % (v, u) else: return self._vaxis_format % (v, u) else: # if the value is not scaled, replace the %f format string with %i, # so we don't end up with smething like 5.00 Bytes format = self._vaxis_format i = format.find("%") format = format[:i]+re.sub(r"^%(\.|[0-9])?f", "%i", format[i:]) return format % (value, "") def render(self, c): if self._detail_line_pos is not None: time_offset, detail_values = self.values_at_offset(self._detail_line_pos) else: time_offset, detail_values = None, None c.save() c.translate(self.x, self.y) textColor = Color.getSystemColor(TextColor) # draw axis c.set_line_width(2) c.set_source_rgb(textColor.red, textColor.green, textColor.blue) c.move_to(0, self.height - 0.5) c.line_to(self.width, self.height - 0.5) c.stroke() # draw scale lines c.set_line_width(1) c.set_font_size(10) for i in range(1, self._vertical_dividers+1): y = self.height - i * (self.height - 20) / self._vertical_dividers if i > 0: c.set_source_rgb(0.8, 0.8, 0.8) c.move_to(0, y + 0.5) c.line_to(self.width, y + 0.5) c.stroke() if not detail_values: c.set_source_rgb(textColor.red, textColor.green, textColor.blue) c.move_to(0, y - 2.5) c.show_text(self.format_vaxis_value(i * self._scale / self._vertical_dividers)) if detail_values: y = 10 c.set_font_size(12) for i, value in enumerate(detail_values): c.move_to(0, y) y += 14 c.set_source_rgb(*self._colors[i]) c.show_text("%s - %i seconds ago" % (self.format_vaxis_value(value), time_offset)) if self._points[0]: # draw data c.set_line_width(2) for i, points in enumerate(self._points): r, g, b = self._colors[i] c.set_source_rgba(r, g, b, 1) x = self.width + 0.5 p0, t0 = points[-1] tp = t0 c.move_to(x, self.height - (float(p0) / self._scale) * (self.height - 20)) for p, t in reversed(points[:-1]): x -= round((tp - t) / self._seconds_per_hpixel) tp = t y = int(self.height - (float(p) / self._scale) * (self.height - 20)) + 0.5 c.line_to(x, y) c.stroke() if self._detail_line_pos is not None: c.set_source_rgba(textColor.red, textColor.green, textColor.blue, 0.8 if detail_values else 0.2) c.set_line_width(1) c.move_to(self._detail_line_pos + 0.5, 0) c.line_to(self._detail_line_pos + 0.5, self.height) c.stroke() c.restore() def values_at_offset(self, x): if self._points and self._points[0]: values = [] visible_width = (self._points[0][-1][1] - self._points[0][0][1]) * self._seconds_per_hpixel if visible_width < self.width: offs = x - (self.width - visible_width) if offs < 0: return None, None else: offs = x time_offset = None for points in self._points: p, t0 = points[0] ts = offs / self._seconds_per_hpixel for p, t in points: if ts <= t - t0: values.append(p) time_offset = points[-1][1] - t break return time_offset, values return None, None def process(self, data, timestamp): values = self.calc.handle(data, timestamp) if values is None: return assert type(values) in (tuple, list) and len(values) == self._ndatasets or self._ndatasets == 1 if self._ndatasets == 1 and type(values) not in (list, tuple): self._points[0].append((values, timestamp)) # trim the list of points to fit what we can display while (self._points[0][-1][1] - self._points[0][0][1]) / self._seconds_per_hpixel > self.width: del self._points[0][0] else: for i, value in enumerate(values): self._points[i].append((value, timestamp)) # trim the list of points to fit what we can display while (self._points[i][-1][1] - self._points[i][0][1]) / self._seconds_per_hpixel > self.width: del self._points[i][0] if self._auto_scale_vaxis: max_value = None for points in self._points: # recalculate scale if len(points) > 1: max_value = max(points, key=lambda item: item[0])[0] if max_value is not None: lexvalue = "%i" % int(max_value) scale = int(str(int(lexvalue[0]) + 1) + "0"*len(lexvalue[1:])) self._scale = max(scale, 100) def hover_out(self, x, y): Figure.hover_out(self, x, y) self._detail_line_pos = None def mouse_move(self, x, y): Figure.mouse_move(self, x, y) if self._detail_line_pos != x - self.x: self._detail_line_pos = x - self.x self.invalidate(repaint_only=True) class DBSimpleCounter(RectangleShapeFigure): def __init__(self, calc, format, scale_unit): RectangleShapeFigure.__init__(self, "N/A") self.calc = calc self.set_line_width(2) self.set_font_size(11) self.set_font_bold(True) self.set_corner_radius(6) self._line_spacing = 2 self.set_text_color(0.4, 0.4, 0.4, 1) self._format = format or "%.2f %s" self._scale_unit = scale_unit self.set_usize(90, 35) def set(self, value): if value is None: value = 0 unit = "" if self._scale_unit: value, unit = scale_value(value) self.set_text(self._format % (value, unit)) def process(self, data, timestamp): self.set(self.calc.handle(data, timestamp)) def set_main_color(self, colors): self.set_color(*colors) class DBRoundMeter(TextFigure): def __init__(self, calc, caption): TextFigure.__init__(self, "") self.calc = calc self._value = 0 self._caption = caption self.set_alignment(0.5, 0.5) self.set_line_width(25) self.set_font_bold(True) self.set_font_size(18) self.set_padding(0, 0, 0, 20) self.set_usize(120, 100) def set_main_color(self, color): self.set_color(*color) self.set_text_color(*color) def set(self, value): if value is not None: self._value = value self.set_text("%i%%" % int(self._value * 100)) def process(self, data, timestamp): self.set(self.calc.handle(data, timestamp)) def render(self, c): TextFigure.render(self, c) content_diameter = min(self.width, self.height) c.save() c.translate(self.x, self.y) self.apply_attributes(c) c.save() c.set_source_rgb(0.93, 0.93, 0.93) c.arc(content_diameter/2, content_diameter/2, min(self.width - self._line_width, self.height - self._line_width)/2, 0, math.pi*2) c.stroke() c.restore() c.arc(content_diameter/2, content_diameter/2, min(self.width - self._line_width, self.height - self._line_width)/2, 0, max(self._value * math.pi*2, 0.02)) c.stroke() c.set_font(self._font, False, True) c.set_font_size(16) c.set_source_rgba(*self._text_color) c.move_to(content_diameter + 4 + 16, self.height - (content_diameter - c.text_extents(self._caption).width)/2) c.rotate(-math.pi/2) c.show_text(self._caption) c.stroke() c.restore() class DBHBarMeter(Figure): def __init__(self, calc, label1, label2): Figure.__init__(self) self.calc = calc self._label1 = label1 self._label2 = label2 self._value1 = 0 self._value2 = 0 self.set_usize(150, 56) def render(self, c): c.save() c.translate(self.x, self.y) if self._value2 > 0: p = self.width * (float(self._value1) / self._value2) c.set_source_rgb(0.3, 0.7, 0.3) c.rectangle(0, 0, p, self.height - 25) c.fill() c.set_source_rgb(0.7, 0.3, 0.3) c.rectangle(p, 0, self.width - p, self.height - 25) c.fill() elif self._value1 > 0: c.set_source_rgb(0.3, 0.7, 0.3) c.rectangle(0, 0, self.width, self.height - 25) c.fill() else: c.set_source_rgb(0.5, 0.5, 0.5) c.rectangle(0, 0, self.width, self.height - 25) c.fill() c.set_source_rgb(0.4, 0.4, 0.4) c.set_font_size(11) c.show_text_lines_at(0, self.height - 22, self._label1 % self._value1, 0, 12) c.show_rtext_lines_at(self.width, self.height - 22, self._label2 % self._value2, 0, 12) c.restore() def set(self, value_pair): self._value1, self._value2 = value_pair def process(self, data, timestamp): self.set(self.calc.handle(data, timestamp)) def set_main_color(self, color): self.set_color(*color) class DBLevelMeter(Figure): def __init__(self, calc): Figure.__init__(self) self.calc = calc self._max_value = 100 self._max_seen_value = 0 self._value = 0 self.set_usize(100, 100) def set_main_color(self, color): self.set_color(*color) def init(self, max_value): self._max_value = max_value def set(self, value): self._value = value self._max_seen_value = max(self._max_seen_value, value) def process(self, data, timestamp): self.set(self.calc.handle(data, timestamp)) def render(self, cr): cr.save() cr.translate(self.x, self.y) cr.set_source_rgb(0.8, 0.8, 0.8) cr.rectangle(0, 0, 30, self.height) cr.fill() p1 = (float(self._max_seen_value)/self._max_value)*self.height p2 = (float(self._value)/self._max_value)*self.height cr.set_source_rgb(0.5, 0.5, 0.5) limit_y = cr.text_extents("limit %s" % self._max_value).height cr.move_to(34, limit_y) cr.show_text("limit %s" % self._max_value) if p1 != p2: max_h = cr.text_extents("max %s" % self._max_seen_value).height max_y = self.height - p1 - max_h if max_y < limit_y: cr.move_to(34, limit_y + max_h + 2) cr.show_text("max %s" % self._max_seen_value) else: cr.move_to(34, self.height - p1) cr.show_text("max %s" % self._max_seen_value) cr.set_source_rgb(0.9, 0.9, 0.3) cr.rectangle(0, self.height - p1, 30, p1) cr.fill() self.apply_attributes(cr) cr.rectangle(0, self.height - p2, 30, p2) cr.fill() cr.set_source_rgb(0, 0, 0) cr.move_to((30 - cr.text_extents("%s" % self._value).width)/2, self.height - 1 - p2) cr.show_text("%s" % self._value) cr.restore() class DBImage(ImageFigure): def __init__(self, dummy, path): ImageFigure.__init__(self, path) def set(self, value): if value is None: value = 0 unit = "" if self._scale_unit: value, unit = scale_value(value) self.set_text(self._format % (value, unit)) def set_alignment(self, x, y): pass def process(self, data, timestamp): pass def set_main_color(self, colors): pass class DBVLine(Figure): def __init__(self, dummy, height): Figure.__init__(self) self._height = height def set(self, value): pass def set_alignment(self, x, y): pass def process(self, data, timestamp): pass def set_main_color(self, colors): self.set_color(colors[0], colors[1], colors[2], 1) def render(self, cr): cr.save() cr.translate(self.x, self.y) self.apply_attributes(cr) cr.move_to(0, 0) cr.line_to(0, self.height) cr.stroke() cr.restore() class DBText(TextFigure): def __init__(self, dummy, text): TextFigure.__init__(self) self.set_font_size(10) self.set_alignment(0.5, 0) self.set_text(text) def set(self, value): pass def process(self, data, timestamp): pass def set_main_color(self, colors): self.set_text_color(colors[0], colors[1], colors[2], 1)