souffle  2.0.2-371-g6315b36
DebugReport.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file DebugReport.cpp
12  *
13  * Defines classes for creating HTML reports of debugging information.
14  *
15  ***********************************************************************/
16 
17 #include "reports/DebugReport.h"
18 #include "Global.h"
21 #include <fstream>
22 #include <ostream>
23 #include <sstream>
24 #include <vector>
25 
26 namespace souffle {
27 
28 namespace {
29 /**
30  * Generate a full-content diff between two sources.
31  * Both arguments are passed into a `std::ostream` so you may exploit stream implementations.
32  */
33 template <typename A, typename B>
34 std::string generateDiff(const A& prev, const B& curr) {
35  TempFileStream in_prev;
36  TempFileStream in_curr;
37  in_prev << prev;
38  in_curr << curr;
39  in_prev.flush();
40  in_curr.flush();
41  std::string diff_cmd =
42  "diff --new-line-format='+%L' "
43  " --old-line-format='-%L' "
44  " --unchanged-line-format=' %L' ";
45  return execStdOut(diff_cmd + in_prev.getFileName() + " " + in_curr.getFileName()).str();
46 }
47 
48 std::string replaceAll(std::string_view text, std::string_view key, std::string_view replacement) {
49  std::ostringstream ss;
50  for (auto tail = text; !tail.empty();) {
51  auto i = tail.find(key);
52  ss << tail.substr(0, i);
53  if (i == std::string::npos) break;
54 
55  ss << replacement;
56  tail = tail.substr(i + key.length());
57  }
58  return ss.str();
59 }
60 } // namespace
61 
62 void DebugReportSection::printIndex(std::ostream& out) const {
63  out << "<a href=\"#" << id << "\">" << title << "</a>\n";
64  out << "<ul>\n";
65  bool isLeaf = true;
66  for (const DebugReportSection& subsection : subsections) {
67  if (subsection.hasSubsections()) {
68  isLeaf = false;
69  break;
70  }
71  }
72  for (const DebugReportSection& subsection : subsections) {
73  if (isLeaf) {
74  out << "<li class='leaf'>";
75  } else {
76  out << "<li>";
77  }
78  subsection.printIndex(out);
79  out << "</li>";
80  }
81  out << "</ul>\n";
82 }
83 
84 void DebugReportSection::printTitle(std::ostream& out) const {
85  out << "<a id=\"" << id << "\"></a>\n";
86  out << "<div class='headerdiv'>\n";
87  out << "<h1>" << title << "</h1>\n";
88  out << "<a href='#'>(return to top)</a>\n";
89  out << "</div><div style='clear:both'></div>\n";
90 }
91 
92 void DebugReportSection::printContent(std::ostream& out) const {
93  printTitle(out);
94  out << "<div style='padding-left: 1em'>\n";
95  out << body << "\n";
96  for (const DebugReportSection& subsection : subsections) {
97  subsection.printContent(out);
98  }
99  out << "</div>\n";
100 }
101 
103  while (!currentSubsections.empty()) {
104  endSection("forced-closed", "Forcing end of unknown section");
105  }
106 
107  flush();
108 }
109 
110 void DebugReport::flush() {
111  auto&& dst = Global::config().get("debug-report");
112  if (dst.empty() || empty()) return;
113 
114  std::ofstream(dst) << *this;
115 }
116 
117 void DebugReport::addSection(std::string id, std::string title, const std::string_view code) {
118  addSection(DebugReportSection(
119  std::move(id), std::move(title), tfm::format("<pre>%s</pre>", replaceAll(code, "<", "&lt"))));
120 }
121 
122 void DebugReport::addCodeSection(std::string id, std::string title, std::string_view language,
123  std::string_view prev, std::string_view curr) {
124  auto diff =
125  replaceAll(replaceAll(prev.empty() ? curr : generateDiff(prev, curr), "\\", "\\\\"), "`", "\\`");
126  auto divId = nextUniqueId++;
127  auto html = R"(
128  <div id="code-id-%d"></div>
129  <script type="text/javascript"> renderDiff('%s', 'code-id-%d', `%s`) </script>
130  )";
131  addSection(DebugReportSection(
132  std::move(id), std::move(title), tfm::format(html, divId, language, divId, diff)));
133 }
134 
135 void DebugReport::endSection(std::string currentSectionName, std::string currentSectionTitle) {
136  auto subsections = std::move(currentSubsections.top());
137  currentSubsections.pop();
138  addSection(DebugReportSection(
139  std::move(currentSectionName), std::move(currentSectionTitle), std::move(subsections), ""));
140  flush();
141 }
142 
143 void DebugReport::print(std::ostream& out) const {
144  out << R"(
145 <!DOCTYPE html>
146 <html lang='en-AU'>
147 <head>
148 <meta charset=\"UTF-8\">
149 <title>Souffle Debug Report ()";
150  out << Global::config().get("") << R"()</title>
151 <style>
152  ul { list-style-type: none; }
153  ul > li.leaf { display: inline-block; padding: 0em 1em; }
154  ul > li.nonleaf { padding: 0em 1em; }
155  * { font-family: sans-serif; }
156  pre { white-space: pre-wrap; font-family: monospace; }
157  a:link { text-decoration: none; color: blue; }
158  a:visited { text-decoration: none; color: blue; }
159  div.headerdiv { background-color:lightgrey; margin:10px; padding-left:10px; padding-right:10px;
160  padding-top:3px; padding-bottom:3px; border-radius:5px }
161  .headerdiv h1 { display:inline; }
162  .headerdiv a { float:right; }
163 </style>
164 
165 <link rel="stylesheet" type="text/css" href=
166  "https://cdn.jsdelivr.net/npm/highlight.js@10.0.0/styles/default.min.css" />
167 <script type="text/javascript" src=
168  "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/highlight.min.js"></script>
169 
170 <link rel="stylesheet" type="text/css" href=
171  "https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
172 <script type="text/javascript" src=
173  "https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui-base.min.js"></script>
174 
175 <script>
176  function toggleVisibility(id) {
177  var element = document.getElementById(id);
178  if (element.style.display == 'none') {
179  element.style.display = 'block';
180  } else {
181  element.style.display = 'none';
182  }
183  }
184 
185  if (typeof hljs !== 'undefined') {
186  hljs.registerLanguage('souffle', function (hljs) {
187  let COMMENT_MODES = [
188  hljs.C_LINE_COMMENT_MODE,
189  hljs.C_BLOCK_COMMENT_MODE,
190  ]
191 
192  let KEYWORDS = {
193  $pattern: '\\.?\\w+',
194  literal: 'true false',
195  keyword: '.pragma .functor .component .decl .input .output ' +
196  'ord strlen strsub range matches land lor lxor lnot bwand bwor bwxor bwnot bshl bshr bshru',
197  }
198 
199  let STRING = hljs.QUOTE_STRING_MODE
200  let NUMBERS = {
201  className: 'number', relevance: 0, variants: [
202  { begin: /0b[01]+/ },
203  { begin: /\d+\.\d+/ }, // float
204  { begin: /\d+\.\d+.\d+.\d+/ }, // IPv4 literal
205  { begin: /\d+u?/ },
206  { begin: /0x[a-fA-F0-9]+u?/ }
207  ]
208  }
209 
210  let PREPROCESSOR = {
211  className: 'meta',
212  begin: /#\s*[a-z]+\b/,
213  end: /$/,
214  keywords: {
215  'meta-keyword': 'if else elif endif define undef warning error line pragma ifdef ifndef include'
216  },
217  contains: [
218  { begin: /\\\n/, relevance: 0 },
219  hljs.inherit(STRING, { className: 'meta-string' }),
220  ].concat(COMMENT_MODES)
221  };
222 
223  let ATOM = { begin: /[a-z][A-Za-z0-9_]*/, relevance: 0 }
224  let VAR = {
225  className: 'symbol', relevance: 0, variants: [
226  { begin: /[A-Z][a-zA-Z0-9_]*/ },
227  { begin: /_[A-Za-z0-9_]*/ },
228  ]
229  }
230  let PARENTED = { begin: /\(/, end: /\)/, relevance: 0 }
231  let LIST = { begin: /\[/, end: /\]/ }
232  let PRED_OP = { begin: /:-/ } // relevance booster
233 
234  let INNER = [
235  ATOM,
236  VAR,
237  PARENTED,
238  PRED_OP,
239  LIST,
240  STRING,
241  NUMBERS,
242  ].concat(COMMENT_MODES)
243 
244  PARENTED.contains = INNER;
245  LIST.contains = INNER;
246 
247  return {
248  name: 'souffle',
249  keywords: KEYWORDS,
250  contains: INNER.concat([{ begin: /\.$/ }]) // relevance booster
251  };
252  })
253  // TODO: Add a highlighter for `ram`
254  hljs.configure({ languages: ['souffle'] })
255  }
256 
257  if (typeof Diff2HtmlUI !== 'undefined' && typeof hljs !== 'undefined') {
258  function renderDiff(lang, id, diff) {
259  // file extension determines the language used for highlighting
260  let file = `Datalog.${lang}`
261  let prefix = `diff ${file} ${file}
262 --- ${file}
263 +++ ${file}
264 @@ -1 +1 @@
265 `
266  new Diff2HtmlUI(document.getElementById(id), prefix + diff, {
267  drawFileList: false,
268  highlight: true,
269  matching: 'none',
270  outputFormat: 'side-by-side',
271  synchronisedScroll: true,
272  }, hljs).draw()
273  }
274  } else { // fallback to plain text
275  function renderDiff(lang, id, diff) {
276  document.getElementById(id).innerText = diff
277  }
278  }
279 </script>
280 </head>
281 <body>
282 <div class='headerdiv'><h1>Souffle Debug Report ()";
283  out << Global::config().get("") << ")</h1></div>\n";
284  for (const DebugReportSection& section : sections) {
285  section.printIndex(out);
286  }
287  for (const DebugReportSection& section : sections) {
288  section.printContent(out);
289  }
290  out << R"(<a href='#'>(return to top)</a>
291 </body>
292 </html>)";
293 }
294 
295 } // end of namespace souffle
souffle::execStdOut
std::stringstream execStdOut(char const *cmd)
Definition: FileUtil.h:274
souffle::DebugReport::sections
std::vector< DebugReportSection > sections
Definition: DebugReport.h:126
DebugReport.h
tinyformat::format
void format(std::ostream &out, const char *fmt)
Definition: tinyformat.h:1089
souffle::DebugReportSection::printTitle
void printTitle(std::ostream &out) const
Outputs the HTML code for the title header to the given stream.
souffle::DebugReport::nextUniqueId
uint32_t nextUniqueId
Definition: DebugReport.h:128
souffle::DebugReportSection::printIndex
void printIndex(std::ostream &out) const
Outputs the HTML code for the index to the given stream, consisting of a link to the section body fol...
souffle::DebugReport::flush
void flush()
souffle::DebugReport::addSection
void addSection(DebugReportSection section)
Definition: DebugReport.h:93
tinyformat.h
souffle::DebugReport::print
void print(std::ostream &out) const
Outputs a complete HTML document to the given stream, consisting of an index of all of the sections o...
souffle::DebugReportSection::printContent
void printContent(std::ostream &out) const
Outputs the HTML code for the content of the section to the given stream, consisting of the title hea...
Global.h
souffle::DebugReport::addCodeSection
void addCodeSection(std::string id, std::string title, std::string_view language, std::string_view prev, std::string_view curr)
souffle::detail::brie::tail
auto tail(C &s)
Definition: Brie.h:110
i
size_t i
Definition: json11.h:663
souffle::DebugReportSection::body
std::string body
Definition: DebugReport.h:82
souffle::DebugReport::empty
bool empty() const
Definition: DebugReport.h:130
souffle::DebugReport::~DebugReport
~DebugReport()
souffle::DebugReportSection::DebugReportSection
DebugReportSection(std::string id, std::string title, std::string body)
Definition: DebugReport.h:47
souffle::Global::config
static MainConfig & config()
Definition: Global.h:141
souffle::DebugReport::endSection
void endSection(std::string currentSectionName, std::string currentSectionTitle)
souffle::DebugReportSection::title
std::string title
Definition: DebugReport.h:80
FileUtil.h
souffle
Definition: AggregateOp.h:25
souffle::DebugReport::currentSubsections
std::stack< std::vector< DebugReportSection > > currentSubsections
Definition: DebugReport.h:127
souffle::DebugReportSection::subsections
std::vector< DebugReportSection > subsections
Definition: DebugReport.h:81
souffle::profile::ss
class souffle::profile::Tui ss
Definition: Tui.h:336