|
|
|
|
|
|
|
# Monkey-patch os.fork() to produce a latency histogram on run completion.
|
|
|
|
# Requires 'hdrhsitograms' PyPI module.
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import os
|
|
|
|
import resource
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
|
|
|
|
import ansible.plugins.callback
|
|
|
|
|
|
|
|
try:
|
|
|
|
import hdrh.histogram
|
|
|
|
except ImportError:
|
|
|
|
hdrh = None
|
|
|
|
|
|
|
|
|
|
|
|
def get_fault_count(who=resource.RUSAGE_CHILDREN):
|
|
|
|
ru = resource.getrusage(who)
|
|
|
|
return ru.ru_minflt + ru.ru_majflt
|
|
|
|
|
|
|
|
|
|
|
|
class CallbackModule(ansible.plugins.callback.CallbackBase):
|
|
|
|
hist = None
|
|
|
|
|
|
|
|
def v2_playbook_on_start(self, playbook):
|
|
|
|
if self.hist is not None:
|
|
|
|
return
|
|
|
|
|
|
|
|
if hdrh and 'FORK_HISTOGRAM' in os.environ:
|
|
|
|
self.hist = hdrh.histogram.HdrHistogram(1, int(1e6*60), 3)
|
|
|
|
self.fork_latency_sum_usec = 0.0
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
def install(self):
|
|
|
|
self.faults_at_start = get_fault_count(resource.RUSAGE_SELF)
|
|
|
|
self.run_start_time = time.time()
|
|
|
|
self.real_fork = os.fork
|
|
|
|
os.fork = self.my_fork
|
|
|
|
|
|
|
|
self_fault_usec = 1.113664156753052
|
|
|
|
child_fault_usec = 4.734975610975617
|
|
|
|
|
|
|
|
dummy_heap_size = int(os.environ.get('FORK_STATS_FAKE_HEAP_MB', '0'))
|
|
|
|
dummy_heap = 'x' * (dummy_heap_size * 1048576)
|
|
|
|
|
|
|
|
def my_fork(self):
|
|
|
|
# doesnt count last child, oh well
|
|
|
|
now_faults = get_fault_count()
|
|
|
|
t0 = time.time()
|
|
|
|
try:
|
|
|
|
return self.real_fork()
|
|
|
|
finally:
|
|
|
|
latency_usec = (1e6 * (time.time() - t0))
|
|
|
|
self.fork_latency_sum_usec += latency_usec
|
|
|
|
self.hist.record_value(latency_usec)
|
|
|
|
|
|
|
|
def playbook_on_stats(self, stats):
|
|
|
|
if hdrh is None or 'FORK_HISTOGRAM' not in os.environ:
|
|
|
|
return
|
|
|
|
|
|
|
|
self_faults = get_fault_count(resource.RUSAGE_SELF) - self.faults_at_start
|
|
|
|
child_faults = get_fault_count()
|
|
|
|
run_duration_sec = time.time() - self.run_start_time
|
|
|
|
fault_wastage_usec = (
|
|
|
|
((self.self_fault_usec * self_faults) +
|
|
|
|
(self.child_fault_usec * child_faults))
|
|
|
|
)
|
|
|
|
fork_wastage = self.hist.get_total_count()
|
|
|
|
all_wastage_usec = ((2*self.fork_latency_sum_usec) + fault_wastage_usec)
|
|
|
|
|
|
|
|
print('--- Fork statistics ---')
|
|
|
|
print('Post-boot run duration: %.02f ms, %d total forks' % (
|
|
|
|
1000 * run_duration_sec,
|
|
|
|
self.hist.get_total_count(),
|
|
|
|
))
|
|
|
|
print('Self faults during boot: %d, post-boot: %d, avg %d/child' % (
|
|
|
|
self.faults_at_start,
|
|
|
|
self_faults,
|
|
|
|
self_faults / self.hist.get_total_count(),
|
|
|
|
))
|
|
|
|
print('Total child faults: %d, avg %d/child' % (
|
|
|
|
child_faults,
|
|
|
|
child_faults / self.hist.get_total_count(),
|
|
|
|
))
|
|
|
|
print('Est. wastage on faults: %d ms, forks+faults+waits: %d ms (%.2f%%)' % (
|
|
|
|
fault_wastage_usec / 1000,
|
|
|
|
all_wastage_usec / 1000,
|
|
|
|
100 * (all_wastage_usec / (run_duration_sec * 1e6)),
|
|
|
|
))
|
|
|
|
print('99th%% fork latency: %.03f msec, max %d new tasks/sec' % (
|
|
|
|
self.hist.get_value_at_percentile(99) / 1000.0,
|
|
|
|
1e6 / self.hist.get_value_at_percentile(99),
|
|
|
|
))
|
|
|
|
|
|
|
|
self.hist.output_percentile_distribution(sys.stdout, 1000)
|
|
|
|
print('--- End fork statistics ---')
|
|
|
|
print()
|