Совсем недавно передо мной встала задача тюнинга среды моделирования (MPI-приложение) на сервере T5140. Так как потоков на сервере много и они все виртуальные, определиться сходу насколько потоков должно параллелиться приложение оказалось несколько затруднительно :-) К тому же необходимо было сразу оценить где возникает "затык". Типичные средства для мониторинга - начиная от SAR и заканчивая guds мне показались неудобными, так как мне было необходимо было собирать подробную статистику за короткие промежутки времени (выполнение бенчмарка), и привязаться к запуску и остановке приложения, поэтому я написал свой сборщик на Питоне.
Запускаем сборщики
Как правило сборка статистики - это вызов функций типа vmstat, mpstat, выводящие статистику каждые interval секунд count раз, и ожидающих SIGINT. Однако это не всегда так, например lockstat обычно вешается к sleep 5, и его нужно постоянно дергать самостоятельно. Также некоторые сборщики хотелось бы немного кастомизировать.
Итак, каждый сборщик реализует простейший протокол:
class gethosts:
def setoutdir(self, outdir):
"""setoutdir получает директорию вывода результатов и если необходимо,
открывает файловые дескрипторы"""
self.outdir = outdir
def getinfo(self):
"""getinfo возвращает кортеж (interval, runonce)
runonce = True - для сборщиков, запускаемые единократно (как vmstat 3),
interval - интервал вызова poll() (для runonce-сборщика) или запуска start/stop"""
return (10, True)
def start(self):
"""Запуск сборщика"""
hosts = file('/etc/hosts', 'r');
out = file(os.path.join(self.outdir, 'hosts'), 'w')
out.write(hosts.read())
def poll(self):
"""Опрос сборщика"""
return 0
def stop(self):
"""Остановка сборщика"""
pass
def __str__(self):
return "<%s>" % (self.__class__.__name__)
Сборщик, реализующий работу с внешними командами (statcmd) представлен в модуле bench. Он основан на модуле subprocess и при каждом вызове start() вызывает subprocess.Popen, перенаправляя вывод в соответствующие файлы, а при вызове stop() отправляет SIGINT а затем SIGTERM.
Затем, сборщик упаковывается в класс statcollector, который реализует поток в Python и запускающий сборщик в бесконечном цикле:
def run(self):
"""Главный поток выполнения"""
if self.runonce:
self.obj.start();
while self.isrun:
if self.runonce:
if not self.obj.poll():
# Сборщик мертв - перезагружаем
self.obj.start();
time.sleep(self.interval);
else:
self.obj.start();
time.sleep(self.interval);
self.obj.stop();
def done(self):
"""Останавливает сборщик и заставляет тред остановиться"""
if self.runonce:
self.obj.stop();
self.isrun = 0;
Запускаем тесты
Теперь собственно запустим необходимую команду тестироваться, предварительно "упаковав" все написанные нами сборщики в объекты класса statcollector
def run_test(cmd, stats, outfile, outdir = '/tmp'):
"""run_test(cmd, stats, outfile[, outdir])
Запускает и тестирует команду cmd сборщиками статистики stats
Вывод сборщиков записывает в outdir, вывод самой команды в
outfile.err и outfile.out"""
# Создаем директорию в которую будут выводиться данные
# Права 0777 в случае если сборщики запускаются от суперпользователя,
# а сама тестовая программа не от суперпользователя.
# ЗАМЕЧАНИЕ: Если директория уже существует, получим исключение
os.umask(0)
os.makedirs(outdir, 0777);
try:
filename = os.path.join(outdir, outfile)
print "Running tests for %s" % cmd
statthreads = []
# Запускаем все сборщики в отдельных тредах
for stat in stats:
thread = statcollector(stat, outdir)
statthreads.append(thread)
thread.start()
print "Running %s" % stat
try:
# Запускаем команду и анализируем результат
cmd = cmd + ' 1>%s.out 2>%s.err' % (filename, filename)
print "Running %s" % cmd
testret = subprocess.call(cmd, shell = True)
if testret != 0:
raise test_failure(cmd, testret)
finally:
# Останавливаем треды
# ЗАМЕЧАНИЕ: т.к. треды спят с использование time.sleep,
# сборщик покидает не сразу а по завершение своего интервала
for thread in statthreads:
thread.done()
except:
# В случае ошибки удаляем все результаты
print 'Exiting'
answer = ''
while not answer or answer not in 'yYnN':
answer = raw_input('Clean output directory? [y/n]: ')
if answer in 'nN':
raise
shutil.rmtree(outdir)
raise
print "Finished"
Результат
Полученный код можно использовать например вот так:
cmd = "sleep 40"
benchdir = '/var/tmp/mybench-%s/' % datetime.now().strftime('%Y.%m.%d.%H.%M')
print 'Destination is %s' % benchdir
stats = [bench.statcmd('date', [], 5, False),
bench.statcmd('vmstat', ['5']),
gethosts()]
bench.run_test(cmd, stats, 'sleep', benchdir)
Код я оттестировал на Linux и Solaris
Модуль bench и пример лежат тут: py-bench.tar.gz