Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Навык применения утилиты logman для сбора метрик продуктивности приложения в Windows

Anna | 16.06.2014 | нет комментариев

В своем первом посте я хотел бы поделитьcя навыком применения такой утилиты как logman, с поддержкой которой дозволено собирать информацию о потреблении источников (и не только) приложением под Windows.

В один из дней на работе была поставлена задача снятия метрик продуктивности для одного приложения под Windows. Основным условием было применять по минимуму какие-либо сторонние утилиты, и так как навыка в сходственных вещах у меня к тому времени не было, немножко покопавшись, выбор пал на logman. Специальной точности не требовалось и нужно было лишь осознать нрав потребления источников приложением, для чего logman с первого взора абсолютно подходил.

Выходит, перейдем непринужденно к сути поста.

Что же такое logman?

Многие из вас скорее каждого в работе либо для домашних нужд применяли нативную Windows утилиту Performance monitor (perfmon). Так вот logman — это дерзко говоря command line представление perfmon’а. Он разрешает создавать, запускать, останавливать счетчики производителности, писать итог во внешние файлы и много чего еще. Мне был нужен только базовый функционал, тот, что я и опишу ниже.

Основные операции

Малое примечание: для выполнения нижеследующих операций нужно владеть правами менеджера, напротив может вылететь такая оплошность.

Оплошность:
Отказано в доступе.
Применяется ограниченный токен, используйте расширенный.

Просмотр сделанных и запущенных счетчиков

Посмотреть список сборщиков данных дозволено командой logman
В итоге чего будет получен приблизительно такой итог:

Группа сборщиков данных                  Тип                           Состояние
-------------------------------------------------------------------------------
cpu                                     Счетчик                       Остановлено

Команда исполнена удачно.

Создание счетчика продуктивности

Для создания счетчика я применял следующую команду (с полным списком опций дозволено ознакомиться вофициальной документации.):

logman create counter "counter_name" -f csv -si "interval" --v -o "output_file" -c counter
  • counter_name — имя счетчика. Может быть любым, скажем «explorer_cpu»
  • -f — формат файла с итогами измерений
  • –v — эта опция подавляет итог версии в выходном файле
  • -si — промежуток. с которым будут записываться измерения
  • -o — файл с итогами измерений
  • -c — идентификатор счетчика продуктивности

Остановимся подробнее на последней опции. Дабы осознать, что вписывать в поле «counter», дозволено проследовать в perfmon, сделать группу сборщиков данных и предпочесть «Сделать -> Сборщик данных». Дальше предпочесть «Добавить счетчик» и в появившемся окне обнаружить волнующий нас тип счетчика.

Меня волновало потребление cpu и памяти определенным процессом, следственно я применял для измерений следующие счетчики (в расчете CPU мне помог дальнейший пост):

  • “\Processor(_Total)\% User time” — процент CPU в пользовательском режиме
  • “\Process(application_name)\% Processor time” — доля CPU приложения от пользовательского режима
  • “\Process(application_name)\Working Set” — применение оперативной памяти процессом

Процент потребления CPU процессом дозволено расчитать как (“\Processor(_Total)\% User time” * “\Process(application_name)\% Processor time”) / количество_процессоров

Счетчики дозволено записывать через пробел, тогда в выходном файле значения будут всякий в своем столбце.

В результате приведу пример готовой команды для создания счетчика:

logman create counter "explorer-cpu.usage" -f csv -si 1 --v -o "d:\logman-test\raw\explorer-cpu.usage.csv" -c "\Processor(_Total)\% User time" "\Process(explorer)\% Processor time"

Запуск и остановка счетчика

Выполняются соответствующими командами:

logman start "counter_name"
logman stop "counter_name"

Удаление счетчика

Удалить счетчик дозволено дальнейшей командой (он непременно должен быть перед этим остановлен):

logman delete "counter_name"

Обертка на Python

Раньше чем перейти к коду, хотелось бы рассказать о маленький (правда для меня она стала достаточно значительной) задаче, которая появилась в ходе сбора метрик.

Так как собирать данные предполагалось за достаточно маленький интервал времени (5-15 секунд), то стало ясно, что промежуток сбора в 1 с (задаваемый через опцию “-si”) слишком огромный. Поискав на просторах интернета и в оф. документации, мне не удалось обнаружить метод, которым данный промежуток дозволено было уменьшить, а число получаемых точек на графике дюже хотелось увеличить. Немножко поразмыслив, я решил пойти от отвратного — если невозможно уменьшить промежуток, то дозволено увеличить количетство счетчиков. Таким образом, запуская подряд несколько сборщиков с промежутком в 100-500 миллисекунд, дозволено получить за одну секунду сколько необходимо данных.

Процесс сбора данных должен был быть интегрирован в присутствующий механический тест и в финальном была написана маленькая вспомогательная обертка для выполения всех вышеуказанных действий.

Ниже представлен начальный код модуля, собирающий данные по CPU и оперативной памяти для указанного приложения с нужной точностью.

pylogman.py

import os
import logging
import random
import time
import subprocess
import glob
import multiprocessing

CPU_USER_COUNTER = '"\Processor(_Total)\% User time"'
CPU_APP_COUNTER = '"\Process({app})\% Processor time"'
MEMORY_COUNTER = '"\Process({app})\Working Set"'
APP_PLACEHOLDER = '{app}'

class PerflogManager(object):

    def __init__(self, app_name, precision, results_dir, max_mem=1, interval=1):
        """
        Args:
            app_name: name of the application without "exe" extention
            precision: amount of
            results_dir: output folder
            max_mem: this value is used to calculate % from total avaliable memory for further plotting
            interval: interval which will be used as "-si" logman option value
        """

        self._app_name = app_name
        self._precision = precision
        self._counters = { 'cpu.usage': '{cpu_user} {cpu_app}'.format(cpu_user=CPU_USER_COUNTER,
                                                                      cpu_app=CPU_APP_COUNTER).replace(APP_PLACEHOLDER, app_name),
                           'mem.usage': MEMORY_COUNTER.replace(APP_PLACEHOLDER, app_name) }
        self._results_dir = os.path.normpath(results_dir)
        self._raw_dir = os.path.join(self._results_dir, 'raw')
        self._final_dir = os.path.join(self._results_dir, 'csv')
        self._interval = interval
        self._max_mem = max_mem
        self._collectors = []
        if not os.path.exists(self._results_dir):
            os.makedirs(self._results_dir)
            os.makedirs(self._raw_dir)
            os.makedirs(self._final_dir)
            logging.info('Directory "{path}" for logman results has been created.'.format(path=self._results_dir))
        logging.info('Performance logman manager has been created for "{}" application.'.format(self._app_name))

    def init_collectors(self):
        """
        Creates collectors by "logman create" command
        """

        logging.info('Creating collectors set...')
        for lang in self._counters:
            for idx in range(self._precision):
                collector = '{app}-{collector}-{id}'.format(app=self._app_name,
                                                            collector=lang,
                                                            id=idx)
                output = os.path.join(self._raw_dir, collector   '.csv')
                self.__create(collector, self._counters[lang], output)
                self._collectors.append(collector)

    def start_all(self):
        """
        Starts all the collectors from self._collectors list
        """

        for collector in self._collectors:
            self.__start(collector)
            time.sleep(random.uniform(0.1, 0.9))

    def stop_all(self):
        """
        Stops and deletes all initiated collectors from self._collectors list
        """

        for collector in self._collectors:
            self.__stop(collector)
            self.__delete(collector)

    def cleanup(self):
        """
        Cleans raw_dir directory which contains unprocessed csv file from logman
        """
        logging.info('Cleaning results directory from unnecessary files.')
        for collector in self._collectors:
            csv = os.path.join(self._raw_dir, '{}.csv'.format(collector))
            if os.path.isfile(csv):
                os.remove(csv)

    def process_csv(self):
        """
        Composes and formats data from all counters for further plotting
        """

        for lang in self._counters:
            final_output = os.path.join(self._final_dir, '{}.csv'.format(lang))
            self.__compose_csv(final_output, lang)
            self.__format_data(final_output)

    def __compose_csv(self, output, counter):
        """
        Concatenates gathered csv data to one file
        """

        logging.info('Composing file "{}".'.format(output))
        try:
            with open(output, 'a') as outfile:
                for csv in glob.glob(os.path.join(self._raw_dir, '*{}*'.format(counter))):
                     with open(csv, 'r') as file:
                        outfile.writelines(file.readlines()[1:]) # Get rid of a file header with service info
            logging.info('File {} successfully created.'.format(output))
        except (FileNotFoundError, IOError) as e:
            logging.error('Failed to compose file {file}: {exception}'.format(file=output,
                                                                              exception=e))

    def __format_data(self, file):
        """
        Sorts data after self.__compose_csv function and calculates % of CPU and Memory
        """

        try:
            with open(file, 'r') as csv:
                raw_data = csv.readlines()
            with open(file, 'w') as csv:
                sorted_data = [line.replace('"', '').replace(',', ';')
                            for line in sorted(raw_data)
                            if '" "' not in line]
                csv_data = []
                if 'cpu' in file:
                    for line in sorted_data:
                        time, cpu_user, cpu_app = line.split(';')
                        cpu = (float(cpu_user) * float(cpu_app)) / (multiprocessing.cpu_count() * 100)
                        csv_data.append('{};{:.2f}\n'.format(time, cpu))
                if 'mem' in file:
                    for line in sorted_data:
                        time, total_memory = line.split(';')
                        mem = float(total_memory) / (self._max_mem * 10000)
                        csv_data.append('{};{:.2f}\n'.format(time, mem))
                csv.writelines(csv_data)
        except (FileNotFoundError, IOError) as e:
            logging.error('Failed to process file "{file}": {exception}'.format(file=file,
                                                                                exception=e))

    def __logman(self, cmd):
        """
        Default wrapper for logman commands

        Args:
            cmd: windows command to be executed
        """

        try:
            logging.debug('Running {}'.format(cmd))
            subprocess.check_call(cmd)
        except (subprocess.CalledProcessError, OSError) as e:
            logging.error('Failed to execute command "{}": {}.'.format(cmd, e))

    def __create(self, name, counter, output):
        """
        Creates logman counter

        Args:
            name: uniq name of a collector
            counter: type of the counter (can be taken from perfmon)
            output: csv file for results
        """

        logging.info('Creating collector "{}"'.format(name))
        cmd = 'logman create counter "{name}" -f csv -si {interval} ' \
              '--v -o "{output}" -c {counter}'.format(name=name,
                                                      interval=str(self._interval),
                                                      output=output,
                                                      counter=counter)
        self.__logman(cmd)

    def __start(self, name):
        """
        Starts logman collector

        Args:
            name: uniq name of a collector
        """

        logging.info('Starting collector "{}".'.format(name))
        cmd = 'logman start {}'.format(name)
        self.__logman(cmd)

    def __stop(self, name):
        """
        Stops logman collector

        Args:
            name: uniq name of a collector
        """

        logging.info('Stopping collector "{}".'.format(name))
        cmd = 'logman stop {}'.format(name)
        self.__logman(cmd)

    def __delete(self, name):
        """
        Deletes logman collector

        Args:
            name: uniq name of a collector
        """

        logging.info('Deleting collector "{}".'.format(name))
        cmd = 'logman delete {}'.format(name)
        self.__logman(cmd)

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    app = 'skype'
    start_time = time.strftime('%Y%m%d-%H%M%S', time.gmtime())
    logman = PerflogManager(app, 3, 'd:/logman-test/')
    logman.init_collectors()
    logman.start_all()
    time.sleep(20)
    logman.stop_all()
    logman.process_csv()

Завершение

В завершении хотелось бы сказать, что logman — достаточно пригодная утилита, если небходимо с минимальными расходами автоматизировать сбор метрик продуктивности под Windows. В сжатые сроки мне не удалось обнаружить больше комфортного метода, так что буду рад узнать о других путях и подходах в решении такого рода задач в комментариях.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB