| 1558 | } |
| 1559 | |
| 1560 | nlohmann::json RuntimeManager::Logs(const std::string& vm_id, size_t max_lines) const { |
| 1561 | nlohmann::json lines = nlohmann::json::array(); |
| 1562 | if (auto session = FindSession(vm_id)) { |
| 1563 | std::lock_guard<std::mutex> lock(session->console_mutex); |
| 1564 | const size_t start = session->log_lines.size() > max_lines ? session->log_lines.size() - max_lines : 0; |
| 1565 | for (size_t i = start; i < session->log_lines.size(); ++i) { |
| 1566 | lines.push_back(session->log_lines[i]); |
| 1567 | } |
| 1568 | return {{"lines", std::move(lines)}}; |
| 1569 | } |
| 1570 | // No live session: tail the on-disk log so the operator can still read |
| 1571 | // crash output. Walk back through current + rotated files until we have |
| 1572 | // enough lines (or run out of files). |
| 1573 | auto record = store_.Get(vm_id); |
| 1574 | if (!record) return {{"lines", lines}}; |
| 1575 | const fs::path base = RuntimeLogPath(record->spec.vm_dir); |
| 1576 | std::vector<std::string> collected; |
| 1577 | auto absorb = [&](const fs::path& path) { |
| 1578 | if (collected.size() >= max_lines) return; |
| 1579 | auto chunk = TailLogFile(path, max_lines - collected.size()); |
| 1580 | chunk.insert(chunk.end(), |
| 1581 | std::make_move_iterator(collected.begin()), |
| 1582 | std::make_move_iterator(collected.end())); |
| 1583 | collected = std::move(chunk); |
| 1584 | }; |
| 1585 | absorb(base); |
| 1586 | for (int i = 1; i <= kLogRotateKeep && collected.size() < max_lines; ++i) { |
| 1587 | absorb(base.string() + "." + std::to_string(i)); |
| 1588 | } |
| 1589 | for (auto& l : collected) lines.push_back(std::move(l)); |
| 1590 | return {{"lines", std::move(lines)}}; |
| 1591 | } |
| 1592 | |
| 1593 | ProcessResources RuntimeManager::SampleProcessResources(const std::string& vm_id) const { |
| 1594 | auto session = FindSession(vm_id); |
no test coverage detected