diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 617dad5e3f..3744999062 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -72,6 +72,10 @@ using StreamStatus = StreamStatus; using UpdateStatus = UpdateStatus; +using MonitoringSystem = MonitoringSystem; + +using MetricValueKind = MetricValueKind; + using TimePoint = TimePoint; using SystemTime = SystemTime; @@ -245,6 +249,13 @@ inline std::expected madvise(void* addr, size_t length, int32_t a return {}; } +inline std::expected write_metric(const std::span serialized_metric, k2::MonitoringSystem ms) noexcept { + if (auto error_code{k2_write_metric(serialized_metric.data(), serialized_metric.size(), ms)}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + return {}; +} + inline void please_shutdown(k2::descriptor descriptor) noexcept { k2_please_shutdown(descriptor); } diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 25b8789f7f..c2830635e6 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -97,6 +97,22 @@ enum UpdateStatus { NewDescriptor = 2, }; +enum MonitoringSystem { StatsHouse }; + +/* + * Serialized metric format: + * ... + * + * value_format: + * - single float value + * ... - array of float values + * - count value + * - counter increment + * + * msg_size: sizeof(metric_name_len) + sizeof(metric_name) + sizeof(tag1_name_len) + sizeof(tag1_name) + sizeof(tag1_value_len) + sizeof(tag1_value) + ... + */ +enum MetricValueKind : uint8_t { VALUE, VALUES_ARRAY, COUNT, INC }; + struct ImageInfo { // Base const char* image_name; @@ -383,6 +399,18 @@ void* k2_mmap(uint64_t* md, void* addr, size_t length, int32_t prot, int32_t fla */ int32_t k2_madvise(void* addr, size_t length, int32_t advise); +/** + * Writes a pre-serialized metric to the specified monitoring system. + * The buffer must contain a metric serialized according to the format described above + * (see `MetricValueKind` and the serialized metric format comment). + * + * @param `buf` A pointer to the serialized metric data. + * @param `buf_len` The length of the serialized metric data in bytes. + * @param `ms` The target monitoring system. + * @return returns 0 if everything is fine, otherwise error code + */ +int32_t k2_write_metric(const void* buf, size_t buf_len, enum MonitoringSystem ms); + /** * Sets `StreamStatus.please_whutdown_write=true` for the component on the * opposite side (does not affect `StreamStatus` on your side). diff --git a/runtime-light/stdlib/diagnostics/metrics.h b/runtime-light/stdlib/diagnostics/metrics.h new file mode 100644 index 0000000000..14cba39765 --- /dev/null +++ b/runtime-light/stdlib/diagnostics/metrics.h @@ -0,0 +1,223 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2026 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "runtime-common/core/allocator/script-allocator.h" +#include "runtime-common/core/std/containers.h" +#include "runtime-light/k2-platform/k2-api.h" + +namespace kphp::diagnostics { +struct metric final { +public: + using bytes_vector = kphp::stl::vector; + +private: + const bytes_vector buffer; + k2::MonitoringSystem ms; + + explicit metric(bytes_vector buffer, k2::MonitoringSystem ms) noexcept + : buffer{std::move(buffer)}, + ms{ms} {} + + template + requires std::is_arithmetic_v + static void store_number(bytes_vector& buf, const T& number) noexcept { + const auto* src{static_cast(static_cast(std::addressof(number)))}; + buf.insert(buf.end(), src, src + sizeof(T)); + } + + static void store_string(bytes_vector& buf, const std::string_view& string) noexcept { + metric::store_number(buf, string.size()); + buf.append_range(std::as_bytes(std::span{string.data(), string.size()})); + } + + static void store_tag(bytes_vector& buf, std::string_view tag_name, std::string_view tag_value) noexcept { + metric::store_string(buf, tag_name); + metric::store_string(buf, tag_value); + } + + static void store_msg(bytes_vector& buf, std::string_view metric_name, size_t msg_size, + std::span> tags) noexcept { + metric::store_number(buf, msg_size); + metric::store_string(buf, metric_name); + for (const auto& [tag_name, tag_value] : tags) { + metric::store_tag(buf, tag_name, tag_value); + } + } + + static size_t string_sizeof(const std::string_view& string) noexcept { + return sizeof(size_t) + string.size(); + } + + static uint32_t s_timestamp_now() noexcept { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + static size_t calc_msg_size(std::string_view metric_name, std::span> tags) noexcept { + size_t result{string_sizeof(metric_name)}; + for (const auto& [tag_name, tag_value] : tags) { + result += metric::string_sizeof(tag_name) + metric::string_sizeof(tag_value); + } + return result; + } + +public: + static metric from_value(k2::MonitoringSystem ms, std::string_view metric_name, std::span> tags, double value, + std::optional timestamp = std::nullopt) noexcept { + size_t msg_size{metric::calc_msg_size(metric_name, tags)}; + + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(double) + sizeof(size_t) + + msg_size); // timestamp_u32 + value_kind_u8 + value_f64 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(metric::s_timestamp_now())}; + + metric::store_number(buf, s_timestamp); + metric::store_number(buf, static_cast(k2::MetricValueKind::VALUE)); + metric::store_number(buf, value); + metric::store_msg(buf, metric_name, msg_size, tags); + return metric{buf, ms}; + } + + static metric from_values_array(k2::MonitoringSystem ms, std::string_view metric_name, std::span> tags, + std::span values, std::optional timestamp = std::nullopt) noexcept { + size_t msg_size{metric::calc_msg_size(metric_name, tags)}; + + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(size_t) + sizeof(double) * values.size() + sizeof(size_t) + + msg_size); // timestamp_u32 + value_kind_u8 + array_len_usize + value_f64*array_len + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(metric::s_timestamp_now())}; + + metric::store_number(buf, s_timestamp); + metric::store_number(buf, static_cast(k2::MetricValueKind::VALUES_ARRAY)); + metric::store_number(buf, values.size()); + for (const auto& value : values) { + metric::store_number(buf, value); + } + metric::store_msg(buf, metric_name, msg_size, tags); + return metric{buf, ms}; + } + + static metric from_count(k2::MonitoringSystem ms, std::string_view metric_name, std::span> tags, uint32_t count, + std::optional timestamp = std::nullopt) noexcept { + size_t msg_size{metric::calc_msg_size(metric_name, tags)}; + + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t) + sizeof(size_t) + + msg_size); // timestamp_u32 + value_kind_u8 + count_u32 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(metric::s_timestamp_now())}; + + metric::store_number(buf, s_timestamp); + metric::store_number(buf, static_cast(k2::MetricValueKind::COUNT)); + metric::store_number(buf, count); + metric::store_msg(buf, metric_name, msg_size, tags); + + return metric{buf, ms}; + } + + static metric from_increment(k2::MonitoringSystem ms, std::string_view metric_name, std::span> tags, + std::optional timestamp = std::nullopt) noexcept { + size_t msg_size{metric::calc_msg_size(metric_name, tags)}; + + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(size_t) + msg_size); // timestamp_u32 + value_kind_u8 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(metric::s_timestamp_now())}; + + metric::store_number(buf, s_timestamp); + metric::store_number(buf, static_cast(k2::MetricValueKind::INC)); + metric::store_msg(buf, metric_name, msg_size, tags); + return metric{buf, ms}; + } + + std::expected send() { + return k2::write_metric(this->buffer, this->ms); + } +}; + +// --------------------------------------------------------------------------------------------------------- + +struct metric_builder final { +public: + using bytes_vector = kphp::stl::vector; + +private: + kphp::stl::string metric_name; + kphp::stl::vector, kphp::stl::string>, + kphp::memory::script_allocator> + tags; + size_t msg_size{0}; + k2::MonitoringSystem ms; + + explicit metric_builder(std::string_view metric_name, k2::MonitoringSystem ms) noexcept + : metric_name{metric_name}, + ms{ms} { + this->msg_size += metric_builder::string_sizeof(metric_name); + } + + kphp::stl::vector, kphp::memory::script_allocator> convert_tags() const noexcept { + auto tags_view{this->tags | + std::views::transform( + [](const std::pair, kphp::stl::string>& tag) noexcept + -> std::pair { return std::pair{tag.first, tag.second}; })}; + + kphp::stl::vector, kphp::memory::script_allocator> tags{}; + tags.reserve(this->tags.size()); + std::ranges::copy(tags_view, std::back_inserter(tags)); + return tags; + } + + static size_t string_sizeof(const std::string_view& string) noexcept { + return sizeof(size_t) + string.size(); + } + +public: + static std::expected send(bytes_vector& buf, k2::MonitoringSystem ms) noexcept { + return k2::write_metric(std::span{buf.data(), buf.size()}, ms); + } + + static metric_builder metric(std::string_view metric_name, k2::MonitoringSystem ms) noexcept { + return metric_builder{metric_name, ms}; + } + + metric_builder& tag(std::string_view tag_name, std::string_view tag_value) noexcept { + this->tags.emplace_back(tag_name, tag_value); + this->msg_size += metric_builder::string_sizeof(tag_name) + metric_builder::string_sizeof(tag_value); + return *this; + } + + kphp::diagnostics::metric build_value(double value, std::optional timestamp = std::nullopt) const noexcept { + auto sv_tags{this->convert_tags()}; + return metric::from_value(this->ms, this->metric_name, sv_tags, value, timestamp); + } + + kphp::diagnostics::metric build_values_array(std::span values, std::optional timestamp = std::nullopt) const noexcept { + auto sv_tags{this->convert_tags()}; + return metric::from_values_array(this->ms, this->metric_name, sv_tags, values, timestamp); + } + + kphp::diagnostics::metric build_count(uint32_t count, std::optional timestamp = std::nullopt) const noexcept { + auto sv_tags{this->convert_tags()}; + return metric::from_count(this->ms, this->metric_name, sv_tags, count, timestamp); + } + + kphp::diagnostics::metric build_increment(std::optional timestamp = std::nullopt) const noexcept { + auto sv_tags{this->convert_tags()}; + return metric::from_increment(this->ms, this->metric_name, sv_tags, timestamp); + } +}; +} // namespace kphp::diagnostics