Beispiel #1
0
    def compute_tick(self, model='conservative', estimator='mean'):
        """
        Computes the tick, T of the network: a parameter that is measured
        from the mean and standard deviation of latencies in the network.

        The howard model proposes T = 2(mu + 2sd)
        The bailis model proposes T = 10mu
        The conservative model proposes T = 6(mu + 4sd)
        The optimistic model proposes   T = 2(mu + 4sd)

        For Raft parameters are usually set as follows:
            - heartbeat interval = T/2
            - election timeout = (T, 2T)

        Anti-Entropy intervals can also be specified via T.

        The estimator specifies how to choose the mean and standard deviation
        from all the connections. Choices are mean, max, or min.
        """

        # Estimator mapping
        estimators = {
            'mean': mean,
            'max': max,
            'min': min,
        }

        # Select the estimator
        if estimator not in estimators:
            raise BadValue("Unknown estimator '{}', choose from {}".format(
                estimator, ", ".join(estimators.keys())))
        est = estimators[estimator]

        # Compute the latency mean and standard deviation
        lmu = est(map(lambda c: c.get_latency_mean(), self.iter_connections()))
        lsd = est(
            map(lambda c: c.get_latency_stddev(), self.iter_connections()))

        # Model mapping
        models = {
            'bailis': lambda mu, sd: 10 * mu,
            'howard': lambda mu, sd: 2 * (mu + (2 * sd)),
            'conservative': lambda mu, sd: 6 * (mu + (4 * sd)),
            'optimistic': lambda mu, sd: 2 * (mu + (4 * sd)),
        }

        # Select the model
        if model not in models:
            raise BadValue("Unknown model '{}', choose from {}".format(
                model, ", ".join(models.keys())))

        # Compute T with the model and return
        return models[model](lmu, lsd)
Beispiel #2
0
def create_messages_dataframe(results):
    """
    Creates a DataFrame of messages in order to analyze communications.
    """

    if isinstance(results, (list, tuple)):
        raise BadValue(
            "This analysis function works only on a single results object")

    # Specify the modes we want to count messages on
    # Specify the fields name in the sent/recv timeseries
    modes = ('sent', 'recv')
    fields = ['replica', 'timestamp', 'type', 'latency']

    def messages(results):
        """
        Inner generator for looping through the sent and recv series.
        """
        for mode in modes:
            for value in results[mode]:
                msg = dict(zip(fields, value))
                msg['recv'] = 1 if mode == 'recv' else 0
                msg['sent'] = 1 if mode == 'sent' else 0
                yield msg

    return pd.DataFrame(messages(results.results))
Beispiel #3
0
def details(results):
    """
    Returns a string with text formated details about the report.
    """

    if isinstance(results, (list, tuple)):
        raise BadValue(
            "This report function works only on a single results object"
        )

    banner = (
        "Simulation: {} (Cloudscope v{})\n"
        "{}\n\n"
        "Ran on: {} ({})\n\n"
        "Settings\n"
        "========\n"
    ).format(
        results.simulation, results.version, results.topology['meta']['description'],
        epochptime(results.timer['started']).strftime('%b %d, %Y at %H:%M %Z'),
        results.timer['elapsed'],
        results.randseed,
    )

    longest = max(len(key) for key in results.settings)
    frmt = "{{: <{0}}} {{: >12}}".format(longest)


    return banner + "\n".join([
        frmt.format(title_snaked(key), value)
        for key, value in results.settings.items()
    ])
Beispiel #4
0
 def colors(self, value):
     """
     Converts color strings into a color listing.
     """
     if isinstance(value, basestring):
         if value not in PALETTES:
             raise BadValue("'{}' is not a registered color palette")
         self._colors = copy(PALETTES[value])
     elif isinstance(value, list):
         self._colors = value
     else:
         self._colors = list(value)
Beispiel #5
0
def create_per_replica_dataframe(results):
    """
    Expects a single results object and creates a data frame, aggregating
    values on a per-replica basis rather than on a per experiment basis.
    """

    if isinstance(results, (list, tuple)):
        raise BadValue(
            "This analysis function works only on a single results object")

    # Set up the various data structures we will be using
    replicas = defaultdict(lambda: defaultdict(list))
    topology = results.topology
    config = results.settings
    series = results.results

    # Separate the series into per-replica series
    for key, values in series.iteritems():
        for value in values:
            # Append the item from the series to the correct replica series
            replicas[value[0]][key].append(value)

    # Create a table with each replica id
    table = []
    for replica, series in replicas.iteritems():
        row = {'replica': replica}

        # Perform per-replica aggregations for each series
        for key, values in series.iteritems():
            row.update(aggregator(key, values))

        # Add in topology information
        for node in topology['nodes']:
            if node['id'] == replica:
                row.update(node)
                break

        # Help with missing keys

        # Append the row to the table
        table.append(row)

    # Create the data frame and compute final aggregations
    df = pd.DataFrame(sorted(table, key=itemgetter('replica')))
    df['partially replicated writes'] = df['writes'] - df['visible writes']
    df['visibility ratio'] = df['visible writes'] / df['writes']

    # Remove the "unforked writes"
    if 'unforked writes' in df.columns:
        df['forked writes'] -= df['unforked writes']

    return df
Beispiel #6
0
def iter_results_values(results, *keys):
    """
    Collects all the values for a particular key or nested keys from all
    results in the results collection. Input can be either a Results object
    or a dictionary loaded from a JSON file.
    """

    try:
        results = iter(results)
    except TypeError:
        raise BadValue(
            "This analysis function requires a collection of results objects")

    for result in results:
        # Each result is a single Result object or a dict
        # Continue fetching value from each subkey
        yield result_value(result * keys)
Beispiel #7
0
def topology(results):
    """
    Returns a string with a text formatted description of the topology
    """

    if isinstance(results, (list, tuple)):
        raise BadValue(
            "This report function works only on a single results object"
        )

    topology = deepcopy(results.topology)
    nodes = topology['nodes']
    links = topology['links']

    for link in links:
        latency = link['latency']
        if link['connection'] == 'constant':
            # Convert the latency into a list
            latency = [latency]

        for rid in ('source', 'target'):
            node = nodes[link[rid]]

            if 'minlat' not in node:
                node['minlat'] = latency[0]
            else:

                node['minlat'] = min(node['minlat'], latency[0])

            if 'maxlat' not in node:
                node['maxlat'] = latency[-1]
            else:
                node['maxlat'] = max(node['maxlat'], latency[-1])

    output = []
    for node in sorted(nodes, key=itemgetter('id')):
        output.append(
            "{}: {} ({}, {}) {}-{}ms connection".format(
                node['id'], node['label'], node['location'],
                node['consistency'], node['minlat'], node['maxlat']
            )
        )
    return "\n".join(output)
Beispiel #8
0
def extract_graph(results, kind='graph_tool', **kwargs):
    """
    Extracts a graph from the node and edge extractor from the results.
    Kind can be either graph_tool (gt) or networkx (nx), returns a directed
    graph of either type with properties annotated on the graph, nodes, edges.
    """

    extractors = {
        'graph_tool': extract_graph_tool_graph,
        'gt': extract_graph_tool_graph,
        'networkx': extract_networkx_graph,
        'nx': extract_networkx_graph,
    }

    if kind not in extractors:
        raise BadValue(
            "Unknown graph kind '{}' chose from one of {}".format(
                kind, ", ".join(extractors.keys())
            )
        )

    return extractors[kind](results, **kwargs)
Beispiel #9
0
def create_per_experiment_dataframe(results):
    """
    Creates a DataFrame of aggregations per experiment rather than per replica
    by iterating through a list of results objects. This does not really work
    for a single results object, and so this function expects a list or tuple.
    """

    try:
        iter(results)
    except TypeError:
        raise BadValue(
            "This analysis function requires a collection of results objects")

    table = []
    for idx, result in enumerate(results):
        data = {'eid': "e{:0>2}".format(idx)}
        conf = result_value(result, 'settings')

        # Pull information from the configuration
        data['type'] = conf['type']
        data['users'] = conf['users']
        data['tick metric (T)'] = conf['tick_metric']
        data['mean latency (ms)'] = conf['latency_mean']
        data['latency range (ms)'] = conf['latency_range']
        data['standard deviation of latency (ms)'] = conf['latency_stddev']
        data['anti-entropy delay (ms)'] = conf['anti_entropy_delay']
        data['heartbeat interval (ms)'] = conf['heartbeat_interval']
        data['election timeout (ms, ms)'] = conf['election_timeout']
        data['T parameter model'] = conf['tick_param_model']
        data['conflict probability'] = conf['conflict_prob']
        data['sync probability'] = conf['sync_prob']
        data['local probability'] = conf['local_prob']

        # Aggregate the timeseries resuts data
        for key, values in result_value(result, 'results').iteritems():
            data.update(aggregator(key, values))

        # If we didn't do an aggregation from the time series, get it
        # directly from the messages and latencies objects.
        messages = result.messages.messages
        latencies = result.latencies

        if 'message types' not in data:
            data['message types'] = messages.get('sent', {})

        if 'sent' not in data:
            data['sent messages'] = sum(messages.get('sent', {}).values())

        if 'recv' not in data:
            data['recv messages'] = sum(messages.get('recv', {}).values())

        if 'dropped' not in data:
            data['dropped messages'] = sum(messages.get('drop', {}).values())

        # Get the simulation time from the results
        data['simulation time (secs)'] = result.timer[
            'finished'] - result.timer['started']

        table.append(data)

    df = pd.DataFrame(table)
    df = df.fillna(0)

    # Remove the "unforked writes"
    if 'unforked writes' in df.columns:
        df['inconsistent writes'] = df['forked writes'] - df['unforked writes']

    return df