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
10 changes: 10 additions & 0 deletions runtime-light/k2-platform/k2-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <utility>

#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

Expand Down Expand Up @@ -72,6 +74,10 @@ using StreamStatus = StreamStatus;

using UpdateStatus = UpdateStatus;

using MonitoringSystem = MonitoringSystem;

using MetricValueMask = MetricValueMask;

using TimePoint = TimePoint;

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

inline void write_metric(const kphp::stl::vector<uint8_t, kphp::memory::script_allocator>& 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);
}
Expand Down
25 changes: 25 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,20 @@ 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>...
*
* value_format:
* <VALUE_MASK><f64> - single float value
* <VALUES_ARRAY_MASK><array_len><f64_1><f64_2>... - array of float values
* <COUNT_MASK><f64> - count value
* <INC_MASK> - 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;
Expand Down Expand Up @@ -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).
Expand Down
126 changes: 126 additions & 0 deletions runtime-light/stdlib/diagnostics/metrics.h
Original file line number Diff line number Diff line change
@@ -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 <chrono>
#include <cstddef>
#include <cstdint>

#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<uint8_t, kphp::memory::script_allocator>;

std::string metric_name;
kphp::stl::unordered_map<std::string, std::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 uint8_t*>(static_cast<const void*>(&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::seconds>(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<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_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<uint8_t>(k2::MetricValueMask::VALUE_MASK));
MetricBuilder::store_number(buf, value);
this->store_msg(buf);
return buf;
}

bytes_vector build_values_array(const kphp::stl::vector<double, kphp::memory::script_allocator>& 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_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<uint8_t>(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<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_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<uint8_t>(k2::MetricValueMask::COUNT_MASK));
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_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<uint8_t>(k2::MetricValueMask::INC_MASK));
this->store_msg(buf);
return buf;
}
};
Loading