Add DynamicStats (#7704)

diff --git a/.gitignore b/.gitignore
index 64feec8..849f3ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,6 +90,7 @@
 src/tscore/test_tscore
 src/tscpp/util/test_tscpputil
 lib/records/test_librecords
+lib/records/test_librecords_on_eventsystem
 lib/perl/lib/Apache/TS.pm
 
 iocore/net/test_certlookup
diff --git a/lib/records/DynamicStats.h b/lib/records/DynamicStats.h
new file mode 100644
index 0000000..d31c71f
--- /dev/null
+++ b/lib/records/DynamicStats.h
@@ -0,0 +1,170 @@
+/** @file
+
+  Dynamic Stats
+
+  @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.
+ */
+
+#pragma once
+
+#include "records/I_RecCore.h"
+#include "records/I_RecDefs.h"
+#include "records/I_RecProcess.h"
+
+/**
+   @class DynamicStats
+   @details
+     core version of TSStat* APIs
+
+     - supported: TSStatCreate, TSStatIntIncrement/TSStatIntDecrement, TSStatIntGet, TSStatIntSet, TSStatFindName
+
+   CAVEAT: The librecords doesn't have APIs for "unregister stats" or "realloc rsb".
+   If you want to register ton of stats, bump -maxRecords option of traffic_manager/traffic_server.
+
+ */
+class DynamicStats
+{
+public:
+  // Modifiers
+  void init(int max_stats);
+  int create(RecT rec_type, const char *name, RecDataT data_type, RecRawStatSyncCb sync_cb, bool is_persistent = false);
+  int increment(int id, int64_t amount);
+  int set_sum(int id, int64_t value);
+
+  // References
+  int64_t get_sum(int id) const;
+  int find(const char *name) const;
+  bool is_allocated() const;
+
+private:
+  RecRawStatBlock *_rsb       = nullptr;
+  std::atomic<int> _rsb_index = 0;
+};
+
+////
+// Inline functions
+//
+inline void
+DynamicStats::init(int max_stats)
+{
+  _rsb = RecAllocateRawStatBlock(max_stats);
+}
+
+/**
+   TSStatCreate
+ */
+inline int
+DynamicStats::create(RecT rec_type, const char *name, RecDataT data_type, RecRawStatSyncCb sync_cb, bool is_persistent)
+{
+  if (name == nullptr || _rsb_index >= _rsb->max_stats) {
+    return REC_ERR_FAIL;
+  }
+
+  int stat_id = _rsb_index;
+
+  if (is_persistent) {
+    int r = RecRegisterRawStat(_rsb, rec_type, name, data_type, RECP_PERSISTENT, stat_id, sync_cb);
+    if (r != REC_ERR_OKAY) {
+      return REC_ERR_FAIL;
+    }
+  } else {
+    int r = RecRegisterRawStat(_rsb, rec_type, name, data_type, RECP_NON_PERSISTENT, stat_id, sync_cb);
+    if (r != REC_ERR_OKAY) {
+      return REC_ERR_FAIL;
+    }
+  }
+
+  RecSetRawStatSum(_rsb, stat_id, 0);
+  RecSetRawStatCount(_rsb, stat_id, 0);
+
+  _rsb_index++;
+
+  return stat_id;
+}
+
+/**
+   TSStatIntIncrement / TSStatIntDecrement
+ */
+inline int
+DynamicStats::increment(int id, int64_t amount)
+{
+  if (id < 0) {
+    return REC_ERR_FAIL;
+  }
+
+  return RecIncrRawStat(_rsb, nullptr, id, amount);
+}
+
+/**
+   TSStatIntSet
+ */
+inline int
+DynamicStats::set_sum(int id, int64_t value)
+{
+  if (id < 0) {
+    return REC_ERR_FAIL;
+  }
+
+  return RecSetGlobalRawStatSum(_rsb, id, value);
+}
+
+/**
+   TSStatIntGet
+ */
+inline int64_t
+DynamicStats::get_sum(int id) const
+{
+  int64_t value = -1;
+
+  if (id < 0) {
+    return value;
+  }
+
+  RecGetGlobalRawStatSum(_rsb, id, &value);
+
+  return value;
+}
+
+/**
+   TSStatFindName
+ */
+inline int
+DynamicStats::find(const char *name) const
+{
+  if (name == nullptr) {
+    return REC_ERR_FAIL;
+  }
+
+  int id;
+  if (RecGetRecordOrderAndId(name, nullptr, &id, true, true) != REC_ERR_OKAY) {
+    return REC_ERR_FAIL;
+  }
+
+  if (RecGetGlobalRawStatPtr(_rsb, id) == nullptr) {
+    return REC_ERR_FAIL;
+  }
+
+  return id;
+}
+
+inline bool
+DynamicStats::is_allocated() const
+{
+  return _rsb != nullptr;
+}
diff --git a/lib/records/Makefile.am b/lib/records/Makefile.am
index 8fc83fb..2dd0ca5 100644
--- a/lib/records/Makefile.am
+++ b/lib/records/Makefile.am
@@ -18,7 +18,7 @@
 
 include $(top_srcdir)/build/tidy.mk
 
-check_PROGRAMS = test_librecords
+check_PROGRAMS = test_librecords test_librecords_on_eventsystem
 
 AM_CPPFLAGS += \
 	-I$(abs_top_srcdir)/iocore/eventsystem \
@@ -71,7 +71,7 @@
 TESTS = $(check_PROGRAMS)
 
 test_librecords_CPPFLAGS = $(AM_CPPFLAGS)\
-        -I$(abs_top_srcdir)/tests/include
+	-I$(abs_top_srcdir)/tests/include
 
 test_librecords_SOURCES = \
     unit_tests/unit_test_main.cc \
@@ -86,6 +86,21 @@
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
 	@HWLOC_LIBS@ @LIBCAP@
 
+test_librecords_on_eventsystem_CPPFLAGS = $(AM_CPPFLAGS)\
+	-I$(abs_top_srcdir)/tests/include
+
+test_librecords_on_eventsystem_SOURCES = \
+    unit_tests/unit_test_main_on_eventsystem.cc \
+	unit_tests/test_DynamicStats.cc
+
+test_librecords_on_eventsystem_LDADD = \
+	$(top_builddir)/lib/records/librecords_p.a \
+	$(top_builddir)/mgmt/libmgmt_p.la \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
+	@HWLOC_LIBS@ @LIBCAP@
 
 clang-tidy-local: $(sort $(DIST_SOURCES))
 	$(CXX_Clang_Tidy)
diff --git a/lib/records/unit_tests/test_DynamicStats.cc b/lib/records/unit_tests/test_DynamicStats.cc
new file mode 100644
index 0000000..7eef407
--- /dev/null
+++ b/lib/records/unit_tests/test_DynamicStats.cc
@@ -0,0 +1,47 @@
+/** @file
+
+    Unit tests for BufferWriter.h.
+
+    @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 "catch.hpp"
+
+#include "DynamicStats.h"
+#include "P_RecProcess.h"
+
+#include <string_view>
+
+TEST_CASE("DynamicStats", "[DynamicStats]")
+{
+  SECTION("set/get_sum")
+  {
+    DynamicStats stats;
+    stats.init(10);
+    REQUIRE(stats.is_allocated() == true);
+
+    std::string_view name = "proxy.process.test.dynamic.stats";
+    int id                = stats.create(RECT_PROCESS, name.data(), RECD_INT, RecRawStatSyncSum);
+    REQUIRE(id == 0);
+    CHECK(id == stats.find(name.data()));
+
+    CHECK(stats.set_sum(id, 12345) == REC_ERR_OKAY);
+    CHECK(stats.get_sum(id) == 12345);
+  }
+}
diff --git a/lib/records/unit_tests/unit_test_main_on_eventsystem.cc b/lib/records/unit_tests/unit_test_main_on_eventsystem.cc
new file mode 100644
index 0000000..1df1d98
--- /dev/null
+++ b/lib/records/unit_tests/unit_test_main_on_eventsystem.cc
@@ -0,0 +1,56 @@
+/** @file
+
+  Catch based unit tests on EventSystem
+
+  @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.
+ */
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
+#include "I_EventSystem.h"
+#include "tscore/I_Layout.h"
+#include "tscore/TSSystemState.h"
+
+#include "diags.i"
+
+namespace
+{
+constexpr int TEST_THREADS = 2;
+}
+
+struct EventProcessorListener : Catch::TestEventListenerBase {
+  using TestEventListenerBase::TestEventListenerBase;
+
+  void
+  testRunStarting(Catch::TestRunInfo const &testRunInfo) override
+  {
+    Layout::create();
+    init_diags("", nullptr);
+    RecProcessInit(RECM_STAND_ALONE);
+
+    ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION);
+    eventProcessor.start(TEST_THREADS);
+
+    EThread *main_thread = new EThread;
+    main_thread->set_specific();
+  }
+};
+
+CATCH_REGISTER_LISTENER(EventProcessorListener);