Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions runtime-light/k2-platform/k2-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ using StreamStatus = StreamStatus;

using UpdateStatus = UpdateStatus;

using MonitoringSystem = MonitoringSystem;

using MetricValueKind = MetricValueKind;

using TimePoint = TimePoint;

using SystemTime = SystemTime;
Expand Down Expand Up @@ -245,6 +249,13 @@ inline std::expected<void, int32_t> madvise(void* addr, size_t length, int32_t a
return {};
}

inline std::expected<void, int32_t> write_metric(const std::span<std::byte> 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);
}
Expand Down
27 changes: 27 additions & 0 deletions runtime-light/k2-platform/k2-header.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ enum UpdateStatus {
NewDescriptor = 2,
};

enum MonitoringSystem { StatsHouse };

/*
* Serialized metric format:
* <timestamp_u32><value_format><msg_size><metric_name_len><metric_name><tag1_name_len><tag1_name><tag1_value_len><tag1_value>...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is msg_size? Let's add a more detailed description

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*
* value_format:
* <VALUE><f64> - single float value
* <VALUES_ARRAY><array_len><f64_1><f64_2>... - array of float values
* <COUNT><f64> - count value
* <INC> - 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;
Expand Down Expand Up @@ -383,6 +399,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.
*/
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).
Expand Down
201 changes: 201 additions & 0 deletions runtime-light/stdlib/diagnostics/metrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2026 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <chrono>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <initializer_list>
#include <memory>
#include <string_view>

#include "common/containers/final_action.h"
#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"

namespace kphp::diagnostics {
struct MetricBuilder final : vk::movable_only {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put it in kphp::diagnostics namespace as kphp::diagnostics::metric_builder

public:
using bytes_vector = kphp::stl::vector<std::byte, kphp::memory::script_allocator>;

private:
using string = kphp::stl::string<kphp::memory::script_allocator>;

string metric_name;
kphp::stl::vector<std::pair<string, string>, kphp::memory::script_allocator> 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<typename T>
requires std::is_arithmetic_v<T>
static void store_number(bytes_vector& buf, const T& number) noexcept {
const auto* src{static_cast<const std::byte*>(static_cast<const void*>(std::addressof(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());
std::transform(string.begin(), string.end(), std::back_inserter(buf), [](char c) { return std::byte(c); });
}

static void store_tag(bytes_vector& buf, std::pair<std::string_view, std::string_view> tag) noexcept {
MetricBuilder::store_string(buf, tag.first);
MetricBuilder::store_string(buf, tag.second);
}

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_tag(buf, {std::string_view{tag_name.c_str(), tag_name.size()}, std::string_view{tag_value.c_str(), tag_value.size()}});
}
}

auto send_helper(const std::initializer_list<std::pair<std::string_view, std::string_view>>& tags) noexcept {
size_t tags_size{};
for (const auto& [tag_name, tag_value] : tags) {
tags_size += MetricBuilder::string_sizeof(tag_name) + MetricBuilder::string_sizeof(tag_value);
}
this->msg_size += tags_size;
return vk::final_action{[tags_size, this]() { this->msg_size -= tags_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::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

public:
static std::expected<void, int32_t> send(bytes_vector& buf, k2::MonitoringSystem ms) noexcept {
return k2::write_metric(std::span{buf.data(), buf.size()}, ms);
}

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.emplace_back(tag_name, 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<uint32_t> 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_kind_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<uint8_t>(k2::MetricValueKind::VALUE));
MetricBuilder::store_number(buf, value);
this->store_msg(buf);
return buf;
}

bytes_vector build_values_array(std::initializer_list<double> values, std::optional<uint32_t> 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_kind_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<uint8_t>(k2::MetricValueKind::VALUES_ARRAY));
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<uint32_t> 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_kind_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<uint8_t>(k2::MetricValueKind::COUNT));
MetricBuilder::store_number(buf, count);
this->store_msg(buf);
return buf;
}

bytes_vector build_increment(std::optional<uint32_t> 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_kind_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<uint8_t>(k2::MetricValueKind::INC));
this->store_msg(buf);
return buf;
}

std::expected<void, int32_t> send_value(double value, k2::MonitoringSystem ms, std::initializer_list<std::pair<std::string_view, std::string_view>> tags = {},
std::optional<uint32_t> timestamp = std::nullopt) noexcept {
auto final_action{send_helper(tags)};

auto buf{this->build_value(value, timestamp)};
for (const auto& tag : tags) {
MetricBuilder::store_tag(buf, tag);
}

return MetricBuilder::send(buf, ms);
}

std::expected<void, int32_t> send_values_array(std::initializer_list<double> values, k2::MonitoringSystem ms,
std::initializer_list<std::pair<std::string_view, std::string_view>> tags = {},
std::optional<uint32_t> timestamp = std::nullopt) noexcept {
auto final_action{send_helper(tags)};

auto buf{this->build_values_array(values, timestamp)};
for (const auto& tag : tags) {
MetricBuilder::store_tag(buf, tag);
}

return MetricBuilder::send(buf, ms);
}

std::expected<void, int32_t> send_count(double count, k2::MonitoringSystem ms, std::initializer_list<std::pair<std::string_view, std::string_view>> tags = {},
std::optional<uint32_t> timestamp = std::nullopt) noexcept {
auto final_action{send_helper(tags)};

auto buf{this->build_count(count, timestamp)};
for (const auto& tag : tags) {
MetricBuilder::store_tag(buf, tag);
}

return MetricBuilder::send(buf, ms);
}

std::expected<void, int32_t> send_increment(k2::MonitoringSystem ms, std::initializer_list<std::pair<std::string_view, std::string_view>> tags = {},
std::optional<uint32_t> timestamp = std::nullopt) noexcept {
auto final_action{send_helper(tags)};

auto buf{this->build_increment(timestamp)};
for (const auto& tag : tags) {
MetricBuilder::store_tag(buf, tag);
}

return MetricBuilder::send(buf, ms);
}
};
} // namespace kphp::diagnostics
Loading