| 1 | #pragma once
 | 
| 2 | 
 | 
| 3 | #include "souffle/utility/ContainerUtil.h"
 | 
| 4 | #include "souffle/utility/MiscUtil.h"
 | 
| 5 | #include "souffle/utility/json11.h"
 | 
| 6 | #include <cassert>
 | 
| 7 | #include <chrono>
 | 
| 8 | #include <cstddef>
 | 
| 9 | #include <fstream>
 | 
| 10 | #include <iostream>
 | 
| 11 | #include <iterator>
 | 
| 12 | #include <map>
 | 
| 13 | #include <memory>
 | 
| 14 | #include <mutex>
 | 
| 15 | #include <set>
 | 
| 16 | #include <stdexcept>
 | 
| 17 | #include <string>
 | 
| 18 | #include <utility>
 | 
| 19 | #include <vector>
 | 
| 20 | 
 | 
| 21 | namespace souffle {
 | 
| 22 | namespace profile {
 | 
| 23 | 
 | 
| 24 | class DirectoryEntry;
 | 
| 25 | class DurationEntry;
 | 
| 26 | class SizeEntry;
 | 
| 27 | class TextEntry;
 | 
| 28 | class TimeEntry;
 | 
| 29 | 
 | 
| 30 | /**
 | 
| 31 |  * Visitor Interface
 | 
| 32 |  */
 | 
| 33 | class Visitor {
 | 
| 34 | public:
 | 
| 35 |     virtual ~Visitor() = default;
 | 
| 36 | 
 | 
| 37 |     // visit entries in a directory
 | 
| 38 |     virtual void visit(DirectoryEntry& e);
 | 
| 39 | 
 | 
| 40 |     // visit entries
 | 
| 41 |     virtual void visit(DurationEntry&) {}
 | 
| 42 |     virtual void visit(SizeEntry&) {}
 | 
| 43 |     virtual void visit(TextEntry&) {}
 | 
| 44 |     virtual void visit(TimeEntry&) {}
 | 
| 45 | };
 | 
| 46 | 
 | 
| 47 | /**
 | 
| 48 |  * Entry class
 | 
| 49 |  *
 | 
| 50 |  * abstract class for a key/value entry in a hierarchical database
 | 
| 51 |  */
 | 
| 52 | class Entry {
 | 
| 53 | private:
 | 
| 54 |     // entry key
 | 
| 55 |     std::string key;
 | 
| 56 | 
 | 
| 57 | public:
 | 
| 58 |     Entry(std::string key) : key(std::move(key)) {}
 | 
| 59 |     virtual ~Entry() = default;
 | 
| 60 | 
 | 
| 61 |     // get key
 | 
| 62 |     const std::string& getKey() const {
 | 
| 63 |         return key;
 | 
| 64 |     };
 | 
| 65 | 
 | 
| 66 |     // accept visitor
 | 
| 67 |     virtual void accept(Visitor& v) = 0;
 | 
| 68 | 
 | 
| 69 |     // print
 | 
| 70 |     virtual void print(std::ostream& os, int tabpos) const = 0;
 | 
| 71 | };
 | 
| 72 | 
 | 
| 73 | /**
 | 
| 74 |  * DirectoryEntry entry
 | 
| 75 |  */
 | 
| 76 | class DirectoryEntry : public Entry {
 | 
| 77 | private:
 | 
| 78 |     std::map<std::string, Own<Entry>> entries;
 | 
| 79 |     mutable std::mutex lock;
 | 
| 80 | 
 | 
| 81 | public:
 | 
| 82 |     DirectoryEntry(const std::string& name) : Entry(name) {}
 | 
| 83 | 
 | 
| 84 |     // get keys
 | 
| 85 |     const std::set<std::string> getKeys() const {
 | 
| 86 |         std::set<std::string> result;
 | 
| 87 |         std::lock_guard<std::mutex> guard(lock);
 | 
| 88 |         for (auto const& cur : entries) {
 | 
| 89 |             result.insert(cur.first);
 | 
| 90 |         }
 | 
| 91 |         return result;
 | 
| 92 |     }
 | 
| 93 | 
 | 
| 94 |     // write entry
 | 
| 95 |     Entry* writeEntry(Own<Entry> entry) {
 | 
| 96 |         assert(entry != nullptr && "null entry");
 | 
| 97 |         std::lock_guard<std::mutex> guard(lock);
 | 
| 98 |         const std::string& keyToWrite = entry->getKey();
 | 
| 99 |         // Don't rewrite an existing entry
 | 
| 100 |         if (entries.count(keyToWrite) == 0) {
 | 
| 101 |             entries[keyToWrite] = std::move(entry);
 | 
| 102 |         }
 | 
| 103 |         return entries[keyToWrite].get();
 | 
| 104 |     }
 | 
| 105 | 
 | 
| 106 |     // read entry
 | 
| 107 |     Entry* readEntry(const std::string& keyToRead) const {
 | 
| 108 |         std::lock_guard<std::mutex> guard(lock);
 | 
| 109 |         auto it = entries.find(keyToRead);
 | 
| 110 |         if (it != entries.end()) {
 | 
| 111 |             return (*it).second.get();
 | 
| 112 |         } else {
 | 
| 113 |             return nullptr;
 | 
| 114 |         }
 | 
| 115 |     }
 | 
| 116 | 
 | 
| 117 |     // read directory
 | 
| 118 |     DirectoryEntry* readDirectoryEntry(const std::string& keyToRead) const {
 | 
| 119 |         return as<DirectoryEntry>(readEntry(keyToRead));
 | 
| 120 |     }
 | 
| 121 | 
 | 
| 122 |     // accept visitor
 | 
| 123 |     void accept(Visitor& v) override {
 | 
| 124 |         v.visit(*this);
 | 
| 125 |     }
 | 
| 126 | 
 | 
| 127 |     // print directory
 | 
| 128 |     void print(std::ostream& os, int tabpos) const override {
 | 
| 129 |         os << std::string(tabpos, ' ') << '"' << getKey() << "\": {" << std::endl;
 | 
| 130 |         bool first{true};
 | 
| 131 |         for (auto const& cur : entries) {
 | 
| 132 |             if (!first) {
 | 
| 133 |                 os << ',' << std::endl;
 | 
| 134 |             } else {
 | 
| 135 |                 first = false;
 | 
| 136 |             }
 | 
| 137 |             cur.second->print(os, tabpos + 1);
 | 
| 138 |         }
 | 
| 139 |         os << std::endl << std::string(tabpos, ' ') << '}';
 | 
| 140 |     }
 | 
| 141 | };
 | 
| 142 | 
 | 
| 143 | /**
 | 
| 144 |  * SizeEntry
 | 
| 145 |  */
 | 
| 146 | class SizeEntry : public Entry {
 | 
| 147 | private:
 | 
| 148 |     std::size_t size;  // size
 | 
| 149 | public:
 | 
| 150 |     SizeEntry(const std::string& key, std::size_t size) : Entry(key), size(size) {}
 | 
| 151 | 
 | 
| 152 |     // get size
 | 
| 153 |     std::size_t getSize() const {
 | 
| 154 |         return size;
 | 
| 155 |     }
 | 
| 156 | 
 | 
| 157 |     // accept visitor
 | 
| 158 |     void accept(Visitor& v) override {
 | 
| 159 |         v.visit(*this);
 | 
| 160 |     }
 | 
| 161 | 
 | 
| 162 |     // print entry
 | 
| 163 |     void print(std::ostream& os, int tabpos) const override {
 | 
| 164 |         os << std::string(tabpos, ' ') << "\"" << getKey() << "\": " << size;
 | 
| 165 |     }
 | 
| 166 | };
 | 
| 167 | 
 | 
| 168 | /**
 | 
| 169 |  * TextEntry
 | 
| 170 |  */
 | 
| 171 | class TextEntry : public Entry {
 | 
| 172 | private:
 | 
| 173 |     // entry text
 | 
| 174 |     std::string text;
 | 
| 175 | 
 | 
| 176 | public:
 | 
| 177 |     TextEntry(const std::string& key, std::string text) : Entry(key), text(std::move(text)) {}
 | 
| 178 | 
 | 
| 179 |     // get text
 | 
| 180 |     const std::string& getText() const {
 | 
| 181 |         return text;
 | 
| 182 |     }
 | 
| 183 | 
 | 
| 184 |     // accept visitor
 | 
| 185 |     void accept(Visitor& v) override {
 | 
| 186 |         v.visit(*this);
 | 
| 187 |     }
 | 
| 188 | 
 | 
| 189 |     // write size entry
 | 
| 190 |     void print(std::ostream& os, int tabpos) const override {
 | 
| 191 |         os << std::string(tabpos, ' ') << "\"" << getKey() << "\": \"" << text << "\"";
 | 
| 192 |     }
 | 
| 193 | };
 | 
| 194 | 
 | 
| 195 | /**
 | 
| 196 |  * Duration Entry
 | 
| 197 |  */
 | 
| 198 | class DurationEntry : public Entry {
 | 
| 199 | private:
 | 
| 200 |     // duration start
 | 
| 201 |     microseconds start;
 | 
| 202 | 
 | 
| 203 |     // duration end
 | 
| 204 |     microseconds end;
 | 
| 205 | 
 | 
| 206 | public:
 | 
| 207 |     DurationEntry(const std::string& key, microseconds start, microseconds end)
 | 
| 208 |             : Entry(key), start(start), end(end) {}
 | 
| 209 | 
 | 
| 210 |     // get start
 | 
| 211 |     microseconds getStart() const {
 | 
| 212 |         return start;
 | 
| 213 |     }
 | 
| 214 | 
 | 
| 215 |     // get end
 | 
| 216 |     microseconds getEnd() const {
 | 
| 217 |         return end;
 | 
| 218 |     }
 | 
| 219 | 
 | 
| 220 |     // accept visitor
 | 
| 221 |     void accept(Visitor& v) override {
 | 
| 222 |         v.visit(*this);
 | 
| 223 |     }
 | 
| 224 | 
 | 
| 225 |     // write size entry
 | 
| 226 |     void print(std::ostream& os, int tabpos) const override {
 | 
| 227 |         os << std::string(tabpos, ' ') << '"' << getKey();
 | 
| 228 |         os << R"_(": { "start": )_";
 | 
| 229 |         os << start.count();
 | 
| 230 |         os << ", \"end\": ";
 | 
| 231 |         os << end.count();
 | 
| 232 |         os << '}';
 | 
| 233 |     }
 | 
| 234 | };
 | 
| 235 | 
 | 
| 236 | /**
 | 
| 237 |  * Time Entry
 | 
| 238 |  */
 | 
| 239 | class TimeEntry : public Entry {
 | 
| 240 | private:
 | 
| 241 |     // time since start
 | 
| 242 |     microseconds time;
 | 
| 243 | 
 | 
| 244 | public:
 | 
| 245 |     TimeEntry(const std::string& key, microseconds time) : Entry(key), time(time) {}
 | 
| 246 | 
 | 
| 247 |     // get start
 | 
| 248 |     microseconds getTime() const {
 | 
| 249 |         return time;
 | 
| 250 |     }
 | 
| 251 | 
 | 
| 252 |     // accept visitor
 | 
| 253 |     void accept(Visitor& v) override {
 | 
| 254 |         v.visit(*this);
 | 
| 255 |     }
 | 
| 256 | 
 | 
| 257 |     // write size entry
 | 
| 258 |     void print(std::ostream& os, int tabpos) const override {
 | 
| 259 |         os << std::string(tabpos, ' ') << '"' << getKey();
 | 
| 260 |         os << R"_(": { "time": )_";
 | 
| 261 |         os << time.count();
 | 
| 262 |         os << '}';
 | 
| 263 |     }
 | 
| 264 | };
 | 
| 265 | 
 | 
| 266 | inline void Visitor::visit(DirectoryEntry& e) {
 | 
| 267 |     std::cout << "Dir " << e.getKey() << "\n";
 | 
| 268 |     for (const auto& cur : e.getKeys()) {
 | 
| 269 |         std::cout << "\t :" << cur << "\n";
 | 
| 270 |         e.readEntry(cur)->accept(*this);
 | 
| 271 |     }
 | 
| 272 | }
 | 
| 273 | 
 | 
| 274 | class Counter : public Visitor {
 | 
| 275 | private:
 | 
| 276 |     std::size_t ctr{0};
 | 
| 277 |     std::string key;
 | 
| 278 | 
 | 
| 279 | public:
 | 
| 280 |     Counter(std::string key) : key(std::move(key)) {}
 | 
| 281 |     void visit(SizeEntry& e) override {
 | 
| 282 |         std::cout << "Size entry : " << e.getKey() << " " << e.getSize() << "\n";
 | 
| 283 |         if (e.getKey() == key) {
 | 
| 284 |             ctr += e.getSize();
 | 
| 285 |         }
 | 
| 286 |     }
 | 
| 287 |     std::size_t getCounter() const {
 | 
| 288 |         return ctr;
 | 
| 289 |     }
 | 
| 290 | };
 | 
| 291 | 
 | 
| 292 | /**
 | 
| 293 |  * Hierarchical databas
 | 
| 294 |  */
 | 
| 295 | class ProfileDatabase {
 | 
| 296 | private:
 | 
| 297 |     Own<DirectoryEntry> root;
 | 
| 298 | 
 | 
| 299 | protected:
 | 
| 300 |     /**
 | 
| 301 |      * Find path: if directories along the path do not exist, create them.
 | 
| 302 |      */
 | 
| 303 |     DirectoryEntry* lookupPath(const std::vector<std::string>& path) {
 | 
| 304 |         DirectoryEntry* dir = root.get();
 | 
| 305 |         for (const std::string& key : path) {
 | 
| 306 |             assert(!key.empty() && "Key is empty!");
 | 
| 307 |             DirectoryEntry* newDir = dir->readDirectoryEntry(key);
 | 
| 308 |             if (newDir == nullptr) {
 | 
| 309 |                 newDir = as<DirectoryEntry>(dir->writeEntry(mk<DirectoryEntry>(key)));
 | 
| 310 |             }
 | 
| 311 |             assert(newDir != nullptr && "Attempting to overwrite an existing entry");
 | 
| 312 |             dir = newDir;
 | 
| 313 |         }
 | 
| 314 |         return dir;
 | 
| 315 |     }
 | 
| 316 | 
 | 
| 317 |     void parseJson(const json11::Json& json, Own<DirectoryEntry>& node) {
 | 
| 318 |         for (auto& cur : json.object_items()) {
 | 
| 319 |             if (cur.second.is_object()) {
 | 
| 320 |                 std::string err;
 | 
| 321 |                 // Duration entries are also maps
 | 
| 322 |                 if (cur.second.has_shape(
 | 
| 323 |                             {{"start", json11::Json::NUMBER}, {"end", json11::Json::NUMBER}}, err)) {
 | 
| 324 |                     auto start = std::chrono::microseconds(cur.second["start"].long_value());
 | 
| 325 |                     auto end = std::chrono::microseconds(cur.second["end"].long_value());
 | 
| 326 |                     node->writeEntry(mk<DurationEntry>(cur.first, start, end));
 | 
| 327 |                 } else if (cur.second.has_shape({{"time", json11::Json::NUMBER}}, err)) {
 | 
| 328 |                     auto time = std::chrono::microseconds(cur.second["time"].long_value());
 | 
| 329 |                     node->writeEntry(mk<TimeEntry>(cur.first, time));
 | 
| 330 |                 } else {
 | 
| 331 |                     auto dir = mk<DirectoryEntry>(cur.first);
 | 
| 332 |                     parseJson(cur.second, dir);
 | 
| 333 |                     node->writeEntry(std::move(dir));
 | 
| 334 |                 }
 | 
| 335 |             } else if (cur.second.is_string()) {
 | 
| 336 |                 node->writeEntry(mk<TextEntry>(cur.first, cur.second.string_value()));
 | 
| 337 |             } else if (cur.second.is_number()) {
 | 
| 338 |                 node->writeEntry(mk<SizeEntry>(cur.first, cur.second.long_value()));
 | 
| 339 |             } else {
 | 
| 340 |                 std::string err;
 | 
| 341 |                 cur.second.dump(err);
 | 
| 342 |                 std::cerr << "Unknown types in profile log: " << cur.first << ": " << err << std::endl;
 | 
| 343 |             }
 | 
| 344 |         }
 | 
| 345 |     }
 | 
| 346 | 
 | 
| 347 | public:
 | 
| 348 |     ProfileDatabase() : root(mk<DirectoryEntry>("root")) {}
 | 
| 349 | 
 | 
| 350 |     ProfileDatabase(const std::string& filename) : root(mk<DirectoryEntry>("root")) {
 | 
| 351 |         std::ifstream file(filename);
 | 
| 352 |         if (!file.is_open()) {
 | 
| 353 |             throw std::runtime_error("Log file could not be opened.");
 | 
| 354 |         }
 | 
| 355 |         std::string jsonString((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));
 | 
| 356 |         std::string error;
 | 
| 357 |         json11::Json json = json11::Json::parse(jsonString, error);
 | 
| 358 |         if (!error.empty()) {
 | 
| 359 |             throw std::runtime_error("Parse error: " + error);
 | 
| 360 |         }
 | 
| 361 |         parseJson(json["root"], root);
 | 
| 362 |     }
 | 
| 363 | 
 | 
| 364 |     // add size entry
 | 
| 365 |     void addSizeEntry(std::vector<std::string> qualifier, std::size_t size) {
 | 
| 366 |         assert(qualifier.size() > 0 && "no qualifier");
 | 
| 367 |         std::vector<std::string> path(qualifier.begin(), qualifier.end() - 1);
 | 
| 368 |         DirectoryEntry* dir = lookupPath(path);
 | 
| 369 | 
 | 
| 370 |         const std::string& key = qualifier.back();
 | 
| 371 |         Own<SizeEntry> entry = mk<SizeEntry>(key, size);
 | 
| 372 |         dir->writeEntry(std::move(entry));
 | 
| 373 |     }
 | 
| 374 | 
 | 
| 375 |     // add text entry
 | 
| 376 |     void addTextEntry(std::vector<std::string> qualifier, const std::string& text) {
 | 
| 377 |         assert(qualifier.size() > 0 && "no qualifier");
 | 
| 378 |         std::vector<std::string> path(qualifier.begin(), qualifier.end() - 1);
 | 
| 379 |         DirectoryEntry* dir = lookupPath(path);
 | 
| 380 | 
 | 
| 381 |         const std::string& key = qualifier.back();
 | 
| 382 |         Own<TextEntry> entry = mk<TextEntry>(key, text);
 | 
| 383 |         dir->writeEntry(std::move(entry));
 | 
| 384 |     }
 | 
| 385 | 
 | 
| 386 |     // add duration entry
 | 
| 387 |     void addDurationEntry(std::vector<std::string> qualifier, microseconds start, microseconds end) {
 | 
| 388 |         assert(qualifier.size() > 0 && "no qualifier");
 | 
| 389 |         std::vector<std::string> path(qualifier.begin(), qualifier.end() - 1);
 | 
| 390 |         DirectoryEntry* dir = lookupPath(path);
 | 
| 391 | 
 | 
| 392 |         const std::string& key = qualifier.back();
 | 
| 393 |         Own<DurationEntry> entry = mk<DurationEntry>(key, start, end);
 | 
| 394 |         dir->writeEntry(std::move(entry));
 | 
| 395 |     }
 | 
| 396 | 
 | 
| 397 |     // add time entry
 | 
| 398 |     void addTimeEntry(std::vector<std::string> qualifier, microseconds time) {
 | 
| 399 |         assert(qualifier.size() > 0 && "no qualifier");
 | 
| 400 |         std::vector<std::string> path(qualifier.begin(), qualifier.end() - 1);
 | 
| 401 |         DirectoryEntry* dir = lookupPath(path);
 | 
| 402 | 
 | 
| 403 |         const std::string& key = qualifier.back();
 | 
| 404 |         Own<TimeEntry> entry = mk<TimeEntry>(key, time);
 | 
| 405 |         dir->writeEntry(std::move(entry));
 | 
| 406 |     }
 | 
| 407 | 
 | 
| 408 |     // compute sum
 | 
| 409 |     std::size_t computeSum(const std::vector<std::string>& qualifier) {
 | 
| 410 |         assert(qualifier.size() > 0 && "no qualifier");
 | 
| 411 |         std::vector<std::string> path(qualifier.begin(), qualifier.end() - 1);
 | 
| 412 |         DirectoryEntry* dir = lookupPath(path);
 | 
| 413 | 
 | 
| 414 |         const std::string& key = qualifier.back();
 | 
| 415 |         std::cout << "Key: " << key << std::endl;
 | 
| 416 |         Counter ctr(key);
 | 
| 417 |         dir->accept(ctr);
 | 
| 418 |         return ctr.getCounter();
 | 
| 419 |     }
 | 
| 420 | 
 | 
| 421 |     /**
 | 
| 422 |      * Return the entry at the given path.
 | 
| 423 |      */
 | 
| 424 |     Entry* lookupEntry(const std::vector<std::string>& path) const {
 | 
| 425 |         DirectoryEntry* dir = root.get();
 | 
| 426 |         auto last = --path.end();
 | 
| 427 |         for (auto it = path.begin(); it != last; ++it) {
 | 
| 428 |             dir = dir->readDirectoryEntry(*it);
 | 
| 429 |             if (dir == nullptr) {
 | 
| 430 |                 return nullptr;
 | 
| 431 |             }
 | 
| 432 |         }
 | 
| 433 |         return dir->readEntry(*last);
 | 
| 434 |     }
 | 
| 435 | 
 | 
| 436 |     /**
 | 
| 437 |      * Return a map of string keys to string values.
 | 
| 438 |      */
 | 
| 439 |     std::map<std::string, std::string> getStringMap(const std::vector<std::string>& path) const {
 | 
| 440 |         std::map<std::string, std::string> kvps;
 | 
| 441 |         auto* parent = as<DirectoryEntry>(lookupEntry(path));
 | 
| 442 |         if (parent == nullptr) {
 | 
| 443 |             return kvps;
 | 
| 444 |         }
 | 
| 445 | 
 | 
| 446 |         for (const auto& key : parent->getKeys()) {
 | 
| 447 |             auto* text = as<TextEntry>(parent->readEntry(key));
 | 
| 448 |             if (text != nullptr) {
 | 
| 449 |                 kvps[key] = text->getText();
 | 
| 450 |             }
 | 
| 451 |         }
 | 
| 452 | 
 | 
| 453 |         return kvps;
 | 
| 454 |     }
 | 
| 455 | 
 | 
| 456 |     // print database
 | 
| 457 |     void print(std::ostream& os) const {
 | 
| 458 |         os << '{' << std::endl;
 | 
| 459 |         root->print(os, 1);
 | 
| 460 |         os << std::endl << '}' << std::endl;
 | 
| 461 |     };
 | 
| 462 | };
 | 
| 463 | 
 | 
| 464 | }  // namespace profile
 | 
| 465 | }  // namespace souffle
 |