| /** |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| Copyright 2019, Oath Inc. |
| */ |
| |
| #include "txn_box/common.h" |
| |
| #include <string> |
| |
| #include <swoc/TextView.h> |
| #include <swoc/Errata.h> |
| #include <swoc/BufferWriter.h> |
| |
| #include "txn_box/Config.h" |
| #include "txn_box/Directive.h" |
| #include "txn_box/Context.h" |
| |
| #include "txn_box/ts_util.h" |
| |
| using swoc::BufferWriter; |
| using swoc::Errata; |
| using swoc::Rv; |
| using swoc::TextView; |
| using namespace swoc::literals; |
| |
| /* ------------------------------------------------------------------------------------ */ |
| /// Define a plugin statistic. |
| class Do_stat_define : public Directive |
| { |
| using self_type = Do_stat_define; ///< Self reference type. |
| using super_type = Directive; ///< Parent type. |
| friend struct Stat; |
| |
| protected: |
| struct CfgInfo; |
| |
| public: |
| static inline const std::string KEY{"stat-define"}; ///< Directive name. |
| static const HookMask HOOKS; ///< Valid hooks for directive. |
| |
| Errata invoke(Context &ctx) override; ///< Runtime activation. |
| |
| /** Load from YAML node. |
| * |
| * @param cfg Configuration data. |
| * @param rtti Configuration level static data for this directive. |
| * @param drtv_node Node containing the directive. |
| * @param name Name from key node tag. |
| * @param arg Arg from key node tag. |
| * @param key_value Value for directive @a KEY |
| * @return A directive, or errors on failure. |
| */ |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| static Errata cfg_init(Config &cfg, CfgStaticData const *rtti); |
| |
| protected: |
| /// Mapping internal names to full names. |
| using Names = std::unordered_map<TextView, TextView, std::hash<std::string_view>>; |
| /// Data in reserved configuration storage. |
| struct CfgInfo { |
| Names _names; ///< Map of internal names to full names. |
| }; |
| |
| /** Get the full stat name. |
| * |
| * @param cfg Configuration instance. |
| * @param name Stat name in configuration. |
| * @return The full name of the stat if defined, or a localized copy if not. |
| */ |
| static TextView expand_and_localize(Config &cfg, TextView const &name); |
| |
| TextView _name; ///< Stat name (internal) |
| TextView _full_name; ///< Fullname including prefix. |
| int _value = 0; ///< Initial value. |
| bool _persistent_p = false; ///< Make persistent. |
| |
| static inline const std::string NAME_TAG{"name"}; |
| static inline const std::string VALUE_TAG{"value"}; |
| static inline const std::string PERSISTENT_TAG{"persistent"}; |
| static inline const std::string PREFIX_TAG{"prefix"}; |
| }; |
| |
| const HookMask Do_stat_define::HOOKS{MaskFor(Hook::POST_LOAD)}; |
| |
| TextView |
| Do_stat_define::expand_and_localize(Config &cfg, TextView const &name) |
| { |
| if (auto cfg_info = cfg.named_object<CfgInfo>(KEY); cfg_info) { |
| if (auto spot = cfg_info->_names.find(name); spot != cfg_info->_names.end()) { |
| return spot->second; |
| } |
| } |
| return cfg.localize(name); |
| } |
| |
| Errata |
| Do_stat_define::cfg_init(Config &cfg, CfgStaticData const *) |
| { |
| auto cfg_info = cfg.obtain_named_object<CfgInfo>(KEY); |
| // Clean it up when the config is destroyed. |
| cfg.mark_for_cleanup(cfg_info); |
| return {}; |
| } |
| |
| Errata |
| Do_stat_define::invoke(Context &) |
| { |
| auto &&[idx, errata]{ts::plugin_stat_define(_full_name, _value, _persistent_p)}; |
| return std::move(errata); |
| } |
| |
| Rv<Directive::Handle> |
| Do_stat_define::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &, |
| YAML::Node key_value) |
| { |
| auto self = new self_type(); |
| Handle handle(self); |
| |
| // Prefix is optional - defaults to "plugin.txn_box" |
| // Need this before the name so it can be combined as needed. |
| auto prefix_node = key_value[PREFIX_TAG]; |
| TextView prefix; |
| if (prefix_node) { |
| auto &&[prefix_expr, prefix_errata]{cfg.parse_expr(prefix_node)}; |
| if (!prefix_errata.is_ok()) { |
| prefix_errata.note("While parsing {} directive at {}.", KEY, drtv_node.Mark()); |
| return std::move(prefix_errata); |
| } |
| if (!prefix_expr.is_literal() || !prefix_expr.result_type().can_satisfy(STRING)) { |
| return Errata(S_ERROR, "{} value at {} for {} directive at {} must be a literal string.", PREFIX_TAG, prefix_node.Mark(), KEY, |
| drtv_node.Mark()); |
| } |
| |
| prefix = std::get<IndexFor(STRING)>(std::get<Expr::LITERAL>(prefix_expr._raw)); |
| drtv_node.remove(prefix_node); |
| } else { |
| prefix = "plugin.txn_box"_tv; // default if not explicitly set. |
| } |
| // Note - prefix isn't localized yet, need to wait for name. |
| |
| auto name_node = key_value[NAME_TAG]; |
| if (!name_node) { |
| return Errata(S_ERROR, "{} directive at {} must have a {} key.", KEY, drtv_node.Mark(), NAME_TAG); |
| } |
| |
| auto &&[name_expr, name_errata]{cfg.parse_expr(name_node)}; |
| if (!name_errata.is_ok()) { |
| name_errata.note("While parsing {} directive at {}.", KEY, drtv_node.Mark()); |
| return std::move(name_errata); |
| } |
| if (!name_expr.is_literal() || !name_expr.result_type().can_satisfy(STRING)) { |
| return Errata(S_ERROR, "{} value at {} for {} directive at {} must be a literal string.", NAME_TAG, name_node.Mark(), KEY, |
| drtv_node.Mark()); |
| } |
| TextView name = std::get<IndexFor(STRING)>(std::get<Expr::LITERAL>(name_expr._raw)); |
| if (name.empty()) { |
| return Errata(S_ERROR, "{} value at {} for {} directive at {} must be a non-empty literal string.", NAME_TAG, name_node.Mark(), |
| KEY, drtv_node.Mark()); |
| } |
| |
| // Localize prefix and name in the same string, storing views in to that string. |
| if (prefix.empty()) { |
| self->_full_name = self->_name = cfg.localize(name, Config::LOCAL_CSTR); |
| } else { |
| swoc::FixedBufferWriter w{cfg.allocate_cfg_storage(prefix.size() + 1 + name.size() + 1)}; |
| w.write(prefix).write('.').write(name).write('\0'); |
| self->_full_name = TextView{w.view()}.remove_suffix(1); // drop terminal null. |
| self->_name = self->_full_name.suffix(name.size()); |
| } |
| Names &names = cfg.named_object<CfgInfo>(KEY)->_names; |
| names.insert({self->_name, self->_full_name}); |
| drtv_node.remove(name_node); |
| |
| auto value_node = key_value[VALUE_TAG]; |
| if (value_node) { |
| auto &&[value_expr, value_errata]{cfg.parse_expr(value_node)}; |
| if (!value_errata.is_ok()) { |
| value_errata.note("While parsing {} directive at {}.", KEY, drtv_node.Mark()); |
| return std::move(value_errata); |
| } |
| if (!value_expr.is_literal() || !value_expr.result_type().can_satisfy(INTEGER)) { |
| return Errata(S_ERROR, "{} value at {} for {} directive at {} must be a literal integer.", VALUE_TAG, value_node.Mark(), KEY, |
| drtv_node.Mark()); |
| } |
| drtv_node.remove(value_node); |
| self->_value = std::get<IndexFor(INTEGER)>(std::get<Expr::LITERAL>(value_expr._raw)); |
| } |
| |
| auto persistent_node = key_value[PERSISTENT_TAG]; |
| if (persistent_node) { |
| auto &&[persistent_expr, persistent_errata]{cfg.parse_expr(persistent_node)}; |
| if (!persistent_errata.is_ok()) { |
| persistent_errata.note("While parsing {} directive at {}.", KEY, drtv_node.Mark()); |
| return std::move(persistent_errata); |
| } |
| if (!persistent_expr.is_literal() || !persistent_expr.result_type().can_satisfy(BOOLEAN)) { |
| return Errata(S_ERROR, "{} value at {} for {} directive at {} must be a literal string.", PERSISTENT_TAG, |
| persistent_node.Mark(), KEY, drtv_node.Mark()); |
| } |
| drtv_node.remove(persistent_node); // ugly, need to fix the overall API. |
| self->_persistent_p = std::get<IndexFor(BOOLEAN)>(std::get<Expr::LITERAL>(persistent_expr._raw)); |
| } |
| return handle; |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| /// Statistic information. |
| /// The name is used when it can't be resolved during configuration loading. |
| struct Stat { |
| static constexpr int UNRESOLVED = -1; |
| static constexpr int INVALID = -2; |
| TextView _name; ///< Statistic name. |
| int _idx = UNRESOLVED; ///< Statistic index. |
| |
| Stat(Config &cfg, TextView const &name) { this->assign(cfg, name); } |
| |
| Stat & |
| assign(Config &cfg, TextView name) |
| { |
| _name = Do_stat_define::expand_and_localize(cfg, name); |
| |
| _idx = ts::plugin_stat_index(_name); |
| _idx = _idx < 0 ? UNRESOLVED : _idx; // normalize. |
| return *this; |
| } |
| |
| int |
| index() |
| { |
| if (_idx == UNRESOLVED) { |
| _idx = ts::plugin_stat_index(_name); |
| if (_idx < 0) { // On a lookup failure, give up and prevent future lookups. |
| _idx = INVALID; |
| } |
| } |
| return _idx; |
| } |
| |
| Feature |
| value() |
| { |
| auto n{this->index()}; |
| return n < 0 ? NIL_FEATURE : feature_type_for<INTEGER>{ts::plugin_stat_value(_idx)}; |
| } |
| |
| Stat & |
| update(feature_type_for<INTEGER> value) |
| { |
| auto n{this->index()}; |
| if (n >= 0) { |
| ts::plugin_stat_update(n, value); |
| } |
| return *this; |
| } |
| }; |
| /* ------------------------------------------------------------------------------------ */ |
| class Do_stat_update : public Directive |
| { |
| using self_type = Do_stat_update; ///< Self reference type. |
| using super_type = Directive; ///< Parent type. |
| public: |
| static const std::string KEY; ///< Directive name. |
| static const HookMask HOOKS; ///< Valid hooks for directive. |
| |
| Errata invoke(Context &ctx) override; ///< Runtime activation. |
| |
| /** Load from YAML node. |
| * |
| * @param cfg Configuration data. |
| * @param rtti Configuration level static data for this directive. |
| * @param drtv_node Node containing the directive. |
| * @param name Name from key node tag. |
| * @param arg Arg from key node tag. |
| * @param key_value Value for directive @a KEY |
| * @return A directive, or errors on failure. |
| */ |
| static Rv<Handle> load(Config &cfg, CfgStaticData const *rtti, YAML::Node drtv_node, swoc::TextView const &name, |
| swoc::TextView const &arg, YAML::Node key_value); |
| |
| protected: |
| Stat _stat; ///< Stat to update. |
| Expr _expr; ///< Value of update. |
| |
| Do_stat_update(Config &cfg, TextView const &name, Expr &&expr); |
| }; |
| |
| const std::string Do_stat_update::KEY{"stat-update"}; |
| const HookMask Do_stat_update::HOOKS{MaskFor({Hook::CREQ, Hook::PREQ, Hook::PRE_REMAP, Hook::REMAP, Hook::POST_REMAP, Hook::PRSP, |
| Hook::URSP, Hook::TXN_START, Hook::TXN_CLOSE})}; |
| |
| Do_stat_update::Do_stat_update(Config &cfg, TextView const &name, Expr &&expr) : _stat{cfg, name}, _expr(std::move(expr)) {} |
| |
| Errata |
| Do_stat_update::invoke(Context &ctx) |
| { |
| auto [value, errata]{ctx.extract(_expr).as_integer(0)}; |
| if (value != 0) { |
| _stat.update(value); |
| } |
| return std::move(errata); |
| } |
| |
| Rv<Directive::Handle> |
| Do_stat_update::load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &, swoc::TextView const &arg, |
| YAML::Node key_value) |
| { |
| if (key_value.IsNull()) { |
| return Handle(new self_type(cfg, arg, Expr{feature_type_for<INTEGER>(1)})); |
| } |
| |
| auto &&[expr, errata]{cfg.parse_expr(key_value)}; |
| if (!errata.is_ok()) { |
| return std::move(errata); |
| } |
| |
| if (!expr.result_type().can_satisfy(INTEGER)) { |
| return Errata(S_ERROR, "Value for {} directive at {} must be an integer.", KEY, drtv_node.Mark()); |
| } |
| |
| return Handle(new self_type{cfg, arg, std::move(expr)}); |
| } |
| /* ------------------------------------------------------------------------------------ */ |
| class Ex_stat : public Extractor |
| { |
| using self_type = Ex_stat; ///< Self reference type. |
| using super_type = Extractor; ///< Parent type. |
| public: |
| static constexpr TextView NAME{"stat"}; |
| |
| Rv<ActiveType> validate(Config &cfg, Spec &spec, TextView const &arg) override; |
| using Extractor::extract; // un-hide the overloaded |
| Feature extract(Context &ctx, Spec const &spec) override; |
| BufferWriter &format(BufferWriter &w, Spec const &spec, Context &ctx) override; |
| }; |
| |
| Rv<ActiveType> |
| Ex_stat::validate(Config &cfg, Spec &spec, const TextView &arg) |
| { |
| if (arg.empty()) { |
| return Errata(S_ERROR, R"("{}" extractor requires an argument to specify the statistic.)", NAME); |
| } |
| spec._data.span = cfg.alloc_span<Stat>(1); // allocate and stash. |
| spec._data.span.rebind<Stat>()[0].assign(cfg, arg); |
| return {INTEGER}; |
| } |
| |
| Feature |
| Ex_stat::extract(Context &, const Spec &spec) |
| { |
| return spec._data.span.rebind<Stat>()[0].value(); |
| } |
| |
| BufferWriter & |
| Ex_stat::format(BufferWriter &w, Spec const &spec, Context &ctx) |
| { |
| return bwformat(w, spec, this->extract(ctx, spec)); |
| } |
| |
| /* ------------------------------------------------------------------------------------ */ |
| |
| namespace |
| { |
| Ex_stat stat; |
| |
| [[maybe_unused]] bool INITIALIZED = []() -> bool { |
| Config::define<Do_stat_define>(); |
| Config::define<Do_stat_update>(); |
| Extractor::define(Ex_stat::NAME, &stat); |
| return true; |
| }(); |
| } // namespace |