Fawkes API  Fawkes Development Version
execution_time_estimator.cpp
1 /***************************************************************************
2  * execution_time_estimator.cpp - An execution time estimator for skills
3  *
4  * Created: Sun 22 Dec 2019 17:41:18 CET 17:41
5  * Copyright 2019 Till Hofmann <hofmann@kbsg.rwth-aachen.de>
6  ****************************************************************************/
7 
8 /* This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Library General Public License for more details.
17  *
18  * Read the full text in the LICENSE.GPL file in the doc directory.
19  */
20 
21 #include "execution_time_estimator.h"
22 
23 #include <core/exception.h>
24 #include <utils/misc/string_split.h>
25 
26 #include <cassert>
27 #include <iostream>
28 #include <map>
29 #include <regex>
30 #include <string>
31 
32 // implementation workaround for type dependent static_assert
33 // see https://en.cppreference.com/w/cpp/language/if#Constexpr_If
34 template <class T>
35 struct dependent_false : std::false_type
36 {
37 };
38 
39 namespace fawkes {
40 
41 /** Use the ExecutionTimeEstimator's skill. */
43 
44 /** @class ExecutionTimeEstimator
45  * An abstract estimator for the execution time of a skill.
46  * Inherit from this class if you want to implement an estimator for a skill or
47  * a set of skills.
48  *
49  * @fn float ExecutionTimeEstimator::get_execution_time(const Skill &skill) const
50  * Get the estimated execution time for the given skill string.
51  * @param skill The skill object to compute the execution time for.
52  * @return The execution time in seconds.
53  *
54  * @fn bool ExecutionTimeEstimator::can_provide_exec_time(const Skill &skill) const
55  * Check if this estimator can give an estimate for a given skill.
56  * @param skill The skill object to check.
57  * @return true if this estimator can give an execution time estimate for the given skill.
58  *
59  * @fn bool ExecutionTimeEstimator::can_execute(const Skill &skill)
60  * Check if this estimator is both allowed and able to give an estimate for a given skill.
61  * @param skill The skill object to check.
62  * @return true if this estimator can give an execution time estimate for the given skill.
63  *
64  * @fn std::pair<SkillerInterface::SkillStatusEnum, std::string> ExecutionTimeEstimator::execute(const Skill &skill) const
65  * Let the estimator know that we are executing this skill, so it can apply
66  * possible side effects.
67  * @param skill The skill to execute
68  * @return skill status after simulated execution along with an error description in case the skill fails
69  *
70  * @fn std::map<std::string, Skill> ExecutionTimeEstimator::get_skills_from_config(const std::string &path) const
71  * Load skill descriptions from a yaml config. The skills are represented via
72  * suffixes /<description-id>/name and /<description-id>/args following the
73  * @param path config path under which the skill descriptions are located
74  * @return all skills found under the given path sorted by <description-id>.
75  *
76  * @fn template <typename T> T ExecutionTimeEstimator::get_property(const Property<T> &property) const
77  * Get the current property value for \a active_whitelist_entry_.
78  * @param property property where the current value should be retrieved from
79  * @return property accoring to \a active_whitelist_entry_ or the default value if no whitelist entry is active
80  *
81  * @fn bool Skill::matches(const Skill &other) const
82  * Check, whether the skill matches another skill description.
83  * @param other The skill description that should be matched
84  * @return true if all skill args in other are contained in the args of this skill and the skill names match
85  *
86  * @fn template <typename T> ExecutionTimeEstimator::Property<T>::Property(fawkes::Configuration *config, const std::string & path, const std::string &property, const T &default_value)
87  * Constructor.
88  * Create a property by reading all values from the config.
89  * @param config Config to read form
90  * @param path Path under which the config values can be found
91  * @param property Property name
92  * @param default_value Default value in case values are not specified
93  *
94  * @fn template <typename T> T ExecutionTimeEstimator::Property<T>::get_default_value() const
95  * Get the default value if it is set, otherwise throw an exception
96  * @return the default value for the property
97  *
98  * @fn template <typename T> T ExecutionTimeEstimator::Property<T>::get_property(const std::string &key) const
99  * Get the property falue for a given skill.
100  * @param key Skill entry id
101  * @return Value associated with \a key or the default value, if no skill-specific value can be found
102  */
103 
104 /** Constructor.
105  * Load config values that are common for all executors.
106  * @param config configuration to read all values from
107  * @param cfg_prefix prefix where the estimator-specific configs are located
108  */
110  const ::std::string &cfg_prefix)
111 : config_(config),
112  cfg_prefix_(cfg_prefix),
113  speed_(config->get_float_or_default((cfg_prefix_ + "speed").c_str(), 1)),
114  whitelist_(get_skills_from_config(cfg_prefix_ + "whitelist")),
115  blacklist_(get_skills_from_config(cfg_prefix_ + "blacklist"))
116 {
117  assert(speed_ > 0);
118 }
119 
120 bool
122 {
123  bool allowed_to_execute = false;
124  if (whitelist_.empty()) {
125  allowed_to_execute = true;
127  } else {
129  std::find_if(whitelist_.begin(), whitelist_.end(), [skill](const auto &s) {
130  return skill.matches(s.second);
131  });
132 
133  allowed_to_execute = active_whitelist_entry_ != whitelist_.end();
134  }
135  if (!blacklist_.empty()) {
136  allowed_to_execute =
137  allowed_to_execute
138  && blacklist_.end()
139  == std::find_if(blacklist_.begin(), blacklist_.end(), [skill](const auto &s) {
140  return skill.matches(s.second);
141  });
142  }
143  return allowed_to_execute && can_provide_exec_time(skill);
144 }
145 
146 /** Constructor.
147  * Create a skill from the skill string.
148  * @param skill_string The skill string to create the skill object from.
149  */
150 Skill::Skill(const std::string &skill_string)
151 {
152  std::string skill_no_newlines(skill_string);
153  skill_no_newlines.erase(std::remove(skill_no_newlines.begin(), skill_no_newlines.end(), '\n'),
154  skill_no_newlines.end());
155  if (skill_no_newlines.empty()) {
156  return;
157  }
158  const std::regex regex("(\\w+)(?:\\(\\)|\\{(.+)?\\})");
159  std::smatch match;
160  if (std::regex_match(skill_no_newlines, match, regex)) {
161  assert(match.size() > 1);
162  skill_name = match[1];
163  if (match.size() > 2) {
164  const std::string args = match[2];
165  parse_args(args);
166  }
167  } else {
168  throw Exception("Unexpected skill string: '%s'", skill_no_newlines.c_str());
169  }
170 }
171 
172 bool
173 Skill::matches(const Skill &other) const
174 {
175  bool args_match = true;
176  for (const auto &arg : other.skill_args) {
177  auto search_arg = skill_args.find(arg.first);
178  if (search_arg == skill_args.end()
179  || !std::regex_match(search_arg->second, std::regex(arg.second))) {
180  args_match = false;
181  break;
182  }
183  }
184  return args_match && skill_name == other.skill_name;
185 }
186 
187 void
188 Skill::parse_args(const std::string &args)
189 {
190  const std::regex skill_args_regex("(?:([^,]+),\\s*)*?([^,]+)");
191  std::smatch args_match;
192  if (std::regex_match(args, args_match, skill_args_regex)) {
193  const std::regex skill_arg_regex("(\\w+)=(['\"]?)([^'\"]*)\\2\\s*");
194  for (auto kv_match = std::next(args_match.begin()); kv_match != args_match.end(); kv_match++) {
195  const std::string key_arg = *kv_match;
196  std::smatch m;
197  if (std::regex_match(key_arg, m, skill_arg_regex)) {
198  skill_args[m[1]] = m[3];
199  }
200  }
201  }
202 }
203 
204 std::map<std::string, Skill>
206 {
207  const size_t id_index = 0;
208  const size_t property_index = 1;
209  std::unique_ptr<Configuration::ValueIterator> it(config_->search(path.c_str()));
210  std::map<std::string, std::string> skill_strings;
211  while (it->next()) {
212  std::vector<std::string> skill_property =
213  str_split(std::string(it->path()).substr(path.size()));
214  if (skill_property.size() != 2) {
215  continue;
216  }
217  if (skill_property[property_index] == "args") {
218  skill_strings[skill_property[id_index]] += str_join(it->get_strings(), ',');
219  } else if (skill_property[property_index] == "name") {
220  skill_strings[skill_property[id_index]] =
221  it->get_string() + "{" + skill_strings[skill_property[id_index]];
222  }
223  }
224  std::map<std::string, Skill> res;
225  for (const auto &skill_string : skill_strings) {
226  res.insert(std::make_pair(skill_string.first, Skill(skill_string.second + "}")));
227  }
228  return res;
229 }
230 
231 template <typename T>
232 T
234 {
235  if (active_whitelist_entry_ == whitelist_.end()) {
236  return property.get_default_value();
237  ;
238  } else {
239  return property.get_property(active_whitelist_entry_->first);
240  }
241 }
242 
243 template <typename T>
245  const std::string & path,
246  const std::string & property,
247  const std::optional<T> &default_val)
248 {
249  try {
250  if constexpr (std::is_same<T, std::string>()) {
251  default_value = config->get_string((path + property).c_str());
252  } else if constexpr (std::is_same<T, float>()) {
253  default_value = config->get_float((path + property).c_str());
254  } else if constexpr (std::is_same<T, bool>()) {
255  default_value = config->get_bool((path + property).c_str());
256  } else {
257  static_assert(dependent_false<T>::value,
258  "Property with this template type is not implemented");
259  }
260  } catch (Exception &e) {
261  default_value = default_val;
262  }
263  const size_t id_index = 0;
264  const size_t property_index = 1;
265  std::string whitelist_path = path + "whitelist";
266  std::unique_ptr<Configuration::ValueIterator> it(config->search(whitelist_path.c_str()));
267  while (it->next()) {
268  std::vector<std::string> skill_property =
269  str_split(std::string(it->path()).substr(whitelist_path.size()));
270  if (skill_property.size() != 2) {
271  break;
272  }
273  if (skill_property[property_index] == property) {
274  if constexpr (std::is_same<T, std::string>()) {
275  property_entries[skill_property[id_index]] += it->get_string();
276  } else if constexpr (std::is_same<T, float>()) {
277  property_entries[skill_property[id_index]] += it->get_float();
278  } else if constexpr (std::is_same<T, bool>()) {
279  property_entries[skill_property[id_index]] += it->get_bool();
280  } else {
281  static_assert(dependent_false<T>::value,
282  "Property with this template type is not implemented");
283  }
284  }
285  }
286 }
287 
288 template <typename T>
289 T
291 {
292  if (default_value) {
293  return default_value.value();
294  } else {
295  throw Exception("failed to get property");
296  }
297 }
298 
299 template <typename T>
300 T
302 {
303  auto property_specified = property_entries.find(key);
304  if (property_specified == property_entries.end()) {
305  return get_default_value();
306  }
307  return property_specified->second;
308 }
309 
313 template std::string
315 template bool ExecutionTimeEstimator::get_property(const Property<bool> &property) const;
316 template float ExecutionTimeEstimator::get_property(const Property<float> &property) const;
317 } // namespace fawkes
Interface for configuration handling.
Definition: config.h:65
virtual bool get_bool(const char *path)=0
Get value from configuration which is of type bool.
virtual float get_float(const char *path)=0
Get value from configuration which is of type float.
virtual ValueIterator * search(const char *path)=0
Iterator with search results.
virtual std::string get_string(const char *path)=0
Get value from configuration which is of type string.
Base class for exceptions in Fawkes.
Definition: exception.h:36
A configurable property that is skill-specific and may have a default value.
Property(fawkes::Configuration *config, const std::string &path, const std::string &property, const std::optional< T > &default_value=std::nullopt)
Constructor.
T get_property(const std::string &key) const
Get the property falue for a given skill.
T get_default_value() const
Get the default value if it is set, otherwise throw an exception.
A structured representation of a skill.
bool matches(const Skill &skill) const
Check, whether the skill matches another skill description.
std::string skill_name
The name of the skill.
Skill(const std::string &skill_string)
Constructor.
std::unordered_map< std::string, std::string > skill_args
A map of the skill's argument keys to argument values.
virtual bool can_provide_exec_time(const Skill &skill) const =0
Check if this estimator can give an estimate for a given skill.
Configuration *const config_
Config to obtain common configurables.
std::map< std::string, Skill >::const_iterator active_whitelist_entry_
Points to the whitelist entry that matches the skill to execute.
const std::map< std::string, Skill > blacklist_
Blacklist of skills that the estimator must not process.
virtual bool can_execute(const Skill &skill)
Check if this estimator is both allowed and able to give an estimate for a given skill.
const std::map< std::string, Skill > whitelist_
Whitelist of skills that the estimator is allowed to process.
T get_property(const Property< T > &property) const
Get the current property value for active_whitelist_entry_.
std::map< std::string, Skill > get_skills_from_config(const std::string &path) const
Load skill descriptions from a yaml config.
ExecutionTimeEstimator(Configuration *config, const ::std::string &cfg_prefix)
Constructor.
const float speed_
Config estimator-specific speedup factor.
Fawkes library namespace.
static std::string str_join(const std::vector< std::string > &v, char delim='/')
Join vector of strings string using given delimiter.
Definition: string_split.h:99
static std::vector< std::string > str_split(const std::string &s, char delim='/')
Split string by delimiter.
Definition: string_split.h:41