Teuchos - Trilinos Tools Package  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Teuchos_XMLPerfTestArchive.cpp
1 // @HEADER
2 // *****************************************************************************
3 // Teuchos: Common Tools Package
4 //
5 // Copyright 2004 NTESS and the Teuchos contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
10 #include <iostream>
11 #include <fstream>
12 #include <cstring>
13 #include <cstdlib>
14 #include <Teuchos_XMLObject.hpp>
17 #ifdef _WIN32
18 #include <winsock2.h>
19 #else
20 #include <unistd.h>
21 #endif
22 
23 namespace Teuchos {
24 
25 ValueTolerance::ValueTolerance() {
26  value = 0;
27  lower = 0;
28  upper = 0;
29  tolerance = 0;
30  use_tolerance = true;
31 }
32 
33 ValueTolerance::ValueTolerance(double val, double tol) {
34  value = val;
35  lower = 0;
36  upper = 0;
37  tolerance = tol;
38  use_tolerance = true;
39 }
40 
41 ValueTolerance::ValueTolerance(double val, double low, double up) {
42  value = val;
43  upper = up;
44  lower = low;
45  tolerance = 0;
46  use_tolerance = false;
47 }
48 
49 ValueTolerance::ValueTolerance(std::string str) {
50  from_string(str);
51 }
52 
53 bool ValueTolerance::operator ==(ValueTolerance& rhs) {
54  return (value == rhs.value) &&
55  (tolerance == rhs.tolerance) &&
56  (lower == rhs.lower) &&
57  (upper == rhs.upper) &&
58  (use_tolerance == rhs.use_tolerance);
59 }
60 
61 std::string ValueTolerance::as_string(){
62  std::ostringstream strs;
63  if(use_tolerance)
64  strs << value << " , " << tolerance;
65  else
66  strs << value << " , " << lower << " , " << upper;
67  return strs.str();
68 }
69 
70 void ValueTolerance::from_string(const std::string& valtol_str) {
71  std::string value_str = valtol_str.substr(0,valtol_str.find(","));
72  value = atof(value_str.c_str());
73  std::string tol_str = valtol_str.substr(valtol_str.find(",")+1);
74  if(tol_str.find(",")<=tol_str.length()) {
75  use_tolerance = false;
76  std::string lower_str = tol_str.substr(0,tol_str.find(","));
77  lower = atof(lower_str.c_str());
78  std::string upper_str = tol_str.substr(tol_str.find(",")+1);
79  upper = atof(upper_str.c_str());
80  } else {
81  use_tolerance = true;
82  tolerance = atof(tol_str.c_str());
83  }
84 }
85 
86  XMLTestNode::XMLTestNode():XMLObject() {}
87 
88  XMLTestNode::XMLTestNode(const std::string &tag):XMLObject(tag) {}
89 
90  XMLTestNode::XMLTestNode(XMLObjectImplem *ptr):XMLObject(ptr) {}
91 
92  XMLTestNode::XMLTestNode(XMLObject obj):XMLObject(obj) {}
93 
94  void XMLTestNode::addDouble (const std::string &name, double val) {
95  addAttribute<double>(name,val);
96  }
97 
98  void XMLTestNode::addInt (const std::string &name, int val) {
99  addAttribute<int>(name,val);
100  }
101 
102  void XMLTestNode::addBool (const std::string &name, bool val) {
103  addAttribute<bool>(name,val);
104  }
105 
106  void XMLTestNode::addValueTolerance(const std::string &name, ValueTolerance val){
107  addAttribute<std::string>(name,val.as_string());
108  }
109 
110  void XMLTestNode::addString (const std::string &name, std::string val) {
111  addAttribute<std::string>(name,val);
112  }
113 
114  bool XMLTestNode::hasChild(const std::string &name) const {
115  bool found = false;
116  for(int i = 0; i < numChildren(); i++) {
117  if(name.compare(XMLObject::getChild(i).getTag()) == 0) {
118  found = true;
119  i = numChildren();
120  }
121  }
122  return found;
123  }
124 
125  void XMLTestNode::appendContentLine(const size_t& i, const std::string &str) {
126  ptr_->appendContentLine(i,str);
127  }
128 
129  XMLTestNode XMLTestNode::getChild(const std::string &name) const {
130  XMLTestNode child;
131  for(int i = 0; i < numChildren(); i++) {
132  if(name.compare(XMLObject::getChild(i).getTag()) == 0)
133  child = XMLObject::getChild(i);
134  }
135  return child;
136  }
137 
138  XMLTestNode XMLTestNode::getChild(const int &i) const {
139  return XMLObject::getChild(i);
140  }
141 
142  const XMLObject* XMLTestNode::xml_object() const {
143  return (XMLObject*) this;
144  }
145 
146  bool XMLTestNode::hasSameElements(XMLTestNode const & lhs) const {
147 
148  if((numChildren()!=lhs.numChildren()) ||
149  (numContentLines()!= lhs.numContentLines()) ||
150  (getTag().compare(lhs.getTag())!=0)) return false;
151 
152  for(int i = 0; i<numChildren(); i++) {
153  const XMLTestNode child = XMLObject::getChild(i);
154  if( (!lhs.hasChild(child.getTag())) ||
155  (!child.hasSameElements(lhs.getChild(child.getTag()))) ) return false;
156  }
157 
158  for(int i = 0; i<numContentLines(); i++)
159  if(getContentLine(i).compare(lhs.getContentLine(i))!=0) return false;
160 
161  return true;
162  }
163 
165 
166  // Get CPUName, Number of Sockets, Number of Cores, Number of Hyperthreads
167  std::string cpuname("Undefined");
168  unsigned int threads = 0;
169  unsigned int cores_per_socket = 0;
170  unsigned int highest_socketid = 0;
171 
172  {
173  std::ifstream cpuinfo("/proc/cpuinfo");
174  std::string line;
175  if((cpuinfo.rdstate()&cpuinfo.failbit)) std::cout<<"Failed to open filen\n";
176  while (!cpuinfo.eof() && !(cpuinfo.rdstate()&cpuinfo.failbit)) {
177  getline (cpuinfo,line);
178  if (line.find("model name") < line.size()) {
179  cpuname = line.substr(line.find(":")+2);
180  threads++;
181  }
182  if (line.find("physical id") < line.size()) {
183  unsigned int socketid = atoi(line.substr(line.find(":")+2).c_str());
184  highest_socketid = highest_socketid>socketid?highest_socketid:socketid;
185  }
186  if (line.find("cpu cores") < line.size()) {
187  cores_per_socket = atoi(line.substr(line.find(":")+2).c_str());
188  }
189  }
190  }
191 
192 
193  XMLTestNode machine_config("MachineConfiguration");
194 
195  machine_config.addString("Compiler", TEUCHOS_COMPILER_NAME);
196  machine_config.addInt("Compiler_Version", TEUCHOS_COMPILER_VERSION);
197  machine_config.addString("CPU_Name", cpuname);
198  machine_config.addInt("CPU_Sockets", highest_socketid+1);
199  machine_config.addInt("CPU_Cores_Per_Socket", cores_per_socket);
200  machine_config.addInt("CPU_Total_HyperThreads", threads);
201  return machine_config;
202 }
203 
206  XMLTestNode new_test,
207  const std::string filename,
208  const std::string ext_hostname)
209 {
210  XMLTestNode database;
211  PerfTestResult return_value = PerfTestPassed;
212  bool is_new_config = true;
213 
214  // Open Database File
215  //
216  // FIXME (mfh 09 Apr 2014) This actually opens the file twice.
217  if (std::ifstream (filename.c_str ())) {
218  database = FileInputSource (filename).getObject ();
219  }
220 
221  // Get Current Hostname
222  char hostname[256];
223  memset (hostname, 0, 256);
224  if (ext_hostname.empty ()) {
225  gethostname (hostname, 255);
226  } else {
227  strncat (hostname, ext_hostname.c_str (), 255);
228  }
229 
230  XMLTestNode new_test_entry = new_test.getChild ("TestEntry");
231 
232  if (database.isEmpty ()) {
233  database = XMLTestNode ("PerfTests");
234  }
235  // Does hostname exist?
236  if (database.hasChild (hostname)) {
237  XMLTestNode machine = database.getChild (hostname);
238 
239  // Find matching machine configuration
240  for (int i = 0; i < machine.numChildren (); ++i) {
241  XMLTestNode configuration = machine.getChild (i);
243  configuration.getTag ().compare ("Configuration") != 0,
244  std::runtime_error, "Unexpected Tag \"" << configuration.getTag ()
245  << "\"; only children with Tag = \"Configuration\" are allowed in a "
246  "MachineEntry.");
247 
249  ! configuration.hasChild ("MachineConfiguration") ||
250  ! configuration.hasChild ("Tests"),
251  std::runtime_error,
252  "A Configuration needs to have a child \"MachineConfiguration\" and a "
253  "child \"Tests\".");
254 
255  XMLTestNode machine_configuration = configuration.getChild ("MachineConfiguration");
256  XMLTestNode old_tests = configuration.getChild ("Tests");
257 
258  if (machine_configuration.hasSameElements (machine_config)) {
259  is_new_config = false;
260 
261  // Find existing test with same tag as the new test
262  if (old_tests.hasChild (new_test.getTag ())) {
263 
264  XMLTestNode old_test = old_tests.getChild (new_test.getTag ());
265 
266  int new_test_config = -1;
267  for (int k = 0; k < old_test.numChildren (); ++k) {
268  XMLTestNode old_test_entry = old_test.getChild (k);
269 
271  ! old_test_entry.hasChild ("TestConfiguration") ||
272  ! new_test_entry.hasChild ("TestResults"),
273  std::runtime_error, "A TestEntry needs to have a child "
274  "\"TestConfiguration\" and a child \"TestResults\".");
275 
276  if (old_test_entry.getChild ("TestConfiguration").hasSameElements (new_test_entry.getChild ("TestConfiguration"))) {
277  new_test_config = k;
278  }
279  }
280 
281  if (new_test_config < 0) {
282  old_test.addChild (new_test_entry);
283  return_value = PerfTestNewTestConfiguration;
284  } else {
285  bool deviation = false;
286  XMLTestNode old_test_entry = old_test.getChild (new_test_config);
287  XMLTestNode old_results = old_test_entry.getChild ("TestResults");
288  XMLTestNode new_results = new_test_entry.getChild ("TestResults");
289 
290  // Compare all entries
291  for (int old_r = 0; old_r < old_results.numChildren (); ++old_r) {
292  XMLTestNode result_entry = old_results.getChild (old_r);
293 
294  // Finding entry with same name
295  bool exists = new_results.hasChild (result_entry.getTag ());
296 
297  if (exists) {
298  std::string oldv_str = result_entry.getContentLine (0);
299 
300  // If it is a time or result compare numeric values with tolerance
301  if((result_entry.getTag().find("Time")==0) || (result_entry.getTag().find("Result")==0)) {
302  ValueTolerance old_valtol(oldv_str);
303  ValueTolerance new_valtol(new_results.getChild(result_entry.getTag()).getContentLine(0));
304 
305  if(old_valtol.use_tolerance) {
306  double diff = old_valtol.value - new_valtol.value;
307  diff*=diff;
308 
309  double normalization = old_valtol.value;
310  normalization*=normalization;
311 
312  if(normalization==0?diff>0:diff/normalization>old_valtol.tolerance*old_valtol.tolerance) {
313  deviation = true;
314  std::cout << std::endl
315  << "DeviationA in Test: \"" << old_test.getTag()
316  << "\" for entry \"" << result_entry.getTag() << "\"" << std::endl;
317  std::cout << " Existing Value: \"" << oldv_str << "\"" << std::endl;
318  std::cout << " New Value: \"" << new_results.getChild(result_entry.getTag()).getContentLine(0) << "\"" << std::endl << std::endl;
319  }
320  } else {
321  if( (old_valtol.lower>new_valtol.value) || (old_valtol.upper<new_valtol.value)) {
322  deviation = true;
323  std::cout << std::endl
324  << "DeviationB in Test: \"" << old_test.getTag()
325  << "\" for entry \"" << result_entry.getTag() << "\"" << std::endl;
326  std::cout << " Existing Value: \"" << oldv_str << "\"" << std::endl;
327  std::cout << " New Value: \"" << new_results.getChild(result_entry.getTag()).getContentLine(0) << "\"" << std::endl << std::endl;
328  }
329  }
330  } else {
331  // Compare exact match for every other type of entry
332  if(oldv_str.compare(new_results.getChild(result_entry.getTag()).getContentLine(0))!=0) {
333  deviation = true;
334  std::cout << std::endl
335  << "DeviationC in Test: \"" << old_test.getTag()
336  << "\" for entry \"" << result_entry.getTag() << "\"" << std::endl;
337  std::cout << " Existing Value: \"" << oldv_str << "\"" << std::endl;
338  std::cout << " New Value: \"" << new_results.getChild(result_entry.getTag()).getContentLine(0) << "\"" << std::endl << std::endl;
339  }
340  }
341  }
342  // An old value was not given in the new test: this is an error;
343  if(!exists) {
344  std::cout << "Error New test has same name as an existing one, but one of the old entries is missing." << std::endl;
345  deviation = true;
346  }
347  }
348 
349  if(deviation) { return_value = PerfTestFailed; }
350  else {
351  // Did someone add new values to the test?
352  if(new_results.numChildren()!=old_results.numChildren()) {
353  for(int new_r = 0; new_r < new_results.numChildren() ; new_r++) {
354  if(!old_results.hasChild(new_results.getChild(new_r).getTag())) {
355  old_results.addChild(new_results.getChild(new_r));
356  }
357  }
358 
359  return_value = PerfTestUpdatedTest;
360  }
361  }
362  }
363  } else { // End Test Exists
364  // Add new test if no match was found
365  old_tests.addChild(new_test);
366  return_value = PerfTestNewTest;
367  }
368  } // End MachineConfiguration Exists
369  } // End loop over MachineConfigurations
370 
371  // Did not find matching MachineConfiguration
372  if(is_new_config) {
373  XMLTestNode config("Configuration");
374  config.addChild(machine_config);
375  XMLTestNode tests("Tests");
376  tests.addChild(new_test);
377 
378  config.addChild(tests);
379  machine.addChild(config);
380 
381  return_value = PerfTestNewConfiguration;
382  }
383  } else { // Machine Entry does not exist
384  XMLTestNode machine(hostname);
385 
386  XMLTestNode config("Configuration");
387  config.addChild(machine_config);
388  XMLTestNode tests("Tests");
389  tests.addChild(new_test);
390  config.addChild(tests);
391 
392  machine.addChild(config);
393 
394  database.addChild(machine);
395 
396  return_value = PerfTestNewMachine;
397  }
398 
399 
400  if(return_value>PerfTestPassed) {
401  std::ofstream fout(filename.c_str());
402  fout << database << std::endl;
403  }
404 
405  return return_value;
406 }
407 }
const std::string & getTag() const
Return the tag of the current node.
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Macro for throwing an exception with breakpointing to ease debugging.
void addChild(const XMLObject &child)
Add a child node to the node.
bool isEmpty() const
Find out if a node is empty.
XMLObject()
Empty constructor.
ValueTolerance is a struct to keep a tuple of value and a tolerance. The tolerance can be either expr...
Tools for an XML-based performance test archive.
XMLObject getObject() const
Get an object by invoking the TreeBuildingXMLHandler on the input data.
Instantiation of XMLInputSource class for reading XML from a file.
PerfTestResult
ReturnValues for PerfTest_CheckOrAdd_Test.
int numChildren() const
Return the number of child nodes owned by this node.
Subclass of XMLObject used by the performance archive.
int numContentLines() const
Return the number of lines of character content stored in this node.
Definition of XMLInputSource derived class for reading XML from a file.
const XMLObject & getChild(int i) const
Return the i-th child node.
const std::string & getContentLine(int i) const
Return the i-th line of character content stored in this node.
PerfTestResult PerfTest_CheckOrAdd_Test(XMLTestNode machine_config, XMLTestNode new_test, const std::string filename, const std::string ext_hostname)
Check whether a test is present and match an existing test in an archive.
XMLTestNode PerfTest_MachineConfig()
PerfTest_MachineConfig generates a basic machine configuration XMLTestNode.
An object representation of a subset of XML data.