#include "manager.h"

#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/io/iobase.h>

#include <library/cpp/json/json_writer.h>
#include <library/cpp/http/misc/httpcodes.h>
#include <util/folder/dirut.h>


namespace NSrvKernel {

    namespace {
        TString UnistatSigopt(TWorkerAggregation aggr, bool gauge) noexcept {
            switch (aggr) {
                case TWorkerAggregation::Sum:
                    if (gauge) {
                        return "ammv";
                    } else {
                        return "summ";
                    }

                case TWorkerAggregation::Max:
                    if (gauge) {
                        return "axxx";
                    } else {
                        return "dxxx";
                    }

                case TWorkerAggregation::Min:
                    if (gauge) {
                        return "annn";
                    } else {
                        return "dnnn";
                    }

                case TWorkerAggregation::Avg:
                    if (gauge) {
                        return "ammv";
                    } else {
                        return "dmmv";
                    }

                default:
                    Y_VERIFY(false);
            }
        }

        TString FullUnistatName(const NMonitoring::TLabels& labels, const TString& sigopt) {
            TStringBuilder name;
            for (const auto& label : labels) {
                if (label.Name() != "sensor") {
                    name << label.Name() << "=" << label.Value() << ";";
                }
            }
            name << labels.Find("sensor")->Value();
            if (!sigopt.empty()) {
                name << "_" << sigopt;
            }
            return name;
        }

        TString WriteLabels(const NMonitoring::TLabels& labels) {
            TStringBuilder result;

            if (auto name = labels.Find("sensor")) {
                result << name->Value();
            }

            result << "{";
            bool isFirstLabel = true;
            for (const auto& label : labels) {
                if (label.Name() == "sensor") {
                    continue;
                }

                if (!isFirstLabel) {
                    result << ", ";
                } else {
                    isFirstLabel = false;
                }

                result << label.Name() << ": " << label.Value();
            }
            result << "}";

            return result;
        }

        TString FullUnistatNameSorted(const NMonitoring::TLabels& labels, const TString& sigopt) {
            TVector<TString> names;
            TStringBuilder name;
            for (const auto& label : labels) {
                if (label.Name() != "sensor") {
                    names.push_back(TStringBuilder() << label.Name() << "=" << label.Value() << ";");
                }
            }
            names.push_back(TStringBuilder() << labels.Find("sensor")->Value() << "_" << sigopt);
            Sort(names.begin(), names.end());
            for (auto n : names) {
                name << n;
            }
            return name;
        }

        TVector<TCounter> BuildCounters(const THashMap<NMonitoring::TLabels, TSharedCounterHolder>& counters) {
            TVector<TCounter> result;
            result.reserve(counters.size());
            for (const auto& entry : counters) {
                result.push_back({
                    FullUnistatName(entry.first, /*sigopt =*/ ""),
                    entry.second.AggregateValues(),
                    entry.second.Opts().Sigopt,
                    entry.second.Opts().MetricType,
                    entry.first
                });
            }
            return result;
        }

        TVector<std::pair<double, ui64>> BuildHistogram(const TSharedHistogramHolder& histogram) {
            auto aggregatedHistogram = histogram.AggregateValues();
            TVector<std::pair<double, ui64>> values;
            values.reserve(aggregatedHistogram.size());
            const auto& intervals = histogram.Intervals();
            for (size_t i = 0; i < aggregatedHistogram.size(); i++) {
                values.emplace_back(intervals[i] / histogram.Opts().Scale, aggregatedHistogram[i]);
            }
            return values;
        }

        TVector<THistogram> BuildHistograms(const THashMap<NMonitoring::TLabels, TSharedHistogramHolder>& histograms) {
            TVector<THistogram> result;
            result.reserve(histograms.size());
            for (const auto& entry : histograms) {
                result.push_back({
                    FullUnistatName(entry.first, /*sigopt =*/ ""),
                    BuildHistogram(entry.second),
                    entry.second.Opts().Sigopt,
                    entry.second.Opts().MetricType,
                    entry.first
                });
            }
            return result;
        }

        void WriteUnistatResponse(IOutputStream* out, const TBalancerStats& stats) {
            /* Unistat structure
             * [
             *  ["counter_summ", 0],
             *  ["counter_hgram",[[0,0],[1,1],[2,2]]
             * ]
             */
            Y_ASSERT(out);

            NJson::TJsonWriter writer(out, /* formatOutput = */ false);
            // Main array
            writer.OpenArray();

            for (const auto& counter : stats.Counters) {
                writer.OpenArray();
                writer.Write(counter.Name + "_" + counter.Sigopt);
                writer.Write(counter.Value);
                writer.CloseArray();
            }

            for (const auto& histogram : stats.Histograms) {
                writer.OpenArray();

                writer.Write(histogram.Name + "_" + histogram.Sigopt);

                writer.OpenArray();
                for (const auto& [key, value] : histogram.Values) {
                    writer.OpenArray();
                    writer.Write(key);
                    writer.Write(value);
                    writer.CloseArray();
                }
                writer.CloseArray();
                writer.CloseArray();
            }

            // End main array
            writer.CloseArray();
        }

        void WriteSolomonResponse(IOutputStream* out, const TBalancerStats& stats)
        {
            Y_ASSERT(out);

            auto encoder = NMonitoring::EncoderJson(out);

            encoder->OnStreamBegin();

            EncodeSolomonResponse(encoder, stats);

            encoder->OnStreamEnd();
        }
    }  // namespace

    TAtomic* TSharedAtomicsDispenser::AllocateAtomic(size_t child) {
        constexpr size_t AtomicsPerCacheLine = 64 / sizeof(TAtomic);

        if (Atomics_[child].empty()) {
            TAtomic* atomics = Allocator_.AllocateAlignedRawArray<TAtomic, 64>(AtomicsPerCacheLine);
            for (size_t i = 0; i < AtomicsPerCacheLine; i++) {
                AtomicSet(atomics[i], 0);
                Atomics_[child].push_back(&atomics[i]);
            }
        }

        TAtomic* atomic = Atomics_[child].back();
        Atomics_[child].pop_back();
        return atomic;
    }

    TSharedCounterHolder::TSharedCounterHolder(
            TSharedAtomicsDispenser& dispenser, size_t workerCount, size_t extraWorkersCount,
            TSharedCounterOpts opts) noexcept
            : Opts_(std::move(opts))
            , WorkerCount_(workerCount)
    {
        size_t atomicCount = workerCount + extraWorkersCount + 1;
        for (size_t i = 0; i < atomicCount; i++) {
            TAtomic* atomic = dispenser.AllocateAtomic(i);
            AtomicSet(*atomic, opts.DefaultValue);
            Atomics_.push_back(atomic);
        }
        PointerToAtomic_ = Atomics_[0];
    }

    TAtomic* TSharedCounterHolder::GetAtomicForCounter(TMaybe<size_t> workerId) noexcept {
        if (workerId.Defined()) {
            Y_VERIFY(0 <= *workerId && *workerId < Atomics_.size());
            return Atomics_[*workerId];
        } else {
            // We assume this is master who gets counter with no workerId specified.
            return Atomics_[0];
        }
    }

    void TSharedCounterHolder::SetAtomicInSharedCounter(TSharedCounter& counter, TMaybe<size_t> workerId) noexcept {
        counter.SetAtomic(GetAtomicForCounter(workerId));
    }

    ui64 TSharedCounterHolder::AggregateValues() const noexcept {
        ui64 result;

        switch (Opts_.Aggregation) {
            case TWorkerAggregation::Sum:
                result = AggregateSum();
                break;

            case TWorkerAggregation::Max:
                result = AggregateMax();
                break;

            case TWorkerAggregation::Min:
                result = AggregateMin();
                break;

            case TWorkerAggregation::Avg:
                result = AggregateAvg();
                break;
        }

        if (Opts_.PostProcess.Defined()) {
            result = (*Opts_.PostProcess)(result);
        }

        return result;
    }

    ui64 TSharedCounterHolder::AggregateSum() const noexcept {
        const size_t startFrom = Opts_.AggregateMaster ? 0 : 1;

        ui64 sum = 0;
        for (size_t i = startFrom; i < Atomics_.size(); i++) {
            sum += AtomicGet(*Atomics_[i]);
        }
        return sum;
    }

    ui64 TSharedCounterHolder::AggregateMin() const noexcept {
        const size_t startFrom = Opts_.AggregateMaster ? 0 : 1;

        if (startFrom >= Atomics_.size()) {
            return Opts_.DefaultValue;
        }

        ui64 min = AtomicGet(*Atomics_[startFrom]);
        for (size_t i = startFrom + 1; i < Atomics_.size(); i++) {
            ui64 elem = AtomicGet(*Atomics_[i]);
            if (min > elem) {
                min = elem;
            }
        }
        return min;
    }

    ui64 TSharedCounterHolder::AggregateMax() const noexcept {
        const size_t startFrom = Opts_.AggregateMaster ? 0 : 1;

        if (startFrom >= Atomics_.size()) {
            return Opts_.DefaultValue;
        }

        ui64 max = AtomicGet(*Atomics_[startFrom]);
        for (size_t i = startFrom + 1; i < Atomics_.size(); i++) {
            ui64 elem = AtomicGet(*Atomics_[i]);
            if (max < elem) {
                max = elem;
            }
        }
        return max;
    }

    ui64 TSharedCounterHolder::AggregateAvg() const noexcept {
        const size_t startFrom = Opts_.AggregateMaster ? 0 : 1;
        ui64 sum = 0;
        for (size_t i = startFrom; i < Atomics_.size(); i++) {
            sum += AtomicGet(*Atomics_[i]);
        }
        return sum / WorkerCount_;
    }

    TSharedCounter::TSharedCounter(TAtomic* atomic)
        : Atomic_(atomic)
    {}

    TSharedCounter::TSharedCounter(TSharedCounterHolder& counterHolder, TMaybe<size_t> workerId)
        : CounterHolder_(&counterHolder)
    {
        CounterHolder_->SetAtomicInSharedCounter(*this, workerId);
    }

    TSharedCounter::TSharedCounter(const TSharedCounter& counter, size_t workerId)
        : CounterHolder_(counter.CounterHolder_)
    {
        CounterHolder_->SetAtomicInSharedCounter(*this, workerId);
    }

    void TSharedCounter::Set(ui64 x) noexcept {
        Y_VERIFY(Atomic_ || CounterHolder_, "Shared counter is not initialized yet");
        if (Atomic_) {
            AtomicSet(*Atomic_, x);
        } else {
            AtomicSet(*CounterHolder_->PointerToAtomic(), x);
        }
    }

    void TSharedCounter::Add(ui64 x) noexcept {
        Y_VERIFY(Atomic_ || CounterHolder_, "Shared counter is not initialized yet");
        if (!x) {
            return;
        }
        if (Atomic_) {
            AtomicAdd(*Atomic_, x);
        } else {
            AtomicAdd(*CounterHolder_->PointerToAtomic(), x);
        }
    }

    void TSharedCounter::Sub(ui64 x) noexcept {
        Y_VERIFY(Atomic_ || CounterHolder_, "Shared counter is not initialized yet");
        if (!x) {
            return;
        }
        if (Atomic_) {
            AtomicSub(*Atomic_, x);
        } else {
            AtomicSub(*CounterHolder_->PointerToAtomic(), x);
        }
    }

    ui64 TSharedCounter::Value() const noexcept {
        if (Atomic_) {
            return AtomicGet(*Atomic_);
        } else {
            return AtomicGet(*CounterHolder_->PointerToAtomic());
        }
    }

    TCombinedCounter::TCombinedCounter(const TCombinedCounter& other, size_t workerId) {
        if (other.Counter_) {
            Counter_ = TSharedCounter(*other.Counter_, workerId);
        }
    }

    TSharedCounterBuilder::TSharedCounterBuilder(TSharedStatsManager& manager, TString name, bool gauge)
        : Name_(name)
        , StatsManager_(manager)
        , IsGauge_(gauge)
    {}

    TSharedCounter TSharedCounterBuilder::Build() {
        TWorkerAggregation aggregation = Aggregation_.GetOrElse(TWorkerAggregation::Sum);

        if (!SolomonLabels_.Defined()) {
            SolomonLabels_.ConstructInPlace();
        }

        if (!AllowDuplicate_.Defined()) {
            AllowDuplicate_ = false;
        }

        TSharedCounterOpts counterOpts{
            aggregation,
            std::move(PostProcess_),
            MetricType_.GetOrElse(
                IsGauge_ ? NMonitoring::EMetricType::IGAUGE : NMonitoring::EMetricType::RATE
            ),
            Sigopt_.GetOrElse(UnistatSigopt(aggregation, IsGauge_)),
            IsGauge_,
            DefaultValue_.GetOrElse(0),
            AggregateMaster_.GetOrElse(true),
        };

        return StatsManager_.MakeIntSignal(
                std::move(Name_),
                std::move(*SolomonLabels_),
                *AllowDuplicate_,
                std::move(counterOpts)
        );
    }

    TSharedCounterBuilder& TSharedCounterBuilder::Aggregation(TWorkerAggregation aggregation) {
        Y_VERIFY(!Aggregation_.Defined(), "Aggregation type is already defined for counter");
        Aggregation_ = aggregation;
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::AllowDuplicate(bool value) {
        Y_VERIFY(!AllowDuplicate_.Defined(), "Allow duplicate parameter is already set for counter");
        AllowDuplicate_ = value;
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::Default(ui64 value) {
        Y_VERIFY(!DefaultValue_.Defined(), "Default value is already set for counter");
        DefaultValue_ = value;
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::Labels(NMonitoring::TLabels labels) {
        Y_VERIFY(!SolomonLabels_.Defined(), "Solomon labels have been already set for counter");
        SolomonLabels_ = std::move(labels);
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::PostProcess(std::function<ui64(ui64)> f) {
        Y_VERIFY(!PostProcess_.Defined(), "PostProcessing function is already set for counter");
        PostProcess_ = std::move(f);
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::Sigopt(TString sigopt) {
        Y_VERIFY(!Sigopt_.Defined(), "Sigopt is already set for counter");
        Sigopt_ = std::move(sigopt);
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::Kind(NMonitoring::EMetricType kind) {
        Y_VERIFY(!MetricType_.Defined(), "Metric type is already set for counter");
        MetricType_ = kind;
        return *this;
    }

    TSharedCounterBuilder& TSharedCounterBuilder::AggregateMaster(bool value) {
        Y_VERIFY(!AggregateMaster_.Defined(), "AggregateMaster flag is already set for counter");
        AggregateMaster_ = value;
        return *this;
    }

    TSharedHistogramHolder::TSharedHistogramHolder(TSharedAtomicsDispenser& dispenser, size_t workersCount,
                                                   TVector<ui64> intervals, TSharedHistogramOpts opts)
            : Intervals_(std::move(intervals))
            , Opts_(std::move(opts))
    {
        Y_VERIFY(Opts_.Scale >= 1);

        Atomics_.reserve(workersCount);

        size_t intervalsCount = Intervals_.size();
        for (size_t i = 0; i < workersCount; i++) {
            Atomics_.emplace_back(intervalsCount);
            for (size_t j = 0; j < intervalsCount; j++) {
                Atomics_.back()[j] = dispenser.AllocateAtomic(i);
            }
        }
        PointerToAtomics_ = Atomics_[0];
    }

    TVector<TAtomic*> TSharedHistogramHolder::GetAtomicsForHistogram(TMaybe<size_t> workerId) noexcept {
        if (!workerId.Defined()) {
            // We assume this is master who gets counter with no workerId specified.
            return Atomics_[0];
        } else {
            Y_VERIFY(0 <= *workerId && *workerId < Atomics_.size());
            return Atomics_[*workerId];
        }
    }

    void TSharedHistogramHolder::SetAtomicsInSharedHistogram(TSharedHistogram& histogram, TMaybe<size_t> workerId) {
        histogram.SetHistogram(GetAtomicsForHistogram(workerId));
    }

    TVector<ui64> TSharedHistogramHolder::AggregateValues() const noexcept {
        Y_ASSERT(!Atomics_.empty());

        TVector<ui64> result(Intervals_.size());
        for (const TVector<TAtomic*>& workerHistogram : Atomics_) {
            Y_ASSERT(workerHistogram.size() == result.size());
            for (size_t i = 0; i < result.size(); i++) {
                result[i] += AtomicGet(*workerHistogram[i]);
            }
        }
        return result;
    }

    TSharedHistogram::TSharedHistogram(TSharedHistogramHolder& histogramHolder, TMaybe<size_t> workerId)
        : HistogramHolder_(&histogramHolder)
    {
        // https://wiki.yandex-team.ru/golovan/stat-handle/
        if (HistogramHolder_->Intervals().size() > 50) {
            ythrow NConfig::TConfigParseError{} << "at most 50 buckets allowed for histogram signal";
        }

        if (HistogramHolder_->Intervals().empty()) {
            ythrow NConfig::TConfigParseError{} << "empty interval is not allowed";
        }

        HistogramHolder_->SetAtomicsInSharedHistogram(*this, workerId);
    }

    TSharedHistogram::TSharedHistogram(const TSharedHistogram& histogram, size_t workerId)
        : HistogramHolder_(histogram.HistogramHolder_)
    {
        HistogramHolder_->SetAtomicsInSharedHistogram(*this, workerId);
    }

    void TSharedHistogram::AddValue(ui64 value) noexcept {
        Y_ASSERT(!HistogramHolder_->Intervals().empty());

        for (size_t i = HistogramHolder_->Intervals().size(); i > 0; i--) {
            if (value >= HistogramHolder_->Intervals()[i - 1]) {
                if (i - 1 < Histogram_.size() && Histogram_[i - 1]) {
                    AtomicAdd(*Histogram_[i - 1], 1);
                } else {
                    auto& pointerToAtomics = HistogramHolder_->PointerToAtomics();
                    Y_ASSERT(i - 1 < pointerToAtomics.size() && pointerToAtomics[i - 1]);
                    AtomicAdd(*pointerToAtomics[i - 1], 1);
                }
                break;
            }
        }
    }

    TSharedHistogramBuilder::TSharedHistogramBuilder(TSharedStatsManager& manager, TString name, TVector<ui64> intervals)
        : Name_(name)
        , StatsManager_(manager)
        , Intervals_(std::move(intervals))
    {}

    TSharedHistogram TSharedHistogramBuilder::Build() {
        if (!SolomonLabels_.Defined()) {
            SolomonLabels_.ConstructInPlace();
        }

        if (!AllowDuplicate_.Defined()) {
            AllowDuplicate_ = false;
        }

        TSharedHistogramOpts histogramOpts{
            MetricType_.GetOrElse(
                NMonitoring::EMetricType::HIST_RATE
            ),
            Sigopt_.GetOrElse("hgram"),
            Scale_.GetOrElse(1),
        };

        return StatsManager_.MakeHistogramImpl(
            std::move(Name_),
            std::move(*SolomonLabels_),
            *AllowDuplicate_,
            std::move(Intervals_),
            std::move(histogramOpts)
        );
    }

    void TSharedStatsManager::WriteResponseNoWait(TResponseType type, IOutputStream* out) {
        TBalancerStats stats{GetCounters(), GetHistograms()};
        // There is no way to add counters from Unified Agent logging
        // library to TSharedStatsManager counters. Here we dynamically adding
        // them from library. All counters are atomics.
        AppendUnifiedAgentCounters(stats);

        switch (type) {
            case TResponseType::Unistat:
                WriteUnistatResponse(out, stats);
                break;

            case TResponseType::Solomon:
                WriteSolomonResponse(out, stats);
                break;

            default:
                Y_UNREACHABLE();
        }
    }

    void TSharedStatsManager::WriteResponse(TCont* cont, TResponseType type, IOutputStream* out) noexcept {
        TChunksOutputStream contentStream;

        while (!cont->Cancelled()) {
            try {
                WriteResponseNoWait(type, out);
            } catch (TResourceBusyError&) {
                cont->SleepT(TDuration::MilliSeconds(100));
                continue;
            }

            break;
        }
    }

    void TSharedStatsManager::CheckState() {
        if (SharedAllocator_.Frozen()) {
            ythrow NConfig::TConfigParseError{} << "shared allocator has been frozen (trying to register signal in worker?)";
        }

        if (!WorkersCount_.Defined() || !ExtraWorkersCount_.Defined()) {
            ythrow NConfig::TConfigParseError{} << "shared statistics manager has not been initialized yet";
        }
    }

    void TSharedStatsManager::DumpSignals() const {
        TGuard<TMutex> guard(Mutex_);
        for (const auto& entry : Counters_) {
            Cout << FullUnistatNameSorted(entry.first, entry.second.Opts().Sigopt) << Endl;
        }

        for (const auto& entry : Histograms_) {
            Cout << FullUnistatNameSorted(entry.first, entry.second.Opts().Sigopt) << Endl;
        }
    }

    TMaybe<TSharedCounter> TSharedStatsManager::GetExistingCounter(TString signalName, NMonitoring::TLabels labels, TMaybe<size_t> workerId) {
        labels.Add("sensor", std::move(signalName));

        TGuard<TMutex> guard(Mutex_);
        auto* existingHolder = MapFindPtr(Counters_, labels);
        if (existingHolder) {
            return TSharedCounter(*existingHolder, workerId);
        } else {
            return Nothing();
        }
    }

    TBalancerStats TSharedStatsManager::GetBalancerStats() const {
        return TBalancerStats{GetCounters(), GetHistograms()};
    }

    TSharedCounter TSharedStatsManager::MakeIntSignal(TString signalName,
        NMonitoring::TLabels labels, bool allowDuplicate, TSharedCounterOpts opts) {
        labels.Add("sensor", std::move(signalName));

        TGuard<TMutex> guard(Mutex_);
        auto* existingHolder = MapFindPtr(Counters_, labels);
        if (existingHolder) {
            if (!allowDuplicate) {
                ythrow NConfig::TConfigParseError{} << "trying to register duplicate counter: " << WriteLabels(labels);
            }
            return TSharedCounter(*existingHolder);
        }

        CheckState();

        TSharedCounterHolder holder(
            AtomicsDispenser_,
            *WorkersCount_,
            *ExtraWorkersCount_,
            std::move(opts)
        );
        return TSharedCounter(Counters_.emplace(std::move(labels), std::move(holder)).first->second);
    }

    TSharedHistogram TSharedStatsManager::MakeHistogramImpl(TString signalName, NMonitoring::TLabels labels,
        bool allowDuplicate, TVector<ui64> intervals, TSharedHistogramOpts opts) {

        labels.Add("sensor", std::move(signalName));

        for (size_t i = 0; i + 1 < intervals.size(); ++i) {
            if (intervals[i] >= intervals[i + 1]) {
                ythrow NConfig::TConfigParseError{} << "histogram bucket bounds must ascend: " << WriteLabels(labels);
            }
        }

        TGuard<TMutex> guard(Mutex_);
        auto* existingHolder = MapFindPtr(Histograms_, labels);
        if (existingHolder) {
            if (!allowDuplicate) {
                ythrow NConfig::TConfigParseError{} << "trying to register duplicate histogram: " << WriteLabels(labels);
            }

            if (intervals != existingHolder->Intervals()) {
                ythrow NConfig::TConfigParseError{}
                    << "trying to register histogram with same name but different ranges: " << WriteLabels(labels);
            }

            return TSharedHistogram(*existingHolder);
        }

        CheckState();

        TSharedHistogramHolder holder{
            AtomicsDispenser_,
            *WorkersCount_ + *ExtraWorkersCount_ + 1,
            std::move(intervals),
            std::move(opts)
        };
        return TSharedHistogram(Histograms_.emplace(std::move(labels), std::move(holder)).first->second);
    }

    TVector<TCounter> TSharedStatsManager::GetCounters() const {
        TGuard<TMutex> guard(Mutex_);
        return BuildCounters(Counters_);
    }

    TVector<THistogram> TSharedStatsManager::GetHistograms() const {
        TGuard<TMutex> guard(Mutex_);
        return BuildHistograms(Histograms_);
    }

    void TSharedStatsManager::AppendUnifiedAgentCounters(TBalancerStats& stats) {
        for (const auto& [logname, counters] : UnifiedAgentLogsCounters_) {
            // Maybe someday this will prevent from crashing
            if (!counters) {
                continue;
            }

            auto& preparedCounters = UnifiedAgentStatsCounters_[logname];
            if (Y_LIKELY(preparedCounters)) {
                // Only update values. Counters/Sensors initialization goes below
                auto changeCounter = [&](TStringBuf counterName, int64_t value) {
                    auto& item = preparedCounters[counterName];
                    item.Value = value;
                    stats.Counters.push_back(item);
                };

                changeCounter("errors", static_cast<ui64>(counters->GetDefaultSessionCounters()->ErrorsCount.Val()));
                changeCounter("dropped", static_cast<ui64>(counters->GetDefaultSessionCounters()->DroppedBytes.Val()));
                changeCounter("inflight", static_cast<ui64>(counters->GetDefaultSessionCounters()->InflightBytes.Val()));
                changeCounter("ack", static_cast<ui64>(counters->GetDefaultSessionCounters()->AcknowledgedBytes.Val()));
            } else {
                // drop prefix "unified_agent:unix:///"
                auto filename = GetBaseName(logname);

                auto addCounter = [&](TStringBuf filename, TStringBuf counterName, int64_t value) {
                    TStringStream unistatName;
                    unistatName << "unified_agent_" << filename << "_" << counterName;
                    TStringStream solomonName;
                    solomonName << "unified-agent-" << filename << "-" << counterName;

                    NMonitoring::TLabels labels;
                    labels.Add("sensor", solomonName.Str());

                    auto& item = preparedCounters[counterName];
                    item.Name = unistatName.Str();
                    item.Value = value;
                    item.Sigopt = "summ";
                    item.MetricType = NMonitoring::EMetricType::RATE;
                    item.Labels = std::move(labels);

                    stats.Counters.push_back(item);
                };

                // Prepare TCounter objects only on first request
                // On all other requests we will change only values
                addCounter(filename, "errors", static_cast<ui64>(counters->GetDefaultSessionCounters()->ErrorsCount.Val()));
                addCounter(filename, "dropped", static_cast<ui64>(counters->GetDefaultSessionCounters()->DroppedBytes.Val()));
                addCounter(filename, "inflight", static_cast<ui64>(counters->GetDefaultSessionCounters()->InflightBytes.Val()));
                addCounter(filename, "ack", static_cast<ui64>(counters->GetDefaultSessionCounters()->AcknowledgedBytes.Val()));
            }
        }
    }

    const TIntrusivePtr<NUnifiedAgent::TClientCounters>& TSharedStatsManager::GetUnifiedAgentCounters(const TStringBuf name) {
        auto& counter = UnifiedAgentLogsCounters_[name];
        if (!counter) {
            counter = MakeIntrusive<NUnifiedAgent::TClientCounters>();
        }
        return counter;
    }
}  // namespace NSrvKernel

