Ctrl K
ComunidadGuías…

queue_report.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 "core_queue_sink.h"
#include <ostream>
#include <sstream>
namespace queue_audio {
struct ReadAheadVerdict {
    bool ok = true;
    size_t pairs = 0, incomplete = 0;
    double mismatchedSeconds = 0;
    std::string reasons;
    double worst = -1;
    std::string line() const { return ok ? "LMS read-ahead OK" : "LMS read-ahead INSUFFICIENT"; }
};
inline ReadAheadVerdict writeReadAheadReport(const std::vector<QueueRecord> &rows, std::ostream &report,
                                             std::ostream &tsv, double speed = 1, double wallSeconds = -1) {
    auto cell = [](const std::string &text) {
        std::string out;
        for (char c : text) {
            if (c == '\t')
                out += "\\t";
            else if (c == '\n')
                out += "\\n";
            else if (c == '\r')
                out += "\\r";
            else if (c == '|')
                out += "\\|";
            else if (c == '\\')
                out += "\\\\";
            else
                out += c;
        }
        return out;
    };
    auto count = [](std::optional<uint64_t> n) { return n ? std::to_string(*n) : "unknown"; };
    std::ostringstream table;
    table << "occurrence\trate\texact_frames\tstrm\tdecode_complete\tSTMd\tnext_"
             "strm\taudible_"
             "start\tSTMs\taudible_end\tsuccessor_lead_s\ttransition\toverlap_"
             "frames\tpromised_"
             "frames\tactual_frames\tprefix_"
             "frames\tpublication\tfault\tcodec\tstartup_delay_s"
             "\tpair_status\texpected_lms_index\tlms_poll_stamp\tlms_playlist_"
             "index\tlms_time\tlms_title"
             "\tlms_matches_audible\tmismatched_seconds\tspeed\tstrm_wall\taudible_start_wall\taudible_end_"
             "wall\tdecode_complete_wall\tSTMd_wall\tnext_strm_wall\tSTMs_wall\tsuccessor_lead_wall_"
             "s\tstartup_delay_wall_s\tlms_audible_elapsed_s\tlms_time_error_wall_s\n";
    ReadAheadVerdict v;
    size_t polls = 0, known = 0;
    for (size_t i = 0; i < rows.size(); ++i) {
        const auto &r = rows[i];
        double next = -1;
        if (r.source.occurrence < UINT64_MAX / 2)
            for (size_t j = i + 1; j < rows.size(); ++j)
                if (rows[j].source.occurrence < UINT64_MAX / 2) {
                    next = rows[j].strm;
                    break;
                }
        double end = r.end;
        bool pendingTail = false;
        for (const auto &tail : rows)
            if (tail.source.occurrence == UINT64_MAX - r.source.occurrence) {
                pendingTail = pendingTail || tail.end < 0;
                if (tail.end >= 0)
                    end = std::max(end, tail.end);
            }
        if (i + 1 < rows.size() && rows[i + 1].prefixFrames) {
            pendingTail = pendingTail || rows[i + 1].audible < 0;
            if (rows[i + 1].audible >= 0)
                end = std::max(end, rows[i + 1].audible);
        }
        if (pendingTail)
            end = -1;
        const double lead = end >= 0 && next >= 0 ? end - next : -1;
        std::string status;
        if (r.source.occurrence >= UINT64_MAX / 2) {
            status = "continuation";
        } else if (end >= 0 && next >= 0) {
            status = "completed";
            if (!v.pairs || lead < v.worst)
                v.worst = lead;
            ++v.pairs;
            if (lead < 12) {
                v.ok = false;
                v.reasons += " lead-below-12s";
            }
        } else {
            ++v.incomplete;
            status = !r.incomplete.empty() ? r.incomplete : !r.fault.empty() ? "source-fault" : "run-end";
        }
        if (r.storageFault) {
            v.ok = false;
            v.reasons += " storage-fault";
        }
        if (r.lmsMismatch) {
            v.ok = false;
            v.reasons += " lms-track-mismatch";
        }
        v.mismatchedSeconds += r.mismatchedSeconds;
        std::ostringstream stamps, indices, times, titles, elapsed, errors;
        bool unknown = r.lms.empty(), mismatch = false;
        for (size_t j = 0; j < r.lms.size(); ++j) {
            if (j) {
                stamps << '|';
                indices << '|';
                times << '|';
                titles << '|';
                elapsed << '|';
                errors << '|';
            }
            const auto &sample = r.lms[j];
            ++polls;
            if (sample.matches.has_value())
                ++known;
            stamps << sample.stamp;
            indices << sample.index;
            times << sample.time;
            titles << cell(sample.title);
            elapsed << sample.audibleElapsed;
            errors << sample.timeErrorWall;
            unknown = unknown || !sample.matches;
            mismatch = mismatch || (sample.matches && !*sample.matches);
        }
        table << r.source.occurrence << '\t' << r.source.rate << '\t' << count(r.source.sourceFrames) << '\t'
              << r.strm << '\t' << r.decoded << '\t' << r.stmd << '\t' << next << '\t' << r.audible << '\t'
              << r.stms << '\t' << end << '\t' << lead << '\t' << r.transition << '\t' << r.overlap << '\t'
              << count(r.promised) << '\t' << r.actual << '\t' << r.prefixFrames << '\t' << r.publication
              << '\t' << r.fault << '\t'
              << (r.source.codec == 'f'   ? "FLAC"
                  : r.source.codec == 'l' ? "ALAC"
                  : r.source.codec == 'm' ? "MP3"
                  : r.source.codec == 'p' ? "PCM"
                  : r.source.codec == 'a' ? "AAC"
                                          : "unknown")
              << '\t' << (r.audible >= 0 && r.strm >= 0 ? r.audible - r.strm : -1) << '\t' << status << '\t'
              << r.expectedIndex << '\t' << stamps.str() << '\t' << indices.str() << '\t' << times.str()
              << '\t' << titles.str() << '\t'
              << (mismatch  ? "false"
                  : unknown ? "unknown"
                            : "true")
              << '\t' << r.mismatchedSeconds << '\t' << speed << '\t' << (r.strm < 0 ? -1 : r.strm / speed)
              << '\t' << (r.audible < 0 ? -1 : r.audible / speed) << '\t' << (end < 0 ? -1 : end / speed)
              << '\t' << (r.decoded < 0 ? -1 : r.decoded / speed) << '\t'
              << (r.stmd < 0 ? -1 : r.stmd / speed) << '\t' << (next < 0 ? -1 : next / speed) << '\t'
              << (r.stms < 0 ? -1 : r.stms / speed) << '\t' << (lead < 0 ? -1 : lead / speed) << '\t'
              << (r.audible >= 0 && r.strm >= 0 ? (r.audible - r.strm) / speed : -1) << '\t' << elapsed.str()
              << '\t' << errors.str() << '\n';
    }
    if (!v.pairs) {
        v.ok = false;
        v.reasons += " zero-completed-pairs";
    }
    if (!known) {
        v.ok = false;
        v.reasons += " lms-cli-unavailable";
    }
    if (polls && (polls - known) * 10 > polls)
        report << "WARNING: LMS CLI unknown for more than 10% of audible polls (" << polls - known << "/"
               << polls << ").\n\n";
    report << "Speed factor: " << speed << "; wall-clock duration: " << wallSeconds << " s.\n\n"
           << "LMS time is compared with simulated audible elapsed; both are divided by speed for the "
              "lms_time_error_wall_s diagnostic. STMt already carries simulated elapsed; multiplying "
              "LMS time again would count speed twice. LMS interpolates between updates in wall time.\n\n";
    tsv << table.str();
    report << "# Queue mode phase 2: LMS read-ahead\n\nPlayer: Queue Test "
              "(YeneY), MAC 02:59:51:54:00:01. No "
              "Sonos.\n\nTimes are simulated monotonic seconds from sink creation; -1 "
              "means unobserved, not "
              "zero.\n\n```tsv\n"
           << table.str() << "```\n\nSuccessor pairs: " << v.pairs << "; worst lead: " << v.worst
           << " s. Need >=12 s for every completed pair. Incomplete rows: " << v.incomplete
           << " (run-end, source-fault or flushed).\n\nLMS mismatched seconds: " << v.mismatchedSeconds
           << ". A sustained mismatch >2 s fails with lms-track-mismatch. CLI "
              "values are sampled at 1 Hz; "
              "parallel pipe-separated columns retain every sample. Titles "
              "are URL-decoded; tabs, newlines, backslashes and pipes are backslash-escaped.\n\nReasons:"
           << (v.reasons.empty() ? " none" : v.reasons) << "\n\n"
           << v.line() << '\n';
    return v;
}
} // namespace queue_audio