summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nemesis/causal_event_loop.py24
-rw-r--r--nemesis/html_gen.py79
-rwxr-xr-xnemesis/nemesis.py37
3 files changed, 109 insertions, 31 deletions
diff --git a/nemesis/causal_event_loop.py b/nemesis/causal_event_loop.py
index 8514dfc..1a9be98 100644
--- a/nemesis/causal_event_loop.py
+++ b/nemesis/causal_event_loop.py
@@ -52,6 +52,8 @@ class CausalEventLoop(asyncio.SelectorEventLoop):
_time_fd_registered = dict()
_time_entered_coro = None
_accumulated_time = 0
+ _num_processed_tasks = 0
+ _total_time_in_coro = 0
_time_dilation = 1.0
_processing = collections.deque()
@@ -63,12 +65,22 @@ class CausalEventLoop(asyncio.SelectorEventLoop):
_select = self._selector.select
def select(timeout: float):
- return _select(timeout / self._time_dilation)
+ return _select(None if timeout == None else timeout / self._time_dilation)
self._selector.select = select
- def _set_dilation(self, dilation):
+ def set_dilation(self, dilation):
self._time_dilation = dilation
+
+ # reset experiment counters
self._time_entered_coro = None
+ self._num_processed_tasks = 0
+ self._total_time_in_coro = 0
+
+ def get_time_in_coro(self):
+ return self.total_time_in_coro
+
+ def get_num_processed_tasks(self):
+ return self._num_processed_tasks
def _get_time_in_coro(self):
t = self._accumulated_time
@@ -168,10 +180,16 @@ class CausalEventLoop(asyncio.SelectorEventLoop):
logger.warning('Executing %s took %.3f seconds',
_format_handle(handle), dt * 1/self._time_dilation)
+ time_inside_coro = self._get_time_in_coro()
+
+ # update stat counters
+ self._total_time_in_coro += time_inside_coro
+ self._num_processed_tasks += 1
+
# calculate the amount of time to slow this callback down by. We only want
# to add time proportionate to the amount of time spent OUTSIDE of the
# coroutine of interest.
- time_outside_coro = dt - self._get_time_in_coro()
+ time_outside_coro = dt - time_inside_coro
delay = time_outside_coro * (1 / self._time_dilation - 1)
t0 = super().time()
# do it this way so the python interpreter still receives signals.
diff --git a/nemesis/html_gen.py b/nemesis/html_gen.py
index ea7df15..44c7d56 100644
--- a/nemesis/html_gen.py
+++ b/nemesis/html_gen.py
@@ -1,38 +1,83 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
+import hashlib
+
+def get_color(name):
+ hash_object = hashlib.md5(name.encode())
+ color_index = int(hash_object.hexdigest(), 16) % 360
+ return f'hsl({color_index}, 100%, 50%)'
def plot_results(results, filename):
- fig = make_subplots(rows=len(results), cols=1, shared_xaxes=True)
+ fig = make_subplots(rows=3, cols=1, shared_xaxes=True)
for i, (coro_name, x_values) in enumerate(results.items(), start=1):
x_list = []
- y_list = []
- hover_text = []
+ y_starve_list = []
+ y_throughput_list = []
+ y_time_in_coro_list = []
+ starve_hover_text = []
+ throughput_hover_text = []
for speedup, experiments in x_values.items():
for experiment in experiments:
- y_value = sum(experiment.values())
+ starve = experiment["time_starving"]
+ processed = experiment["processed"]
+ time_in_coro = experiment["time_in_coro"]
+ real_duration = experiment["real_duration"]
x_list.append(speedup)
- y_list.append(y_value)
- breakdown = "<br>".join([f" {key[0]} ({key[1]}): {round(value, 4)}" for key, value in experiment.items()])
- hover_text.append(f"{coro_name}<br>Speedup: {speedup}<br>Total Wait: {round(y_value, 4)}<br>Breakdown:<br>{breakdown}")
+ # handle starve graph
+ starve_time = sum(starve.values())
+ y_starve_list.append(starve_time)
+
+ breakdown = "<br>".join([f" {key[0]} ({key[1]}): {round(value, 4)}" for key, value in starve.items()])
+ starve_hover_text.append(f"({speedup}, {round(starve_time, 4)})<br>{coro_name}<br>Breakdown:<br>{breakdown}")
+
+ # handle throughput graph
+ throughput = (speedup * processed) / real_duration
+ y_throughput_list.append(throughput)
+
+ throughput_hover_text.append(f"({speedup}, {round(throughput, 4 )})<br>{coro_name}<br>True experiment duration: {round(real_duration, 4)}<br>Total Processed: {processed}")
+
+ # handle time in coro graph
+ y_time_in_coro_list.append(time_in_coro / speedup)
fig.add_trace(go.Scatter(
x=x_list,
- y=y_list,
+ y=y_starve_list,
mode='markers',
name=coro_name,
hoverinfo='text',
- hovertext=hover_text,
- ))
-
- fig.update_layout(
- title="Potential Speedups for ",
- xaxis_title="Speedup (times faster)",
- yaxis_title="Total Wait (seconds)",
- showlegend=True,
- )
+ hovertext=starve_hover_text,
+ marker=dict(color=get_color(coro_name)),
+ showlegend=True,
+ ), row=1, col=1)
+
+ fig.add_trace(go.Scatter(
+ x=x_list,
+ y=y_throughput_list,
+ mode='markers',
+ name=coro_name,
+ hovertext=throughput_hover_text,
+ marker=dict(color=get_color(coro_name)),
+ showlegend=False,
+ ), row=2, col=1)
+
+ fig.add_trace(go.Scatter(
+ x=x_list,
+ y=y_time_in_coro_list,
+ mode='markers',
+ name=coro_name,
+ marker=dict(color=get_color(coro_name)),
+ showlegend=False,
+ ), row=3, col=1)
+
+ fig.update_xaxes(title_text="Speedup (times faster)", row=1, col=1)
+ fig.update_xaxes(title_text="Speedup (times faster)", row=2, col=1)
+ fig.update_xaxes(title_text="Speedup (times faster)", row=3, col=1)
+ fig.update_yaxes(title_text="Total Starvation Time (seconds)", row=1, col=1)
+ fig.update_yaxes(title_text="Total Throughput (handles per second)", row=2, col=1)
+ fig.update_yaxes(title_text="Synchronous Time in Coroutine (seconds)", row=3, col=1)
fig.write_html(filename)
diff --git a/nemesis/nemesis.py b/nemesis/nemesis.py
index 46e8094..ce5e0a6 100755
--- a/nemesis/nemesis.py
+++ b/nemesis/nemesis.py
@@ -72,6 +72,10 @@ class Nemesis(object):
experiment_coro = None
# the speedup of the current experiment
experiment_spdp = None
+ # the total time this experiment has been running
+ experiment_time = None
+ # The number of seconds this performance experiment should run
+ experiment_dura = None
# results from previous experiments. Keys represent names of coroutines.
results = defaultdict(lambda: defaultdict(lambda: []))
@@ -82,9 +86,6 @@ class Nemesis(object):
# The base duration of each performance experiment
e_duration = None
- # The number of seconds remaining in this performance experiment.
- r_duration = None
-
# A mapping of event loops to the previous running coroutine.
prev_coro = defaultdict(lambda: None)
@@ -93,7 +94,6 @@ class Nemesis(object):
Nemesis.signal_interval = signal_interval
Nemesis.e_duration = e_duration
Nemesis.filename = filename
- Nemesis.r_duration = 0
@staticmethod
def start():
@@ -112,24 +112,39 @@ class Nemesis(object):
@staticmethod
def _start_experiment(coro, speedup):
- Nemesis.r_duration = Nemesis.e_duration * speedup
+ Nemesis.experiment_dura = Nemesis.e_duration * speedup
Nemesis.prev_coro = defaultdict(lambda: None)
Nemesis.experiment_coro = coro
Nemesis.experiment_spdp = speedup
+ Nemesis.experiment_time = 0
loops = Nemesis._get_event_loops()
for l in loops:
if not isinstance(l, CausalEventLoop):
raise RuntimeException("Nemesis requires a custom event loop to insert slowdowns. It does not work on programs which change the event loop policy.")
- l._set_dilation(1.0 / speedup)
+ l.set_dilation(1.0 / speedup)
Nemesis.experiment_data = Experiment(loops)
@staticmethod
def _stop_experiment():
if Nemesis.experiment_data is not None:
- print(f'finished running experiment on {Nemesis.experiment_coro}')
- results = Nemesis.experiment_data.samples
+ loops = Nemesis.experiment_data.get_loops()
+
+ num_processed = 0
+ sec_in_coro = 0
+
+ for loop in loops:
+ num_processed += loop.get_num_processed_tasks()
+ sec_in_coro += loop.get_num_processed_tasks()
+
+ print(f'Ran {Nemesis.experiment_coro} at {Nemesis.experiment_spdp} speed')
+ results = {
+ "processed": num_processed,
+ "time_starving": Nemesis.experiment_data.samples,
+ "time_in_coro": sec_in_coro,
+ "real_duration": Nemesis.experiment_time,
+ }
Nemesis.results[Nemesis.experiment_coro][Nemesis.experiment_spdp].append(results)
del Nemesis.experiment_data
@@ -139,7 +154,6 @@ class Nemesis(object):
passed_time = curr_sample - Nemesis.last_sample
Nemesis.last_sample = curr_sample
-
if getattr(Nemesis, 'experiment_data', None):
loops = Nemesis.experiment_data.get_loops()
exp_coro = Nemesis.experiment_coro
@@ -157,8 +171,9 @@ class Nemesis(object):
handles = Nemesis._get_waiting_handles(loop)
Nemesis.experiment_data.add_handles(handles, loop, passed_time)
- Nemesis.r_duration -= passed_time
- if (Nemesis.r_duration <= 0):
+
+ Nemesis.experiment_time += passed_time
+ if (Nemesis.experiment_dura <= Nemesis.experiment_time):
Nemesis._stop_experiment()
else: