def get_tests_summary_with_wildcards(job_names): """ Like get_tests_summary(job_names) but allowing wildcards. @param job_names: Names of the suite jobs to get the summary from. @returns: A summary of all the passed and failed tests per suite job. """ query = '''SELECT IF (status = 'GOOD', status, 'FAIL') AS test_status, COUNT(*) num FROM tko_test_view_2 WHERE job_name LIKE %s AND test_name <> 'SERVER_JOB' AND test_name NOT LIKE 'CLIENT_JOB%%' AND status <> 'TEST_NA' GROUP BY IF (status = 'GOOD', status, 'FAIL')''' summaries = {} cursor = readonly_connection.cursor() for job_name in job_names: cursor.execute(query, job_name) results = rpc_utils.fetchall_as_list_of_dicts(cursor) summary = summaries.setdefault(job_name, {}) for result in results: status = 'passed' if result['test_status'] == 'GOOD' else 'failed' summary[status] = result['num'] return summaries
def get_tests_summary(job_names): """ Gets the count summary of all passed and failed tests per suite. @param job_names: Names of the suite jobs to get the summary from. @returns: A summary of all the passed and failed tests per suite job. """ # Take advantage of Django's literal escaping to prevent SQL injection sql_list = ','.join(['%s'] * len(job_names)) query = ('''SELECT job_name, IF (status = 'GOOD', status, 'FAIL') AS test_status, COUNT(*) num FROM tko_test_view_2 WHERE job_name IN (%s) AND test_name <> 'SERVER_JOB' AND test_name NOT LIKE 'CLIENT_JOB%%%%' AND status <> 'TEST_NA' GROUP BY job_name, IF (status = 'GOOD', status, 'FAIL')''' % sql_list) cursor = readonly_connection.cursor() cursor.execute(query, job_names) results = rpc_utils.fetchall_as_list_of_dicts(cursor) summaries = {} for result in results: status = 'passed' if result['test_status'] == 'GOOD' else 'failed' summary = summaries.setdefault(result['job_name'], {}) summary[status] = result['num'] return summaries
def get_num_groups(self, query, group_by): """Gets the number of distinct groups for a query. @param query: The query to use. @param group_by: The fields by which to group. @return The number of distinct groups for the given query grouped by the fields in group_by. """ sql, params = self._get_num_groups_sql(query, group_by) cursor = readonly_connection.cursor() cursor.execute(sql, params) return self._cursor_rowcount(cursor)
def execute_group_query(self, query, group_by): """Performs the given query grouped by the specified fields. The given query's extra select fields are added. @param query: The query to perform. @param group_by: The fields by which to group. @return A list of dicts, where each dict corresponds to single row and contains a key for each grouped field as well as all of the extra select fields. """ sql, params = self._get_group_query_sql(query, group_by) cursor = readonly_connection.cursor() cursor.execute(sql, params) field_names = self._get_column_names(cursor) row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()] return row_dicts
def _create_qual_histogram_helper(plot_info, extra_text=None): """\ Create a machine qualification histogram of the given data. plot_info: a QualificationHistogram extra_text: text to show at the upper-left of the graph TODO(showard): move much or all of this into methods on QualificationHistogram """ cursor = readonly_connection.cursor() cursor.execute(plot_info.query) if not cursor.rowcount: raise NoDataError('query did not return any data') # Lists to store the plot data. # hist_data store tuples of (hostname, pass_rate) for machines that have # pass rates between 0 and 100%, exclusive. # no_tests is a list of machines that have run none of the selected tests # no_pass is a list of machines with 0% pass rate # perfect is a list of machines with a 100% pass rate hist_data = [] no_tests = [] no_pass = [] perfect = [] # Construct the lists of data to plot for hostname, total, good in cursor.fetchall(): if total == 0: no_tests.append(hostname) continue if good == 0: no_pass.append(hostname) elif good == total: perfect.append(hostname) else: percentage = 100.0 * good / total hist_data.append((hostname, percentage)) interval = plot_info.interval bins = range(0, 100, interval) if bins[-1] != 100: bins.append(bins[-1] + interval) figure, height = _create_figure(_SINGLE_PLOT_HEIGHT) subplot = figure.add_subplot(1, 1, 1) # Plot the data and get all the bars plotted _, _, bars = subplot.hist([data[1] for data in hist_data], bins=bins, align='left') bars += subplot.bar([-interval], len(no_pass), width=interval, align='center') bars += subplot.bar([bins[-1]], len(perfect), width=interval, align='center') bars += subplot.bar([-3 * interval], len(no_tests), width=interval, align='center') buckets = [(bin, min(bin + interval, 100)) for bin in bins[:-1]] # set the x-axis range to cover all the normal bins plus the three "special" # ones - N/A (3 intervals left), 0% (1 interval left) ,and 100% (far right) subplot.set_xlim(-4 * interval, bins[-1] + interval) subplot.set_xticks([-3 * interval, -interval] + bins + [100 + interval]) subplot.set_xticklabels(['N/A', '0%'] + ['%d%% - <%d%%' % bucket for bucket in buckets] + ['100%'], rotation=90, size='small') # Find the coordinates on the image for each bar x = [] y = [] for bar in bars: x.append(bar.get_x()) y.append(bar.get_height()) f = subplot.plot(x, y, linestyle='None')[0] upper_left_coords = f.get_transform().transform(zip(x, y)) bottom_right_coords = f.get_transform().transform([(x_val + interval, 0) for x_val in x]) # Set the title attributes titles = [ '%d%% - <%d%%: %d machines' % (bucket[0], bucket[1], y_val) for bucket, y_val in zip(buckets, y) ] titles.append('0%%: %d machines' % len(no_pass)) titles.append('100%%: %d machines' % len(perfect)) titles.append('N/A: %d machines' % len(no_tests)) # Get the hostnames for each bucket in the histogram names_list = [ _get_hostnames_in_bucket(hist_data, bucket) for bucket in buckets ] names_list += [no_pass, perfect] if plot_info.filter_string: plot_info.filter_string += ' AND ' # Construct the list of drilldown parameters to be passed when the user # clicks on the bar. params = [] for names in names_list: if names: hostnames = ','.join(_quote(hostname) for hostname in names) hostname_filter = 'hostname IN (%s)' % hostnames full_filter = plot_info.filter_string + hostname_filter params.append({'type': 'normal', 'filterString': full_filter}) else: params.append({'type': 'empty'}) params.append({'type': 'not_applicable', 'hosts': '<br />'.join(no_tests)}) area_data = [ dict(left=ulx, top=height - uly, right=brx, bottom=height - bry, title=title, callback=plot_info.drilldown_callback, callback_arguments=param_dict) for (ulx, uly), (brx, bry), title, param_dict in zip( upper_left_coords, bottom_right_coords, titles, params) ] # TODO(showard): extract these magic numbers to named constants if extra_text: figure.text(.1, .95, extra_text, size='xx-small') return (figure, area_data)
def _create_metrics_plot_helper(plot_info, extra_text=None): """ Create a metrics plot of the given plot data. plot_info: a MetricsPlot object. extra_text: text to show at the uppper-left of the graph TODO(showard): move some/all of this logic into methods on MetricsPlot """ query = plot_info.query_dict['__main__'] cursor = readonly_connection.cursor() cursor.execute(query) if not cursor.rowcount: raise NoDataError('query did not return any data') rows = cursor.fetchall() # "transpose" rows, so columns[0] is all the values from the first column, # etc. columns = zip(*rows) plots = [] labels = [str(label) for label in columns[0]] needs_resort = (cursor.description[0][0] == 'kernel') # Collect all the data for the plot col = 1 while col < len(cursor.description): y = columns[col] label = cursor.description[col][0] col += 1 if (col < len(cursor.description) and 'errors-' + label == cursor.description[col][0]): errors = columns[col] col += 1 else: errors = None if needs_resort: y = _resort(labels, y) if errors: errors = _resort(labels, errors) x = [index for index, value in enumerate(y) if value is not None] if not x: raise NoDataError('No data for series ' + label) y = [y[i] for i in x] if errors: errors = [errors[i] for i in x] plots.append({'label': label, 'x': x, 'y': y, 'errors': errors}) if needs_resort: labels = _resort(labels, labels) # Normalize the data if necessary normalize_to = plot_info.normalize_to if normalize_to == 'first' or normalize_to.startswith('x__'): if normalize_to != 'first': baseline = normalize_to[3:] try: baseline_index = labels.index(baseline) except ValueError: raise ValidationError( {'Normalize': 'Invalid baseline %s' % baseline}) for plot in plots: if normalize_to == 'first': plot_index = 0 else: try: plot_index = plot['x'].index(baseline_index) # if the value is not found, then we cannot normalize except ValueError: raise ValidationError({ 'Normalize': ('%s does not have a value for %s' % (plot['label'], normalize_to[3:])) }) base_values = [plot['y'][plot_index]] * len(plot['y']) if plot['errors']: base_errors = [plot['errors'][plot_index]] * len( plot['errors']) plot['y'], plot['errors'] = _normalize(plot['y'], plot['errors'], base_values, None or base_errors) elif normalize_to.startswith('series__'): base_series = normalize_to[8:] _normalize_to_series(plots, base_series) # Call the appropriate function to draw the line or bar plot if plot_info.is_line: figure, area_data = _create_line(plots, labels, plot_info) else: figure, area_data = _create_bar(plots, labels, plot_info) # TODO(showard): extract these magic numbers to named constants if extra_text: text_y = .95 - .0075 * len(plots) figure.text(.1, text_y, extra_text, size='xx-small') return (figure, area_data)
def execute_query_with_param(query, param): cursor = readonly_connection.cursor() cursor.execute(query, param) return cursor.fetchall()