profile.cpp
Go to the documentation of this file.
1 // *****************************************************************************
2 //
3 // Copyright (c) 2015, Southwest Research Institute® (SwRI®)
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Southwest Research Institute® (SwRI®) nor the
14 // names of its contributors may be used to endorse or promote products
15 // derived from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE
21 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
27 // DAMAGE.
28 //
29 // *****************************************************************************
32 #include <algorithm>
33 #include <set>
34 #include <QStringList>
35 #include <QDebug>
36 
37 namespace swri_profiler_tools
38 {
39 // A null object to return for invalid keys.
41 
43  :
44  profile_key_(-1),
45  min_time_s_(0),
46  max_time_s_(0)
47 {
48  // Add the root node.
49  node_key_from_path_[""] = 0;
50  ProfileNode &root_node = nodes_[0];
51  root_node.node_key_ = 0;
52  root_node.name_ = "";
53  root_node.path_ = "";
54  root_node.measured_ = false;
55  root_node.depth_ = 0;
56  root_node.parent_ = -1;
57 }
58 
60 {
61 }
62 
63 void Profile::initialize(int profile_key, const QString &name)
64 {
65  if (isValid()) {
66  qWarning("Re-initializing a valid profile (%d,%s) with (%d,%s). "
67  "Something is probably horribly wrong.",
68  profile_key_, qPrintable(name_),
69  profile_key, qPrintable(name));
70  }
71  profile_key_ = profile_key;
72  name_ = name;
73 }
74 
76 {
77  if (profile_key_ < 0) {
78  qWarning("Attempt to add %zu elements to an invalid profile.", data.size());
79  return;
80  }
81 
82  if (data.size() == 0) {
83  return;
84  }
85 
86  std::set<uint64_t> modified_times;
87 
88  bool nodes_added = false;
89  for (auto const &item : data) {
90  QString path = normalizeNodePath(item.label);
91 
92  // Since we're setting the data at this item's timestamp, we
93  // always add the timestamp to the modified list.
94  modified_times.insert(item.wall_stamp_sec);
95 
96  // If this item is outside our current timeline, we need to expand
97  // the timeline (and fill in the gaps). This could influence a larger
98  expandTimeline(item.wall_stamp_sec);
99 
100  // Touching the node guarantees that it and all of its ancestor
101  // nodes exist.
102  nodes_added |= touchNode(path);
103 
104  if (node_key_from_path_.count(path) == 0) {
105  qWarning("Failed to touch node for %s. Data will be dropped. This should never happend.",
106  qPrintable(path));
107  continue;
108  }
109  int node_key = node_key_from_path_.at(path);
110 
111  // At this point, we know that the corresponding node and timeslot
112  // exist, so we can store the data. Storing data may influence
113  // subsequent times.
114  storeItemData(modified_times, node_key, item);
115  }
116 
117  // If nodes were created, we need to update our indices.
118  if (nodes_added) {
119  rebuildIndices();
120  Q_EMIT nodesAdded(profile_key_);
121  }
122 
123  // Finally, we need to update derived data that may have changed
124  // from the update.
125  for (auto const &t : modified_times) {
127  }
128 
129  // Notify observers that the profile has new data.
130  Q_EMIT dataAdded(profile_key_);
131 }
132 
133 void Profile::expandTimeline(const uint64_t sec)
134 {
135  if (sec >= min_time_s_ && sec < max_time_s_) {
136  // This time is already in our timeline, so ignore it.
137  } else if (min_time_s_ == max_time_s_) {
138  // The timeline is empty
139  min_time_s_ = sec;
140  max_time_s_ = sec+1;
141  addDataToAllNodes(true, 1);
142  } else if (sec >= max_time_s_) {
143  // New data extends the back of the timeline.
144  size_t new_elements = sec - max_time_s_ + 1;
145  max_time_s_ = sec+1;
146  addDataToAllNodes(true, new_elements);
147  } else {
148  // New data must be at the front of the timeline. This case
149  // should be rare.
150  size_t new_elements = min_time_s_ - sec;
151  min_time_s_ = sec;
152  addDataToAllNodes(false, new_elements);
153  }
154 }
155 
156 void Profile::addDataToAllNodes(const bool back, const size_t count)
157 {
158  if (back) {
159  for (auto &it : nodes_) {
160  std::deque<ProfileEntry> &data = it.second.data_;
161  ProfileEntry initial_value;
162  if (!data.empty()) {
163  initial_value = data.back();
164  }
165  initial_value.projected = true;
166  data.insert(data.end(), count, initial_value);
167  }
168  } else {
169  ProfileEntry initial_value;
170  initial_value.projected = true;
171  for (auto &it : nodes_) {
172  std::deque<ProfileEntry> &data = it.second.data_;
173  data.insert(data.begin(), count, initial_value);
174  }
175  }
176 }
177 
178 bool Profile::touchNode(const QString &path)
179 {
180  // If the node already exists, it's ancestors must already exist
181  // too. We can just lookup the key and return false because no
182  // nodes are being added.
183  if (node_key_from_path_.count(path)) {
184  return false;
185  }
186 
187  QStringList all_parts = path.split('/');
188  if (all_parts.isEmpty()) {
189  qWarning("Node path (%s) does not have a root component. This should never happen.",
190  qPrintable(path));
191  // This should seriously never happen because even the empty
192  // string will come back as a single item list.
193  return false;
194  }
195 
196  if (all_parts.front() != "") {
197  qWarning("Split of node path '%s' did not yield root as first element. This should never happend.",
198  qPrintable(path));
199  } else {
200  all_parts.removeFirst();
201  }
202 
203  QString this_path = "";
204  int this_depth = 0;
205  int parent_key = 0;
206 
207  while (!all_parts.isEmpty()) {
208  QString this_name = all_parts.takeFirst();
209  this_path = this_path + "/" + this_name;
210  this_depth += 1;
211 
212  if (node_key_from_path_.count(this_path)) {
213  parent_key = node_key_from_path_.at(this_path);
214  continue;
215  }
216 
217  // Otherwise we need to create a new node.
218  int this_key = nodes_.size();
219  while (nodes_.count(this_key)) { this_key++; }
220 
221  node_key_from_path_[this_path] = this_key;
222  ProfileNode &this_node = nodes_[this_key];
223 
224  this_node.node_key_ = this_key;
225  this_node.name_ = this_name;
226  this_node.path_ = this_path;
227  this_node.measured_ = false;
228 
229  ProfileEntry initial_value;
230  initial_value.projected = true;
231  this_node.data_.resize(max_time_s_ - min_time_s_, initial_value);
232 
233  this_node.depth_ = this_depth;
234  this_node.parent_ = parent_key;
235  parent_key = this_key;
236 
237  // children_ will be set by rebuildChildren.
238  //
239  // Note: Why not just modify the index as we add nodes? Mainly
240  // for simplicity. Doing everything at once makes it a little
241  // easier to guarantee proper ordering and consistency without
242  // having to do a lot of searching/sorting. Adding nodes should
243  // be a very infrequent operation, so the added cost of redoing
244  // the work isn't an issue.
245  }
246 
247  return true;
248 }
249 
250 void Profile::storeItemData(std::set<uint64_t> &modified_times,
251  const int node_key,
252  const NewProfileData &item)
253 {
254  size_t index = indexFromSec(item.wall_stamp_sec);
255  ProfileNode &node = nodes_.at(node_key);
256 
257  node.measured_ = true;
258  node.data_[index].projected = false;
259  node.data_[index].cumulative_call_count = item.cumulative_call_count;
260  node.data_[index].cumulative_inclusive_duration_ns = item.cumulative_inclusive_duration_ns;
261  node.data_[index].incremental_inclusive_duration_ns = item.incremental_inclusive_duration_ns;
262  node.data_[index].incremental_max_duration_ns = item.incremental_max_duration_ns;
263  // Exclusive timing fields are derived data and are set in updateDerivedData().
264 
265  // If the subsequent elements are projected data, we should
266  // propogate this new data forward until the next firm data point.
267  for (size_t i = index+1; i < node.data_.size(); i++) {
268  if (node.data_[i].projected == false) {
269  break;
270  }
271  node.data_[i] = node.data_[index];
272  node.data_[i].projected = true;
273  modified_times.insert(secFromIndex(i));
274  }
275 }
276 
278 {
281 }
282 
284 {
285  QStringList paths;
286  for (auto const &it : nodes_) {
287  paths.append(it.second.path());
288  }
289  paths.sort();
290 
291  flat_index_.clear();
292  flat_index_.reserve(paths.size());
293  for (int i = 0; i < paths.size(); i++) {
294  if (node_key_from_path_.count(paths[i]) == 0) {
295  qWarning("Path is missing from node_key_from_path_. This should never happen.");
296  continue;
297  }
298  flat_index_.push_back(node_key_from_path_.at(paths[i]));
299  }
300 }
301 
302 // Compares the first N items of two string lists.
304  const QStringList &list1,
305  const QStringList &list2)
306 {
307  int size = std::min(list1.size(), list2.size());
308 
309  if (size == 0) {
310  return true;
311  }
312 
313  // Comparing in reverse because, in our use case, the differences
314  // are more likely to be at the end of the lists.
315  for (int i = size; i > 0; i--) {
316  if (list1[i-1] != list2[i-1]) {
317  return false;
318  }
319  }
320  return true;
321 }
322 
323 // static void printTree(ProfileTreeNode *node, const QString &prefix)
324 // {
325 // qWarning(qPrintable(prefix + node->name));
326 // for (size_t i = 0; i < node->child_nodes.size(); i++) {
327 // printTree(&(node->child_nodes[i]), prefix + " ");
328 // }
329 // }
330 
331 // Basic integrity check that we should add somewhere
332  // ProfileNode &node = nodes_.at(key);
333  // if (node.node_key_ != key) {
334  // qWarning("Profile at nodes_[%d] has mismatched key (%d). This should never happen.", key, node.node_key_);
335  // continue;
336  // }
337 
339 {
340  // Start by clearing out all children
341  for (auto &it : nodes_) {
342  it.second.children_.clear();
343  }
344 
345  for (int key : flat_index_) {
346  if (nodes_.count(key) == 0) {
347  qWarning("Key (%d) in flat index was not found in nodes_ map. This should never happen.", key);
348  continue;
349  }
350 
351  const ProfileNode &node = nodes_.at(key);
352  if (node.parent_ < 0) {
353  if (key != 0) {
354  qWarning("Profile node %d does not have a valid parent. This should never happen.", key);
355  }
356  continue;
357  }
358 
359  if (nodes_.count(node.parentKey()) == 0) {
360  qWarning("Profile node %d's parent (%d) was not found in nodes_. This should never happen.",
361  key, node.parentKey());
362  continue;
363  }
364 
365  ProfileNode &parent = nodes_.at(node.parentKey());
366  parent.children_.push_back(key);
367  }
368 }
369 
370 void Profile::updateDerivedData(size_t index)
371 {
372  if (nodes_.count(0) == 0) {
373  qWarning("Profile is missing root node.");
374  return;
375  }
376 
377  updateDerivedDataInternal(nodes_.at(0), index);
378 }
379 
381 {
382  uint64_t children_cum_call_count = 0;
383  uint64_t children_cum_incl_duration = 0;
384  uint64_t children_inc_incl_duration = 0;
385  uint64_t children_inc_max_duration = 0;
386 
387  for (auto &child_key : node.childKeys()) {
388  if (nodes_.count(child_key) == 0) {
389  qWarning("Invalid child key in updateDerivedDataInternal");
390  continue;
391  }
392  ProfileNode &child = nodes_.at(child_key);
393 
394  updateDerivedDataInternal(child, index);
395  const ProfileEntry &data = child.data_[index];
396  children_cum_call_count += data.cumulative_call_count;
397  children_cum_incl_duration += data.cumulative_inclusive_duration_ns;
398  children_inc_incl_duration += data.incremental_inclusive_duration_ns;
399  children_inc_max_duration = std::max(children_inc_max_duration, data.incremental_max_duration_ns);
400  }
401 
402  ProfileEntry &data = node.data_[index];
403  if (!node.measured_) {
404  data.cumulative_call_count = children_cum_call_count;
405  data.cumulative_inclusive_duration_ns = children_cum_incl_duration;
406  data.incremental_inclusive_duration_ns = children_inc_incl_duration;
407  data.incremental_max_duration_ns = children_inc_max_duration;
408  }
409 
410  if (children_cum_incl_duration > data.cumulative_inclusive_duration_ns) {
411  // This case has not been observed yet.
412  qWarning("Node's (%s) cumulative inclusive duration is less than it's combined"
413  " children (%zu < %zu). I have not seen this before, so it may or may"
414  " not be a big issue.",
415  qPrintable(node.name()),
417  children_cum_incl_duration);
418  data.cumulative_inclusive_duration_ns = children_cum_incl_duration;
420  } else {
421  data.cumulative_exclusive_duration_ns = data.cumulative_inclusive_duration_ns - children_cum_incl_duration;
422  }
423 
424  if (children_inc_incl_duration > data.incremental_inclusive_duration_ns) {
425  // This case was happening very frequently for long running nodes
426  // with fast running small parts. The profiler was limiting the
427  // duration to 1 second while the smaller parts add up to just
428  // slightly more than a second. The profiler was updated to help
429  // compensate by using the real publishing interval instead of the
430  // expected interval.
431 
432  // If it still happens, we want to adjust the data to be
433  // internally consistent. First, we allow a threshold to permit
434  // small errors without a warning. We're using a tenth of a
435  // millisecond here
436  if (children_inc_incl_duration - data.incremental_inclusive_duration_ns > 100000) {
437  qWarning("Node's (%s) incremental inclusive timing is less than it's combined "
438  "children (%zu < %zu). If this happens frequently, something is wrong.",
439  qPrintable(node.name()),
441  children_inc_incl_duration);
442  }
443 
444  data.incremental_inclusive_duration_ns = children_inc_incl_duration;
446  } else {
447  data.incremental_exclusive_duration_ns = data.incremental_inclusive_duration_ns - children_inc_incl_duration;
448  }
449 }
450 
451 void Profile::setName(const QString &name)
452 {
453  name_ = name;
455 }
456 
457 const ProfileNode& Profile::node(int node_key) const
458 {
459  if (nodes_.count(node_key) == 0) {
460  qWarning("Someone requested an invalid node (%d) from profile (%d)",
461  node_key, profile_key_);
462  return invalid_node_;
463  }
464  return nodes_.at(node_key);
465 }
466 
468 {
469  return node(0);
470 }
471 
472 const std::vector<int>& Profile::nodeKeys() const
473 {
474  return flat_index_;
475 }
476 } // namespace swri_profiler_tools
static const ProfileNode invalid_node_
Definition: profile.cpp:40
const ProfileNode & rootNode() const
Definition: profile.cpp:467
static bool compareInitialStringList(const QStringList &list1, const QStringList &list2)
Definition: profile.cpp:303
void setName(const QString &name)
Definition: profile.cpp:451
void addData(const NewProfileDataVector &data)
Definition: profile.cpp:75
std::unordered_map< int, ProfileNode > nodes_
Definition: profile.h:170
const std::vector< int > & childKeys() const
Definition: profile.h:137
std::map< QString, int > node_key_from_path_
Definition: profile.h:169
QString normalizeNodePath(const QString &path)
Definition: util.cpp:35
uint64_t cumulative_exclusive_duration_ns
Definition: profile.h:62
const ProfileNode & node(int node_key) const
Definition: profile.cpp:457
std::vector< int > children_
Definition: profile.h:113
std::vector< NewProfileData > NewProfileDataVector
void expandTimeline(const uint64_t sec)
Definition: profile.cpp:133
uint64_t incremental_exclusive_duration_ns
Definition: profile.h:63
const QString & name() const
Definition: profile.h:211
size_t indexFromSec(const uint64_t secs) const
Definition: profile.h:193
void storeItemData(std::set< uint64_t > &modified_times, const int node_key, const NewProfileData &item)
Definition: profile.cpp:250
const QString & name() const
Definition: profile.h:131
uint64_t secFromIndex(const uint64_t index) const
Definition: profile.h:194
const std::vector< int > & nodeKeys() const
Definition: profile.cpp:472
uint64_t cumulative_inclusive_duration_ns
Definition: profile.h:60
void addDataToAllNodes(const bool back, const size_t count)
Definition: profile.cpp:156
void initialize(int profile_key, const QString &name)
Definition: profile.cpp:63
void nodesAdded(int profile_key)
void updateDerivedData(size_t index)
Definition: profile.cpp:370
bool touchNode(const QString &path)
Definition: profile.cpp:178
std::vector< int > flat_index_
Definition: profile.h:175
void profileModified(int profile_key)
uint64_t incremental_inclusive_duration_ns
Definition: profile.h:61
void dataAdded(int profile_key)
std::deque< ProfileEntry > data_
Definition: profile.h:102
void updateDerivedDataInternal(ProfileNode &node, size_t index)
Definition: profile.cpp:380


swri_profiler_tools
Author(s):
autogenerated on Fri Nov 27 2020 03:44:18