640 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			640 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
#
 | 
						|
# OtterTune - utils.py
 | 
						|
#
 | 
						|
# Copyright (c) 2017-18, Carnegie Mellon University Database Group
 | 
						|
#
 | 
						|
import datetime
 | 
						|
import json
 | 
						|
import logging
 | 
						|
import math
 | 
						|
import os
 | 
						|
import string
 | 
						|
import tarfile
 | 
						|
import time
 | 
						|
from collections import OrderedDict
 | 
						|
from io import BytesIO
 | 
						|
from random import choice
 | 
						|
from subprocess import Popen, PIPE
 | 
						|
 | 
						|
import celery
 | 
						|
import numpy as np
 | 
						|
from django.contrib.auth.models import User
 | 
						|
from django.core.management import call_command
 | 
						|
from django.db.models import Case, When
 | 
						|
from django.utils.text import capfirst
 | 
						|
from django_db_logger.models import StatusLog
 | 
						|
from djcelery.models import TaskMeta
 | 
						|
 | 
						|
from .models import DBMSCatalog, MetricCatalog, KnobCatalog, Result, Session, SessionKnob
 | 
						|
from .settings import common
 | 
						|
from .types import LabelStyleType, VarType
 | 
						|
 | 
						|
LOG = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
class JSONUtil(object):
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def loads(config_str):
 | 
						|
        return json.loads(config_str,
 | 
						|
                          encoding="UTF-8",
 | 
						|
                          object_pairs_hook=OrderedDict)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def dumps(config, pprint=False, sort=False, encoder='custom'):
 | 
						|
        json_args = dict(indent=4 if pprint is True else None,
 | 
						|
                         ensure_ascii=False)
 | 
						|
 | 
						|
        if encoder == 'custom':
 | 
						|
            json_args.update(default=JSONUtil.custom_converter)
 | 
						|
 | 
						|
        if sort is True:
 | 
						|
            if isinstance(config, dict):
 | 
						|
                config = OrderedDict(sorted(config.items()))
 | 
						|
            else:
 | 
						|
                config = sorted(config)
 | 
						|
 | 
						|
        return json.dumps(config, **json_args)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def custom_converter(o):
 | 
						|
        if isinstance(o, datetime.datetime):
 | 
						|
            return str(o)
 | 
						|
        elif isinstance(o, np.ndarray):
 | 
						|
            return o.tolist()
 | 
						|
 | 
						|
 | 
						|
class MediaUtil(object):
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def upload_code_generator(size=20,
 | 
						|
                              chars=string.ascii_uppercase + string.digits):
 | 
						|
        new_upload_code = ''.join(choice(chars) for _ in range(size))
 | 
						|
        return new_upload_code
 | 
						|
 | 
						|
 | 
						|
class TaskUtil(object):
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_task_ids_from_tuple(task_tuple):
 | 
						|
        task_res = celery.result.result_from_tuple(task_tuple)
 | 
						|
        task_ids = []
 | 
						|
        task = task_res
 | 
						|
        while task is not None:
 | 
						|
            task_ids.insert(0, task)
 | 
						|
            task = task.parent
 | 
						|
        return task_ids
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_tasks(task_ids):
 | 
						|
        task_ids = task_ids or []
 | 
						|
        if isinstance(task_ids, str):
 | 
						|
            task_ids = task_ids.split(',')
 | 
						|
        preserved = Case(*[
 | 
						|
            When(task_id=task_id, then=pos) for pos, task_id in enumerate(task_ids)])
 | 
						|
        return TaskMeta.objects.filter(task_id__in=task_ids).order_by(preserved)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_task_status(tasks, num_tasks):
 | 
						|
        overall_status = 'UNAVAILABLE'
 | 
						|
        num_completed = 0
 | 
						|
        for task in tasks:
 | 
						|
            status = task.status
 | 
						|
            if status == "SUCCESS":
 | 
						|
                num_completed += 1
 | 
						|
            elif status in ('FAILURE', 'REVOKED', 'RETRY'):
 | 
						|
                overall_status = status
 | 
						|
                break
 | 
						|
            else:
 | 
						|
                if status not in ('PENDING', 'RECEIVED', 'STARTED'):
 | 
						|
                    LOG.warning("Task %s: invalid task status: '%s' (task_id=%s)",
 | 
						|
                                task.id, status, task.task_id)
 | 
						|
                overall_status = status
 | 
						|
 | 
						|
        if num_tasks > 0 and num_tasks == num_completed:
 | 
						|
            overall_status = 'SUCCESS'
 | 
						|
 | 
						|
        return overall_status, num_completed
 | 
						|
 | 
						|
 | 
						|
class DataUtil(object):
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_knob_bounds(knob_labels, session):
 | 
						|
        minvals = []
 | 
						|
        maxvals = []
 | 
						|
        for _, knob in enumerate(knob_labels):
 | 
						|
            knob_object = KnobCatalog.objects.get(dbms=session.dbms, name=knob, tunable=True)
 | 
						|
            knob_session_object = SessionKnob.objects.filter(knob=knob_object, session=session,
 | 
						|
                                                             tunable=True)
 | 
						|
            if knob_object.vartype is VarType.ENUM:
 | 
						|
                enumvals = knob_object.enumvals.split(',')
 | 
						|
                minval = 0
 | 
						|
                maxval = len(enumvals) - 1
 | 
						|
            elif knob_object.vartype is VarType.BOOL:
 | 
						|
                minval = 0
 | 
						|
                maxval = 1
 | 
						|
            elif knob_session_object.exists():
 | 
						|
                minval = float(knob_session_object[0].minval)
 | 
						|
                maxval = float(knob_session_object[0].maxval)
 | 
						|
            else:
 | 
						|
                minval = float(knob_object.minval)
 | 
						|
                maxval = float(knob_object.maxval)
 | 
						|
            minvals.append(minval)
 | 
						|
            maxvals.append(maxval)
 | 
						|
        return np.array(minvals), np.array(maxvals)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def aggregate_data(results, ignore=None):
 | 
						|
        if ignore is None:
 | 
						|
            ignore = ['range_test']
 | 
						|
        knob_labels = sorted(JSONUtil.loads(results[0].knob_data.data).keys())
 | 
						|
        metric_labels = sorted(JSONUtil.loads(results[0].metric_data.data).keys())
 | 
						|
        X_matrix = []
 | 
						|
        y_matrix = []
 | 
						|
        rowlabels = []
 | 
						|
 | 
						|
        for result in results:
 | 
						|
            if any(symbol in result.metric_data.name for symbol in ignore):
 | 
						|
                continue
 | 
						|
            param_data = JSONUtil.loads(result.knob_data.data)
 | 
						|
            if len(param_data) != len(knob_labels):
 | 
						|
                raise Exception(
 | 
						|
                    ("Incorrect number of knobs "
 | 
						|
                     "(expected={}, actual={})").format(len(knob_labels),
 | 
						|
                                                        len(param_data)))
 | 
						|
 | 
						|
            metric_data = JSONUtil.loads(result.metric_data.data)
 | 
						|
            if len(metric_data) != len(metric_labels):
 | 
						|
                raise Exception(
 | 
						|
                    ("Incorrect number of metrics "
 | 
						|
                     "(expected={}, actual={})").format(len(metric_labels),
 | 
						|
                                                        len(metric_data)))
 | 
						|
 | 
						|
            X_matrix.append([param_data[l] for l in knob_labels])
 | 
						|
            y_matrix.append([metric_data[l] for l in metric_labels])
 | 
						|
            rowlabels.append(result.pk)
 | 
						|
        return {
 | 
						|
            'X_matrix': np.array(X_matrix, dtype=np.float64),
 | 
						|
            'y_matrix': np.array(y_matrix, dtype=np.float64),
 | 
						|
            'rowlabels': rowlabels,
 | 
						|
            'X_columnlabels': knob_labels,
 | 
						|
            'y_columnlabels': metric_labels,
 | 
						|
        }
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def combine_duplicate_rows(X_matrix, y_matrix, rowlabels):
 | 
						|
        X_unique, idxs, invs, cts = np.unique(X_matrix,
 | 
						|
                                              return_index=True,
 | 
						|
                                              return_inverse=True,
 | 
						|
                                              return_counts=True,
 | 
						|
                                              axis=0)
 | 
						|
        num_unique = X_unique.shape[0]
 | 
						|
        if num_unique == X_matrix.shape[0]:
 | 
						|
            # No duplicate rows
 | 
						|
 | 
						|
            # For consistency, tuple the rowlabels
 | 
						|
            rowlabels = np.array([tuple([x]) for x in rowlabels])  # pylint: disable=bad-builtin,deprecated-lambda
 | 
						|
            return X_matrix, y_matrix, rowlabels
 | 
						|
 | 
						|
        # Combine duplicate rows
 | 
						|
        y_unique = np.empty((num_unique, y_matrix.shape[1]))
 | 
						|
        rowlabels_unique = np.empty(num_unique, dtype=tuple)
 | 
						|
        ix = np.arange(X_matrix.shape[0])
 | 
						|
        for i, count in enumerate(cts):
 | 
						|
            if count == 1:
 | 
						|
                y_unique[i, :] = y_matrix[idxs[i], :]
 | 
						|
                rowlabels_unique[i] = (rowlabels[idxs[i]],)
 | 
						|
            else:
 | 
						|
                dup_idxs = ix[invs == i]
 | 
						|
                y_unique[i, :] = np.median(y_matrix[dup_idxs, :], axis=0)
 | 
						|
                rowlabels_unique[i] = tuple(rowlabels[dup_idxs])
 | 
						|
        return X_unique, y_unique, rowlabels_unique
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def clean_metric_data(metric_matrix, metric_labels, useful_views):
 | 
						|
        # Make metric_labels identical to useful_labels (if not None)
 | 
						|
        if useful_views is None:
 | 
						|
            return metric_matrix, metric_labels
 | 
						|
 | 
						|
        useful_labels = []
 | 
						|
        for label in metric_labels:
 | 
						|
            for view in useful_views:
 | 
						|
                if view in label:
 | 
						|
                    useful_labels.append(label)
 | 
						|
                    break
 | 
						|
 | 
						|
        missing_columns = sorted(set(useful_labels) - set(metric_labels))
 | 
						|
        unused_columns = set(metric_labels) - set(useful_labels)
 | 
						|
        LOG.debug("clean_metric_data: added %d metrics and removed %d metric.",
 | 
						|
                  len(missing_columns), len(unused_columns))
 | 
						|
        default_val = 0
 | 
						|
        useful_labels_size = len(useful_labels)
 | 
						|
        matrix = np.ones((len(metric_matrix), useful_labels_size)) * default_val
 | 
						|
        metric_labels_dict = {n: i for i, n in enumerate(metric_labels)}
 | 
						|
        # column labels in matrix has the same order as ones in useful_labels
 | 
						|
        # missing values are filled with default_val
 | 
						|
        for i, metric_name in enumerate(useful_labels):
 | 
						|
            if metric_name in metric_labels_dict:
 | 
						|
                index = metric_labels_dict[metric_name]
 | 
						|
                matrix[:, i] = metric_matrix[:, index]
 | 
						|
        LOG.debug("clean_metric_data: final ~ matrix: %s, labels: %s", matrix.shape,
 | 
						|
                  useful_labels_size)
 | 
						|
        return matrix, useful_labels
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def clean_knob_data(knob_matrix, knob_labels, sessions):
 | 
						|
        # Filter and amend knob_matrix and knob_labels according to the tunable knobs in the session
 | 
						|
        knob_matrix = np.array(knob_matrix)
 | 
						|
        session_knobs = []
 | 
						|
        knob_cat = []
 | 
						|
        for session in sessions:
 | 
						|
            knobs_for_this_session = SessionKnob.objects.get_knobs_for_session(session)
 | 
						|
            for knob in knobs_for_this_session:
 | 
						|
                if knob['name'] not in knob_cat:
 | 
						|
                    session_knobs.append(knob)
 | 
						|
            knob_cat = [k['name'] for k in session_knobs]
 | 
						|
 | 
						|
        if len(knob_cat) == 0 or knob_cat == knob_labels:
 | 
						|
            # Nothing to do!
 | 
						|
            return knob_matrix, knob_labels
 | 
						|
 | 
						|
        LOG.info("session_knobs: %s, knob_labels: %s, missing: %s, extra: %s", len(knob_cat),
 | 
						|
                 len(knob_labels), len(set(knob_cat) - set(knob_labels)),
 | 
						|
                 len(set(knob_labels) - set(knob_cat)))
 | 
						|
 | 
						|
        nrows = knob_matrix.shape[0]  # pylint: disable=unsubscriptable-object
 | 
						|
        new_labels = []
 | 
						|
        new_columns = []
 | 
						|
 | 
						|
        for knob in session_knobs:
 | 
						|
            knob_name = knob['name']
 | 
						|
            if knob_name not in knob_labels:
 | 
						|
                # Add missing column initialized to knob's default value
 | 
						|
                default_val = knob['default']
 | 
						|
                try:
 | 
						|
                    if knob['vartype'] == VarType.ENUM:
 | 
						|
                        default_val = knob['enumvals'].split(',').index(default_val)
 | 
						|
                    elif knob['vartype'] == VarType.BOOL:
 | 
						|
                        default_val = str(default_val).lower() in ("on", "true", "yes", "0")
 | 
						|
                    else:
 | 
						|
                        default_val = float(default_val)
 | 
						|
                except ValueError:
 | 
						|
                    LOG.warning("Error parsing knob '%s' default value: %s. Setting default to 0.",
 | 
						|
                                knob_name, default_val, exc_info=True)
 | 
						|
                    default_val = 0
 | 
						|
                new_col = np.ones((nrows, 1), dtype=float) * default_val
 | 
						|
                new_lab = knob_name
 | 
						|
            else:
 | 
						|
                index = knob_labels.index(knob_name)
 | 
						|
                new_col = knob_matrix[:, index].reshape(-1, 1)
 | 
						|
                new_lab = knob_labels[index]
 | 
						|
 | 
						|
            new_labels.append(new_lab)
 | 
						|
            new_columns.append(new_col)
 | 
						|
 | 
						|
        new_matrix = np.hstack(new_columns).reshape(nrows, -1)
 | 
						|
        LOG.debug("Cleaned matrix: %s, knobs (%s): %s", new_matrix.shape,
 | 
						|
                  len(new_labels), new_labels)
 | 
						|
 | 
						|
        assert new_labels == knob_cat, \
 | 
						|
            "Expected knobs: {}\nActual knobs:  {}\n".format(
 | 
						|
                knob_cat, new_labels)
 | 
						|
        assert new_matrix.shape == (nrows, len(knob_cat)), \
 | 
						|
            "Expected shape: {}, Actual shape:  {}".format(
 | 
						|
                (nrows, len(knob_cat)), new_matrix.shape)
 | 
						|
 | 
						|
        return new_matrix, new_labels
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def dummy_encoder_helper(featured_knobs, dbms):
 | 
						|
        n_values = []
 | 
						|
        cat_knob_indices = []
 | 
						|
        cat_knob_names = []
 | 
						|
        noncat_knob_names = []
 | 
						|
        binary_knob_indices = []
 | 
						|
        dbms_info = DBMSCatalog.objects.filter(pk=dbms.pk)
 | 
						|
 | 
						|
        if len(dbms_info) == 0:
 | 
						|
            raise Exception("DBMSCatalog cannot find dbms {}".format(dbms.full_name()))
 | 
						|
        full_dbms_name = dbms_info[0]
 | 
						|
 | 
						|
        for i, knob_name in enumerate(featured_knobs):
 | 
						|
            # knob can be uniquely identified by (dbms, knob_name)
 | 
						|
            knobs = KnobCatalog.objects.filter(name=knob_name,
 | 
						|
                                               dbms=dbms)
 | 
						|
            if len(knobs) == 0:
 | 
						|
                raise Exception(
 | 
						|
                    "KnobCatalog cannot find knob of name {} in {}".format(
 | 
						|
                        knob_name, full_dbms_name))
 | 
						|
            knob = knobs[0]
 | 
						|
            # check if knob is ENUM
 | 
						|
            if knob.vartype == VarType.ENUM:
 | 
						|
                # enumvals is a comma delimited list
 | 
						|
                enumvals = knob.enumvals.split(",")
 | 
						|
                if len(enumvals) > 2:
 | 
						|
                    # more than 2 values requires dummy encoding
 | 
						|
                    n_values.append(len(enumvals))
 | 
						|
                    cat_knob_indices.append(i)
 | 
						|
                    cat_knob_names.append(knob_name)
 | 
						|
                else:
 | 
						|
                    # knob is binary
 | 
						|
                    noncat_knob_names.append(knob_name)
 | 
						|
                    binary_knob_indices.append(i)
 | 
						|
            else:
 | 
						|
                if knob.vartype == VarType.BOOL:
 | 
						|
                    binary_knob_indices.append(i)
 | 
						|
                noncat_knob_names.append(knob_name)
 | 
						|
 | 
						|
        n_values = np.array(n_values)
 | 
						|
        cat_knob_indices = np.array(cat_knob_indices)
 | 
						|
        categorical_info = {'n_values': n_values,
 | 
						|
                            'categorical_features': cat_knob_indices,
 | 
						|
                            'cat_columnlabels': cat_knob_names,
 | 
						|
                            'noncat_columnlabels': noncat_knob_names,
 | 
						|
                            'binary_vars': binary_knob_indices}
 | 
						|
        return categorical_info
 | 
						|
 | 
						|
 | 
						|
class ConversionUtil(object):
 | 
						|
 | 
						|
    DEFAULT_BYTES_SYSTEM = (
 | 
						|
        (1024 ** 5, 'PB'),
 | 
						|
        (1024 ** 4, 'TB'),
 | 
						|
        (1024 ** 3, 'GB'),
 | 
						|
        (1024 ** 2, 'MB'),
 | 
						|
        (1024 ** 1, 'kB'),
 | 
						|
        (1024 ** 0, 'B'),
 | 
						|
    )
 | 
						|
 | 
						|
    DEFAULT_TIME_SYSTEM = (
 | 
						|
        (1000 * 60 * 60 * 24, 'd'),
 | 
						|
        (1000 * 60 * 60, 'h'),
 | 
						|
        (1000 * 60, 'min'),
 | 
						|
        (1000, 's'),
 | 
						|
        (1, 'ms'),
 | 
						|
    )
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_raw_size(value, system):
 | 
						|
        for factor, suffix in system:
 | 
						|
            if value.endswith(suffix):
 | 
						|
                if len(value) == len(suffix):
 | 
						|
                    amount = 1
 | 
						|
                else:
 | 
						|
                    try:
 | 
						|
                        amount = int(value[:-len(suffix)])
 | 
						|
                    except ValueError:
 | 
						|
                        continue
 | 
						|
                return amount * factor
 | 
						|
        return None
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_human_readable(value, system):
 | 
						|
        from hurry.filesize import size
 | 
						|
        return size(value, system=system)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_human_readable2(value, system, min_suffix):
 | 
						|
        # Converts the value to larger units only if there is no loss of resolution.
 | 
						|
        # pylint: disable=line-too-long
 | 
						|
        # From https://github.com/le0pard/pgtune/blob/master/webpack/components/configurationView/index.jsx#L74
 | 
						|
        # pylint: enable=line-too-long
 | 
						|
        min_factor = None
 | 
						|
        unit = None
 | 
						|
        mod_system = []
 | 
						|
        for i, (factor, suffix) in enumerate(system):
 | 
						|
            if suffix == min_suffix:
 | 
						|
                if value < factor:
 | 
						|
                    if i + 1 >= len(system):
 | 
						|
                        LOG.warning("Error converting value '%s': min_suffix='%s' at index='%s' "
 | 
						|
                                    "is already the smallest suffix.", value, min_suffix, i)
 | 
						|
                        return value
 | 
						|
 | 
						|
                    min_suffix = system[i + 1][1]
 | 
						|
                    LOG.warning('The value is smaller than the min factor: %s < %s (1%s). '
 | 
						|
                                'Setting min_suffix=%s...', value, factor, suffix, min_suffix)
 | 
						|
                else:
 | 
						|
                    min_factor = factor
 | 
						|
                    unit = min_suffix
 | 
						|
                    value = math.floor(float(value) / min_factor)
 | 
						|
                    break
 | 
						|
 | 
						|
            mod_system.append((factor, suffix))
 | 
						|
 | 
						|
        if min_factor is None:
 | 
						|
            raise ValueError('Invalid min suffix for system: suffix={}, system={}'.format(
 | 
						|
                min_suffix, system))
 | 
						|
 | 
						|
        for factor, suffix in mod_system:
 | 
						|
            adj_factor = factor / min_factor
 | 
						|
            if value % adj_factor == 0:
 | 
						|
                value = math.floor(float(value) / adj_factor)
 | 
						|
                unit = suffix
 | 
						|
                break
 | 
						|
 | 
						|
        return '{}{}'.format(int(value), unit)
 | 
						|
 | 
						|
 | 
						|
class LabelUtil(object):
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def style_labels(label_map, style=LabelStyleType.DEFAULT_STYLE):
 | 
						|
        style_labels = {}
 | 
						|
        for name, verbose_name in list(label_map.items()):
 | 
						|
            if style == LabelStyleType.TITLE:
 | 
						|
                label = verbose_name.title()
 | 
						|
            elif style == LabelStyleType.CAPFIRST:
 | 
						|
                label = capfirst(verbose_name)
 | 
						|
            elif style == LabelStyleType.LOWER:
 | 
						|
                label = verbose_name.lower()
 | 
						|
            else:
 | 
						|
                raise Exception('Invalid style: {}'.format(style))
 | 
						|
            if style != LabelStyleType.LOWER and 'dbms' in label.lower():
 | 
						|
                label = label.replace('dbms', 'DBMS')
 | 
						|
                label = label.replace('Dbms', 'DBMS')
 | 
						|
            style_labels[name] = str(label)
 | 
						|
        return style_labels
 | 
						|
 | 
						|
 | 
						|
def dump_debug_info(session, pretty_print=False):
 | 
						|
    files = {}
 | 
						|
 | 
						|
    # Session
 | 
						|
    session_values = Session.objects.filter(pk=session.pk).values()[0]
 | 
						|
    session_values['dbms'] = session.dbms.full_name
 | 
						|
    session_values['hardware'] = session.hardware.name
 | 
						|
 | 
						|
    # Session knobs
 | 
						|
    knob_instances = SessionKnob.objects.filter(
 | 
						|
        session=session, tunable=True).select_related('knob')
 | 
						|
    knob_values = list(knob_instances.values())
 | 
						|
    for knob, knob_dict in zip(knob_instances, knob_values):
 | 
						|
        assert knob.pk == knob_dict['id']
 | 
						|
        knob_dict['knob'] = knob.name
 | 
						|
    session_values['knobs'] = knob_values
 | 
						|
 | 
						|
    # Save binary field types to separate files
 | 
						|
    binary_fields = [
 | 
						|
        'ddpg_actor_model',
 | 
						|
        'ddpg_critic_model',
 | 
						|
        'ddpg_reply_memory',
 | 
						|
        'dnn_model',
 | 
						|
    ]
 | 
						|
    for bf in binary_fields:
 | 
						|
        if session_values[bf]:
 | 
						|
            filename = os.path.join('binaries', '{}.pickle'.format(bf))
 | 
						|
            content = session_values[bf]
 | 
						|
            session_values[bf] = filename
 | 
						|
            files[filename] = content
 | 
						|
 | 
						|
    files['session.json'] = session_values
 | 
						|
 | 
						|
    # Results from session
 | 
						|
    result_instances = Result.objects.filter(session=session).select_related(
 | 
						|
        'knob_data', 'metric_data').order_by('creation_time')
 | 
						|
    results = []
 | 
						|
 | 
						|
    for result, result_dict in zip(result_instances, result_instances.values()):
 | 
						|
        assert result.pk == result_dict['id']
 | 
						|
        result_dict = OrderedDict(result_dict)
 | 
						|
        next_config = result.next_configuration or '{}'
 | 
						|
        result_dict['next_configuration'] = JSONUtil.loads(next_config)
 | 
						|
 | 
						|
        tasks = {}
 | 
						|
        task_ids = result.task_ids
 | 
						|
        task_ids = task_ids.split(',') if task_ids else []
 | 
						|
        for task_id in task_ids:
 | 
						|
            task = TaskMeta.objects.filter(task_id=task_id).values()
 | 
						|
            task = task[0] if task else None
 | 
						|
            tasks[task_id] = task
 | 
						|
        result_dict['tasks'] = tasks
 | 
						|
 | 
						|
        knob_data = result.knob_data.data or '{}'
 | 
						|
        metric_data = result.metric_data.data or '{}'
 | 
						|
        result_dict['knob_data'] = JSONUtil.loads(knob_data)
 | 
						|
        result_dict['metric_data'] = JSONUtil.loads(metric_data)
 | 
						|
        results.append(result_dict)
 | 
						|
 | 
						|
    files['results.json'] = results
 | 
						|
 | 
						|
    # Log messages written to the database using django-db-logger
 | 
						|
    logs = StatusLog.objects.filter(create_datetime__gte=session.creation_time)
 | 
						|
    logger_names = logs.order_by().values_list('logger_name', flat=True).distinct()
 | 
						|
 | 
						|
    # Write log files at app scope (e.g., django, website, celery)
 | 
						|
    logger_names = set([l.split('.', 1)[0] for l in logger_names])
 | 
						|
 | 
						|
    for logger_name in logger_names:
 | 
						|
        log_values = list(logs.filter(logger_name__startswith=logger_name).order_by(
 | 
						|
            'create_datetime').values())
 | 
						|
        for lv in log_values:
 | 
						|
            lv['level'] = logging.getLevelName(lv['level'])
 | 
						|
        files['logs/{}.log'.format(logger_name)] = log_values
 | 
						|
 | 
						|
    timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
 | 
						|
    root = 'debug_{}'.format(timestamp)
 | 
						|
 | 
						|
    mtime = time.time()
 | 
						|
    tarstream = BytesIO()
 | 
						|
    with tarfile.open(mode='w:gz', fileobj=tarstream) as tar:
 | 
						|
        for filename, content in files.items():  # pylint: disable=not-an-iterable
 | 
						|
            if isinstance(content, (dict, list)):
 | 
						|
                content = JSONUtil.dumps(content, pprint=pretty_print)
 | 
						|
            if isinstance(content, str):
 | 
						|
                content = content.encode('utf-8')
 | 
						|
            assert isinstance(content, bytes), (filename, type(content))
 | 
						|
            bio = BytesIO(content)
 | 
						|
            path = os.path.join(root, filename)
 | 
						|
            tarinfo = tarfile.TarInfo(name=path)
 | 
						|
            tarinfo.size = len(bio.getvalue())
 | 
						|
            tarinfo.mtime = mtime
 | 
						|
            tar.addfile(tarinfo, bio)
 | 
						|
 | 
						|
    tarstream.seek(0)
 | 
						|
    return tarstream, root
 | 
						|
 | 
						|
 | 
						|
def create_user(username, password, email=None, superuser=False):
 | 
						|
    user = User.objects.filter(username=username).first()
 | 
						|
    if user:
 | 
						|
        created = False
 | 
						|
    else:
 | 
						|
        if superuser:
 | 
						|
            email = email or '{}@noemail.com'.format(username)
 | 
						|
            _create_user = User.objects.create_superuser
 | 
						|
        else:
 | 
						|
            _create_user = User.objects.create_user
 | 
						|
 | 
						|
        user = _create_user(username=username, password=password, email=email)
 | 
						|
        created = True
 | 
						|
 | 
						|
    return user, created
 | 
						|
 | 
						|
 | 
						|
def delete_user(username):
 | 
						|
    user = User.objects.filter(username=username).first()
 | 
						|
    if user:
 | 
						|
        delete_info = user.delete()
 | 
						|
        deleted = True
 | 
						|
    else:
 | 
						|
        delete_info = None
 | 
						|
        deleted = False
 | 
						|
 | 
						|
    return delete_info, deleted
 | 
						|
 | 
						|
 | 
						|
def model_to_dict2(m, exclude=None):
 | 
						|
    exclude = exclude or []
 | 
						|
    d = {}
 | 
						|
    for f in m._meta.fields:  # pylint: disable=protected-access
 | 
						|
        fname = f.name
 | 
						|
        if fname not in exclude:
 | 
						|
            d[fname] = getattr(m, fname, None)
 | 
						|
    return d
 | 
						|
 | 
						|
 | 
						|
def check_and_run_celery():
 | 
						|
    celery_status = os.popen('python3 manage.py celery inspect ping').read()
 | 
						|
    if 'pong' in celery_status:
 | 
						|
        return 'celery is running'
 | 
						|
 | 
						|
    rabbitmq_url = common.BROKER_URL.split('@')[-1]
 | 
						|
    hostname = rabbitmq_url.split(':')[0]
 | 
						|
    port = rabbitmq_url.split(':')[1].split('/')[0]
 | 
						|
    rabbitmq_status = os.popen('telnet {} {}'.format(hostname, port)).read()
 | 
						|
    LOG.info(rabbitmq_status)
 | 
						|
 | 
						|
    retries = 0
 | 
						|
    while retries < 5:
 | 
						|
        LOG.warning('Celery is not running.')
 | 
						|
        retries += 1
 | 
						|
        call_command('stopcelery')
 | 
						|
        os.popen('python3 manage.py startcelery &')
 | 
						|
        time.sleep(30 * retries)
 | 
						|
        celery_status = os.popen('python3 manage.py celery inspect ping').read()
 | 
						|
        if 'pong' in celery_status:
 | 
						|
            LOG.info('Successfully start celery.')
 | 
						|
            return 'celery stopped but is restarted successfully'
 | 
						|
    LOG.warning('Cannot restart celery.')
 | 
						|
    return 'celery stopped and cannot be restarted'
 | 
						|
 | 
						|
 | 
						|
def git_hash():
 | 
						|
    sha = ''
 | 
						|
    if os.path.exists('/app/.git_commit'):
 | 
						|
        with open('/app/.git_commit', 'r') as f:
 | 
						|
            lines = f.read().strip().split('\n')
 | 
						|
        for line in lines:
 | 
						|
            if line.startswith('base='):
 | 
						|
                sha = line.strip().split('=', 1)[1]
 | 
						|
                break
 | 
						|
    else:
 | 
						|
        try:
 | 
						|
            p = Popen("git log -1 --format=format:%H", shell=True, stdout=PIPE, stderr=PIPE)
 | 
						|
            sha = p.communicate()[0].decode('utf-8')
 | 
						|
        except OSError as e:
 | 
						|
            LOG.warning("Failed to get git commit hash.\n\n%s\n\n", e, exc_info=True)
 | 
						|
 | 
						|
    return sha
 |