Nektar++
MetricExecutionTime.cpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: MetricExecutionTime.cpp
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: Implementation of the execution time metric. A test will fail
32// if the execution time of the test falls outside of an assigned tolerance.
33//
34///////////////////////////////////////////////////////////////////////////////
35
36#include <MetricExecutionTime.h>
37
38#include <boost/algorithm/string.hpp>
39#include <boost/asio/ip/host_name.hpp>
40#include <boost/core/ignore_unused.hpp>
41#include <boost/lexical_cast.hpp>
42
43using namespace std;
44
45namespace Nektar
46{
47std::string MetricExecutionTime::type =
50
51/**
52 * @brief Construct a new MetricExecutionTime object.
53 *
54 * If the metric is not a derived class, then this constructor will parse the
55 * regular expression from the metric's element in the test file, or use a
56 * default value if this doesn't exist. It will also see if an expected
57 * execution time and tolerance have been provided for the current computer's
58 * hostname. If this is the case, these will be used by \p v_Test. If not, then
59 * a skip flag \p m_skip will be set to \p true and the test will automatically
60 * pass.
61 *
62 * @param metric
63 * @param generate
64 */
65MetricExecutionTime::MetricExecutionTime(TiXmlElement *metric, bool generate)
66 : Metric(metric, generate)
67{
68 // If we are a derived class, do nothing
69 if (m_type != "EXECUTIONTIME")
70 {
71 return;
72 }
73
74 // Parse Regex expression
75 TiXmlElement *regex = metric->FirstChildElement("regex");
76
77 if (regex)
78 {
79 ASSERTL0(regex->GetText(), "Failed to read regex element.");
80 m_regex = regex->GetText();
81 }
82 else
83 {
84 // Set the regex to a default value.
85 m_regex = "^.*Total Computation Time\\s*=\\s*(\\d+\\.?\\d*).*";
86 }
87
88 // Inform the Tester this metric supports averaging data from multiple runs.
89 m_average = true;
91
92 // Parse matching values if not generating.
93 if (m_generate)
94 {
95 return;
96 }
97
98 TiXmlElement *value = metric->FirstChildElement("value");
99 ASSERTL0(value || m_generate, "Missing value tag for metric.");
100
101 bool hostnameMatch = false;
102 while (value)
103 {
104 ASSERTL0(value->Attribute("tolerance"),
105 "Missing tolerance in execution time metric.");
106 ASSERTL0(value->Attribute("hostname"),
107 "Missing hostname in execution time metric.");
108 ASSERTL0(!EmptyString(value->GetText()),
109 "Missing value in execution time metric.");
110
111 // Only use the value if it matches the runner's hostname.
112 if (value->Attribute("hostname") == boost::asio::ip::host_name())
113 {
115 val.m_value = value->GetText();
116 val.m_tolerance = atof(value->Attribute("tolerance"));
117 m_match = val;
118
119 hostnameMatch = true;
120
121 // No use finding any more values
122 break;
123 }
124
125 value = value->NextSiblingElement("value");
126 }
127
128 // If no hostname match was found, pass a skip flag to the test.
129 if (!hostnameMatch)
130 {
131 cerr << "WARNING: No execution time value provided for host "
132 << boost::asio::ip::host_name() << ". Skipping metric." << endl;
133 m_match.m_skip = true;
134 }
135}
136
137/**
138 * @brief Test output against a regular expression and its expected value.
139 *
140 * This function will find the execution time for each test run in the output,
141 * using \b m_regex (provided in the test file). It will then average these
142 * times and compare them to the expected execution time for the current
143 * computer's hostname (this behaviour is defined in the constructor). If the
144 * average execution time falls outside of the tolerance then the test will
145 * fail.
146 *
147 * If no execution time value is provided for the current computer's hostname,
148 * \b m_skip will be set to \b true and the test will automatically pass.
149 *
150 * @param pStdout Reference to test output stream.
151 * @param pStderr Reference to test error stream.
152 * @return \b true if the test passes, \b false otherwise.
153 */
154bool MetricExecutionTime::v_Test(istream &pStdout, istream &pStderr)
155{
156 boost::ignore_unused(pStdout, pStderr);
157
158 bool success = true;
159
160 // Select istream to use.
161 istream &is = m_useStderr ? pStderr : pStdout;
162
163 boost::cmatch matches;
164 string line;
165 // Vector of execution times found in the output.
166 vector<double> times;
167 bool matched = false;
168
169 // Process output file line by line searching for regex matches
170 while (getline(is, line))
171 {
172 // Test to see if we have a match on this line.
173 if (boost::regex_match(line.c_str(), matches, m_regex))
174 {
175 // If no matches are found then throw an error.
176 if (matches.size() == 1)
177 {
178 cerr << "No test sections in regex!" << endl;
179 return false;
180 }
181
182 // Check each regex capture group in turn
183 for (int i = 1; i < matches.size(); ++i)
184 {
185 // Extract the captured string
186 string match(matches[i].first, matches[i].second);
187 double val;
188 try
189 {
190 val = boost::lexical_cast<double>(match);
191
192 // Add value to the list of found execution times.
193 times.push_back(val);
194 matched = true;
195 }
196 catch (boost::bad_lexical_cast &e)
197 {
198 cerr << "Could not convert match " << match << " to double"
199 << endl;
200 success = false;
201 continue;
202 }
203 }
204 }
205 }
206
207 if (!matched)
208 {
209 cerr << "No execution times were found in the test output. Consider "
210 "providing a custom regex for this test metric."
211 << endl;
212 success = false;
213 }
214
215 // Average the found execution times.
216 double avgTime = 0.0;
217 for (unsigned int i = 0; i < times.size(); ++i)
218 {
219 avgTime += times[i];
220 }
221 avgTime /= times.size();
222
223 // If a skip flag is set (due to no matching hostname) then return
224 // successful test and output the average time.
225 if (m_match.m_skip)
226 {
227 cerr << "Average execution time for host "
228 << boost::asio::ip::host_name() << ": " << avgTime << endl;
229 return success;
230 }
231
232 ASSERTL0(m_match.m_value != "nil",
233 "No test conditions defined for execution time.");
234
235 // Check that the average time is within the tolerance.
236 if (fabs(avgTime - boost::lexical_cast<double>(m_match.m_value)) >
239 {
240 cerr << endl;
241 cerr << "Failed tolerance match." << endl;
242 cerr << " Expected avg execution time: " << m_match.m_value << " +/- "
243 << m_match.m_tolerance << endl;
244 cerr << " Actual avg: " << avgTime << endl;
245
246 for (unsigned int i = 0; i < times.size(); ++i)
247 {
248 cerr << " Run " << i << ": " << times[i] << endl;
249 }
250
251 success = false;
252 }
253
254 return success;
255}
256
257/**
258 * @brief Generate an accepted execution time value using a test output or error
259 * stream.
260 *
261 * This function generate an execution time match by finding the first execution
262 * time in the output that matches \p m_regex (provided in the test file).
263 *
264 * @param pStdout Reference to test output stream.
265 * @param pStderr Reference to test error stream.
266 */
267void MetricExecutionTime::v_Generate(istream &pStdout, istream &pStderr)
268{
269 boost::ignore_unused(pStderr);
270
271 // Select istream to use
272 istream &is = m_useStderr ? pStderr : pStdout;
273
274 boost::cmatch matches;
275
276 string line;
277 // Vector of execution times found in the output
278 vector<double> sampleTimes;
279 bool matched = false;
280
281 // Process output file line by line searching for regex matches
282 while (getline(is, line))
283 {
284 // Test to see if we have a match on this line
285 if (boost::regex_match(line.c_str(), matches, m_regex))
286 {
287 // If no fields in regex then throw an error
288 ASSERTL0(matches.size() != 1, "No test sections in regex!");
289
290 // Check each regex capture group in turn
291 for (int i = 1; i < matches.size(); ++i)
292 {
293 // Extract the captured string
294 string match(matches[i].first, matches[i].second);
295 double val;
296 try
297 {
298 val = boost::lexical_cast<double>(match);
299
300 sampleTimes.push_back(val);
301 matched = true;
302 }
303 catch (boost::bad_lexical_cast &e)
304 {
305 cerr << "Could not convert match " << match << " to double"
306 << endl;
307 continue;
308 }
309 }
310 }
311 }
312
313 // Stores the accepted average execution time and a default tolerance.
315
316 if (matched)
317 {
318 // Average the found execution times and set it as the accepted value.
319 double avgTime = 0.0;
320 for (unsigned int i = 0; i < sampleTimes.size(); ++i)
321 {
322 avgTime += sampleTimes[i];
323 }
324 avgTime /= sampleTimes.size();
325
326 okValue.m_value = to_string(avgTime);
327 m_match = okValue;
328 }
329 else
330 {
331 cerr << "No execution times were found in the test output. Value set "
332 "to 'nil'."
333 << endl;
334 }
335
336 // If we are not a derived class then create a new structure.
337 if (m_type == "EXECUTIONTIME")
338 {
339 // Remove values if they already exist.
340 while (m_metric->FirstChildElement("value"))
341 {
342 ASSERTL0(
343 m_metric->RemoveChild(m_metric->FirstChildElement("value")),
344 "Couldn't remove value from metric!");
345 }
346
347 TiXmlElement *val = new TiXmlElement("value");
348
349 val->SetAttribute("tolerance",
350 boost::lexical_cast<string>(m_match.m_tolerance));
351 val->SetAttribute("hostname", boost::asio::ip::host_name());
352 val->LinkEndChild(new TiXmlText(m_match.m_value));
353 m_metric->LinkEndChild(val);
354 }
355}
356} // namespace Nektar
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:215
bool m_useStderr
If true, use stderr for testing/generation instead of stdout.
static MetricSharedPtr create(TiXmlElement *metric, bool generate)
MetricExecutionTime(TiXmlElement *metric, bool generate)
Construct a new MetricExecutionTime object.
MetricExecutionTimeFieldValue m_match
Stores each execution time found in the test output.
boost::regex m_regex
Regex used to match an execution time in a test output.
virtual void v_Generate(std::istream &pStdout, std::istream &pStderr)
Generate an accepted execution time value using a test output or error stream.
virtual bool v_Test(std::istream &pStdout, std::istream &pStderr)
Test output against a regular expression and its expected value.
std::string RegisterCreatorFunction(std::string key, CreatorFunction func)
Definition: Metric.h:138
Base class for all metrics. Metric represents a test metric that can be used to evaluate the function...
Definition: Metric.h:70
TiXmlElement * m_metric
Pointer to XML structure containing metric definition.
Definition: Metric.h:106
bool m_average
Indicates whether a metric supports averaging results from multiple runs.
Definition: Metric.h:104
std::string m_type
Stores the type of this metric (uppercase).
Definition: Metric.h:99
bool m_generate
Determines whether to generate this metric or not.
Definition: Metric.h:101
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
bool EmptyString(const char *s)
Check to see whether the given string s is empty (or null).
Definition: Metric.h:55
MetricFactory & GetMetricFactory()
Definition: Metric.cpp:42
Data structure for an execution time field value.
double m_tolerance
The tolerance to use for checking the execution time. Defaults to 5.0.
std::string m_value
The value to match. Defaults to empty string.
bool m_skip
Indicates whether the metric should be skipped. Defaults to false.