null

Пишем простой сборщик статистики на Python

Совсем недавно передо мной встала задача тюнинга среды моделирования (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

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs