def __init__(self, environment=None, service=None, time=None, units_planned=None, units_current=None, tasks_current=None, unknown_states=None, maxlen=100, simulate=False, simulated_units_start_latency_range=None, simulated_units_stop_latency_range=None, simulated_tasks_per_unit_range=None, simulated_tasks_divider=None): self.environment, self.service = environment, service self.time = time or deque(maxlen=maxlen) self.units_planned = units_planned or pygal_deque(maxlen=maxlen) self.units_current = units_current or {state: pygal_deque(maxlen=maxlen) for state in py_juju.ALL_STATES} self.tasks_current = tasks_current or {status: pygal_deque(maxlen=maxlen) for status in self.tasks_status} self.unknown_states = defaultdict(int) self.simulate = simulate self.simulated_units = SimulatedUnits(simulated_units_start_latency_range or (10, 13), simulated_units_stop_latency_range or (1, 2)) self.simulated_tasks_per_unit_range = simulated_tasks_per_unit_range or (2, 3) self.simulated_tasks_divider = simulated_tasks_divider or self.simulated_tasks_per_unit_range[1] self.simulated_tasks_progress = 0
class ServiceStatistics(PickleableObject): u"""Store statistics about a service.""" def __init__(self, environment=None, service=None, time=None, units_planned=None, units_current=None, tasks_current=None, unknown_states=None, maxlen=100, simulate=False, simulated_units_start_latency_range=None, simulated_units_stop_latency_range=None, simulated_tasks_per_unit_range=None, simulated_tasks_divider=None): self.environment, self.service = environment, service self.time = time or deque(maxlen=maxlen) self.units_planned = units_planned or pygal_deque(maxlen=maxlen) self.units_current = units_current or {state: pygal_deque(maxlen=maxlen) for state in py_juju.ALL_STATES} self.tasks_current = tasks_current or {status: pygal_deque(maxlen=maxlen) for status in self.tasks_status} self.unknown_states = defaultdict(int) self.simulate = simulate self.simulated_units = SimulatedUnits(simulated_units_start_latency_range or (10, 13), simulated_units_stop_latency_range or (1, 2)) self.simulated_tasks_per_unit_range = simulated_tasks_per_unit_range or (2, 3) self.simulated_tasks_divider = simulated_tasks_divider or self.simulated_tasks_per_unit_range[1] self.simulated_tasks_progress = 0 @property def environment_label(self): return ENVIRONMENT_TO_LABEL.get(self.environment, self.environment) @property def environment_type(self): return ENVIRONMENT_TO_TYPE.get(self.environment, self.environment) @property def service_label(self): return SERVICE_TO_LABEL.get(self.service, self.service) @property def tasks_status(self): return (TaskModel.PROGRESS,) # TaskModel.PENDING, TaskModel.SUCCESS, def update(self, now_string, planned, units=None, tasks=None): self.units_planned.append(planned) current = defaultdict(int) if self.simulate: # Retrieve state of all simulated units and update time of 1 tick self.simulated_units.ensure_num_units(num_units=planned) for unit in self.simulated_units.units.itervalues(): if unit.state in py_juju.ALL_STATES: current[unit.state] += 1 else: self.unknown_states[unit.state] += 1 self.simulated_units.tick() elif units: # Retrieve agent-state of all units for unit in units.itervalues(): state = unit.get(u'agent-state', u'unknown') if state in py_juju.ALL_STATES: current[state] += 1 else: self.unknown_states[state] += 1 # Append newest values to the statistics about the units for state, history in self.units_current.iteritems(): history.append(current[state]) units_started = current[py_juju.STARTED] current = defaultdict(int) if self.simulate: progress = self.simulated_tasks_progress target = random.randint(*self.simulated_tasks_per_unit_range) * units_started delta = target - progress self.simulated_tasks_progress = progress + delta / self.simulated_tasks_divider if units_started > 0 else 0 current[TaskModel.PROGRESS] = int(self.simulated_tasks_progress) elif tasks: for task in tasks: status = task.status if status in TaskModel.PENDING_STATUS: current[TaskModel.PENDING] += 1 elif status in TaskModel.RUNNING_STATUS: current[TaskModel.PROGRESS] += 1 elif status in TaskModel.SUCCESS_STATUS: current[TaskModel.SUCCESS] += 1 # ... else do not add to statistics. # Append newest values to the statistics about the tasks for status, history in self.tasks_current.iteritems(): history.append(current[status]) self.time.append(now_string) def _write_own_chart(self, chart, charts_path, prefix, add_x_labels=True): filename = u'{0}_{1}_{2}'.format(prefix, self.environment, self.service_label) return ServiceStatistics._write_chart(self.time, chart, charts_path, filename, add_x_labels=add_x_labels) def generate_units_pie_chart_by_status(self, charts_path, width=300, height=300): chart = pygal.Pie(width=width, height=height, no_data_text=u'No unit') chart.title = u'Number of {0} {1} nodes'.format(self.environment_type, self.service_label) for states in (py_juju.ERROR_STATES, py_juju.STARTED_STATES, py_juju.PENDING_STATES): units_number = sum((self.units_current.get(state, pygal_deque()).last or 0) for state in states) chart.add(u'{0} {1}'.format(units_number, states[0]), units_number) return self._write_own_chart(chart, charts_path, u'pie_units', add_x_labels=False) def generate_units_line_chart(self, charts_path, enable_current=True, width=1900, height=300): chart = pygal.Line(width=width, height=height, show_dots=True, no_data_text=u'No unit') chart.title = u'Number of {0} nodes'.format(self.service_label) planned_list, current_list = self.units_planned.list(), self.units_current[py_juju.STARTED].list() chart.add(u'{0} planned'.format(planned_list[-1] if len(planned_list) > 0 else 0), planned_list) if enable_current: chart.add(u'{0} current'.format(current_list[-1] if len(current_list) > 0 else 0), current_list) return self._write_own_chart(chart, charts_path, u'line_units') def generate_tasks_line_chart(self, charts_path, width=1200, height=300): total, lines = 0, {} for status in self.tasks_status: current_list = self.tasks_current[status].list() number = current_list[-1] if len(current_list) > 0 else 0 total += number lines[status] = (number, current_list) # , range=(0, total) chart = pygal.Line(width=width, height=height, show_dots=True, no_data_text=u'No task') chart.title = u'Scheduling of {0} tasks on {1}'.format(self.service_label, self.environment_label) for status in self.tasks_status: chart.add(u'{0} {1}'.format(lines[status][0], status), lines[status][1]) return self._write_own_chart(chart, charts_path, u'line_tasks') @staticmethod def _write_chart(time, chart, charts_path, filename, add_x_labels=True): if add_x_labels: chart.x_labels = list(time) chart.x_labels_major_count = 3 chart.x_label_rotation = 0 chart.show_minor_x_labels = False chart.label_font_size = chart.major_label_font_size = 12 chart.explicit_size = True chart.order_min = 0 chart.truncate_label = 20 chart.truncate_legend = 20 tmp_file = join(charts_path, u'{0}.new.svg'.format(filename)) dst_file = join(charts_path, u'{0}.svg'.format(filename)) chart.render_to_file(tmp_file) shutil.copy(tmp_file, dst_file) return dst_file @staticmethod def generate_units_stacked_chart(statistics, charts_path, enable_current=True, width=1900, height=300): labels = set(s.service_label for s in statistics) if len(labels) != 1: raise ValueError(to_bytes(u'Cannot generate a chart of different services, values: {0}'.format(labels))) service_label = labels.pop() chart = pygal.StackedLine(width=width, height=height, fill=True, show_dots=False, no_data_text=u'No unit') chart.title = u'Number of {0} nodes'.format(service_label) for statistic in statistics: planned_list = statistic.units_planned.list(fill=True) current_list = statistic.units_current[py_juju.STARTED].list(fill=True) chart.add(statistic.environment_label, planned_list) #if enable_current: # TODO return ServiceStatistics._write_chart(statistics[0].time, chart, charts_path, u'sum_{0}'.format(service_label))