| /** @file |
| * |
| * Traffic Manager custom metrics. |
| * |
| * @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. |
| */ |
| |
| #include <cmath> |
| |
| #include "ts/ink_config.h" |
| #include "ts/ink_memory.h" |
| #include "ts/Ptr.h" |
| #include "ts/Vec.h" |
| #include "ts/I_Layout.h" |
| #include "bindings/bindings.h" |
| #include "bindings/metrics.h" |
| #include "I_RecCore.h" |
| #include "MgmtDefs.h" |
| #include "MgmtUtils.h" |
| #include "WebOverview.h" |
| #include "metrics.h" |
| |
| struct Evaluator { |
| Evaluator() : rec_name(nullptr), data_type(RECD_NULL), ref(LUA_NOREF) {} |
| ~Evaluator() { ink_release_assert(this->ref == LUA_NOREF); } |
| Evaluator(const Evaluator &) = delete; |
| Evaluator &operator=(const Evaluator &) = delete; |
| |
| bool |
| bind(lua_State *L, const char *metric, const char *expression) |
| { |
| if (RecGetRecordDataType(metric, &this->data_type) != REC_ERR_OKAY) { |
| return false; |
| } |
| |
| this->rec_name = ats_strdup(metric); |
| |
| switch (luaL_loadstring(L, expression)) { |
| case LUA_ERRSYNTAX: |
| case LUA_ERRMEM: |
| Debug("lua", "loadstring failed for %s", metric); |
| luaL_error(L, "invalid expression for %s", metric); |
| return false; |
| case 0: |
| break; // success |
| } |
| |
| // The loaded chunk is now on the top of the stack. Stuff it into the registry |
| // so we can evaluate it later. |
| this->ref = luaL_ref(L, LUA_REGISTRYINDEX); |
| |
| return true; |
| } |
| |
| void |
| unbind(lua_State *L) |
| { |
| if (this->ref != LUA_NOREF) { |
| luaL_unref(L, LUA_REGISTRYINDEX, this->ref); |
| } |
| |
| ats_free(this->rec_name); |
| |
| this->ref = LUA_NOREF; |
| this->rec_name = nullptr; |
| this->data_type = RECD_NULL; |
| } |
| |
| void |
| eval(lua_State *L) const |
| { |
| // Push the stashed expression chunk onto the stack. |
| lua_rawgeti(L, LUA_REGISTRYINDEX, this->ref); |
| |
| // Evaluate it. Note that we don't emit a warning for |
| // evaluation errors. This is because not all metrics (eg. |
| // cache metrics) are available early in startup so we don't |
| // want to log spurious warning. Unfortunately it means that |
| // to check your config for errors you need to enable |
| // diagnostic tags. |
| lua_pushstring(L, this->rec_name); |
| if (lua_pcall(L, 1 /* nargs */, 1 /* nresults */, 0) != 0) { |
| Debug("lua", "failed to evaluate %s: %s", this->rec_name, lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return; |
| } |
| |
| // If we got a return value, set it on the record. Records can return nil to |
| // indicate they don't want to be set on this round. |
| if (!lua_isnil(L, -1)) { |
| RecData rec_value; |
| |
| switch (this->data_type) { |
| case RECD_INT: |
| rec_value.rec_int = lua_tointeger(L, -1); |
| break; |
| case RECD_COUNTER: |
| rec_value.rec_counter = lua_tointeger(L, -1); |
| break; |
| case RECD_FLOAT: |
| // Lua will eval 0/0 to NaN rather than 0. |
| rec_value.rec_float = lua_tonumber(L, -1); |
| if (std::isnan(rec_value.rec_float)) { |
| rec_value.rec_float = 0.0; |
| } |
| break; |
| default: |
| goto done; |
| } |
| |
| RecSetRecord(RECT_NULL, this->rec_name, this->data_type, &rec_value, nullptr, REC_SOURCE_EXPLICIT); |
| } |
| |
| done: |
| // Pop the return value. |
| lua_pop(L, 1); |
| } |
| |
| private: |
| char *rec_name; |
| RecDataT data_type; |
| |
| int ref; |
| }; |
| |
| struct EvaluatorList { |
| EvaluatorList() : update(true), passes(0) {} |
| ~EvaluatorList() |
| { |
| forv_Vec (Evaluator, e, this->evaluators) { |
| delete e; |
| } |
| } |
| |
| EvaluatorList(const EvaluatorList &) = delete; |
| EvaluatorList &operator=(const EvaluatorList &) = delete; |
| |
| void |
| push_back(Evaluator *e) |
| { |
| evaluators.push_back(e); |
| } |
| |
| void |
| unbind(lua_State *L) const |
| { |
| forv_Vec (Evaluator, e, this->evaluators) { |
| e->unbind(L); |
| } |
| } |
| |
| void |
| evaluate(lua_State *L) const |
| { |
| ink_hrtime start = ink_get_hrtime_internal(); |
| ink_hrtime elapsed; |
| |
| forv_Vec (Evaluator, e, this->evaluators) { |
| e->eval(L); |
| } |
| |
| elapsed = ink_hrtime_diff(ink_get_hrtime_internal(), start); |
| Debug("lua", "evaluated %u metrics in %fmsec", evaluators.length(), ink_hrtime_to_usec(elapsed) / 1000.0); |
| } |
| |
| bool update; |
| int64_t passes; |
| Vec<Evaluator *> evaluators; |
| }; |
| |
| static int |
| update_metrics_namespace(lua_State *L) |
| { |
| lua_Integer count; |
| |
| lua_metrics_install(L); |
| count = lua_tointeger(L, 1); |
| lua_pop(L, 1); |
| |
| return count; |
| } |
| |
| static int64_t |
| timestamp_now_msec() |
| { |
| ink_hrtime now = ink_get_hrtime_internal(); |
| return ink_hrtime_to_msec(now); |
| } |
| |
| static int |
| metrics_register_evaluator(lua_State *L) |
| { |
| const char *metric; |
| const char *chunk; |
| Evaluator *eval; |
| EvaluatorList *evaluators; |
| BindingInstance *binding; |
| |
| // The metric name is the first upvalue (from the record creation closure). |
| metric = lua_tostring(L, lua_upvalueindex(1)); |
| // The evaluation chunk is the (only) argument. |
| chunk = lua_tostring(L, -1); |
| |
| binding = BindingInstance::self(L); |
| evaluators = (EvaluatorList *)binding->retrieve_ptr("evaluators"); |
| |
| ink_release_assert(evaluators != nullptr); |
| |
| eval = new Evaluator(); |
| eval->bind(L, metric, chunk); |
| |
| evaluators->push_back(eval); |
| return 0; |
| } |
| |
| static int |
| metrics_create_record(lua_State *L, RecDataT data_type) |
| { |
| const char *name; |
| RecT rec_type = RECT_NULL; |
| int error = REC_ERR_FAIL; |
| |
| BindingInstance::typecheck(L, "record.create", LUA_TSTRING, LUA_TNONE); |
| |
| // Get the name of the record to create. |
| name = lua_tostring(L, -1); |
| |
| if (strncmp(name, "proxy.process.", sizeof("proxy.process.") - 1) == 0) { |
| rec_type = RECT_PROCESS; |
| } else if (strncmp(name, "proxy.node.", sizeof("proxy.node.") - 1) == 0) { |
| rec_type = RECT_NODE; |
| } else if (strncmp(name, "proxy.cluster.", sizeof("proxy.cluster.") - 1) == 0) { |
| rec_type = RECT_CLUSTER; |
| } |
| |
| // You have to follow the naming convention. |
| if (rec_type == RECT_NULL) { |
| luaL_error(L, "invalid metric name '%s'", name); |
| } |
| |
| switch (data_type) { |
| case RECD_INT: |
| error = RecRegisterStatInt(rec_type, name, 0, RECP_NON_PERSISTENT); |
| break; |
| case RECD_FLOAT: |
| error = RecRegisterStatFloat(rec_type, name, 0, RECP_NON_PERSISTENT); |
| break; |
| case RECD_COUNTER: |
| error = RecRegisterStatCounter(rec_type, name, 0, RECP_NON_PERSISTENT); |
| break; |
| default: |
| break; |
| } |
| |
| if (error != REC_ERR_OKAY) { |
| luaL_error(L, "failed to register metric '%s'", name); |
| } |
| |
| // Push a copy of the metric name onto the stack. |
| lua_pushvalue(L, -1); |
| // Push the Evaluator as a closure with the metric name as an upvalue. |
| lua_pushcclosure(L, metrics_register_evaluator, 1); |
| |
| Debug("lua", "registered %s as record type %d", name, rec_type); |
| return 1; |
| } |
| |
| static int |
| metrics_create_integer(lua_State *L) |
| { |
| return metrics_create_record(L, RECD_INT); |
| } |
| |
| static int |
| metrics_create_counter(lua_State *L) |
| { |
| return metrics_create_record(L, RECD_COUNTER); |
| } |
| |
| static int |
| metrics_create_float(lua_State *L) |
| { |
| return metrics_create_record(L, RECD_FLOAT); |
| } |
| |
| static int |
| metrics_cluster_sum(lua_State *L) |
| { |
| const char *rec_name; |
| RecDataT data_type; |
| RecData rec_data; |
| |
| // Get the name of the record to sum. |
| rec_name = lua_tostring(L, -1); |
| |
| // XXX Check whether we have a cached value for this somewhere ... |
| |
| // If not, get the record data type. |
| if (RecGetRecordDataType(rec_name, &data_type) == REC_ERR_FAIL) { |
| luaL_error(L, "unknown metric name '%s'", rec_name); |
| } |
| |
| // Sum the cluster value. |
| if (!overviewGenerator->varClusterDataFromName(data_type, rec_name, &rec_data)) { |
| RecDataZero(data_type, &rec_data); |
| |
| // If we can't get any cluster data, return nil. This will generally cause the |
| // evaluator to fail, which is handled by logging and ignoring the failure. |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| switch (data_type) { |
| case RECD_INT: /* fallthru */ |
| case RECD_COUNTER: |
| lua_pushinteger(L, rec_data.rec_int); |
| break; |
| case RECD_FLOAT: |
| lua_pushnumber(L, rec_data.rec_float); |
| break; |
| case RECD_STRING: |
| lua_pushlstring(L, rec_data.rec_string, strlen(rec_data.rec_string)); |
| break; |
| default: |
| lua_pushnil(L); |
| } |
| |
| // Return 1 value on the stack. |
| return 1; |
| } |
| |
| bool |
| metrics_binding_initialize(BindingInstance &binding) |
| { |
| if (!binding.construct()) { |
| mgmt_fatal(0, "failed to initialize Lua runtime\n"); |
| } |
| |
| // Register the metrics userdata type. |
| lua_metrics_register(binding.lua); |
| update_metrics_namespace(binding.lua); |
| |
| // Register our own API. |
| binding.bind_function("integer", metrics_create_integer); |
| binding.bind_function("counter", metrics_create_counter); |
| binding.bind_function("float", metrics_create_float); |
| binding.bind_function("metrics.cluster.sum", metrics_cluster_sum); |
| |
| binding.bind_constant("metrics.now.msec", timestamp_now_msec()); |
| binding.bind_constant("metrics.update.pass", lua_Integer(0)); |
| |
| // Stash a backpointer to the evaluators. |
| binding.attach_ptr("evaluators", new EvaluatorList()); |
| |
| return true; |
| } |
| |
| void |
| metrics_binding_destroy(BindingInstance &binding) |
| { |
| EvaluatorList *evaluators; |
| |
| evaluators = (EvaluatorList *)binding.retrieve_ptr("evaluators"); |
| binding.attach_ptr("evaluators", nullptr); |
| |
| evaluators->unbind(binding.lua); |
| delete evaluators; |
| } |
| |
| bool |
| metrics_binding_configure(BindingInstance &binding) |
| { |
| ats_scoped_str sysconfdir(RecConfigReadConfigDir()); |
| ats_scoped_str config(Layout::get()->relative_to(sysconfdir.get(), "metrics.config")); |
| |
| return binding.require(config.get()); |
| } |
| |
| void |
| metrics_binding_evaluate(BindingInstance &binding) |
| { |
| EvaluatorList *evaluators; |
| |
| evaluators = (EvaluatorList *)binding.retrieve_ptr("evaluators"); |
| ink_release_assert(evaluators != nullptr); |
| |
| // Keep updating the namespace until it settles (ie. we make 0 updates). |
| if (evaluators->update) { |
| evaluators->update = update_metrics_namespace(binding.lua) ? true : false; |
| } |
| |
| binding.bind_constant("metrics.now.msec", timestamp_now_msec()); |
| binding.bind_constant("metrics.update.pass", ++evaluators->passes); |
| evaluators->evaluate(binding.lua); |
| |
| // Periodically refresh the namespace to catch newly added metrics. |
| if (evaluators->passes % 10 == 0) { |
| evaluators->update = true; |
| } |
| } |