|
| 1 | +// Compiler for PHP (aka KPHP) |
| 2 | +// Copyright (c) 2026 LLC «V Kontakte» |
| 3 | +// Distributed under the GPL v3 License, see LICENSE.notice.txt |
| 4 | + |
| 5 | +#pragma once |
| 6 | + |
| 7 | +#include <cstddef> |
| 8 | +#include <cstdint> |
| 9 | +#include <expected> |
| 10 | +#include <memory> |
| 11 | +#include <optional> |
| 12 | +#include <ranges> |
| 13 | +#include <span> |
| 14 | +#include <string_view> |
| 15 | +#include <type_traits> |
| 16 | +#include <utility> |
| 17 | +#include <variant> |
| 18 | + |
| 19 | +#include "runtime-common/core/allocator/script-allocator.h" |
| 20 | +#include "runtime-common/core/std/containers.h" |
| 21 | +#include "runtime-light/k2-platform/k2-api.h" |
| 22 | +#include "runtime-light/tl/tl-core.h" |
| 23 | +#include "runtime-light/tl/tl-types.h" |
| 24 | + |
| 25 | +namespace tl { |
| 26 | +struct metricValue final { |
| 27 | + tl::f64 value{}; |
| 28 | + |
| 29 | + void store(tl::storer& tls) const noexcept { |
| 30 | + value.store(tls); |
| 31 | + } |
| 32 | + |
| 33 | + constexpr size_t footprint() const noexcept { |
| 34 | + return value.footprint(); |
| 35 | + } |
| 36 | +}; |
| 37 | + |
| 38 | +struct metricValuesArray final { |
| 39 | + std::span<const double> values; |
| 40 | + |
| 41 | + void store(tl::storer& tls) const noexcept { |
| 42 | + tl::u32{static_cast<uint32_t>(this->values.size())}.store(tls); |
| 43 | + std::ranges::for_each(this->values, [&tls](const double& elem) noexcept { tl::f64{elem}.store(tls); }); |
| 44 | + } |
| 45 | + |
| 46 | + constexpr size_t footprint() const noexcept { |
| 47 | + return std::ranges::fold_left(this->values, tl::u32{static_cast<uint32_t>(this->values.size())}.footprint(), |
| 48 | + [](size_t acc, const double& elem) noexcept { return acc + tl::f64{elem}.footprint(); }); |
| 49 | + } |
| 50 | +}; |
| 51 | + |
| 52 | +struct metricCount final { |
| 53 | + tl::u32 count{}; |
| 54 | + |
| 55 | + void store(tl::storer& tls) const noexcept { |
| 56 | + count.store(tls); |
| 57 | + } |
| 58 | + |
| 59 | + constexpr size_t footprint() const noexcept { |
| 60 | + return count.footprint(); |
| 61 | + } |
| 62 | +}; |
| 63 | + |
| 64 | +struct metricInc final { |
| 65 | + void store(tl::storer& /* tls */) const noexcept {} |
| 66 | + |
| 67 | + constexpr size_t footprint() const noexcept { |
| 68 | + return 0; |
| 69 | + } |
| 70 | +}; |
| 71 | + |
| 72 | +class AnyMetricValue final { |
| 73 | + static constexpr uint32_t VALUE_MAGIC = 0xcb5eb87U; |
| 74 | + static constexpr uint32_t VALUES_ARRAY_MAGIC = 0xd4a59582U; |
| 75 | + static constexpr uint32_t COUNT_MAGIC = 0x941bf7d1U; |
| 76 | + static constexpr uint32_t INC_MAGIC = 0x23e305abU; |
| 77 | + |
| 78 | +public: |
| 79 | + std::variant<metricValue, metricValuesArray, metricCount, metricInc> value; |
| 80 | + |
| 81 | + void store(tl::storer& tls) const noexcept { |
| 82 | + if (std::holds_alternative<metricValue>(value)) { |
| 83 | + tl::magic{.value = AnyMetricValue::VALUE_MAGIC}.store(tls); |
| 84 | + } else if (std::holds_alternative<metricValuesArray>(value)) { |
| 85 | + tl::magic{.value = AnyMetricValue::VALUES_ARRAY_MAGIC}.store(tls); |
| 86 | + } else if (std::holds_alternative<metricCount>(value)) { |
| 87 | + tl::magic{.value = AnyMetricValue::COUNT_MAGIC}.store(tls); |
| 88 | + } else { |
| 89 | + tl::magic{.value = AnyMetricValue::INC_MAGIC}.store(tls); |
| 90 | + } |
| 91 | + |
| 92 | + std::visit([&tls](const auto& v) noexcept { v.store(tls); }, value); |
| 93 | + } |
| 94 | + |
| 95 | + constexpr size_t footprint() const noexcept { |
| 96 | + return tl::magic{}.footprint() + std::visit([](const auto& v) noexcept { return v.footprint(); }, value); |
| 97 | + } |
| 98 | +}; |
| 99 | + |
| 100 | +template<std::ranges::range TagRange> |
| 101 | +requires std::same_as<std::remove_cvref_t<std::ranges::range_value_t<TagRange>>, tl::pair<tl::string, tl::string>> |
| 102 | +struct metric final { |
| 103 | + tl::u64 timestamp{}; |
| 104 | + tl::AnyMetricValue value{}; |
| 105 | + tl::string metric_name{}; |
| 106 | + TagRange tags{}; |
| 107 | + |
| 108 | + void store(tl::storer& tls) const noexcept |
| 109 | + requires tl::serializable<std::ranges::range_value_t<TagRange>> |
| 110 | + { |
| 111 | + timestamp.store(tls); |
| 112 | + metric_name.store(tls); |
| 113 | + value.store(tls); |
| 114 | + |
| 115 | + tl::u32{.value = static_cast<uint32_t>(std::ranges::distance(tags))}.store(tls); |
| 116 | + std::ranges::for_each(tags, [&tls](const auto& elem) noexcept { elem.store(tls); }); |
| 117 | + } |
| 118 | + |
| 119 | + constexpr size_t footprint() const noexcept |
| 120 | + requires tl::footprintable<std::ranges::range_value_t<TagRange>> |
| 121 | + { |
| 122 | + return timestamp.footprint() + value.footprint() + metric_name.footprint() + |
| 123 | + std::ranges::fold_left(tags, tl::u32{.value = static_cast<uint32_t>(std::ranges::distance(tags))}.footprint(), |
| 124 | + [](size_t acc, const auto& elem) noexcept { return acc + elem.footprint(); }); |
| 125 | + } |
| 126 | +}; |
| 127 | +} // namespace tl |
| 128 | + |
| 129 | +// --------------------------------------------------------------------------------------------------------- |
| 130 | + |
| 131 | +namespace kphp::diagnostics { |
| 132 | +template<typename T> |
| 133 | +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>>; |
| 134 | + |
| 135 | +struct metric final { |
| 136 | +private: |
| 137 | + tl::storer tls; |
| 138 | + |
| 139 | + metric() noexcept |
| 140 | + : tls{0} {} |
| 141 | + |
| 142 | + explicit metric(tl::storer&& tls) noexcept |
| 143 | + : tls{std::move(tls)} {} |
| 144 | + |
| 145 | + static uint64_t ns_timestamp_now() noexcept { |
| 146 | + k2::SystemTime st{}; |
| 147 | + k2::system_time(std::addressof(st)); |
| 148 | + return st.since_epoch_ns; |
| 149 | + } |
| 150 | + |
| 151 | + std::expected<void, int32_t> send() const noexcept { |
| 152 | + return k2::write_metrics(this->tls.view()); |
| 153 | + } |
| 154 | + |
| 155 | + // clears buffer and returns it with preserved capacity for reuse by metric::with_buffer() |
| 156 | + std::pair<tl::storer, std::expected<void, int32_t>> send() && noexcept { |
| 157 | + std::expected<void, int32_t> send_result{k2::write_metrics(this->tls.view())}; |
| 158 | + this->tls.clear(); |
| 159 | + return std::pair{std::move(this->tls), std::move(send_result)}; |
| 160 | + } |
| 161 | + |
| 162 | + template<typename Self, tag_range TagRange> |
| 163 | + decltype(auto) build_and_send(this Self&& self, std::string_view metric_name, TagRange&& tags, tl::AnyMetricValue value, |
| 164 | + std::optional<uint64_t> timestamp) noexcept { |
| 165 | + self.tls.clear(); |
| 166 | + |
| 167 | + uint64_t ns_timestamp{timestamp.value_or(metric::ns_timestamp_now())}; |
| 168 | + tl::metric serialized{.timestamp = tl::u64{ns_timestamp}, |
| 169 | + .value = value, |
| 170 | + .metric_name = tl::string{metric_name}, |
| 171 | + .tags = std::forward<TagRange>(tags) | std::views::transform([](const auto& elem) noexcept -> tl::pair<tl::string, tl::string> { |
| 172 | + std::pair<std::string_view, std::string_view> sv_pair{elem}; |
| 173 | + return tl::pair{std::pair{tl::string{sv_pair.first}, tl::string{sv_pair.second}}}; |
| 174 | + })}; |
| 175 | + |
| 176 | + self.tls.reserve(serialized.footprint()); |
| 177 | + serialized.store(self.tls); |
| 178 | + |
| 179 | + return std::forward<Self>(self).send(); |
| 180 | + } |
| 181 | + |
| 182 | +public: |
| 183 | + static metric empty() noexcept { |
| 184 | + return metric{}; |
| 185 | + } |
| 186 | + |
| 187 | + static metric with_buffer(tl::storer&& tls) noexcept { |
| 188 | + return metric{std::move(tls)}; |
| 189 | + } |
| 190 | + |
| 191 | + template<typename Self, tag_range TagRange> |
| 192 | + auto send_value(this Self&& self, std::string_view metric_name, TagRange&& tags, double value, std::optional<uint64_t> timestamp = std::nullopt) noexcept { |
| 193 | + return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricValue{tl::f64{value}}}, timestamp); |
| 194 | + } |
| 195 | + |
| 196 | + template<typename Self, tag_range TagRange> |
| 197 | + auto send_values_array(this Self&& self, std::string_view metric_name, TagRange&& tags, std::span<const double> values, |
| 198 | + std::optional<uint64_t> timestamp = std::nullopt) noexcept { |
| 199 | + return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricValuesArray{values}}, timestamp); |
| 200 | + } |
| 201 | + |
| 202 | + template<typename Self, tag_range TagRange> |
| 203 | + auto send_count(this Self&& self, std::string_view metric_name, TagRange&& tags, uint32_t count, std::optional<uint64_t> timestamp = std::nullopt) noexcept { |
| 204 | + return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricCount{tl::u32{count}}}, timestamp); |
| 205 | + } |
| 206 | + |
| 207 | + template<typename Self, tag_range TagRange> |
| 208 | + auto send_increment(this Self&& self, std::string_view metric_name, TagRange&& tags, std::optional<uint64_t> timestamp = std::nullopt) noexcept { |
| 209 | + return std::forward<Self>(self).build_and_send(metric_name, std::forward<TagRange>(tags), tl::AnyMetricValue{tl::metricInc{}}, timestamp); |
| 210 | + } |
| 211 | +}; |
| 212 | + |
| 213 | +// --------------------------------------------------------------------------------------------------------- |
| 214 | + |
| 215 | +struct metric_builder final { |
| 216 | +private: |
| 217 | + kphp::stl::string<kphp::memory::script_allocator> metric_name; |
| 218 | + kphp::stl::vector<std::pair<kphp::stl::string<kphp::memory::script_allocator>, kphp::stl::string<kphp::memory::script_allocator>>, |
| 219 | + kphp::memory::script_allocator> |
| 220 | + tags; |
| 221 | + |
| 222 | + explicit metric_builder(std::string_view metric_name) noexcept |
| 223 | + : metric_name{metric_name} {} |
| 224 | + |
| 225 | +public: |
| 226 | + static metric_builder metric(std::string_view metric_name) noexcept { |
| 227 | + return metric_builder{metric_name}; |
| 228 | + } |
| 229 | + |
| 230 | + metric_builder& tag(std::string_view tag_name, std::string_view tag_value) noexcept { |
| 231 | + this->tags.emplace_back(tag_name, tag_value); |
| 232 | + return *this; |
| 233 | + } |
| 234 | + |
| 235 | + auto send_value(double value, std::optional<uint64_t> timestamp = std::nullopt) const noexcept { |
| 236 | + return metric::empty().send_value(this->metric_name, this->tags, value, timestamp); |
| 237 | + } |
| 238 | + |
| 239 | + auto send_values_array(std::span<const double> values, std::optional<uint64_t> timestamp = std::nullopt) const noexcept { |
| 240 | + return metric::empty().send_values_array(this->metric_name, this->tags, values, timestamp); |
| 241 | + } |
| 242 | + |
| 243 | + auto send_count(uint32_t count, std::optional<uint64_t> timestamp = std::nullopt) const noexcept { |
| 244 | + return metric::empty().send_count(this->metric_name, this->tags, count, timestamp); |
| 245 | + } |
| 246 | + |
| 247 | + auto send_increment(std::optional<uint64_t> timestamp = std::nullopt) const noexcept { |
| 248 | + return metric::empty().send_increment(this->metric_name, this->tags, timestamp); |
| 249 | + } |
| 250 | +}; |
| 251 | +} // namespace kphp::diagnostics |
0 commit comments