profile.cpp
Go to the documentation of this file.
00001 // *****************************************************************************
00002 //
00003 // Copyright (c) 2015, Southwest Research Institute® (SwRI®)
00004 // All rights reserved.
00005 //
00006 // Redistribution and use in source and binary forms, with or without
00007 // modification, are permitted provided that the following conditions are met:
00008 //     * Redistributions of source code must retain the above copyright
00009 //       notice, this list of conditions and the following disclaimer.
00010 //     * Redistributions in binary form must reproduce the above copyright
00011 //       notice, this list of conditions and the following disclaimer in the
00012 //       documentation and/or other materials provided with the distribution.
00013 //     * Neither the name of Southwest Research Institute® (SwRI®) nor the
00014 //       names of its contributors may be used to endorse or promote products
00015 //       derived from this software without specific prior written permission.
00016 //
00017 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020 // ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE 
00021 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
00022 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
00023 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
00024 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
00025 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
00026 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00027 // DAMAGE.
00028 //
00029 // *****************************************************************************
00030 #include <swri_profiler_tools/profile.h>
00031 #include <swri_profiler_tools/util.h>
00032 #include <algorithm>
00033 #include <set>
00034 #include <QStringList>
00035 #include <QDebug>
00036 
00037 namespace swri_profiler_tools
00038 {
00039 // A null object to return for invalid keys.
00040 static const ProfileNode invalid_node_;
00041 
00042 Profile::Profile()
00043   :
00044   profile_key_(-1),
00045   min_time_s_(0),
00046   max_time_s_(0)
00047 {
00048   // Add the root node.
00049   node_key_from_path_[""] = 0;
00050   ProfileNode &root_node = nodes_[0];
00051   root_node.node_key_ = 0;
00052   root_node.name_ = "";
00053   root_node.path_ = "";
00054   root_node.measured_ = false;
00055   root_node.depth_ = 0;
00056   root_node.parent_ = -1;
00057 }
00058 
00059 Profile::~Profile()
00060 {
00061 }
00062 
00063 void Profile::initialize(int profile_key, const QString &name)
00064 {
00065   if (isValid()) {
00066     qWarning("Re-initializing a valid profile (%d,%s) with (%d,%s). "
00067              "Something is probably horribly wrong.",
00068              profile_key_, qPrintable(name_),
00069              profile_key, qPrintable(name));
00070   }
00071   profile_key_ = profile_key;
00072   name_ = name;
00073 }
00074 
00075 void Profile::addData(const NewProfileDataVector &data)
00076 {
00077   if (profile_key_ < 0) {
00078     qWarning("Attempt to add %zu elements to an invalid profile.", data.size());
00079     return;
00080   }
00081 
00082   if (data.size() == 0) {
00083     return;
00084   }
00085 
00086   std::set<uint64_t> modified_times;
00087   
00088   bool nodes_added = false;
00089   for (auto const &item : data) {
00090     QString path = normalizeNodePath(item.label);  
00091 
00092     // Since we're setting the data at this item's timestamp, we
00093     // always add the timestamp to the modified list.
00094     modified_times.insert(item.wall_stamp_sec);
00095     
00096     // If this item is outside our current timeline, we need to expand
00097     // the timeline (and fill in the gaps).  This could influence a larger 
00098     expandTimeline(item.wall_stamp_sec);
00099 
00100     // Touching the node guarantees that it and all of its ancestor
00101     // nodes exist.
00102     nodes_added |= touchNode(path);
00103 
00104     if (node_key_from_path_.count(path) == 0) {
00105       qWarning("Failed to touch node for %s. Data will be dropped. This should never happend.",
00106                qPrintable(path));
00107       continue;
00108     }
00109     int node_key = node_key_from_path_.at(path);
00110 
00111     // At this point, we know that the corresponding node and timeslot
00112     // exist, so we can store the data.  Storing data may influence
00113     // subsequent times.
00114     storeItemData(modified_times, node_key, item);    
00115   }  
00116 
00117   // If nodes were created, we need to update our indices.
00118   if (nodes_added) {
00119     rebuildIndices();
00120     Q_EMIT nodesAdded(profile_key_);
00121   }
00122 
00123   // Finally, we need to update derived data that may have changed
00124   // from the update.
00125   for (auto const &t : modified_times) {
00126     updateDerivedData(indexFromSec(t));
00127   }
00128 
00129   // Notify observers that the profile has new data.
00130   Q_EMIT dataAdded(profile_key_);
00131 }
00132 
00133 void Profile::expandTimeline(const uint64_t sec)
00134 {
00135   if (sec >= min_time_s_ && sec < max_time_s_) {
00136     // This time is already in our timeline, so ignore it.
00137   } else if (min_time_s_ == max_time_s_) {
00138     // The timeline is empty
00139     min_time_s_ = sec;
00140     max_time_s_ = sec+1;
00141     addDataToAllNodes(true, 1);
00142   } else if (sec >= max_time_s_) {
00143     // New data extends the back of the timeline.
00144     size_t new_elements = sec - max_time_s_ + 1;
00145     max_time_s_ = sec+1;
00146     addDataToAllNodes(true, new_elements);
00147   } else {
00148     // New data must be at the front of the timeline.  This case
00149     // should be rare.
00150     size_t new_elements = min_time_s_ - sec;
00151     min_time_s_ = sec;
00152     addDataToAllNodes(false, new_elements);
00153   }    
00154 }
00155 
00156 void Profile::addDataToAllNodes(const bool back, const size_t count)
00157 {
00158   if (back) {
00159     for (auto &it : nodes_) {
00160       std::deque<ProfileEntry> &data = it.second.data_;
00161       ProfileEntry initial_value;
00162       if (!data.empty()) {
00163         initial_value = data.back();
00164       }          
00165       initial_value.projected = true;
00166       data.insert(data.end(), count, initial_value);
00167     }
00168   } else {
00169     ProfileEntry initial_value;
00170     initial_value.projected = true;
00171     for (auto &it : nodes_) {
00172       std::deque<ProfileEntry> &data = it.second.data_;
00173       data.insert(data.begin(), count, initial_value);
00174     }
00175   }
00176 }
00177 
00178 bool Profile::touchNode(const QString &path)
00179 {
00180   // If the node already exists, it's ancestors must already exist
00181   // too.  We can just lookup the key and return false because no
00182   // nodes are being added.
00183   if (node_key_from_path_.count(path)) {
00184     return false;
00185   }
00186 
00187   QStringList all_parts = path.split('/');
00188   if (all_parts.isEmpty()) {
00189     qWarning("Node path (%s) does not have a root component.  This should never happen.",
00190              qPrintable(path));
00191     // This should seriously never happen because even the empty
00192     // string will come back as a single item list.
00193     return false;
00194   }
00195 
00196   if (all_parts.front() != "") {
00197     qWarning("Split of node path '%s' did not yield root as first element. This should never happend.",
00198              qPrintable(path));
00199   } else {
00200     all_parts.removeFirst();
00201   }
00202 
00203   QString this_path = "";
00204   int this_depth = 0;  
00205   int parent_key = 0;
00206   
00207   while (!all_parts.isEmpty()) {
00208     QString this_name = all_parts.takeFirst();    
00209     this_path = this_path + "/" + this_name;
00210     this_depth += 1;
00211 
00212     if (node_key_from_path_.count(this_path)) {
00213       parent_key = node_key_from_path_.at(this_path);
00214       continue;
00215     }
00216 
00217     // Otherwise we need to create a new node.    
00218     int this_key = nodes_.size();
00219     while (nodes_.count(this_key)) { this_key++; }
00220 
00221     node_key_from_path_[this_path] = this_key;
00222     ProfileNode &this_node = nodes_[this_key];
00223 
00224     this_node.node_key_ = this_key;
00225     this_node.name_ = this_name;
00226     this_node.path_ = this_path;
00227     this_node.measured_ = false;
00228 
00229     ProfileEntry initial_value;
00230     initial_value.projected = true;
00231     this_node.data_.resize(max_time_s_ - min_time_s_, initial_value);
00232 
00233     this_node.depth_ = this_depth;
00234     this_node.parent_ = parent_key;
00235     parent_key = this_key;
00236 
00237     // children_ will be set by rebuildChildren.
00238     // 
00239     // Note: Why not just modify the index as we add nodes?  Mainly
00240     // for simplicity.  Doing everything at once makes it a little
00241     // easier to guarantee proper ordering and consistency without
00242     // having to do a lot of searching/sorting.  Adding nodes should
00243     // be a very infrequent operation, so the added cost of redoing
00244     // the work isn't an issue.
00245   }
00246 
00247   return true;
00248 }
00249 
00250 void Profile::storeItemData(std::set<uint64_t> &modified_times,
00251                             const int node_key,
00252                             const NewProfileData &item)
00253 {
00254   size_t index = indexFromSec(item.wall_stamp_sec);
00255   ProfileNode &node = nodes_.at(node_key);
00256 
00257   node.measured_ = true;
00258   node.data_[index].projected = false;
00259   node.data_[index].cumulative_call_count = item.cumulative_call_count;
00260   node.data_[index].cumulative_inclusive_duration_ns = item.cumulative_inclusive_duration_ns;
00261   node.data_[index].incremental_inclusive_duration_ns = item.incremental_inclusive_duration_ns;
00262   node.data_[index].incremental_max_duration_ns = item.incremental_max_duration_ns;
00263   // Exclusive timing fields are derived data and are set in updateDerivedData().
00264 
00265   // If the subsequent elements are projected data, we should
00266   // propogate this new data forward until the next firm data point.
00267   for (size_t i = index+1; i < node.data_.size(); i++) {
00268     if (node.data_[i].projected == false) {
00269       break;
00270     }
00271     node.data_[i] = node.data_[index];
00272     node.data_[i].projected = true;
00273     modified_times.insert(secFromIndex(i));
00274   }
00275 }
00276 
00277 void Profile::rebuildIndices()
00278 {
00279   rebuildFlatIndex();
00280   rebuildTreeIndex();
00281 }
00282 
00283 void Profile::rebuildFlatIndex()
00284 {
00285   QStringList paths;
00286   for (auto const &it : nodes_) {
00287     paths.append(it.second.path());
00288   }
00289   paths.sort();
00290 
00291   flat_index_.clear();
00292   flat_index_.reserve(paths.size());
00293   for (int i = 0; i < paths.size(); i++) {
00294     if (node_key_from_path_.count(paths[i]) == 0) {
00295       qWarning("Path is missing from node_key_from_path_.  This should never happen.");
00296       continue;
00297     }
00298     flat_index_.push_back(node_key_from_path_.at(paths[i]));
00299   }
00300 }
00301 
00302 // Compares the first N items of two string lists.
00303 static bool compareInitialStringList(
00304   const QStringList &list1,
00305   const QStringList &list2)
00306 {
00307   int size = std::min(list1.size(), list2.size());
00308   
00309   if (size == 0) {
00310     return true;
00311   }
00312 
00313   // Comparing in reverse because, in our use case, the differences
00314   // are more likely to be at the end of the lists.
00315   for (int i = size; i > 0; i--) {
00316     if (list1[i-1] != list2[i-1]) {
00317       return false;
00318     }
00319   }
00320   return true;    
00321 }
00322 
00323 // static void printTree(ProfileTreeNode *node, const QString &prefix)
00324 // {
00325 //   qWarning(qPrintable(prefix + node->name));
00326 //   for (size_t i = 0; i < node->child_nodes.size(); i++) {
00327 //     printTree(&(node->child_nodes[i]), prefix + "  ");
00328 //   }
00329 // }
00330 
00331 // Basic integrity check that we should add somewhere
00332     // ProfileNode &node = nodes_.at(key);
00333     // if (node.node_key_ != key) {
00334     //   qWarning("Profile at nodes_[%d] has mismatched key (%d).  This should never happen.", key, node.node_key_);
00335     //   continue;
00336     // }
00337 
00338 void Profile::rebuildTreeIndex()
00339 {
00340   // Start by clearing out all children
00341   for (auto &it : nodes_) {
00342     it.second.children_.clear();
00343   }
00344 
00345   for (int key : flat_index_) {
00346     if (nodes_.count(key) == 0) {
00347       qWarning("Key (%d) in flat index was not found in nodes_ map. This should never happen.", key);
00348       continue;
00349     }
00350     
00351     const ProfileNode &node = nodes_.at(key);
00352     if (node.parent_ < 0) {
00353       if (key != 0) {
00354         qWarning("Profile node %d does not have a valid parent. This should never happen.", key);
00355       }
00356       continue;
00357     }
00358 
00359     if (nodes_.count(node.parentKey()) == 0) {
00360       qWarning("Profile node %d's parent (%d) was not found in nodes_. This should never happen.",
00361                key, node.parentKey());
00362       continue;
00363     }
00364 
00365     ProfileNode &parent = nodes_.at(node.parentKey());
00366     parent.children_.push_back(key);
00367   }
00368 }
00369 
00370 void Profile::updateDerivedData(size_t index)
00371 {
00372   if (nodes_.count(0) == 0) {
00373     qWarning("Profile is missing root node.");
00374     return;
00375   }
00376   
00377   updateDerivedDataInternal(nodes_.at(0), index);
00378 }
00379 
00380 void Profile::updateDerivedDataInternal(ProfileNode &node, size_t index)
00381 {
00382   uint64_t children_cum_call_count = 0;
00383   uint64_t children_cum_incl_duration = 0;
00384   uint64_t children_inc_incl_duration = 0;
00385   uint64_t children_inc_max_duration = 0;
00386 
00387   for (auto &child_key : node.childKeys()) {
00388     if (nodes_.count(child_key) == 0) {
00389       qWarning("Invalid child key in updateDerivedDataInternal");
00390       continue;
00391     }
00392     ProfileNode &child = nodes_.at(child_key);
00393     
00394     updateDerivedDataInternal(child, index);
00395     const ProfileEntry &data = child.data_[index];
00396     children_cum_call_count += data.cumulative_call_count;
00397     children_cum_incl_duration += data.cumulative_inclusive_duration_ns;
00398     children_inc_incl_duration += data.incremental_inclusive_duration_ns;
00399     children_inc_max_duration = std::max(children_inc_max_duration, data.incremental_max_duration_ns);
00400   }
00401 
00402   ProfileEntry &data = node.data_[index];
00403   if (!node.measured_) {
00404     data.cumulative_call_count = children_cum_call_count;
00405     data.cumulative_inclusive_duration_ns = children_cum_incl_duration;
00406     data.incremental_inclusive_duration_ns = children_inc_incl_duration;
00407     data.incremental_max_duration_ns = children_inc_max_duration;
00408   }
00409 
00410   if (children_cum_incl_duration > data.cumulative_inclusive_duration_ns) {
00411     // This case has not been observed yet.
00412     qWarning("Node's (%s) cumulative inclusive duration is less than it's combined"
00413              " children (%zu < %zu). I have not seen this before, so it may or may"
00414              " not be a big issue.",
00415              qPrintable(node.name()),
00416              data.cumulative_inclusive_duration_ns,
00417              children_cum_incl_duration);
00418     data.cumulative_inclusive_duration_ns = children_cum_incl_duration;
00419     data.cumulative_exclusive_duration_ns = 0;
00420   } else {
00421     data.cumulative_exclusive_duration_ns = data.cumulative_inclusive_duration_ns - children_cum_incl_duration;
00422   }
00423 
00424   if (children_inc_incl_duration > data.incremental_inclusive_duration_ns) {
00425     // This case was happening very frequently for long running nodes
00426     // with fast running small parts.  The profiler was limiting the
00427     // duration to 1 second while the smaller parts add up to just
00428     // slightly more than a second.  The profiler was updated to help
00429     // compensate by using the real publishing interval instead of the
00430     // expected interval.
00431 
00432     // If it still happens, we want to adjust the data to be
00433     // internally consistent.  First, we allow a threshold to permit
00434     // small errors without a warning.  We're using a tenth of a
00435     // millisecond here
00436     if (children_inc_incl_duration - data.incremental_inclusive_duration_ns > 100000) {
00437       qWarning("Node's (%s) incremental inclusive timing is less than it's combined "
00438                "children (%zu < %zu).  If this happens frequently, something is wrong.",
00439                qPrintable(node.name()),
00440                data.incremental_inclusive_duration_ns,
00441                children_inc_incl_duration);
00442     }
00443 
00444     data.incremental_inclusive_duration_ns = children_inc_incl_duration;
00445     data.incremental_exclusive_duration_ns = 0;
00446   } else {    
00447     data.incremental_exclusive_duration_ns = data.incremental_inclusive_duration_ns - children_inc_incl_duration;
00448   }
00449 }
00450 
00451 void Profile::setName(const QString &name)
00452 {
00453   name_ = name;
00454   Q_EMIT profileModified(profile_key_);
00455 }
00456 
00457 const ProfileNode& Profile::node(int node_key) const
00458 {
00459   if (nodes_.count(node_key) == 0) {
00460     qWarning("Someone requested an invalid node (%d) from profile (%d)",
00461              node_key, profile_key_);
00462     return invalid_node_;
00463   }
00464   return nodes_.at(node_key);
00465 }
00466 
00467 const ProfileNode& Profile::rootNode() const
00468 {
00469   return node(0);
00470 }
00471 
00472 const std::vector<int>& Profile::nodeKeys() const
00473 {
00474   return flat_index_;
00475 }
00476 }  // namespace swri_profiler_tools


swri_profiler_tools
Author(s):
autogenerated on Sat Apr 27 2019 03:08:49