diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 617dad5e3f..0b8b8c931a 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -24,6 +24,8 @@ #include #define K2_API_HEADER_H +#include "runtime-common/core/allocator/script-allocator.h" +#include "runtime-common/core/std/containers.h" #include "runtime-light/k2-platform/k2-header.h" #undef K2_API_HEADER_H @@ -72,6 +74,10 @@ using StreamStatus = StreamStatus; using UpdateStatus = UpdateStatus; +using MonitoringSystem = MonitoringSystem; + +using MetricValueMask = MetricValueMask; + using TimePoint = TimePoint; using SystemTime = SystemTime; @@ -245,6 +251,10 @@ inline std::expected madvise(void* addr, size_t length, int32_t a return {}; } +inline void write_metric(const kphp::stl::vector& serialized_metric, k2::MonitoringSystem ms) noexcept { + k2_write_metric(serialized_metric.data(), serialized_metric.size(), ms); +} + 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..f4408693ab 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -97,6 +97,20 @@ enum UpdateStatus { NewDescriptor = 2, }; +enum MonitoringSystem { StatsHouse }; + +/* + * Serialized metric format: + * ... + * + * value_format: + * - single float value + * ... - array of float values + * - count value + * - counter increment (no payload) + */ +enum MetricValueMask : uint8_t { VALUE_MASK = 0, VALUES_ARRAY_MASK = 1, COUNT_MASK = 2, INC_MASK = 3 }; + struct ImageInfo { // Base const char* image_name; @@ -383,6 +397,17 @@ 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 `MetricValueMask` 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. + */ +void k2_write_metric(const uint8_t* 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..36424b9a86 --- /dev/null +++ b/runtime-light/stdlib/diagnostics/metrics.h @@ -0,0 +1,126 @@ +// 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 "common/mixin/movable_only.h" +#include "runtime-common/core/allocator/script-allocator.h" +#include "runtime-common/core/std/containers.h" +#include "runtime-light/k2-platform/k2-api.h" + +struct MetricBuilder final : vk::movable_only { +private: + using bytes_vector = kphp::stl::vector; + + std::string metric_name; + kphp::stl::unordered_map tags; + size_t msg_size{0}; + + explicit MetricBuilder(std::string_view metric_name) noexcept + : metric_name{metric_name} { + this->msg_size += MetricBuilder::string_sizeof(metric_name); + } + + template + requires std::is_arithmetic_v + static void store_number(bytes_vector& buf, const T& number) noexcept { + const auto* src = static_cast(static_cast(&number)); + buf.insert(buf.end(), src, src + sizeof(T)); + } + + static void store_string(bytes_vector& buf, const std::string_view& string) noexcept { + MetricBuilder::store_number(buf, string.size()); + buf.insert(buf.end(), string.begin(), string.end()); + } + + void store_msg(bytes_vector& buf) const noexcept { + MetricBuilder::store_number(buf, this->msg_size); + MetricBuilder::store_string(buf, std::string_view{this->metric_name.c_str(), this->metric_name.size()}); + for (const auto& [tag_name, tag_value] : this->tags) { + MetricBuilder::store_string(buf, std::string_view{tag_name.c_str(), tag_name.size()}); + MetricBuilder::store_string(buf, std::string_view{tag_value.c_str(), tag_value.size()}); + } + } + + 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(); + } + +public: + static MetricBuilder metric(std::string_view metric_name) noexcept { + return MetricBuilder{metric_name}; + } + + MetricBuilder& tag(std::string_view tag_name, std::string_view tag_value) noexcept { + this->tags[std::string{tag_name}] = std::string{tag_value}; + this->msg_size += MetricBuilder::string_sizeof(tag_name) + MetricBuilder::string_sizeof(tag_value); + return *this; + } + + bytes_vector build_value(double value, std::optional timestamp = std::nullopt) const noexcept { + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(double) + sizeof(size_t) + + this->msg_size); // timestamp_u32 + value_mask_u8 + value_f64 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(MetricBuilder::s_timestamp_now())}; + + MetricBuilder::store_number(buf, s_timestamp); + MetricBuilder::store_number(buf, static_cast(k2::MetricValueMask::VALUE_MASK)); + MetricBuilder::store_number(buf, value); + this->store_msg(buf); + return buf; + } + + bytes_vector build_values_array(const kphp::stl::vector& values, + std::optional timestamp = std::nullopt) const noexcept { + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(size_t) + sizeof(double) * values.size() + sizeof(size_t) + + this->msg_size); // timestamp_u32 + value_mask_u8 + array_len_usize + value_f64*array_len + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(MetricBuilder::s_timestamp_now())}; + + MetricBuilder::store_number(buf, s_timestamp); + MetricBuilder::store_number(buf, static_cast(k2::MetricValueMask::VALUES_ARRAY_MASK)); + MetricBuilder::store_number(buf, values.size()); + for (const auto& value : values) { + MetricBuilder::store_number(buf, value); + } + this->store_msg(buf); + return buf; + } + + bytes_vector build_count(double count, std::optional timestamp = std::nullopt) const noexcept { + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(double) + sizeof(size_t) + + this->msg_size); // timestamp_u32 + value_mask_u8 + count_f64 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(MetricBuilder::s_timestamp_now())}; + + MetricBuilder::store_number(buf, s_timestamp); + MetricBuilder::store_number(buf, static_cast(k2::MetricValueMask::COUNT_MASK)); + MetricBuilder::store_number(buf, count); + this->store_msg(buf); + return buf; + } + + bytes_vector build_increment(std::optional timestamp = std::nullopt) const noexcept { + bytes_vector buf{}; + buf.reserve(sizeof(uint32_t) + sizeof(uint8_t) + sizeof(size_t) + this->msg_size); // timestamp_u32 + value_mask_u8 + msg_size_usize + msg_len + + uint32_t s_timestamp{timestamp.value_or(MetricBuilder::s_timestamp_now())}; + + MetricBuilder::store_number(buf, s_timestamp); + MetricBuilder::store_number(buf, static_cast(k2::MetricValueMask::INC_MASK)); + this->store_msg(buf); + return buf; + } +};