2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "util/EventWriter.h"
21 #include <unordered_map>
22 #include <json/json.h>
30 // json type for Chrome Event Trace
34 std::string quote(const std::string &value)
37 ss << '"' << value << '"';
41 std::string field(const std::string &k, const std::string &v)
44 ss << quote(k) << " : " << quote(v);
48 struct Content // One Entry in Chrome Event Trace
50 std::vector<std::pair<std::string, std::string>> flds;
51 std::vector<std::pair<std::string, std::string>> args;
54 std::string object(const Content &content)
60 ss << field(content.flds[0].first, content.flds[0].second);
62 for (uint32_t n = 1; n < content.flds.size(); ++n)
64 ss << ", " << field(content.flds.at(n).first, content.flds.at(n).second);
67 if (content.args.size() > 0)
69 ss << ", " << quote("args") << " : { ";
70 ss << field(content.args.at(0).first, content.args.at(0).second);
72 for (uint32_t n = 1; n < content.args.size(); ++n)
74 ss << ", " << field(content.args.at(n).first, content.args.at(n).second);
85 void fill(Content &content, const Event &evt)
87 content.flds.emplace_back("name", evt.name);
88 content.flds.emplace_back("pid", "0");
89 content.flds.emplace_back("tid", evt.tid);
90 content.flds.emplace_back("ph", evt.ph);
91 content.flds.emplace_back("ts", evt.ts);
94 std::string object(const DurationEvent &evt)
100 return ::object(content);
103 std::string object(const CounterEvent &evt)
109 for (auto it = evt.values.begin(); it != evt.values.end(); ++it)
111 content.args.emplace_back(it->first, it->second);
114 return ::object(content);
123 void writeMDTableRow(std::ostream &os, const std::vector<std::string> &list)
126 for (auto &key : list)
140 uint32_t min_page_reclaims;
141 uint32_t max_page_reclaims;
144 : begin_ts(0), end_ts(0), min_rss(UINT32_MAX), max_rss(0), min_page_reclaims(UINT32_MAX),
150 virtual ~MDContent() = default;
152 void updateRss(uint32_t rss)
154 if (min_rss == UINT32_MAX)
161 else if (max_rss < rss)
165 void updateMinflt(uint32_t minflt)
167 if (min_page_reclaims == UINT32_MAX)
168 min_page_reclaims = minflt;
169 if (max_page_reclaims == 0)
170 max_page_reclaims = minflt;
172 if (min_page_reclaims > minflt)
173 min_page_reclaims = minflt;
174 else if (max_page_reclaims < minflt)
175 max_page_reclaims = minflt;
178 virtual void write(std::ostream &os) const = 0;
181 struct OpSeq : public MDContent
184 uint64_t graph_latency;
188 bool operator()(const OpSeq &lhs, const OpSeq &rhs) const
190 return lhs.begin_ts < rhs.begin_ts;
192 bool operator()(const OpSeq &lhs, const OpSeq &rhs) { return lhs.begin_ts < rhs.begin_ts; }
193 bool operator()(OpSeq &lhs, OpSeq &rhs) { return lhs.begin_ts < rhs.begin_ts; }
196 void write(std::ostream &os) const override
198 uint64_t opseq_latency = end_ts - begin_ts;
199 double opseq_per = static_cast<double>(opseq_latency) / graph_latency * 100.0;
200 writeMDTableRow(os, {name, backend, std::to_string(opseq_latency), std::to_string(opseq_per),
201 std::to_string(min_rss), std::to_string(max_rss),
202 std::to_string(min_page_reclaims), std::to_string(max_page_reclaims)});
206 struct Graph : public MDContent
208 std::set<OpSeq, OpSeq::OpSeqCmp> opseqs;
210 void setOpSeqs(const std::map<std::string, OpSeq> &name_to_opseq)
212 uint64_t graph_latency = end_ts - begin_ts;
213 for (auto it : name_to_opseq)
215 auto opseq = it.second;
216 opseq.graph_latency = graph_latency;
218 opseqs.insert(opseq);
220 updateRss(opseq.min_rss);
221 updateRss(opseq.max_rss);
222 updateMinflt(opseq.min_page_reclaims);
223 updateMinflt(opseq.max_page_reclaims);
227 void write(std::ostream &os) const override
229 static std::vector<std::string> graph_headers{"latency(us)", "rss_min(kb)", "rss_max(kb)",
230 "page_reclaims_min", "page_reclaims_max"};
232 static std::vector<std::string> graph_headers_line{"-----------", "-------", "-------",
233 "-----------------", "-----------------"};
236 writeMDTableRow(os, graph_headers);
237 writeMDTableRow(os, graph_headers_line);
240 writeMDTableRow(os, {std::to_string(end_ts - begin_ts), std::to_string(min_rss),
241 std::to_string(max_rss), std::to_string(min_page_reclaims),
242 std::to_string(max_page_reclaims)});
246 static std::vector<std::string> opseq_headers{
247 "OpSeq name", "backend", "latency(us)", "latency(%)",
248 "rss_min(kb)", "rss_max(kb)", "page_reclaims_min", "page_reclaims_max"};
250 static std::vector<std::string> opseq_headers_line{
251 "----------", "-------", "-----------", "-----------",
252 "-------", "-------", "-----------------", "-----------------"};
254 os << "## OpSequences \n";
257 writeMDTableRow(os, opseq_headers);
258 writeMDTableRow(os, opseq_headers_line);
261 for (auto opseq : opseqs)
270 struct MDTableBuilder
272 MDTableBuilder(const std::vector<DurationEvent> &duration_events,
273 const std::vector<CounterEvent> &counter_events)
274 : _duration_events(duration_events), _counter_events(counter_events)
276 // when ready with low overhead in release build
278 for (const auto &evt : _counter_events)
280 uint64_t ts = std::stoull(evt.ts);
281 auto &name = evt.name;
282 assert(name.compare("maxrss") == 0 || name.compare("minflt") == 0);
283 assert(evt.values.size() == 1);
284 auto &val = evt.values.begin()->second;
285 if (_ts_to_values.find(ts) == _ts_to_values.end())
287 std::pair<uint32_t, uint32_t> values;
288 if (name.compare("maxrss") == 0)
289 values.first = std::stoul(val);
291 values.second = std::stoul(val);
292 _ts_to_values.insert({ts, values});
296 auto &values = _ts_to_values.at(ts);
297 if (name.compare("maxrss") == 0)
298 values.first = std::stoul(val);
300 values.second = std::stoul(val);
306 MDTableBuilder &build()
308 for (auto &it : divideGraph())
310 size_t begin_idx = it.first;
311 size_t end_idx = it.second;
312 std::map<std::string, OpSeq> name_to_opseq;
313 for (size_t i = begin_idx + 1; i < end_idx; ++i)
315 const auto &evt = _duration_events[i];
316 assert(evt.name.compare("Graph") != 0);
317 assert(evt.ph.compare("B") == 0 || evt.ph.compare("E") == 0);
318 if (evt.ph.compare("B") == 0)
320 assert(name_to_opseq.find(evt.name) == name_to_opseq.end());
321 name_to_opseq.insert({evt.name, makeOpSeq(evt)});
325 assert(name_to_opseq.find(evt.name) != name_to_opseq.end());
326 auto &opseq = name_to_opseq.at(evt.name);
327 updateOpSeq(opseq, evt);
331 _graphs.emplace_back(makeGraph(begin_idx, end_idx, name_to_opseq));
337 std::vector<std::pair<size_t, size_t>> divideGraph()
339 std::vector<std::pair<size_t, size_t>> graph_idx_list; // pair<begin_idx, end_idx>
340 for (size_t i = 0, begin_idx = 0; i < _duration_events.size(); ++i)
342 const auto &evt = _duration_events.at(i);
343 if (evt.name.compare("Graph") == 0)
345 if (evt.ph.compare("B") == 0)
348 graph_idx_list.emplace_back(begin_idx, i);
351 return graph_idx_list;
354 OpSeq makeOpSeq(const DurationEvent &evt)
357 opseq.name = evt.name;
358 opseq.begin_ts = std::stoull(evt.ts);
359 opseq.backend = evt.tid;
361 opseq.updateRss(_ts_to_values.at(opseq.begin_ts).first);
362 opseq.updateMinflt(_ts_to_values.at(opseq.begin_ts).second);
365 opseq.updateMinflt(0);
370 void updateOpSeq(OpSeq &opseq, const DurationEvent &evt)
372 opseq.end_ts = std::stoull(evt.ts);
374 opseq.updateRss(_ts_to_values.at(opseq.end_ts).first);
375 opseq.updateMinflt(_ts_to_values.at(opseq.end_ts).second);
378 opseq.updateMinflt(0);
382 Graph makeGraph(size_t begin_idx, size_t end_idx,
383 const std::map<std::string, OpSeq> &name_to_opseq)
386 graph.name = "Graph";
387 graph.begin_ts = std::stoull(_duration_events[begin_idx].ts);
388 graph.end_ts = std::stoull(_duration_events[end_idx].ts);
389 graph.setOpSeqs(name_to_opseq);
391 graph.updateRss(_ts_to_values.at(graph.begin_ts).first);
392 graph.updateMinflt(_ts_to_values.at(graph.begin_ts).second);
393 graph.updateRss(_ts_to_values.at(graph.end_ts).first);
394 graph.updateMinflt(_ts_to_values.at(graph.end_ts).second);
397 graph.updateMinflt(0);
402 void write(std::ostream &os)
405 for (size_t i = 0; i < _graphs.size(); ++i)
407 os << "# Graph " << i << "\n";
408 _graphs.at(i).write(os);
412 const std::vector<DurationEvent> &_duration_events;
413 const std::vector<CounterEvent> &_counter_events;
414 // timestamp to std::pair<maxrss, minflt>
415 std::unordered_map<uint64_t, std::pair<uint32_t, uint32_t>> _ts_to_values;
416 std::vector<Graph> _graphs;
421 EventWriter::EventWriter(const EventRecorder &recorder) : _recorder(recorder)
426 void EventWriter::writeToFiles(const std::string &base_filepath)
428 // Note. According to an internal issue, let snpe json as just file name not '.snpe.json'
429 writeToFile(base_filepath, WriteFormat::SNPE_BENCHMARK);
430 writeToFile(base_filepath + ".chrome.json", WriteFormat::CHROME_TRACING);
431 writeToFile(base_filepath + ".table.md", WriteFormat::MD_TABLE);
434 void EventWriter::writeToFile(const std::string &filepath, WriteFormat write_format)
436 std::ofstream os{filepath, std::ofstream::out};
437 switch (write_format)
439 case WriteFormat::CHROME_TRACING:
440 writeChromeTrace(os);
442 case WriteFormat::SNPE_BENCHMARK:
443 writeSNPEBenchmark(os);
445 case WriteFormat::MD_TABLE:
449 assert(!"Invalid value");
454 void EventWriter::writeSNPEBenchmark(std::ostream &os)
457 auto &exec_data = root["Execution_Data"] = Json::Value{Json::objectValue};
464 uint64_t min = std::numeric_limits<uint64_t>::max();
466 void accumulate(uint64_t val)
470 max = std::max(max, val);
471 min = std::min(min, val);
477 std::unordered_map<std::string, Stat> mem_stats;
478 for (auto &evt : _recorder.counter_events())
480 auto &mem_stat = mem_stats[evt.name];
481 uint64_t val = std::stoull(evt.values.at("value"));
482 mem_stat.accumulate(val);
485 auto &mem = exec_data["memory"] = Json::Value{Json::objectValue};
486 for (auto &kv : mem_stats)
488 auto &key = kv.first;
489 auto &val = kv.second;
490 mem[key]["Avg_Size"] = val.sum / val.count;
491 mem[key]["Max_Size"] = val.max;
492 mem[key]["Min_Size"] = val.min;
493 mem[key]["Runtime"] = "NA";
497 // Operation Execution Time
499 // NOTE This assumes _duration_events is sorted by "ts" ascending
501 // 2D keys : stats[tid][name]
502 std::unordered_map<std::string, std::unordered_map<std::string, Stat>> stats;
503 std::unordered_map<std::string, std::unordered_map<std::string, uint64_t>> begin_timestamps;
504 for (auto &evt : _recorder.duration_events())
506 auto &stat = stats[evt.tid][evt.name];
507 auto &begin_ts = begin_timestamps[evt.tid][evt.name];
508 uint64_t timestamp = std::stoull(evt.ts);
512 throw std::runtime_error{"Invalid Data"};
513 begin_ts = timestamp;
515 else if (evt.ph == "E")
517 if (begin_ts == 0 || timestamp < begin_ts)
518 throw std::runtime_error{"Invalid Data"};
519 stat.accumulate(timestamp - begin_ts);
523 throw std::runtime_error{"Invalid Data - invalid value for \"ph\" : \"" + evt.ph + "\""};
526 for (auto &kv : begin_timestamps)
527 for (auto &kv2 : kv.second)
529 throw std::runtime_error{"Invalid Data - B and E pair does not match."};
531 for (auto &kv : stats)
533 auto &tid = kv.first;
534 auto &map = kv.second;
535 auto &json_tid = exec_data[tid] = Json::Value{Json::objectValue};
538 auto &name = kv.first;
539 auto &val = kv.second;
540 json_tid[name]["Avg_Time"] = val.sum / val.count;
541 json_tid[name]["Max_Time"] = val.max;
542 json_tid[name]["Min_Time"] = val.min;
543 json_tid[name]["Runtime"] = tid;
551 void EventWriter::writeChromeTrace(std::ostream &os)
554 os << " " << quote("traceEvents") << ": [\n";
556 for (auto &evt : _recorder.duration_events())
558 os << " " << object(evt) << ",\n";
561 for (auto &evt : _recorder.counter_events())
563 os << " " << object(evt) << ",\n";
571 void EventWriter::writeMDTable(std::ostream &os)
573 MDTableBuilder(_recorder.duration_events(), _recorder.counter_events()).build().write(os);