[Solved] Wie speichere ich am besten tägliche Datensets (Datenbank?)



  • Hallo zusammen,

    ich habe ein Programm geschrieben, welches jede Sekunde nach dem Vordergrundfenster schaut und somit tracken soll, wie viel Zeit ich womit pro Tag am PC verbringe (oder verschwende?). Ist halt so ne Spaß-Idee.

    Im Moment habe ich einfach Datensätze wie z. B.:

    mintty.exe        60.466s
    firefox.exe      302.615s
    ...
    

    Diese gebe ich momentan "live" im Konsolenfenster aus. Ich würde die Daten jetzt gerne abspeichern (das wollen doch alle immer!).

    Nun weiß ich nicht, wie ich damit am besten umgehen soll. Reicht eine einfache Textdatei, oder mache ich mir damit zu viel Arbeit (erfahrungsgemäß)? Ist eine Datenbank overkill, oder sinnvoll? Für eine kleine Wetterstation habe ich schon mal InfluxDB verwendet (time series database), das war ziemlich cool. Zusätzlich gibt es auch schon tolle Darstellungstools dafür. Allerdings scheint mir das auch wie mit Kanonen auf Spatzen geschossen, oder?

    Wie würdet ihr in diesem Fall die Daten für zukünftige Auswertung/Darstellung abspeichern? Natürlich wäre auch ein Unterschied Vormittags/Nachmittags etc. interessant, aber dann müsste ich wohl schon stündlich Datensätze speichern. Sollte aber an der Grundidee nicht viel ändern.

    Vielen Dank im Voraus für jegliche Hinweise!



  • Einfache Textdatei oder sqlite?

    Wenn du sekündlich nach dem Fenster schaust, würde ich das so speichern:

    Name Start
    firefox 2019-09-19T16:47:00Z
    ssh 2019-09-19T16:49:50Z
    gitk 2019-09-19T16:51:21Z
    firefox 2019-09-19T16:52:00Z

    Also speichern, sobald du ein neues Programm hast (oder gar keines mehr offen ist, aber das wird dann vielleicht explorer sein?)

    Sowas könntest du problemlos mit pandas einlesen und so verarbeiten, wie du es brauchst.
    Dann kannst du die "Offen-Dauer" einfach aus der Differnenz zweier aufeinander folgender Startzeiten berechnen usw.



  • eigentlich sind textdateien doch super.



  • @Wade1234 sagte in Wie speichere ich am besten tägliche Datensets (Datenbank?):

    eigentlich sind textdateien doch super.

    Eigentlich ist das sowas wie csv. Mit 2 Spalten. Aus leidvoller Erfahrung muss ich aber sagen, dass csv-Dateien fast immer Ärger bereiten (Trennzeichen in Text, Encoding des Textes, was ist mit Strings mit \x00, unterschiedliche Datumsformate etc.) und man dieses Problem zumindest bei einer DB so nicht hat.

    Aber ja, Textdatei wäre hier ok, wenn man den Programmnamen ordentlich quotet, sodass man auch mit merkwürig benannten executables zurecht kommt.



  • Textdatei an sich ist für mich kein Problem. Da war mir nur das Format etwas unklar. Ich hatte garnicht daran gedacht, die Berechnung der Laufzeit auch in die Auswertung mitreinzuziehen. Das erscheint mir viel geschickter 👍 Und ja mit Python wird eine Auswertung sicherlich um einiges schneller und leichter gehen!



  • @wob sagte in Wie speichere ich am besten tägliche Datensets (Datenbank?):

    @Wade1234 sagte in Wie speichere ich am besten tägliche Datensets (Datenbank?):

    eigentlich sind textdateien doch super.

    Eigentlich ist das sowas wie csv. Mit 2 Spalten. Aus leidvoller Erfahrung muss ich aber sagen, dass csv-Dateien fast immer Ärger bereiten (Trennzeichen in Text, Encoding des Textes, was ist mit Strings mit \x00, unterschiedliche Datumsformate etc.) und man dieses Problem zumindest bei einer DB so nicht hat.

    Aber ja, Textdatei wäre hier ok, wenn man den Programmnamen ordentlich quotet, sodass man auch mit merkwürig benannten executables zurecht kommt.

    wieso? eigentlich musst du doch bloß string und fstream nehmen.



  • Edit: Nachdem ich das Program eine Weile laufen ließ, stellte ich fest, dass dieser Code schlecht funktioniert. Das Schreiben in die Datei ist nicht zuverlässig und irgendwann fängt es an immer schiefzulaufen. Meine "error_log.txt" hatte dann irgendwann plötzlich 2GB, weil die ganze Zeit Fehler geschrieben wurden und das Programm nicht gut funktioniert.

    Hier die aktuelle Version, welche zuverlässiger läuft und bisher keine Probleme macht. Ich weiß, dass der Code noch schöner aufgebaut werden könnte...

    // Goal: To accurately measure the time spent per day on the computer, including which applications for how long.
    
    #include <filesystem>
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    #include <optional>
    #include <stdexcept>
    #include <string>
    #include <thread>
    #include <vector>
    
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    
    #include <mps/string_utils.hpp>
    #include <mps/Handle.hpp>
    
    using WindowsHandle = mps::Handle<mps::DefWinHandleTraits>;
    
    // Represents a single trackable application instance
    struct Window {
      std::wstring appName;
      std::wstring fullPath;
      std::wstring title;
    };
    
    bool operator==(const Window& lhs, const Window& rhs) {
      return lhs.fullPath == rhs.fullPath && lhs.title == rhs.title;
    }
    
    bool operator!=(const Window& lhs, const Window& rhs) {
      return !(lhs == rhs);
    }
    
    class ActivityTracker {
    public:
      // explicit ActivityTracker(std::wostream& outputStream)
      //   : dataOut{outputStream} { }
        
      explicit ActivityTracker(const std::string& outputPath)
        : path{outputPath}
      {
        const bool writeHeader{ !std::filesystem::exists(outputPath) }; // tellp() gives 0 in MinGW for append mode ...
        if (writeHeader) {
          lines.emplace_back(L"Application,Path,Window_Title,Activation_Time_ISO8601");
        }
      }
        
      void run();
          
    private:
      void tick();
      
      unsigned numTicks{};
      Window lastWindow{};
      std::string path;
      std::vector<std::wstring> lines;
    };
    
    // Retrieve information about the current foreground window using WinAPI.
    std::optional<Window> getForegroundWindow();
    
    int main() {
      constexpr auto outPath{ "app_usage.csv" };
      
      ActivityTracker tracker{outPath};
      tracker.run();
    }
    
    void ActivityTracker::tick() {
      if (++numTicks % 60 == 0) {
        std::wofstream out{path, std::ios::app};
        if (!out) {
          throw std::runtime_error{"ActivityTracker::tick() - Failed to open file: " + path};
        }
        for (const auto& line : lines) {
          out << line;
        }
        lines.clear();
      }
      
      // If the screen is locked, getting a foreground-window fails.
      auto window{ getForegroundWindow() };
    
      if (!window) { // Thus we can detect "user loggs off" or "PC shuts off" with relative ease. (Polling frequency must be high enough though).
        window = { L"NULL", L"NULL", L"NULL" };
      }
      if (*window != lastWindow) {
        std::wostringstream stream;
        std::time_t t{ std::time(nullptr) };
        stream << L"\n\"" << (*window).appName << L"\",\""
                        << (*window).fullPath << L"\",\""
                        << (*window).title << L"\","
                        << std::put_time(std::gmtime(&t), L"%Y-%m-%dT%H:%M:%SZ");
        lines.emplace_back(stream.str());
        lastWindow = *window;
      }
    }
    
    std::optional<Window> getForegroundWindow() {
      Window result;
      
      HWND hwnd{ GetForegroundWindow() };
      if (!hwnd) {
        return std::nullopt;
      }
      
      DWORD pid{};
      GetWindowThreadProcessId(hwnd, &pid);
      WindowsHandle process{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid) };
      if (!process) {
        return std::nullopt;
      }
      
      WCHAR fullProcessName[1024]{};
      WCHAR windowTitle[1024]{};
      DWORD fullProcessNameLength{ sizeof(fullProcessName) };
      
      QueryFullProcessImageNameW(process, 0, fullProcessName, &fullProcessNameLength);
      GetWindowTextW(hwnd, windowTitle, sizeof(windowTitle));
      
      result.appName = wcsrchr(fullProcessName, '\\') + 1; // Don't include leading '\\'
      result.fullPath = mps::replace_copy(fullProcessName, '\\', '/');
      result.title = windowTitle;
      return result;
    }
    
    void ActivityTracker::run() {
      while (true) {
        try {
          tick();
          std::this_thread::sleep_for(std::chrono::seconds(1));
        } catch (const std::exception& e) {
            std::ofstream errorStream{"error_log.txt", std::ios::app};
            errorStream << e.what() << std::endl;
        }
      }
    }
    

    Hier ist der aktuelle alte Code falls es jemanden (warum auch immer) interessieren sollte:

    // Goal: To accurately measure the time spent per day on the computer, including which applications for how long.
    
    #include <filesystem>
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    #include <optional>
    #include <stdexcept>
    #include <string>
    #include <thread>
    
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    
    #include <mps/string_utils.hpp>
    #include <mps/Handle.hpp>
    
    using WindowsHandle = mps::Handle<mps::DefWinHandleTraits>;
    
    // Represents a single trackable application instance
    struct Window {
      std::string appName;
      std::string fullPath;
      std::string title;
    };
    
    bool operator==(const Window& lhs, const Window& rhs) {
      return lhs.fullPath == rhs.fullPath && lhs.title == rhs.title;
    }
    
    bool operator!=(const Window& lhs, const Window& rhs) {
      return !(lhs == rhs);
    }
    
    class ActivityTracker {
    public:
      explicit ActivityTracker(std::ostream& outputStream)
        : dataOut{outputStream} { }
        
      void run();
          
    private:
      void tick();
      
      Window lastWindow{};
      std::ostream& dataOut;
    };
    
    std::optional<Window> getForegroundWindow();
    
    void logWindowActive(std::ostream& out, const Window& window);
    
    int main() {
      constexpr auto outPath{ "app_usage.csv" };
      const bool writeHeader{ !std::filesystem::exists(outPath) }; // tellp() gives 0 in MinGW for append mode ...
      std::ofstream outfile{outPath, std::ios::app };
      if (writeHeader) {
        outfile << "Application,Path,Window_Title,Activation_Time_ISO8601";
      }
      
      ActivityTracker tracker{outfile};
      tracker.run();
    }
    
    void ActivityTracker::tick() {
      // If the screen is locked, getting a foreground-window fails.
      auto window{ getForegroundWindow() };
    
      if (!window) { // Thus we can detect "user loggs off" or "PC shuts off" with relative ease. (Polling frequency must be high enough though).
        window = { "NULL", "NULL", "NULL" };
      }
      if (*window != lastWindow) {
        logWindowActive(dataOut, *window);
        lastWindow = *window;
      }
    }
    
    std::optional<Window> getForegroundWindow() {
      Window result;
      
      HWND hwnd{ GetForegroundWindow() };
      if (!hwnd) {
        return std::nullopt;
      }
      
      DWORD pid{};
      GetWindowThreadProcessId(hwnd, &pid);
      WindowsHandle process{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid) };
      if (!process) {
        return std::nullopt;
      }
      
      char fullProcessName[1024]{};
      char windowTitle[1024]{};
      DWORD fullProcessNameLength{ sizeof(fullProcessName) };
      
      QueryFullProcessImageNameA(process, 0, fullProcessName, &fullProcessNameLength);
      GetWindowTextA(hwnd, windowTitle, sizeof(windowTitle));
      
      result.appName = strrchr(fullProcessName, '\\') + 1; // Don't include leading '\\'
      result.fullPath = fullProcessName;
      mps::replace(result.fullPath, '\\', '/');
      result.title = windowTitle;
      return result;
    }
    
    void ActivityTracker::run() {
      while (true) {
        try {
          tick();
          std::this_thread::sleep_for(std::chrono::seconds(1));
        } catch (const std::exception& e) {
            std::ofstream errorStream{"error_log.txt", std::ios::app};
            errorStream << e.what() << std::endl;
        }
      }
    }
    
    void logWindowActive(std::ostream& out, const Window& window) {
      if (!out) {
        throw std::runtime_error{"Error: ActivityTracker::storeWindow --- data stream is not good for writing."};
      }
      std::time_t t{ std::time(nullptr) };
      out << "\n\"" << window.appName << "\",\""
                      << window.fullPath << "\",\""
                      << window.title << "\","
                      << std::put_time(std::gmtime(&t), "%Y-%m-%dT%H:%M:%SZ")
                      << std::flush;
    }
    
    

    Falls jemand was zum Meckern hat bin ich natürlich froh, lerne ja gerne dazu :p

    P.S.: Wer es noch nicht kennt und CMake nutzt kann sich mal die jucipp IDE anschauen. War früher noch recht unvollständig, wird langsam ganz nett finde ich. Code-completion, CMake Integration und Optik ist super.



  • Erlaubt Windows Sonderzeichen, insbesondere " und den Zeilenumbruch, in App-Namen?



  • @wob Falls die Frage an mich geht, ich bin mir nicht sicher worauf du hinaus möchtest (eine Stelle in meinen Code)?

    Use any character in the current code page for a name, including Unicode characters and characters in the extended character set (128–255), except for the following:

    The following reserved characters:
        < (less than)
        > (greater than)
        : (colon)
        " (double quote)
        / (forward slash)
        \ (backslash)
        | (vertical bar or pipe)
        ? (question mark)
        * (asterisk)
    
    Integer value zero, sometimes referred to as the ASCII NUL character.
    
    Characters whose integer representations are in the range from 1 through 31, except for alternate data streams where these characters are allowed. For more information about file streams, see File Streams.
    
    Any other character that the target file system does not allow.
    

    Source



  • @HarteWare sagte in [Solved] Wie speichere ich am besten tägliche Datensets (Datenbank?):

    Falls die Frage an mich geht, ich bin mir nicht sicher worauf du hinaus möchtest (eine Stelle in meinen Code)?

    Ich wollte wissen, ob dein
    out << "\n\"" << window.appName << "\",\""
    in Ordnung ist oder ob es
    out << "\n\"" << quote(window.appName) << "\",\""
    hätte heißen müssen, wobei quote eine Funktion wäre, die " durch "" ersetzt und ggf. weitere Sonderzeichen behandelt.

    Ich kenne mich mit Windows nicht aus. In welchem Encoding kommen die Daten denn raus? Ist das bei den ...A-Funktionen nicht irgendwie von Lokaleinstellungen abhängig? Sollte man lieber ...W verwenden, wenn du z.B. ne chinesische Executable hast?



  • @wob Achso jetzt verstehe ich (damit mein CSV Format nicht zerstört wird). Laut der Quelle wären " und \n nicht erlaubt, ich mache mir darüber erstmal keine Sorgen. Es wäre vermutlich schon besser, wenn ich die UNICODE Varianten der WinAPI verwende. Habe es in meinem Programm abgeändert, danke für den Hinweis.

    Edit: Wenn ich das hier so lese, habe ich trotzdem wieder alles falsch gemacht, weil ich jetzt eben std::wofstream und std::wstring genommen habe, und trotzdem noch im "Textmodus" die Datei beschreibe^^
    http://utf8everywhere.org/



  • Ne du machst da nichts falsch. Da die UNICODE API von Windows nicht UTF-8 sondern UTF-16 ist. Und dafür passen die wide varianten von std::string (std::wstring) und std::ofstream(std::wofstream).



  • @firefly Ich bezog mich auf dieses "Manifesto", welches besagt man solle doch intern nach UTF-8 konvertieren (in std::string) und seine Dateien im binären Modus schreiben, und schön immer '\n' als Zeilenende verwenden, wobei doch std::fstream und std::wfstream unter Windows sicherlich \r\n ausgeben, wenn im String ein '\n' vorkommt.


Log in to reply