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
34template <class T>
35struct dependent_false : std::false_type
36{
37};
38
39namespace 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
120bool
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 */
150Skill::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
172bool
173Skill::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
187void
188Skill::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
204std::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
231template <typename T>
232T
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
243template <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
288template <typename T>
289T
291{
292 if (default_value) {
293 return default_value.value();
294 } else {
295 throw Exception("failed to get property");
296 }
297}
298
299template <typename T>
300T
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
313template std::string
315template bool ExecutionTimeEstimator::get_property(const Property<bool> &property) const;
316template float ExecutionTimeEstimator::get_property(const Property<float> &property) const;
317} // namespace fawkes
Interface for configuration handling.
Definition: config.h:68
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