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 {

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.

I think we don't need vector here. std::span is enough

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>...

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

*
* 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);

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.

  1. The interface looks like an error is possible, but you never return it
  2. Do we need buf to be uint8_t*? Why is void* not enough?


/**
* 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 {

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

private:
using bytes_vector = kphp::stl::vector<uint8_t, kphp::memory::script_allocator>;

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.

std::byte instead of uint8_t?

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.

It looks a bit weird that you use private type in public API of MetricBuilder


std::string metric_name;

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.

We can't use std::string as it uses malloc internally. Let's replace with kphp::stl::string

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