packages feed

souffle-haskell-1.0.0: cbits/souffle/ProfileEvent.h

/*
 * Souffle - A Datalog Compiler
 * Copyright (c) 2018, The Souffle Developers. All rights reserved.
 * Licensed under the Universal Permissive License v 1.0 as shown at:
 * - https://opensource.org/licenses/UPL
 * - <souffle root>/licenses/SOUFFLE-UPL.txt
 */

/************************************************************************
 *
 * @file ProfileEvent.h
 *
 * Declares classes for profile events
 *
 ***********************************************************************/

#pragma once

#include "EventProcessor.h"
#include "ProfileDatabase.h"
#include "utility/MiscUtil.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <ctime>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <sys/resource.h>
#include <sys/time.h>

namespace souffle {

/**
 * Profile Event Singleton
 */
class ProfileEventSingleton {
    /** profile database */
    profile::ProfileDatabase database;
    std::string filename{""};

    ProfileEventSingleton() = default;

public:
    ~ProfileEventSingleton() {
        stopTimer();
        ProfileEventSingleton::instance().dump();
    }

    /** get instance */
    static ProfileEventSingleton& instance() {
        static ProfileEventSingleton singleton;
        return singleton;
    }

    /** create config record */
    void makeConfigRecord(const std::string& key, const std::string& value) {
        profile::EventProcessorSingleton::instance().process(database, "@config", key.c_str(), value.c_str());
    }

    /** create time event */
    void makeTimeEvent(const std::string& txt) {
        profile::EventProcessorSingleton::instance().process(
                database, txt.c_str(), std::chrono::duration_cast<microseconds>(now().time_since_epoch()));
    }

    /** create an event for recording start and end times */
    void makeTimingEvent(const std::string& txt, time_point start, time_point end, size_t startMaxRSS,
            size_t endMaxRSS, size_t size, size_t iteration) {
        microseconds start_ms = std::chrono::duration_cast<microseconds>(start.time_since_epoch());
        microseconds end_ms = std::chrono::duration_cast<microseconds>(end.time_since_epoch());
        profile::EventProcessorSingleton::instance().process(
                database, txt.c_str(), start_ms, end_ms, startMaxRSS, endMaxRSS, size, iteration);
    }

    /** create quantity event */
    void makeQuantityEvent(const std::string& txt, size_t number, int iteration) {
        profile::EventProcessorSingleton::instance().process(database, txt.c_str(), number, iteration);
    }

    /** create utilisation event */
    void makeUtilisationEvent(const std::string& txt) {
        /* current time */
        microseconds time = std::chrono::duration_cast<microseconds>(now().time_since_epoch());
        /* system CPU time used */
        struct rusage ru {};
        getrusage(RUSAGE_SELF, &ru);
        /* system CPU time used */
        uint64_t systemTime = ru.ru_stime.tv_sec * 1000000 + ru.ru_stime.tv_usec;
        /* user CPU time used */
        uint64_t userTime = ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec;
        /* Maximum resident set size (kb) */
        size_t maxRSS = ru.ru_maxrss;

        profile::EventProcessorSingleton::instance().process(
                database, txt.c_str(), time, systemTime, userTime, maxRSS);
    }

    void setOutputFile(std::string filename) {
        this->filename = filename;
    }
    /** Dump all events */
    void dump() {
        if (!filename.empty()) {
            std::ofstream os(filename);
            if (!os.is_open()) {
                std::cerr << "Cannot open profile log file <" + filename + ">";
            } else {
                database.print(os);
            }
        }
    }

    /** Start timer */
    void startTimer() {
        timer.start();
    }

    /** Stop timer */
    void stopTimer() {
        timer.stop();
    }

    void resetTimerInterval(uint32_t interval = 1) {
        timer.resetTimerInterval(interval);
    }
    const profile::ProfileDatabase& getDB() const {
        return database;
    }

    void setDBFromFile(const std::string& filename) {
        database = profile::ProfileDatabase(filename);
    }

private:
    /**  Profile Timer */
    class ProfileTimer {
    private:
        /** time interval between per utilisation read */
        uint32_t t;

        /** timer is running */
        std::atomic<bool> running{false};

        /** thread timer runs on */
        std::thread th;

        std::condition_variable conditionVariable;
        std::mutex timerMutex;

        /** number of utilisation events */
        std::atomic<uint32_t> runCount{0};

        /** run method for thread th */
        void run() {
            ProfileEventSingleton::instance().makeUtilisationEvent("@utilisation");
            ++runCount;
            if (runCount % 128 == 0) {
                increaseInterval();
            }
        }

        uint32_t getInterval() {
            return t;
        }

        /** Increase value of time interval by factor of 2 */
        void increaseInterval() {
            // Don't increase time interval past 60 seconds
            if (t < 60000) {
                t = t * 2;
            }
        }

    public:
        /*
         *  @param interval the size of the timing interval in milliseconds
         */
        ProfileTimer(uint32_t interval = 10) : t(interval) {}

        /** start timer on the thread th */
        void start() {
            if (running) {
                return;
            }
            running = true;

            th = std::thread([this]() {
                while (running) {
                    run();
                    std::unique_lock<std::mutex> lock(timerMutex);
                    conditionVariable.wait_for(lock, std::chrono::milliseconds(getInterval()));
                }
            });
        }

        /** stop timer on the thread th */
        void stop() {
            running = false;
            conditionVariable.notify_all();
            if (th.joinable()) {
                th.join();
            }
        }

        /** Reset timer interval.
         *
         *  The timer interval increases as the program executes. Resetting the interval is useful to
         *  ensure that detailed usage information is gathered even in long running programs, if desired.
         *
         *  @param interval the size of the timing interval in milliseconds
         */
        void resetTimerInterval(uint32_t interval = 10) {
            t = interval;
            runCount = 0;
            conditionVariable.notify_all();
        }
    };

    ProfileTimer timer;
};

}  // namespace souffle