Ctrl K
ComunidadGuías…

stream_metrics.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
#pragma once
#include "room_state_directory.h"
#include "stream_pacing.h"
#include "room_status.h"
#include "player_name.h"
#include "start_lead.h"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <functional>
#include <mutex>
#include <string>
#include <unistd.h>
namespace bridge {
inline uint64_t streamMonoMs() {
    return std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::steady_clock::now().time_since_epoch()).count();
}
inline bool streamPathActive() {
    const auto *room = roomMode();
    if (!room) return true;
    const auto mode = room->status();
    return mode.active != "queue" || mode.path == "stream";
}
struct StreamMeasurement { double encodedMs = 0, audibleMs = 0; bool valid = false; uint64_t connection = 0; bool locked = false; };
// One room per process. Totals survive reconnects and stream changes; a pid
// identifies restarts. No measurement influences admission or transport.
class StreamMetrics {
    std::mutex mutex;
    std::string roomHex, path;
    std::function<StreamMeasurement()> provider;
    uint64_t lastFile = 0, lastLog = 0;
public:
    std::atomic<uint64_t> overwrites{0}, dropped{0}, socketBlockedUs{0}, socketBlockedCalls{0};
    static std::string hex(const std::string &s) {
        std::string out;
        for (unsigned char c : s) { char b[3]; snprintf(b, sizeof b, "%02x", c); out += b; }
        return out;
    }
    void configure(const std::string &room, const std::string &directory = bridge::roomStateDirectory()) {
        std::lock_guard<std::mutex> lock(mutex);
        roomHex = hex(room);
        path = directory + "/stream-pacing-" + roomHex + ".json";
    }
    void encoder(std::function<StreamMeasurement()> p) {
        std::lock_guard<std::mutex> lock(mutex); provider = std::move(p);
    }
    StreamMeasurement sample() {
        if (!streamPathActive()) return {};
        std::function<StreamMeasurement()> p;
        { std::lock_guard<std::mutex> lock(mutex); p = provider; }
        return p ? p() : StreamMeasurement{};
    }
    void socketWait(uint64_t us) {
        // A blocking send's residence time, excluding sub-millisecond syscall
        // overhead. Includes a timed-out send; does not change socket behavior.
        if (streamPathActive() && us >= 1000) { socketBlockedUs += us; ++socketBlockedCalls; }
    }
    void print(const StreamMeasurement &s) {
        if (!streamPathActive()) return;
        printf("Stream pacing: mode=%s start_lead_ms=%u overwrites=%llu dropped=%llu socket_backpressure_ms=%.3f encoded_ms=%.3f audible_ms=%.3f lead_ms=%.3f valid=%d\n",
            streamPacingSonos() ? "sonos" : "wall", yeney_start_lead_ms(),
            (unsigned long long)overwrites.load(), (unsigned long long)dropped.load(),
            socketBlockedUs.load()/1000., s.encodedMs, s.audibleMs, s.encodedMs-s.audibleMs, s.valid);
    }
    void publishPlayback() {
        { std::lock_guard<std::mutex> lock(mutex); lastFile=0; }
        tick();
    }
    bool tick(uint64_t now = streamMonoMs()) {
        { std::lock_guard<std::mutex> lock(mutex);
          if (roomHex.empty() || (now-lastFile < 1000 && now-lastLog < 60000)) return false; }
        auto s = sample();
        std::lock_guard<std::mutex> lock(mutex);
        if (roomHex.empty()) return false;
        if (now-lastFile >= 1000) {
            lastFile = now;
            // Rename a complete snapshot; readers never see a partial JSON file.
            auto temporary = path + "." + std::to_string(getpid()) + ".tmp";
            if (FILE *f = fopen(temporary.c_str(), "w")) {
                const auto identity = ",\"true_name\":" + RoomStatus::quote(playerName().trueName) + ",\"display_name\":" + RoomStatus::quote(playerName().displayName) + ",\"mac\":" + RoomStatus::quote(playerName().mac);
                const auto playback=identity + (roomMode()?",\"playback\":"+RoomStatus::json(roomMode()->status()):std::string{});
                fprintf(f, "{\"mode\":\"%s\",\"start_lead_ms\":%u,\"pid\":%d,\"monotonic_ms\":%llu,\"overwrites\":%llu,\"dropped\":%llu,\"socket_backpressure_ms\":%.3f,\"socket_backpressure_calls\":%llu,\"encoded_ms\":%.3f,\"audible_ms\":%.3f,\"lead_ms\":%.3f,\"valid\":%s,\"connection_id\":%llu,\"locked\":%s%s}\n",
                    streamPacingSonos() ? "sonos" : "wall", yeney_start_lead_ms(), getpid(), (unsigned long long)now, (unsigned long long)overwrites.load(),
                    (unsigned long long)dropped.load(), socketBlockedUs.load()/1000.,
                    (unsigned long long)socketBlockedCalls.load(), s.encodedMs, s.audibleMs,
                    s.encodedMs-s.audibleMs, s.valid ? "true" : "false",
                    (unsigned long long)s.connection, s.locked ? "true" : "false",playback.c_str());
                if (fclose(f) == 0) rename(temporary.c_str(), path.c_str());
                unlink(temporary.c_str());
            }
        }
        if (!streamPathActive() || now-lastLog < 60000 || (!overwrites && !dropped && !socketBlockedUs && (!s.valid || s.encodedMs == s.audibleMs))) return false;
        lastLog = now;
        printf("yeney: stream-pacing room_hex=%s reason=%s ", roomHex.c_str(), dropped ? "reader-stalled" : "sample"); print(s);
        return true;
    }
};
// Core may call exit() from its worker while HTTP/main threads and earlier
// atexit handlers still sample counters. Keep this bounded registry alive until
// process termination, independent of static destructor ordering.
inline StreamMetrics &streamMetrics() { static auto *metrics = new StreamMetrics; return *metrics; }
}