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 = R"(^.*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 std::smatch 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 (std::regex_match(line, 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 // Find the minimum of the execution times.
216 double minTime = times[0];
217 for (unsigned int i = 0; i < times.size(); ++i)
218 {
219 minTime = (times[i] < minTime) ? times[i] : minTime;
220 }
221
222 // If a skip flag is set (due to no matching hostname) then return
223 // successful test and output the average time.
224 if (m_match.m_skip)
225 {
226 cerr << "Minimum execution time for host "
227 << boost::asio::ip::host_name() << ": " << minTime << endl;
228 return success;
229 }
230
231 ASSERTL0(m_match.m_value != "nil",
232 "No test conditions defined for execution time.");
233
234 // Check that the average time is within the tolerance.
235 if (fabs(minTime - boost::lexical_cast<double>(m_match.m_value)) >
238 {
239 cerr << endl;
240 cerr << "Failed tolerance match." << endl;
241 cerr << " Expected min execution time: " << m_match.m_value << " +/- "
242 << m_match.m_tolerance << endl;
243 cerr << " Actual min: " << minTime << endl;
244
245 for (unsigned int i = 0; i < times.size(); ++i)
246 {
247 cerr << " Run " << i << ": " << times[i] << endl;
248 }
249
250 success = false;
251 }
252
253 return success;
254}
255
256/**
257 * @brief Generate an accepted execution time value using a test output or error
258 * stream.
259 *
260 * This function generate an execution time match by finding the first execution
261 * time in the output that matches \p m_regex (provided in the test file).
262 *
263 * @param pStdout Reference to test output stream.
264 * @param pStderr Reference to test error stream.
265 */
266void MetricExecutionTime::v_Generate(istream &pStdout, istream &pStderr)
267{
268 boost::ignore_unused(pStderr);
269
270 // Select istream to use
271 istream &is = m_useStderr ? pStderr : pStdout;
272
273 std::smatch matches;
274
275 string line;
276 // Vector of execution times found in the output
277 vector<double> sampleTimes;
278 bool matched = false;
279
280 // Process output file line by line searching for regex matches
281 while (getline(is, line))
282 {
283 // Test to see if we have a match on this line
284 if (std::regex_match(line, matches, m_regex))
285 {
286 // If no fields in regex then throw an error
287 ASSERTL0(matches.size() != 1, "No test sections in regex!");
288
289 // Check each regex capture group in turn
290 for (int i = 1; i < matches.size(); ++i)
291 {
292 // Extract the captured string
293 string match(matches[i].first, matches[i].second);
294 double val;
295 try
296 {
297 val = boost::lexical_cast<double>(match);
298
299 sampleTimes.push_back(val);
300 matched = true;
301 }
302 catch (boost::bad_lexical_cast &e)
303 {
304 cerr << "Could not convert match " << match << " to double"
305 << endl;
306 continue;
307 }
308 }
309 }
310 }
311
312 // Stores the accepted average execution time and a default tolerance.
314
315 if (matched)
316 {
317 // Average the found execution times and set it as the accepted value.
318 double minTime = sampleTimes[0];
319 for (unsigned int i = 0; i < sampleTimes.size(); ++i)
320 {
321 minTime = (sampleTimes[i] < minTime ? sampleTimes[i] : minTime);
322 }
323
324 okValue.m_value = to_string(minTime);
325 m_match = okValue;
326 }
327 else
328 {
329 cerr << "No execution times were found in the test output. Value set "
330 "to 'nil'."
331 << endl;
332 }
333
334 // If we are not a derived class then create a new structure.
335 if (m_type == "EXECUTIONTIME")
336 {
337 // Remove values if they already exist.
338 while (m_metric->FirstChildElement("value"))
339 {
340 ASSERTL0(
341 m_metric->RemoveChild(m_metric->FirstChildElement("value")),
342 "Couldn't remove value from metric!");
343 }
344
345 TiXmlElement *val = new TiXmlElement("value");
346
347 val->SetAttribute("tolerance",
348 boost::lexical_cast<string>(m_match.m_tolerance));
349 val->SetAttribute("hostname", boost::asio::ip::host_name());
350 val->LinkEndChild(new TiXmlText(m_match.m_value));
351 m_metric->LinkEndChild(val);
352 }
353}
354} // namespace Nektar
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:208
std::regex m_regex
Regex used to match an execution time in a test output.
bool v_Test(std::istream &pStdout, std::istream &pStderr) override
Test output against a regular expression and its expected value.
bool m_useStderr
If true, use stderr for testing/generation instead of stdout.
void v_Generate(std::istream &pStdout, std::istream &pStderr) override
Generate an accepted execution time value using a test output or error stream.
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.
std::string RegisterCreatorFunction(std::string key, CreatorFunction func)
Definition: Metric.h:133
Base class for all metrics. Metric represents a test metric that can be used to evaluate the function...
Definition: Metric.h:65
TiXmlElement * m_metric
Pointer to XML structure containing metric definition.
Definition: Metric.h:101
bool m_average
Indicates whether a metric supports averaging results from multiple runs.
Definition: Metric.h:99
std::string m_type
Stores the type of this metric (uppercase).
Definition: Metric.h:94
bool m_generate
Determines whether to generate this metric or not.
Definition: Metric.h:96
bool EmptyString(const char *s)
Check to see whether the given string s is empty (or null).
Definition: Metric.h:50
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.