Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
694884f
first version
denisichh Jun 19, 2026
2b7be26
added metric builder + write_serialized_metric to k2-api
denisichh Jun 19, 2026
7bb602d
v1.0
denisichh Jun 19, 2026
f6af9d2
v1.1
denisichh Jun 21, 2026
0608cf2
rename
denisichh Jun 21, 2026
12b21ac
mask size 32bits to 8bits
denisichh Jun 21, 2026
5bb69d9
fixes
denisichh Jun 22, 2026
f8f635f
fixes
denisichh Jun 22, 2026
064d869
add comments
denisichh Jun 22, 2026
de690e2
added noexcept
denisichh Jun 22, 2026
127f856
fixes
denisichh Jun 22, 2026
537b967
minor fix
denisichh Jun 22, 2026
22a6227
added metric type
denisichh Jun 22, 2026
86f1de8
fixes
denisichh Jun 22, 2026
22cdf85
timestamp u32 to u64 + fixes
denisichh Jun 23, 2026
21e5bdb
fix
denisichh Jun 23, 2026
88709e2
fix
denisichh Jun 23, 2026
4590662
fix concept
denisichh Jun 23, 2026
6f3052d
new metrics ctors
denisichh Jun 23, 2026
57d6b2e
rename static method
denisichh Jun 23, 2026
a5f3057
funny fixes
denisichh Jun 23, 2026
6c9137b
minor fix
denisichh Jun 23, 2026
9c63912
minor fix
denisichh Jun 23, 2026
713b47a
returned buffer.clear()
denisichh Jun 23, 2026
a7e392f
fix
denisichh Jun 23, 2026
b1d7add
added include
denisichh Jun 23, 2026
dfd5c0c
chrono to k2::system_time
denisichh Jun 23, 2026
9c5b54e
format changed: my own to tl
denisichh Jun 25, 2026
476e9cf
fixes
denisichh Jun 26, 2026
9e23659
fixes
denisichh Jun 26, 2026
3366cb6
minor rename
denisichh Jun 26, 2026
0ca6ecd
minor fix
denisichh Jun 26, 2026
3fc576a
added tl scheme fot metric type
denisichh Jun 26, 2026
6c6e917
fix .tl
denisichh Jun 26, 2026
67a07ff
added tl::span
denisichh Jun 29, 2026
202accd
rule of 5 for tl::span
denisichh Jun 29, 2026
292da38
added from_bytes for tl::span
denisichh Jun 29, 2026
bd72a56
fix tl scheme
denisichh Jun 29, 2026
1fef69e
removed tl::span, moved metrics tl classes
denisichh Jun 29, 2026
954d130
removed unused include
denisichh Jun 29, 2026
b3b2335
removed unused space
denisichh Jun 29, 2026
8308d75
removed monitoring system enum
denisichh Jun 29, 2026
787afac
fix metric tl classes
denisichh Jun 29, 2026
54003fb
added {}
denisichh Jun 29, 2026
0a1c7a4
added requires
denisichh Jun 29, 2026
6b6b303
send && fixed
denisichh Jun 29, 2026
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
7 changes: 7 additions & 0 deletions runtime-light/k2-platform/k2-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ inline std::expected<void, int32_t> madvise(void* addr, size_t length, int32_t a
return {};
}

inline std::expected<void, int32_t> write_metrics(std::span<const std::byte> serialized_metric) noexcept {
if (auto error_code{k2_write_metrics(serialized_metric.data(), serialized_metric.size())}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return {};
}

inline void please_shutdown(k2::descriptor descriptor) noexcept {
k2_please_shutdown(descriptor);
}
Expand Down
26 changes: 26 additions & 0 deletions runtime-light/k2-platform/k2-header.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,32 @@ 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 metrics to the specified monitoring system.
*
* The buffer must contain a metric serialized according to the following format
* (TL serialization, native byte order):
* <timestamp:u64><value format><metric name:tl string><tags count:u32><tag1><tag2>...
* tag := <name:tl string><value:tl string>
*
* value format:
* <`VALUE_MAGIC`:u32><f64> - single double value
* <`VALUES_ARRAY_MAGIC`:u32><len:u32><f64><f64>... - array of double values
* <`COUNT_MAGIC`:u32><u32> - count value
* <`INC_MAGIC`:u32> - counter increment
*
* tl string is the standard TL string encoding.
*
* Multiple metrics can be sent in a single call by concatenating them sequentially:
* <metric1><metric2>...
* Each metric is serialized independently using the format described above.
*
* @param `buf` A pointer to the serialized metric(s) data.
* @param `buf_len` The length of the serialized metric(s) data in bytes.
* @return returns 0 if everything is fine, otherwise error code
*/
int32_t k2_write_metrics(const void* buf, size_t buf_len);

/**
* Sets `StreamStatus.please_whutdown_write=true` for the component on the
* opposite side (does not affect `StreamStatus` on your side).
Expand Down
13 changes: 13 additions & 0 deletions runtime-light/stdlib/diagnostics/metric.tl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---types---

pair {X:Type} {Y:Type} a:X b:Y = Pair X Y;
span#dad3ae87 {t:Type} count:# data:count*[t] = Span t;

// Metric

metricValue#0cb5eb87 value:double = AnyMetricValue;
metricValuesArray#d4a59582 values:%(Span double) = AnyMetricValue;
metricCount#941bf7d1 count:u32 = AnyMetricValue;
metricInc#23e305ab = AnyMetricValue;

metric#87d62ee3 timestamp:u64 value:AnyMetricValue metric_name:string tags:%(Span %(Pair string string)) = Metric;
253 changes: 253 additions & 0 deletions runtime-light/stdlib/diagnostics/metrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2026 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <ranges>
#include <span>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>

#include "runtime-common/core/allocator/script-allocator.h"
#include "runtime-common/core/std/containers.h"
#include "runtime-light/k2-platform/k2-api.h"
#include "runtime-light/tl/tl-core.h"
#include "runtime-light/tl/tl-types.h"

namespace tl {
struct metricValue final {
tl::f64 value{};

void store(tl::storer& tls) const noexcept {
value.store(tls);
}

constexpr size_t footprint() const noexcept {
return value.footprint();
}
};

struct metricValuesArray final {
std::span<const double> values;

void store(tl::storer& tls) const noexcept {
tl::u32{static_cast<uint32_t>(this->values.size())}.store(tls);
std::ranges::for_each(this->values, [&tls](const double& elem) noexcept { tl::f64{elem}.store(tls); });
}

constexpr size_t footprint() const noexcept {
return std::ranges::fold_left(this->values, tl::u32{static_cast<uint32_t>(this->values.size())}.footprint(),
[](size_t acc, const double& elem) noexcept { return acc + tl::f64{elem}.footprint(); });
}
};

struct metricCount final {
tl::u32 count{};

void store(tl::storer& tls) const noexcept {
count.store(tls);
}

constexpr size_t footprint() const noexcept {
return count.footprint();
}
};

struct metricInc final {
void store(tl::storer& /* tls */) const noexcept {}

constexpr size_t footprint() const noexcept {
return 0;
}
};

class AnyMetricValue final {
static constexpr uint32_t VALUE_MAGIC = 0xcb5eb87U;
static constexpr uint32_t VALUES_ARRAY_MAGIC = 0xd4a59582U;
static constexpr uint32_t COUNT_MAGIC = 0x941bf7d1U;
static constexpr uint32_t INC_MAGIC = 0x23e305abU;

public:
std::variant<metricValue, metricValuesArray, metricCount, metricInc> value;

void store(tl::storer& tls) const noexcept {
if (std::holds_alternative<metricValue>(value)) {
tl::magic{.value = AnyMetricValue::VALUE_MAGIC}.store(tls);
} else if (std::holds_alternative<metricValuesArray>(value)) {
tl::magic{.value = AnyMetricValue::VALUES_ARRAY_MAGIC}.store(tls);
} else if (std::holds_alternative<metricCount>(value)) {
tl::magic{.value = AnyMetricValue::COUNT_MAGIC}.store(tls);
} else {
tl::magic{.value = AnyMetricValue::INC_MAGIC}.store(tls);
}

std::visit([&tls](const auto& v) noexcept { v.store(tls); }, value);
}

constexpr size_t footprint() const noexcept {
return tl::magic{}.footprint() + std::visit([](const auto& v) noexcept { return v.footprint(); }, value);
}
};

template<std::ranges::range TagRange, typename ValueRange = void>
requires std::same_as<std::remove_cvref_t<std::ranges::range_value_t<TagRange>>, tl::pair<tl::string, tl::string>>
struct metric final {
tl::u64 timestamp{};
tl::AnyMetricValue value{};
tl::string metric_name{};
TagRange tags{};

void store(tl::storer& tls) const noexcept
requires tl::serializable<std::ranges::range_value_t<TagRange>>
{
timestamp.store(tls);
value.store(tls);
metric_name.store(tls);

tl::u32{.value = static_cast<uint32_t>(std::ranges::distance(tags))}.store(tls);
std::ranges::for_each(tags, [&tls](const auto& elem) noexcept { elem.store(tls); });
}

constexpr size_t footprint() const noexcept
requires tl::footprintable<std::ranges::range_value_t<TagRange>>
{
return timestamp.footprint() + value.footprint() + metric_name.footprint() +
std::ranges::fold_left(tags, tl::u32{.value = static_cast<uint32_t>(std::ranges::distance(tags))}.footprint(),
[](size_t acc, const auto& elem) noexcept { return acc + elem.footprint(); });
}
};
} // namespace tl

// ---------------------------------------------------------------------------------------------------------

namespace kphp::diagnostics {
template<typename T>
concept tag_range = std::ranges::range<T> && std::is_constructible_v<std::pair<std::string_view, std::string_view>, std::ranges::range_value_t<T>>;

struct metric final {
private:
tl::storer tls;

metric() noexcept
: tls{0} {}

explicit metric(tl::storer&& tls) noexcept
: tls{std::move(tls)} {}

static uint64_t ns_timestamp_now() noexcept {
k2::SystemTime st{};
k2::system_time(std::addressof(st));
return st.since_epoch_ns;
}

std::expected<void, int32_t> send() const noexcept {
return k2::write_metrics(this->tls.view());
}

// clears buffer and returns it with preserved capacity for reuse by metric::with_buffer()
std::pair<tl::storer, std::expected<void, int32_t>> send() && noexcept {
std::expected<void, int32_t> send_result{k2::write_metrics(this->tls.view())};
this->tls.clear();
return std::pair{std::move(this->tls), std::move(send_result)};
}

template<typename Self, tag_range TagRange>
decltype(auto) build_and_send(this Self&& self, std::string_view metric_name, TagRange&& tags, tl::AnyMetricValue value,
std::optional<uint64_t> timestamp) noexcept {
self.tls.clear();

uint64_t ns_timestamp{timestamp.value_or(metric::ns_timestamp_now())};
tl::metric serialized{.timestamp = tl::u64{ns_timestamp},
.value = value,
.metric_name = tl::string{metric_name},
.tags = std::forward<TagRange>(tags) | std::views::transform([](const auto& elem) noexcept -> tl::pair<tl::string, tl::string> {
std::pair<std::string_view, std::string_view> sv_pair{elem};
return tl::pair{std::pair{tl::string{sv_pair.first}, tl::string{sv_pair.second}}};
})};

self.tls.reserve(serialized.footprint());
serialized.store(self.tls);

return std::forward<Self>(self).send();
}

public:
static metric empty() noexcept {
return metric{};
}

static metric with_buffer(tl::storer&& tls) noexcept {
return metric{std::move(tls)};
}

template<typename Self, tag_range TagRange>
decltype(auto) send_value(this Self&& self, std::string_view metric_name, TagRange&& tags, double value,
std::optional<uint64_t> timestamp = std::nullopt) noexcept {
return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricValue{tl::f64{value}}}, timestamp);
}

template<typename Self, tag_range TagRange>
decltype(auto) send_values_array(this Self&& self, std::string_view metric_name, TagRange&& tags, std::span<const double> values,
std::optional<uint64_t> timestamp = std::nullopt) noexcept {
return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricValuesArray{values}}, timestamp);
}

template<typename Self, tag_range TagRange>
decltype(auto) send_count(this Self&& self, std::string_view metric_name, TagRange&& tags, uint32_t count,
std::optional<uint64_t> timestamp = std::nullopt) noexcept {
return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricCount{tl::u32{count}}}, timestamp);
}

template<typename Self, tag_range TagRange>
decltype(auto) send_increment(this Self&& self, std::string_view metric_name, TagRange&& tags, std::optional<uint64_t> timestamp = std::nullopt) noexcept {
return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricInc{}}, timestamp);
}
};

// ---------------------------------------------------------------------------------------------------------

struct metric_builder final {
private:
kphp::stl::string<kphp::memory::script_allocator> metric_name;
kphp::stl::vector<std::pair<kphp::stl::string<kphp::memory::script_allocator>, kphp::stl::string<kphp::memory::script_allocator>>,
kphp::memory::script_allocator>
tags;

explicit metric_builder(std::string_view metric_name) noexcept
: metric_name{metric_name} {}

public:
static metric_builder metric(std::string_view metric_name) noexcept {
return metric_builder{metric_name};
}

metric_builder& tag(std::string_view tag_name, std::string_view tag_value) noexcept {
this->tags.emplace_back(tag_name, tag_value);
return *this;
}

auto send_value(double value, std::optional<uint64_t> timestamp = std::nullopt) const noexcept {
return metric::empty().send_value(this->metric_name, this->tags, value, timestamp);
}

auto send_values_array(std::span<const double> values, std::optional<uint64_t> timestamp = std::nullopt) const noexcept {
return metric::empty().send_values_array(this->metric_name, this->tags, values, timestamp);
}

auto send_count(uint32_t count, std::optional<uint64_t> timestamp = std::nullopt) const noexcept {
return metric::empty().send_count(this->metric_name, this->tags, count, timestamp);
}

auto send_increment(std::optional<uint64_t> timestamp = std::nullopt) const noexcept {
return metric::empty().send_increment(this->metric_name, this->tags, timestamp);
}
};
} // namespace kphp::diagnostics
24 changes: 24 additions & 0 deletions runtime-light/tl/tl-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,30 @@ struct vector final {
}
};

template<typename F, typename S>
struct pair final {
std::pair<F, S> value;

bool fetch(tl::fetcher& tlf) noexcept
requires tl::deserializable<F> && tl::deserializable<S>
{
return value.first.fetch(tlf) && value.second.fetch(tlf);
}

void store(tl::storer& tls) const noexcept
requires tl::serializable<F> && tl::serializable<S>
{
value.first.store(tls);
value.second.store(tls);
}

constexpr size_t footprint() const noexcept
requires tl::footprintable<F> && tl::footprintable<S>
{
return value.first.footprint() + value.second.footprint();
}
};

template<typename T>
struct Vector final {
tl::vector<T> inner{};
Expand Down
Loading