ArmNN
 25.11
Loading...
Searching...
No Matches
Profiling.cpp
Go to the documentation of this file.
1//
2// Copyright © 2017,2024 Arm Ltd. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5#include "Profiling.hpp"
6
7#include <armnn/BackendId.hpp>
9
10#include "JsonPrinter.hpp"
11
12#if ARMNN_STREAMLINE_ENABLED
13#include <streamline_annotate.h>
14#endif
15
16#include <algorithm>
17#include <iomanip>
18#include <iostream>
19#include <fstream>
20#include <map>
21#include <stack>
22
23namespace armnn
24{
25
26// Controls the amount of memory initially allocated to store profiling events.
27// If chosen carefully, the profiling system will not make any additional allocations, thus minimizing its impact on
28// measured times.
29constexpr std::size_t g_ProfilingEventCountHint = 1024;
30
31// Whether profiling reports should include the sequence of events together with their timings.
32constexpr bool g_WriteProfilingEventSequence = true;
33
34// Whether profiling reports should also report detailed information on events grouped by inference.
35// This can spam the output stream, so use carefully (or adapt the code to just output information
36// of interest).
38
39// Whether a call to Profiler::AnalyzeEventsAndWriteResults() will be made when the Profiler is destroyed.
40// It can be convenient for local tests.
42
43Measurement FindMeasurement(const std::string& name, const Event* event)
44{
45
46 ARMNN_THROW_INVALIDARG_MSG_IF_FALSE(event, "event should not be null.");
47
48 // Search though the measurements.
49 for (const auto& measurement : event->GetMeasurements())
50 {
51 if (measurement.m_Name == name)
52 {
53 // Measurement found.
54 return measurement;
55 }
56 }
57
58 // Measurement not found.
59 return Measurement{ "", 0.f, Measurement::Unit::TIME_MS };
60}
61
62std::vector<Measurement> FindKernelMeasurements(const Event* event)
63{
64 ARMNN_THROW_INVALIDARG_MSG_IF_FALSE(event, "event should not be null.");
65
66 std::vector<Measurement> measurements;
67
68 // Search through the measurements.
69 for (const auto& measurement : event->GetMeasurements())
70 {
71 if (measurement.m_Name.rfind("OpenClKernelTimer", 0) == 0
72 || measurement.m_Name.rfind("NeonKernelTimer", 0) == 0)
73 {
74 // Measurement found.
75 measurements.push_back(measurement);
76 }
77 }
78
79 return measurements;
80}
81
82std::map<std::string, ProfilerImpl::ProfilingEventStats> ProfilerImpl::CalculateProfilingEventStats() const
83{
84 std::map<std::string, ProfilingEventStats> nameToStatsMap;
85
86 for (const auto& event : m_EventSequence)
87 {
89
90 double durationMs = measurement.m_Value;
91 auto it = nameToStatsMap.find(event->GetName());
92 if (it != nameToStatsMap.end())
93 {
94 ProfilingEventStats& stats = it->second;
95 stats.m_TotalMs += durationMs;
96 stats.m_MinMs = std::min(stats.m_MinMs, durationMs);
97 stats.m_MaxMs = std::max(stats.m_MaxMs, durationMs);
98 ++stats.m_Count;
99 }
100 else
101 {
102 nameToStatsMap.emplace(event->GetName(), ProfilingEventStats{ durationMs, durationMs, durationMs, 1 });
103 }
104 }
105
106 return nameToStatsMap;
107}
108
109const Event* GetEventPtr(const Event* ptr) { return ptr;}
110const Event* GetEventPtr(const std::unique_ptr<Event>& ptr) {return ptr.get(); }
111
112template<typename ItertType>
113void ProfilerImpl::AnalyzeEventSequenceAndWriteResults(ItertType first, ItertType last, std::ostream& outStream) const
114{
115 // Outputs event sequence, if needed.
117 {
118 // Makes sure timestamps are output with 6 decimals, and save old settings.
119 std::streamsize oldPrecision = outStream.precision();
120 outStream.precision(6);
121 std::ios_base::fmtflags oldFlags = outStream.flags();
122 outStream.setf(std::ios::fixed);
123 // Outputs fields.
124 outStream << "Event Sequence - Name | Duration (ms) | Start (ms) | Stop (ms) | Device" << std::endl;
125 for (auto event = first; event != last; ++event)
126 {
127 const Event* eventPtr = GetEventPtr((*event));
128 double startTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_START, eventPtr).m_Value;
129 double stopTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_STOP, eventPtr).m_Value;
130
131 // Find the WallClock measurement if there is one.
132 double durationMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME, eventPtr).m_Value;
133 outStream << std::setw(50) << eventPtr->GetName() << " "
134 << std::setw(20) << durationMs
135 << std::setw(20) << startTimeMs
136 << std::setw(20) << stopTimeMs
137 << std::setw(20) << eventPtr->GetBackendId().Get()
138 << std::endl;
139 }
140 outStream << std::endl;
141 // Restores previous precision settings.
142 outStream.flags(oldFlags);
143 outStream.precision(oldPrecision);
144 }
145
146 // Aggregates results per event name.
147 std::map<std::string, ProfilingEventStats> nameToStatsMap = CalculateProfilingEventStats();
148
149 // Outputs aggregated stats.
150 outStream << "Event Stats - Name | Avg (ms) | Min (ms) | Max (ms) | Total (ms) | Count" << std::endl;
151 for (const auto& pair : nameToStatsMap)
152 {
153 const std::string& eventLabel = pair.first;
154 const ProfilingEventStats& eventStats = pair.second;
155 const double avgMs = eventStats.m_TotalMs / double(eventStats.m_Count);
156
157 outStream << "\t" << std::setw(50) << eventLabel << " " << std::setw(9) << avgMs << " "
158 << std::setw(9) << eventStats.m_MinMs << " " << std::setw(9) << eventStats.m_MaxMs << " "
159 << std::setw(9) << eventStats.m_TotalMs << " " << std::setw(9) << eventStats.m_Count << std::endl;
160 }
161 outStream << std::endl;
162}
163
165 : m_ProfilingEnabled(false),
167{
169
170#if ARMNN_STREAMLINE_ENABLED
171 // Initialises streamline annotations.
172 ANNOTATE_SETUP;
173#endif
174}
175
177{
179 {
181 {
182 Print(std::cout);
183 }
184 }
185
186 // Un-register this profiler from the current thread.
188}
189
194
195void ProfilerImpl::EnableProfiling(bool enableProfiling)
196{
197 m_ProfilingEnabled = enableProfiling;
198}
199
204
206 const BackendId& backendId,
207 const std::string& label,
208 std::vector<InstrumentPtr>&& instruments,
210{
211 Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
212 m_EventSequence.push_back(std::make_unique<Event>(label,
213 profiler,
214 parent,
215 backendId,
216 std::move(instruments),
217 guid));
218 Event* event = m_EventSequence.back().get();
219 event->Start();
220
221#if ARMNN_STREAMLINE_ENABLED
222 ANNOTATE_CHANNEL_COLOR(uint32_t(m_Parents.size()), GetEventColor(backendId), label.c_str());
223#endif
224
225 m_Parents.push(event);
226 return event;
227}
228
230{
231 event->Stop();
232
233 if (!!m_Parents.empty())
234 {
235 throw armnn::Exception("m_Parents must not be empty.");
236 }
237
238 if (event != m_Parents.top())
239 {
240 throw armnn::Exception("event must match the top of m_Parents.");
241 }
242
243 m_Parents.pop();
244
245 Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
246
247 if (event->GetParentEvent() != parent)
248 {
249 throw armnn::Exception("parent events must match.");
250 }
251
252#if ARMNN_STREAMLINE_ENABLED
253 ANNOTATE_CHANNEL_END(uint32_t(m_Parents.size()));
254#endif
255}
256
257int CalcLevel(const Event* eventPtr)
258{
259 int level = 0;
260 while (eventPtr != nullptr)
261 {
262 eventPtr = eventPtr->GetParentEvent();
263 level++;
264 }
265 return level;
266}
267
268void ProfilerImpl::PopulateParent(std::vector<const Event*>& outEvents, int& outBaseLevel, std::string parentName) const
269{
270 outEvents.reserve(m_EventSequence.size());
271 for (const auto& event : m_EventSequence)
272 {
273 const Event* eventPtrRaw = event.get();
274 if (eventPtrRaw->GetName() == parentName)
275 {
276 outBaseLevel = (outBaseLevel == -1) ? CalcLevel(eventPtrRaw) : outBaseLevel;
277 outEvents.push_back(eventPtrRaw);
278 }
279 }
280}
281
282void ProfilerImpl::PopulateDescendants(std::map<const Event*, std::vector<const Event*>>& outDescendantsMap) const
283{
284 for (const auto& event : m_EventSequence)
285 {
286 const Event* eventPtrRaw = event.get();
287 const Event* parent = eventPtrRaw->GetParentEvent();
288
289 if (!parent)
290 {
291 continue;
292 }
293
294 auto it = outDescendantsMap.find(parent);
295 if (it == outDescendantsMap.end())
296 {
297 outDescendantsMap.emplace(parent, std::vector<const Event*>({ eventPtrRaw }));
298 }
299 else
300 {
301 it->second.push_back(eventPtrRaw);
302 }
303 }
304}
305
307 std::string layerDetailsStr)
308{
310 detailsObject.SetAndParseDetails(layerDetailsStr);
311
312}
313
314void ExtractJsonObjects(unsigned int inferenceIndex,
315 const Event* parentEvent,
316 JsonChildObject& parentObject,
317 std::map<const Event*, std::vector<const Event*>> descendantsMap)
318{
319 ARMNN_THROW_INVALIDARG_MSG_IF_FALSE(parentEvent, "parentEvent must not be null.");
320
321 // If profiling GUID is entered, process it
322 if (parentEvent->GetProfilingGuid().has_value())
323 {
324 arm::pipe::ProfilingGuid profilingGuid;
325 profilingGuid = parentEvent->GetProfilingGuid().value();
326 parentObject.SetGuid(profilingGuid);
327 }
328 std::vector<Measurement> instrumentMeasurements = parentEvent->GetMeasurements();
329 unsigned int childIdx = 0;
330 unsigned int numSkippedKernels = 0;
331 if (inferenceIndex > 0)
332 {
333 for (auto &i: parentEvent->GetInstruments())
334 {
335 if (i->HasKernelMeasurements())
336 {
337 numSkippedKernels = static_cast<unsigned int>(parentObject.m_Children.size() -
338 instrumentMeasurements.size());
339 childIdx = numSkippedKernels;
340 }
341 }
342 }
343
344 for (size_t measurementIndex = 0; measurementIndex < instrumentMeasurements.size(); ++measurementIndex, ++childIdx)
345 {
346 if (inferenceIndex == 0)
347 {
348 // Only add kernel measurement once, in case of multiple inferences
349 JsonChildObject measurementObject{ instrumentMeasurements[measurementIndex].m_Name };
350 measurementObject.SetUnit(instrumentMeasurements[measurementIndex].m_Unit);
351 measurementObject.SetType(JsonObjectType::Measurement);
352
353 if (parentObject.NumChildren() != childIdx)
354 {
355 throw armnn::Exception("parentObject must have the same number of children as childIdx.");
356 }
357 parentObject.AddChild(measurementObject);
358 }
359 else
360 {
361 if (numSkippedKernels > 0)
362 {
363 parentObject.GetChild(--numSkippedKernels).AddMeasurement(0.0);
364 }
365 }
366
367 parentObject.GetChild(childIdx).AddMeasurement(instrumentMeasurements[measurementIndex].m_Value);
368 }
369
370 auto childEventsIt = descendantsMap.find(parentEvent);
371 if (childEventsIt != descendantsMap.end())
372 {
373 for (auto childEvent : childEventsIt->second)
374 {
375 if (inferenceIndex == 0)
376 {
377 // Only add second level once, in case of multiple inferences
378 JsonChildObject childObject{ childEvent->GetName() };
379 childObject.SetType(JsonObjectType::Event);
380 parentObject.AddChild(childObject);
381 }
382
383 // It's possible that childIdx can overrun the parents' child vector. Check before we try to process a
384 // non-existent child.
385 if (childIdx < parentObject.NumChildren())
386 {
387 // Recursively process children.
388 ExtractJsonObjects(inferenceIndex, childEvent, parentObject.GetChild(childIdx), descendantsMap);
389 childIdx++;
390 }
391 }
392 }
393}
394
395void ProfilerImpl::Print(std::ostream& outStream) const
396{
397 // Makes sure timestamps are output with 6 decimals, and save old settings.
398 std::streamsize oldPrecision = outStream.precision();
399 outStream.precision(6);
400 std::ios_base::fmtflags oldFlags = outStream.flags();
401 outStream.setf(std::ios::fixed);
402 JsonPrinter printer(outStream);
403
404 // First find all the parent Events and print out duration measurements.
405 int baseLevel = -1;
406
407 std::vector<const Event*> optimizations;
408 PopulateParent(optimizations, baseLevel, "Optimizer");
409
410 std::vector<const Event*> loadedNetworks;
411 PopulateParent(loadedNetworks, baseLevel, "LoadedNetwork");
412
413 std::vector<const Event*> inferences;
414 PopulateParent(inferences, baseLevel, "EnqueueWorkload");
415
416 // Second map out descendants hierarchy
417 std::map<const Event*, std::vector<const Event*>> descendantsMap;
418 PopulateDescendants(descendantsMap);
419
420 // Extract json objects for each parent event type
421 JsonChildObject optimizeObject{ "optimize_measurements" };
422
423 for (unsigned int optimizeIndex = 0; optimizeIndex < optimizations.size(); ++optimizeIndex)
424 {
425 auto optimization = optimizations[optimizeIndex];
426 ExtractJsonObjects(optimizeIndex, optimization, optimizeObject, descendantsMap);
427 }
428
429 JsonChildObject loadedNetworkObject{ "loaded_network_measurements" };
430
431 for (unsigned int loadedNetworkIndex = 0; loadedNetworkIndex < loadedNetworks.size(); ++loadedNetworkIndex)
432 {
433 auto loadedNetwork = loadedNetworks[loadedNetworkIndex];
434 ExtractJsonObjects(loadedNetworkIndex, loadedNetwork, loadedNetworkObject, descendantsMap);
435 }
436
437 JsonChildObject inferenceObject{ "inference_measurements" };
438
439 for (unsigned int inferenceIndex = 0; inferenceIndex < inferences.size(); ++inferenceIndex)
440 {
441 auto inference = inferences[inferenceIndex];
442 ExtractJsonObjects(inferenceIndex, inference, inferenceObject, descendantsMap);
443 }
444
445 printer.PrintHeader();
446 printer.PrintArmNNHeader();
447
448 if (m_ProfilingDetails.get()->DetailsExist() &&
451 {
452 JsonChildObject detailsObject{ "layer_details" };
454 {
455 detailsObject.EnableDetailsOnly();
456 }
458 detailsObject.SetAndParseDetails(m_ProfilingDetails.get()->GetProfilingDetails());
459
460 size_t id = 0;
461 printer.PrintJsonChildObject(detailsObject, id);
462 }
463
464 // print inference object, also prints child layer and kernel measurements
465 size_t id = 0;
467 {
468 printer.PrintJsonChildObject(optimizeObject, id);
469 printer.PrintSeparator();
470 printer.PrintNewLine();
471 printer.PrintJsonChildObject(loadedNetworkObject, id);
472 printer.PrintSeparator();
473 printer.PrintNewLine();
474 printer.PrintJsonChildObject(inferenceObject, id);
475 printer.PrintNewLine();
476 }
477 // end of ArmNN
478 printer.PrintFooter();
479
480 // end of main JSON object
481 printer.PrintNewLine();
482 printer.PrintFooter();
483 printer.PrintNewLine();
484
485 // Restores previous precision settings.
486 outStream.flags(oldFlags);
487 outStream.precision(oldPrecision);
488
489}
490void ProfilerImpl::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
491{
492 // Stack should be empty now.
493 const bool saneMarkerSequence = m_Parents.empty();
494
495 // Abort if the sequence of markers was found to have incorrect information:
496 // The stats cannot be trusted.
497 if (!saneMarkerSequence)
498 {
499 outStream << "Cannot write profiling stats. "
500 "Unexpected errors were found when analyzing the sequence of logged events, "
501 "which may lead to plainly wrong stats. The profiling system may contain implementation "
502 "issues or could have been used in an unsafe manner." << std::endl;
503 return;
504 }
505
506 // Analyzes the full sequence of events.
508 m_EventSequence.cend(),
509 outStream);
510
511 // Aggregates events by tag if requested (spams the output stream if done for all tags).
513 {
514 outStream << std::endl;
515 outStream << "***" << std::endl;
516 outStream << "*** Per Inference Stats" << std::endl;
517 outStream << "***" << std::endl;
518 outStream << std::endl;
519
520 int baseLevel = -1;
521 std::vector<const Event*> inferences;
522 PopulateParent(inferences, baseLevel, "EnqueueWorkload");
523
524 // Second map out descendants hierarchy
525 std::map<const Event*, std::vector<const Event*>> descendantsMap;
526 PopulateDescendants(descendantsMap);
527
528 std::function<void(const Event*, std::vector<const Event*>&)>
529 FindDescendantEvents = [&](const Event* eventPtr, std::vector<const Event*>& sequence)
530 {
531 sequence.push_back(eventPtr);
532
533 if (CalcLevel(eventPtr) > baseLevel+2) //We only care about levels as deep as workload executions.
534 {
535 return;
536 }
537
538 auto children = descendantsMap.find(eventPtr);
539 if (children == descendantsMap.end())
540 {
541 return;
542 }
543
544 if (!(children->second.empty()))
545 {
546 return FindDescendantEvents(children->second[0], sequence);
547 }
548 };
549
550 // Third, find events belonging to each inference
551 int inferenceIdx = 0;
552 for (auto inference : inferences)
553 {
554 std::vector<const Event*> sequence;
555
556 //build sequence, depth first
557 FindDescendantEvents(inference, sequence);
558
559 outStream << "> Begin Inference: " << inferenceIdx << std::endl;
560 outStream << std::endl;
561 AnalyzeEventSequenceAndWriteResults(sequence.cbegin(),
562 sequence.cend(),
563 outStream);
564 outStream << std::endl;
565 outStream << "> End Inference: " << inferenceIdx << std::endl;
566
567 inferenceIdx++;
568 }
569 }
570}
571
572std::uint32_t ProfilerImpl::GetEventColor(const BackendId& backendId) const
573{
574 static BackendId cpuRef("CpuRef");
575 static BackendId cpuAcc("CpuAcc");
576 static BackendId gpuAcc("GpuAcc");
577 if (backendId == cpuRef)
578 {
579 // Cyan
580 return 0xffff001b;
581 }
582 else if (backendId == cpuAcc)
583 {
584 // Green
585 return 0x00ff001b;
586 }
587 else if (backendId == gpuAcc)
588 {
589 // Purple
590 return 0xff007f1b;
591 }
592 else
593 {
594 // Dark gray
595 return 0x5555551b;
596 }
597}
598
599// The thread_local pointer to the profiler instance.
600thread_local IProfiler* tl_Profiler = nullptr;
601
603{
604 // Global reference to the single ProfileManager instance allowed.
605 static ProfilerManager s_ProfilerManager;
606 return s_ProfilerManager;
607}
608
610{
611 tl_Profiler = profiler;
612}
613
618
619void IProfiler::EnableProfiling(bool enableProfiling)
620{
621 pProfilerImpl->EnableProfiling(enableProfiling);
622}
623
625{
626 pProfilerImpl->EnableNetworkDetailsToStdOut(detailsMethod);
627}
628
630{
631 return pProfilerImpl->IsProfilingEnabled();
632}
633
634void IProfiler::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
635{
636 pProfilerImpl->AnalyzeEventsAndWriteResults(outStream);
637}
638
639void IProfiler::Print(std::ostream& outStream) const
640{
641 pProfilerImpl->Print(outStream);
642}
643
644Event* IProfiler::BeginEvent(const BackendId& backendId,
645 const std::string& label,
646 std::vector<InstrumentPtr>&& instruments,
648{
649 return pProfilerImpl->BeginEvent(this, backendId, label, std::move(instruments), guid);
650}
651
652IProfiler::~IProfiler() = default;
653IProfiler::IProfiler() : pProfilerImpl(new ProfilerImpl())
654{};
655
656} // namespace armnn
#define ARMNN_THROW_INVALIDARG_MSG_IF_FALSE(_cond, _str)
const std::string & Get() const
Event class records measurements reported by BeginEvent()/EndEvent() and returns measurements when Ev...
const Event * GetParentEvent() const
Get the pointer of the parent event.
const std::string & GetName() const
Get the name of the event.
Optional< arm::pipe::ProfilingGuid > GetProfilingGuid() const
Get the associated profiling GUID if the event is a workload.
BackendId GetBackendId() const
Get the backend id of the event.
const std::vector< Measurement > GetMeasurements() const
Get the recorded measurements calculated between Start() and Stop()
const std::vector< InstrumentPtr > & GetInstruments() const
Get the Instruments used by this Event.
Base class for all ArmNN exceptions so that users can filter to just those.
void Print(std::ostream &outStream) const
Print stats for events in JSON Format to the given output stream.
void EnableNetworkDetailsToStdOut(ProfilingDetailsMethod detailsMethod)
Print out details of each layer within the network that possesses a descriptor.
bool IsProfilingEnabled()
Checks whether profiling is enabled.
void AnalyzeEventsAndWriteResults(std::ostream &outStream) const
Analyzes the tracked events and writes the results to the given output stream.
void EnableProfiling(bool enableProfiling)
Enables/disables profiling for this profiler.
void PrintJsonChildObject(const JsonChildObject &object, size_t &id)
void PrintSeparator()
Definition JsonUtils.hpp:70
void PrintArmNNHeader()
Definition JsonUtils.hpp:64
bool has_value() const noexcept
Definition Optional.hpp:53
void Print(std::ostream &outStream) const
std::map< std::string, ProfilingEventStats > CalculateProfilingEventStats() const
Definition Profiling.cpp:82
void EnableNetworkDetailsToStdOut(ProfilingDetailsMethod detailsMethod)
std::vector< EventPtr > m_EventSequence
std::stack< Event * > m_Parents
Event * BeginEvent(armnn::IProfiler *profiler, const BackendId &backendId, const std::string &name, std::vector< InstrumentPtr > &&instruments, const Optional< arm::pipe::ProfilingGuid > &guid)
void EndEvent(Event *event)
void PopulateParent(std::vector< const Event * > &outEvents, int &outBaseLevel, std::string parentName) const
void PopulateDescendants(std::map< const Event *, std::vector< const Event * > > &outDescendantsMap) const
uint32_t GetEventColor(const BackendId &backendId) const
void AnalyzeEventsAndWriteResults(std::ostream &outStream) const
void AnalyzeEventSequenceAndWriteResults(EventIterType first, EventIterType last, std::ostream &outStream) const
ProfilingDetailsMethod m_DetailsToStdOutMethod
void EnableProfiling(bool enableProfiling)
IProfiler * GetProfiler()
void RegisterProfiler(IProfiler *profiler)
static ProfilerManager & GetInstance()
static const std::string WALL_CLOCK_TIME_STOP
static const std::string WALL_CLOCK_TIME
static const std::string WALL_CLOCK_TIME_START
Copyright (c) 2021 ARM Limited and Contributors.
void ExtractJsonObjects(unsigned int inferenceIndex, const Event *parentEvent, JsonChildObject &parentObject, std::map< const Event *, std::vector< const Event * > > descendantsMap)
const Event * GetEventPtr(const Event *ptr)
constexpr bool g_WriteProfilingEventSequence
Definition Profiling.cpp:32
constexpr std::size_t g_ProfilingEventCountHint
Definition Profiling.cpp:29
std::vector< Measurement > FindKernelMeasurements(const Event *event)
Definition Profiling.cpp:62
constexpr bool g_WriteReportToStdOutOnProfilerDestruction
Definition Profiling.cpp:41
Measurement FindMeasurement(const std::string &name, const Event *event)
Definition Profiling.cpp:43
void ConfigureDetailsObject(JsonChildObject &detailsObject, std::string layerDetailsStr)
constexpr bool g_AggregateProfilingEventsByInference
Definition Profiling.cpp:37
thread_local IProfiler * tl_Profiler
ProfilingDetailsMethod
Define the behaviour of the internal profiler when outputting network details.
Definition Types.hpp:72
int CalcLevel(const Event *eventPtr)
JsonChildObject & GetChild(const unsigned int index)
size_t NumChildren() const
void AddChild(const JsonChildObject &childObject)
void SetAndParseDetails(std::string layerDetailsStr)
void SetType(JsonObjectType type)
void SetUnit(const Measurement::Unit unit)
void SetGuid(arm::pipe::ProfilingGuid guid)
std::vector< JsonChildObject > m_Children
void AddMeasurement(const double measurement)