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);
92 content.args = evt.args;
95 std::string object(const DurationEvent &evt)
101 return ::object(content);
104 std::string object(const CounterEvent &evt)
110 for (auto it = evt.values.begin(); it != evt.values.end(); ++it)
112 content.args.emplace_back(it->first, it->second);
115 return ::object(content);
124 void writeMDTableRow(std::ostream &os, const std::vector<std::string> &list)
127 for (auto &key : list)
141 uint32_t min_page_reclaims;
142 uint32_t max_page_reclaims;
145 : begin_ts(0), end_ts(0), min_rss(UINT32_MAX), max_rss(0), min_page_reclaims(UINT32_MAX),
151 virtual ~MDContent() = default;
153 void updateRss(uint32_t rss)
155 if (min_rss == UINT32_MAX)
162 else if (max_rss < rss)
166 void updateMinflt(uint32_t minflt)
168 if (min_page_reclaims == UINT32_MAX)
169 min_page_reclaims = minflt;
170 if (max_page_reclaims == 0)
171 max_page_reclaims = minflt;
173 if (min_page_reclaims > minflt)
174 min_page_reclaims = minflt;
175 else if (max_page_reclaims < minflt)
176 max_page_reclaims = minflt;
179 virtual void write(std::ostream &os) const = 0;
182 struct OpSeq : public MDContent
185 uint64_t graph_latency;
189 bool operator()(const OpSeq &lhs, const OpSeq &rhs) const
191 return lhs.begin_ts < rhs.begin_ts;
193 bool operator()(const OpSeq &lhs, const OpSeq &rhs) { return lhs.begin_ts < rhs.begin_ts; }
194 bool operator()(OpSeq &lhs, OpSeq &rhs) { return lhs.begin_ts < rhs.begin_ts; }
197 void write(std::ostream &os) const override
199 uint64_t opseq_latency = end_ts - begin_ts;
200 double opseq_per = static_cast<double>(opseq_latency) / graph_latency * 100.0;
201 writeMDTableRow(os, {name, backend, std::to_string(opseq_latency), std::to_string(opseq_per),
202 std::to_string(min_rss), std::to_string(max_rss),
203 std::to_string(min_page_reclaims), std::to_string(max_page_reclaims)});
207 struct Graph : public MDContent
209 std::set<OpSeq, OpSeq::OpSeqCmp> opseqs;
211 void setOpSeqs(const std::map<std::string, OpSeq> &name_to_opseq)
213 uint64_t graph_latency = end_ts - begin_ts;
214 for (auto it : name_to_opseq)
216 auto opseq = it.second;
217 opseq.graph_latency = graph_latency;
219 opseqs.insert(opseq);
221 updateRss(opseq.min_rss);
222 updateRss(opseq.max_rss);
223 updateMinflt(opseq.min_page_reclaims);
224 updateMinflt(opseq.max_page_reclaims);
228 void write(std::ostream &os) const override
230 static std::vector<std::string> graph_headers{"latency(us)", "rss_min(kb)", "rss_max(kb)",
231 "page_reclaims_min", "page_reclaims_max"};
233 static std::vector<std::string> graph_headers_line{"-----------", "-------", "-------",
234 "-----------------", "-----------------"};
237 writeMDTableRow(os, graph_headers);
238 writeMDTableRow(os, graph_headers_line);
241 writeMDTableRow(os, {std::to_string(end_ts - begin_ts), std::to_string(min_rss),
242 std::to_string(max_rss), std::to_string(min_page_reclaims),
243 std::to_string(max_page_reclaims)});
247 static std::vector<std::string> opseq_headers{
248 "OpSeq name", "backend", "latency(us)", "latency(%)",
249 "rss_min(kb)", "rss_max(kb)", "page_reclaims_min", "page_reclaims_max"};
251 static std::vector<std::string> opseq_headers_line{
252 "----------", "-------", "-----------", "-----------",
253 "-------", "-------", "-----------------", "-----------------"};
255 os << "## OpSequences \n";
258 writeMDTableRow(os, opseq_headers);
259 writeMDTableRow(os, opseq_headers_line);
262 for (auto opseq : opseqs)
271 struct MDTableBuilder
273 MDTableBuilder(const std::vector<DurationEvent> &duration_events,
274 const std::vector<CounterEvent> &counter_events)
275 : _duration_events(duration_events), _counter_events(counter_events)
277 // when ready with low overhead in release build
279 for (const auto &evt : _counter_events)
281 uint64_t ts = std::stoull(evt.ts);
282 auto &name = evt.name;
283 assert(name.compare("maxrss") == 0 || name.compare("minflt") == 0);
284 assert(evt.values.size() == 1);
285 auto &val = evt.values.begin()->second;
286 if (_ts_to_values.find(ts) == _ts_to_values.end())
288 std::pair<uint32_t, uint32_t> values;
289 if (name.compare("maxrss") == 0)
290 values.first = std::stoul(val);
292 values.second = std::stoul(val);
293 _ts_to_values.insert({ts, values});
297 auto &values = _ts_to_values.at(ts);
298 if (name.compare("maxrss") == 0)
299 values.first = std::stoul(val);
301 values.second = std::stoul(val);
307 MDTableBuilder &build()
309 for (auto &it : divideGraph())
311 size_t begin_idx = it.first;
312 size_t end_idx = it.second;
313 std::map<std::string, OpSeq> name_to_opseq;
314 for (size_t i = begin_idx + 1; i < end_idx; ++i)
316 const auto &evt = _duration_events[i];
317 assert(evt.name.compare("Graph") != 0);
318 assert(evt.ph.compare("B") == 0 || evt.ph.compare("E") == 0);
319 if (evt.ph.compare("B") == 0)
321 assert(name_to_opseq.find(evt.name) == name_to_opseq.end());
322 name_to_opseq.insert({evt.name, makeOpSeq(evt)});
326 assert(name_to_opseq.find(evt.name) != name_to_opseq.end());
327 auto &opseq = name_to_opseq.at(evt.name);
328 updateOpSeq(opseq, evt);
332 _graphs.emplace_back(makeGraph(begin_idx, end_idx, name_to_opseq));
338 std::vector<std::pair<size_t, size_t>> divideGraph()
340 std::vector<std::pair<size_t, size_t>> graph_idx_list; // pair<begin_idx, end_idx>
341 for (size_t i = 0, begin_idx = 0; i < _duration_events.size(); ++i)
343 const auto &evt = _duration_events.at(i);
344 if (evt.name.compare("Graph") == 0)
346 if (evt.ph.compare("B") == 0)
349 graph_idx_list.emplace_back(begin_idx, i);
352 return graph_idx_list;
355 OpSeq makeOpSeq(const DurationEvent &evt)
358 opseq.name = evt.name;
359 opseq.begin_ts = std::stoull(evt.ts);
360 opseq.backend = evt.tid;
362 opseq.updateRss(_ts_to_values.at(opseq.begin_ts).first);
363 opseq.updateMinflt(_ts_to_values.at(opseq.begin_ts).second);
366 opseq.updateMinflt(0);
371 void updateOpSeq(OpSeq &opseq, const DurationEvent &evt)
373 opseq.end_ts = std::stoull(evt.ts);
375 opseq.updateRss(_ts_to_values.at(opseq.end_ts).first);
376 opseq.updateMinflt(_ts_to_values.at(opseq.end_ts).second);
379 opseq.updateMinflt(0);
383 Graph makeGraph(size_t begin_idx, size_t end_idx,
384 const std::map<std::string, OpSeq> &name_to_opseq)
387 graph.name = "Graph";
388 graph.begin_ts = std::stoull(_duration_events[begin_idx].ts);
389 graph.end_ts = std::stoull(_duration_events[end_idx].ts);
390 graph.setOpSeqs(name_to_opseq);
392 graph.updateRss(_ts_to_values.at(graph.begin_ts).first);
393 graph.updateMinflt(_ts_to_values.at(graph.begin_ts).second);
394 graph.updateRss(_ts_to_values.at(graph.end_ts).first);
395 graph.updateMinflt(_ts_to_values.at(graph.end_ts).second);
398 graph.updateMinflt(0);
403 void write(std::ostream &os)
406 for (size_t i = 0; i < _graphs.size(); ++i)
408 os << "# Graph " << i << "\n";
409 _graphs.at(i).write(os);
413 const std::vector<DurationEvent> &_duration_events;
414 const std::vector<CounterEvent> &_counter_events;
415 // timestamp to std::pair<maxrss, minflt>
416 std::unordered_map<uint64_t, std::pair<uint32_t, uint32_t>> _ts_to_values;
417 std::vector<Graph> _graphs;
422 void SNPEWriter::flush(const std::vector<std::unique_ptr<EventRecorder>> &recorders)
425 auto &exec_data = root["Execution_Data"] = Json::Value{Json::objectValue};
432 uint64_t min = std::numeric_limits<uint64_t>::max();
434 void accumulate(uint64_t val)
438 max = std::max(max, val);
439 min = std::min(min, val);
445 std::unordered_map<std::string, Stat> mem_stats;
446 for (auto &recorder : recorders)
448 for (auto &evt : recorder->counter_events())
450 auto &mem_stat = mem_stats[evt.name];
451 uint64_t val = std::stoull(evt.values.at("value"));
452 mem_stat.accumulate(val);
456 auto &mem = exec_data["memory"] = Json::Value{Json::objectValue};
457 for (auto &kv : mem_stats)
459 auto &key = kv.first;
460 auto &val = kv.second;
461 mem[key]["Avg_Size"] = val.sum / val.count;
462 mem[key]["Max_Size"] = val.max;
463 mem[key]["Min_Size"] = val.min;
464 mem[key]["Runtime"] = "NA";
468 // Operation Execution Time
470 // NOTE This assumes _duration_events is sorted by "ts" ascending
472 // 2D keys : stats[tid][name]
473 std::unordered_map<std::string, std::unordered_map<std::string, Stat>> stats;
474 std::unordered_map<std::string, std::unordered_map<std::string, uint64_t>> begin_timestamps;
475 for (auto &recorder : recorders)
477 for (auto &evt : recorder->duration_events())
479 auto &stat = stats[evt.tid][evt.name];
480 auto &begin_ts = begin_timestamps[evt.tid][evt.name];
481 uint64_t timestamp = std::stoull(evt.ts);
485 throw std::runtime_error{"Invalid Data"};
486 begin_ts = timestamp;
488 else if (evt.ph == "E")
490 if (begin_ts == 0 || timestamp < begin_ts)
491 throw std::runtime_error{"Invalid Data"};
492 stat.accumulate(timestamp - begin_ts);
496 throw std::runtime_error{"Invalid Data - invalid value for \"ph\" : \"" + evt.ph + "\""};
500 for (auto &kv : begin_timestamps)
501 for (auto &kv2 : kv.second)
503 throw std::runtime_error{"Invalid Data - B and E pair does not match."};
505 for (auto &kv : stats)
507 auto &tid = kv.first;
508 auto &map = kv.second;
509 auto &json_tid = exec_data[tid] = Json::Value{Json::objectValue};
512 auto &name = kv.first;
513 auto &val = kv.second;
514 json_tid[name]["Avg_Time"] = val.sum / val.count;
515 json_tid[name]["Max_Time"] = val.max;
516 json_tid[name]["Min_Time"] = val.min;
517 json_tid[name]["Runtime"] = tid;
525 void ChromeTracingWriter::flush(const std::vector<std::unique_ptr<EventRecorder>> &recorders)
528 _os << " " << quote("traceEvents") << ": [\n";
530 for (auto &recorder : recorders)
532 flushOneRecord(*recorder);
540 void ChromeTracingWriter::flushOneRecord(const EventRecorder &recorder)
542 for (auto &evt : recorder.duration_events())
544 _os << " " << object(evt) << ",\n";
547 for (auto &evt : recorder.counter_events())
549 _os << " " << object(evt) << ",\n";
553 void MDTableWriter::flush(const std::vector<std::unique_ptr<EventRecorder>> &records)
555 for (auto &recorder : records)
557 MDTableBuilder(recorder->duration_events(), recorder->counter_events()).build().write(_os);
562 std::mutex EventWriter::_mutex;
564 void EventWriter::readyToFlush(std::unique_ptr<EventRecorder> &&recorder)
567 std::unique_lock<std::mutex> lock{_mutex};
569 _recorders.emplace_back(std::move(recorder));
571 if (--_ref_count > 0)
574 // The caller of this method is the last instance that uses EventWriter.
575 // Let's write log files.
577 // Note. According to an internal issue, let snpe json as just file name not '.snpe.json'
578 flush(WriteFormat::SNPE_BENCHMARK);
579 flush(WriteFormat::CHROME_TRACING);
580 flush(WriteFormat::MD_TABLE);
583 void EventWriter::flush(WriteFormat write_format)
585 auto *writer = _actual_writers[write_format].get();
588 writer->flush(_recorders);