Как реализовать эффективную статистику времени выполнения С++

Я хотел бы знать, есть ли хороший способ контролировать внутренние приложения, в идеале в виде существующей библиотеки.

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

Также могут быть другие статистические данные более общим способом, например, сколько потоков генерируется каждую минуту, сколько новых/удаленных имен или более конкретных аспектов приложения; вы называете это.

Что было бы удивительно, это что-то вроде "внутренних страниц", которые у вас есть для Google Chrome, например net или chrome://tracing, но в командной строке.

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

У вас есть какие-то указания по этому вопросу?

Изменить: мое приложение работает в Linux во встроенной среде, к сожалению, не поддерживается Valgrind: (

Ответ 1

Я бы рекомендовал, чтобы в вашем коде вы поддерживали счетчики, которые увеличивались. Счетчики могут быть static членами класса или глобальными. Если вы используете класс для определения своего счетчика, вы можете иметь конструктор, регистрирующий ваш счетчик с одним хранилищем вместе с именем. Затем вы можете запросить и reset свои счетчики, обратившись в репозиторий.

struct Counter {
    unsigned long c_;
    unsigned long operator++ () { return ++c_; }
    operator unsigned long () const { return c_; }
    void reset () { unsigned long c = c_; ATOMIC_DECREMENT(c_, c); }
    Counter (std::string name);
};

struct CounterAtomic : public Counter {
    unsigned long operator++ () { return ATOMIC_INCREMENT(c_, 1); }
    CounterAtomic (std::string name) : Counter(name) {}
};

ATOMIC_INCREMENT будет механизмом, специфичным для платформы, чтобы увеличивать счетчик атомарно. Для этой цели GCC предоставляет встроенный __sync_add_and_fetch. ATOMIC_DECREMENT аналогичен, с встроенным GCC __sync_sub_and_fetch.

struct CounterRepository {
    typedef std::map<std::string, Counter *> MapType;
    mutable Mutex lock_;
    MapType map_;
    void add (std::string n, Counter &c) {
        ScopedLock<Mutex> sl(lock_);
        if (map_.find(n) != map_.end()) throw n;
        map_[n] = &c;
    }
    Counter & get (std::string n) const {
        ScopedLock<Mutex> sl(lock_);
        MapType::const_iterator i = map_.find(n);
        if (i == map_.end()) throw n;
        return *(i->second);
    }
};

CounterRepository counterRepository;

Counter::Counter (std::string name) {
    counterRepository.add(name, *this);
}

Если вы знаете, что один и тот же счетчик будет увеличиваться более чем на один поток, используйте CounterAtomic. Для счетчиков, специфичных для потока, просто используйте Counter.

Ответ 2

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

Как правило, для того, чтобы компилировать статистику времени выполнения, например, из множества источников (например, потоки рабочих), я бы каждый источник (поток) увеличивал свои собственные локальные счетчики самых фундаментальных данных, но не выполнял никаких длинную математику или анализ по этим данным.

Затем вернитесь в основной поток (или там, где вы хотите, чтобы эти статистические данные были проанализированы и отображены), я отправляю сообщение типа RequestProgress для каждого рабочего потока. В ответ рабочие потоки собирают все фундаментальные данные и, возможно, выполняют простой анализ. Эти данные вместе с результатами основного анализа отправляются обратно в запрос (основной) поток в сообщении ProgressReport. Основной поток затем объединяет все эти данные, делает дополнительный (возможно, дорогостоящий) анализ, форматирование и отображение пользователю или протоколирование.

Основной поток отправляет это сообщение RequestProgress либо по запросу пользователя (например, когда они нажимают клавишу S), либо по временному интервалу. Если временной интервал - это то, что я собираюсь, я, как правило, реализую еще один новый поток "heartbeat". Весь этот поток выполняет Sleep() в течение указанного времени, а затем отправляет сообщение Heartbeat в основной поток. Основной поток в свою очередь действует на это сообщение Heartbeat, отправив RequestProgress сообщения каждому рабочему потоку, из которого будут собираться статистические данные.

Акт сбора статистики, похоже, должен быть довольно простым. Так почему же такой сложный механизм? Ответ двухкратный.

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

Во-вторых, вычисление статистики времени выполнения может быть дорогостоящим, если вы слишком часто пытаетесь сделать слишком много. Предположим, например, у вас есть рабочий поток, который отправляет многоадресные данные в сети, и вы хотите собрать данные пропускной способности. Сколько байтов, сколько времени занимает период и среднее количество байтов в секунду. Вы могли бы заставить рабочий поток вычислить все это на лету самостоятельно, но это большая работа, и это время процессора лучше потратить на рабочий поток, выполняя то, что он должен делать, отправляя многоадресные данные. Если вместо этого вы просто увеличивали счетчик, сколько байтов вы отправили каждый раз, когда отправляете сообщение, подсчет имеет минимальное влияние на производительность потока. Затем в ответ на случайное сообщение RequestProgress вы можете узнать время начала и окончания и отправить только это, чтобы основной поток выполнял все деление и т.д.

Ответ 3

Используйте общую память (POSIX, System V, mmap или все, что у вас есть). Поместите массив фиксированной длины волатильных неподписанных 32- или 64-битных целых чисел (т.е. Самый большой, который вы можете увеличить с помощью атома на вашей платформе), отбросив необработанный блок памяти до определения вашего массива. Обратите внимание, что волатильность не дает вам атомарности; он предотвращает оптимизацию компилятора, которая может испортить ваши значения статистики. Используйте встроенные функции, такие как gcc __sync_add_and_fetch() или новые атомарные типы С++ 11.

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

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

Ответ 4

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

Поиск в Интернете для "ведения журнала отладки". Должен появиться источник, с которым вы могли бы играть. Большинство магазинов, в которых я был, обычно сворачивают свои собственные.

Если у вас есть дополнительная энергонезависимая память, вы можете зарезервировать область и написать ее. Это также будет включать файлы, если ваша система достаточно велика для поддержки файловой системы.

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

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

Ответ 5

Посмотрите на valgrind/callgrind.

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

Ответ 6

Это хороший ответ, @Джон Диблинг! У меня была система, очень похожая на это. Тем не менее, мой поток "stat" запрашивал рабочих 10 раз в секунду и влиял на производительность рабочих потоков, поскольку каждый раз, когда поток "stat" запрашивает данные, есть критический раздел, обращающийся к этим данным (счетчикам и т.д.), И это означает, что рабочий поток заблокирован на время получения этих данных. Оказалось, что при большой нагрузке рабочих потоков этот запрос статистики 10 Гц влияет на общую производительность рабочих.

Итак, я переключился на несколько другую модель отчетов о статусе - вместо того, чтобы активно запрашивать рабочие потоки из основных потоков, теперь у меня есть рабочие потоки, чтобы сообщать свои базовые счетчики статистики своим эксклюзивным статистическим репозиториям, которые могут быть запрошены основным потоком в любое время без прямого воздействия на рабочих.

Ответ 7

Если вы находитесь на С++ 11, вы можете использовать std:: atomic < >

#include <atomic>

class GlobalStatistics {
public:

    static GlobalStatistics &get() {
        static GlobalStatistics instance;
        return instance;
    }

    void incrTotalBytesProcessed(unsigned int incrBy) {
        totalBytesProcessed += incrBy;
    }

    long long getTotalBytesProcessed() const { return totalBytesProcessed; }


private:

    std::atomic_llong totalBytesProcessed;

    GlobalStatistics() { }
    GlobalStatistics(const GlobalStatistics &) = delete;
    void operator=(const GlobalStatistics &) = delete;
};