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
|