#include "app/report.h" #include namespace sots::app { namespace { std::string JsonEscape(const std::string& s) { std::string o; for (char c : s) { switch (c) { case '"': o += "\\\""; break; case '\\': o += "\\\\"; break; case '\n': o += "\\n"; break; case '\r': o += "\\r"; break; case '\t': o += "\\t"; break; default: if (static_cast(c) < 0x20) { char b[8]; std::snprintf(b, sizeof b, "\\u%04x", c); o += b; } else { o += c; } } } return o; } void TallyJson(std::ofstream& f, const char* key, const PhaseTally& t) { f << " \"" << key << "\": {\"total\": " << t.total << ", \"verified\": " << t.verified << ", \"implemented\": " << t.implemented << ", \"partial\": " << t.partial << ", \"blocked\": " << t.blocked << ", \"stub\": " << t.stub << ", \"modelled\": " << t.modelled() << ", \"committed\": " << t.committed() << "}"; } } // namespace void PrintPhaseLog(std::FILE* out, const TurnResult& r, bool verbose) { Driver current = static_cast(-1); for (const auto& rec : r.records) { if (rec.desc->driver != current) { current = rec.desc->driver; std::fprintf(out, "\n%s\n", DriverName(current)); } const char* indent = current == Driver::Player ? " " : " "; std::fprintf(out, "%s%c %-4s %-38s %-12s", indent, StatusGlyph(rec.desc->status), rec.desc->id, rec.desc->name, StatusName(rec.desc->status)); if (rec.invocations || rec.leafWrites || rec.wouldWrite || rec.rngWords) { std::fprintf(out, " ran=%-5d writes=%-5d", rec.invocations, rec.leafWrites); if (rec.wouldWrite) std::fprintf(out, " would=%-4d", rec.wouldWrite); if (rec.rngWords) std::fprintf(out, " rng=%d", rec.rngWords); } std::fprintf(out, "\n"); for (const auto& n : rec.notes) std::fprintf(out, "%s - %s\n", indent, n.c_str()); if (verbose && rec.desc->note[0]) std::fprintf(out, "%s # %s\n", indent, rec.desc->note); } } void PrintSummary(std::FILE* out, const TurnResult& r) { const PhaseTally s = TallySpine(); const PhaseTally t = TallyTail(); std::fprintf(out, "\nphases\n"); std::fprintf(out, " turn drivers (the milestone's denominator): %d of %d modelled, %d committed\n" " verified %d implemented %d partial %d blocked %d stub %d\n", s.modelled(), s.total, s.committed(), s.verified, s.implemented, s.partial, s.blocked, s.stub); std::fprintf(out, " post-combat tail (written to the autosave, tracked separately): %d of %d " "modelled\n" " verified %d implemented %d partial %d blocked %d stub %d\n", t.modelled(), t.total, t.verified, t.implemented, t.partial, t.blocked, t.stub); std::fprintf(out, "\nthis run\n"); std::fprintf(out, " leaves written %d\n", r.leafWrites); std::fprintf(out, " leaves NOT written by a blocked phase %d\n", r.wouldWrite); std::fprintf(out, " generator words consumed %d (state %s, %s)\n", r.rngWords, r.rngLoaded ? "loaded" : "UNREADABLE", r.rngCommitted ? "WRITTEN BACK" : "left untouched"); if (!r.rngUnaccounted.empty()) { std::fprintf(out, " generator words NOT accounted (never netted off the above):\n"); for (const auto& u : r.rngUnaccounted) std::fprintf(out, " - %s\n", u.c_str()); } for (const auto& w : r.warnings) std::fprintf(out, " ! %s\n", w.c_str()); } bool WriteMetricJson(const std::string& path, const TurnResult& r, const std::string& inputName, const std::string& outputName) { std::ofstream f(path); if (!f) return false; const PhaseTally s = TallySpine(); const PhaseTally t = TallyTail(); f << "{\n"; f << " \"schema\": \"sots-standalone-metric/1\",\n"; f << " \"input\": \"" << JsonEscape(inputName) << "\",\n"; f << " \"output\": \"" << JsonEscape(outputName) << "\",\n"; TallyJson(f, "spine", s); f << ",\n"; TallyJson(f, "tail", t); f << ",\n"; f << " \"run\": {\"leafWrites\": " << r.leafWrites << ", \"blockedLeafWrites\": " << r.wouldWrite << ", \"rngWords\": " << r.rngWords << ", \"rngLoaded\": " << (r.rngLoaded ? "true" : "false") << ", \"rngCommitted\": " << (r.rngCommitted ? "true" : "false") << ", \"rngUnaccounted\": ["; for (std::size_t i = 0; i < r.rngUnaccounted.size(); ++i) f << (i ? ", " : "") << '"' << JsonEscape(r.rngUnaccounted[i]) << '"'; f << "]},\n"; f << " \"phases\": [\n"; bool first = true; for (const auto& rec : r.records) { if (!first) f << ",\n"; first = false; f << " {\"driver\": \"" << DriverName(rec.desc->driver) << "\", \"id\": \"" << rec.desc->id << "\", \"name\": \"" << rec.desc->name << "\", \"status\": \"" << StatusName(rec.desc->status) << "\", \"ran\": " << rec.invocations << ", \"writes\": " << rec.leafWrites << ", \"blockedWrites\": " << rec.wouldWrite << ", \"rng\": " << rec.rngWords << "}"; } f << "\n ]\n}\n"; return static_cast(f); } } // namespace sots::app