290 : model_(model), opt_(
options), sn_(
lqn::lqn_finalize(model)) {
293 "SolverLQNS requires the lqns and lqsim commands on the system path.\n"
294 "Obtain them from their authors at: http://www.sce.carleton.ca/rads/lqns/\n"
295 "LINE ships no LQNS binary and does not redistribute one.\n\n"
296 "Alternatively, point LINE at a host that already runs LQNS by setting\n"
297 "LqnsOptions::remote and LqnsOptions::remote_url.");
298 const std::string m = opt_.method.empty() ? std::string(
"default") : opt_.method;
300 if (std::find(valid.begin(), valid.end(), m) == valid.end())
302 "' is not a method of this solver; it takes default, lqns, srvn, "
303 "exactmva, srvn.exactmva, sim, lqsim and lqns.default");
305 multiserver_pragma(opt_.multiserver);
323 std::vector<std::string> m;
325 if (name !=
"qns" && name.rfind(
"qns.", 0) != 0) {
334 return method ==
"sim" || method ==
"lqsim";
345 const char* names[] = {
"Sink",
"Source",
"Queue",
346 "Coxian",
"Erlang",
"Exp",
347 "HyperExp",
"Buffer",
"Server",
348 "JobSink",
"RandomSource",
"ServiceTunnel",
349 "SchedStrategy_PS",
"SchedStrategy_FCFS",
"ClosedClass"};
350 return std::vector<std::string>(names, names +
sizeof(names) /
sizeof(names[0]));
372 const std::clock_t t0 = std::clock();
373 const std::string dir = detail::make_temp_dir(
"lqns");
374 const std::string stem = dir +
"/model";
375 const std::string modelfile = stem +
".lqnx";
376 const std::string resultfile = stem +
".lqxo";
381 for (std::size_t i = 0; i < rep.
dropped.size(); ++i)
382 std::fprintf(stderr,
"SolverLQNS: %s\n", rep.
dropped[i].c_str());
384 if (opt_.remote) run_remote(modelfile, resultfile);
385 else run_local(modelfile, resultfile);
388 parse_lqxo(resultfile);
390 if (!opt_.keep) detail::remove_temp_dir(dir);
393 if (!opt_.keep) detail::remove_temp_dir(dir);
394 else if (opt_.verbose) std::fprintf(stderr,
"SolverLQNS: files kept in %s\n", dir.c_str());
395 runtime_ = double(std::clock() - t0) / double(CLOCKS_PER_SEC);
407 const std::size_t n = sn_.nidx;
410 auto fill = [&](std::vector<T>& v, std::vector<bool>& d,
const std::vector<double>& src) {
412 d.assign(n + 1,
false);
413 for (std::size_t i = 1; i <= n; ++i) {
414 if (std::isnan(src[i]))
continue;
425 for (std::size_t i = 1; i <= n; ++i)
438 void run_analyzer_once() {
443 static std::string multiserver_pragma(
const std::string& policy) {
444 if (policy.empty() || policy ==
"none")
return std::string();
445 if (policy ==
"default")
return "-Pmultiserver=rolia";
446 if (policy ==
"conway" || policy ==
"rolia" || policy ==
"zhou" || policy ==
"suri" ||
447 policy ==
"reiser" || policy ==
"schmidt")
448 return "-Pmultiserver=" + policy;
450 "' is not a multiserver policy of lqns; it takes conway, rolia, zhou, "
451 "suri, reiser, schmidt and default");
468 std::vector<std::string> build_argv(
const std::string& modelfile)
const {
470 std::vector<std::string> a;
471 a.push_back(sim ?
"lqsim" :
"lqns");
479 const std::string ms = multiserver_pragma(opt_.multiserver);
480 if (!ms.empty()) a.push_back(ms);
482 if (opt_.method ==
"srvn" || opt_.method ==
"srvn.exactmva") a.push_back(
"-Playering=srvn");
483 if (opt_.method ==
"exactmva" || opt_.method ==
"srvn.exactmva") a.push_back(
"-Pmva=exact-mva");
484 if (sim && opt_.samples > 0) {
486 std::snprintf(buf,
sizeof(buf),
"%.0f", opt_.samples);
493 if (opt_.method !=
"lqns.default") a.push_back(
"-Pstop-on-message-loss=false");
495 a.push_back(modelfile);
499 void run_local(
const std::string& modelfile,
const std::string& resultfile)
const {
500 const std::vector<std::string> argv = build_argv(modelfile);
503 for (std::size_t i = 0; i < argv.size(); ++i) cmd += (i ?
" " :
"") + argv[i];
504 std::fprintf(stderr,
"SolverLQNS command: %s\n", cmd.c_str());
507 const util::ProcResult r =
util::capture(argv, opt_.timeout_seconds,
true);
509 throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: " + argv[0] +
" did not finish within " +
510 std::to_string(opt_.timeout_seconds) +
"s and was killed");
512 throw UnsupportedError(ErrorKind{
"LQNS"},
"SolverLQNS: could not run '" + argv[0] +
513 "'; LINE ships no LQNS binary, so it must be installed and on "
515 if (opt_.verbose && !r.out.empty()) std::fprintf(stderr,
"%s", r.out.c_str());
518 if (r.exitCode != 0 || ::stat(resultfile.c_str(), &st) != 0) {
519 std::string out = r.out;
520 while (!out.empty() && (out.back() ==
'\n' || out.back() ==
' ')) out.pop_back();
521 throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: the LQNS binary (exit status " + std::to_string(r.exitCode) +
522 ") produced no results for this model. Its output was:\n" + out);
533 void run_remote(
const std::string& modelfile,
const std::string& resultfile)
const {
536 std::ifstream
in(modelfile.c_str());
537 if (!in)
throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: cannot read the model it just wrote: " + modelfile);
538 content.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
541 std::string base = opt_.remote_url;
542 while (!base.empty() && base[base.size() - 1] ==
'/') base.erase(base.size() - 1);
543 const std::string url = base + (sim ?
"/api/v1/solve/lqsim" :
"/api/v1/solve/lqns");
546 req[
"model"][
"content"] = content;
547 req[
"model"][
"base64"] =
false;
548 req[
"options"][
"include_raw_output"] =
true;
550 req[
"options"][
"blocks"] = 30;
551 if (opt_.samples > 0) req[
"options"][
"run_time"] = opt_.samples;
553 std::string ms = opt_.multiserver.empty() || opt_.multiserver ==
"default"
554 ? std::string(
"rolia")
556 req[
"options"][
"pragmas"][
"multiserver"] = ms;
557 req[
"options"][
"pragmas"][
"stop_on_message_loss"] =
false;
558 if (opt_.method ==
"srvn" || opt_.method ==
"srvn.exactmva")
559 req[
"options"][
"pragmas"][
"layering"] =
"srvn";
560 if (opt_.method ==
"exactmva" || opt_.method ==
"srvn.exactmva")
561 req[
"options"][
"pragmas"][
"mva"] =
"exact";
564 const int timeout_ms =
565 (opt_.timeout_seconds > 0 ? opt_.timeout_seconds : 300) * 1000;
566 const http::Response resp =
http::post_json(url, req.dump(), timeout_ms);
567 if (resp.status < 200 || resp.status >= 300)
568 throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: remote LQNS at " + url +
" returned HTTP " +
569 std::to_string(resp.status) +
": " + resp.body);
572 out = nlohmann::json::parse(resp.body);
573 }
catch (
const std::exception& e) {
574 throw ExternalError(ErrorKind{
"LQNS"}, std::string(
"SolverLQNS: remote LQNS returned a body that is not JSON: ") +
577 const std::string status = out.value(
"status", std::string());
578 if (status ==
"error" || status ==
"failed")
579 throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: remote solver returned an error: " +
580 out.value(
"error", std::string(
"unknown error")));
582 if (out.contains(
"raw_output") && out[
"raw_output"].is_object())
583 lqxo = out[
"raw_output"].value(
"lqxo", std::string());
585 throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: remote solver did not return an LQXO document");
586 std::ofstream f(resultfile.c_str());
587 if (!f)
throw ExternalError(ErrorKind{
"LQNS"},
"SolverLQNS: cannot write the remote result to " + resultfile);
605 std::size_t index_of(
const std::string& name,
lang::LqnElement kind)
const {
606 const std::map<std::pair<int, std::string>, std::size_t>::const_iterator it =
607 byname_.find(std::make_pair(
static_cast<int>(kind), name));
608 return it == byname_.end() ? 0 : it->second;
611 std::size_t call_index_of(
const std::string& name)
const {
612 const std::map<std::string, std::size_t>::const_iterator it = bycallname_.find(name);
613 return it == bycallname_.end() ? 0 : it->second;
616 void init_name_index() {
617 if (!byname_.empty())
return;
619 for (std::size_t i = 1; i <= sn_.nidx; ++i)
620 byname_.insert(std::make_pair(
621 std::make_pair(
static_cast<int>(sn_.type[i]), sn_.names[i]), i));
622 for (std::size_t c = 1; c <= sn_.ncalls; ++c)
623 bycallname_.insert(std::make_pair(sn_.callnames[c], c));
633 void parse_lqxo(
const std::string& resultfile) {
635 const std::size_t n = sn_.nidx;
636 const double nan = detail::nan_value();
638 raw_.util.assign(n + 1, nan);
639 raw_.phase1util.assign(n + 1, nan);
640 raw_.phase2util.assign(n + 1, nan);
641 raw_.phase1svct.assign(n + 1, nan);
642 raw_.phase2svct.assign(n + 1, nan);
643 raw_.tput.assign(n + 1, nan);
644 raw_.procwaiting.assign(n + 1, nan);
645 raw_.procutil.assign(n + 1, nan);
646 raw_.residvisit.assign(n + 1, nan);
647 raw_.residt.assign(n + 1, nan);
648 raw_.callwaiting.assign(sn_.ncalls + 1, nan);
650 std::unique_ptr<xml::Element> doc;
653 }
catch (
const Error&) {
655 "SolverLQNS: no readable result at " + resultfile +
656 "; the binary ran but wrote no .lqxo, which is what it does when it rejects the "
657 "model (run with verbose to see its diagnostics)");
660 for (
const xml::Element* sp : doc->by_tag(
"solver-params")) {
661 const std::vector<const xml::Element*> g = sp->by_tag(
"result-general");
662 if (!g.empty()) raw_.iterations =
static_cast<int>(detail::attr_num(g[0],
"iterations"));
665 for (
const xml::Element* pe : doc->by_tag(
"processor")) {
667 const std::vector<const xml::Element*> pr = pe->by_tag(
"result-processor");
668 if (pidx && !pr.empty()) raw_.procutil[pidx] = detail::attr_num(pr[0],
"utilization");
670 for (
const xml::Element* te : pe->by_tag(
"task")) {
672 const std::vector<const xml::Element*> tr = te->by_tag(
"result-task");
673 double task_tput = nan;
675 task_tput = detail::attr_num(tr[0],
"throughput");
677 raw_.util[tidx] = detail::attr_num(tr[0],
"utilization");
678 raw_.phase1util[tidx] = detail::attr_num(tr[0],
"phase1-utilization");
679 raw_.phase2util[tidx] = detail::attr_num(tr[0],
"phase2-utilization");
680 raw_.tput[tidx] = task_tput;
681 raw_.procutil[tidx] = detail::attr_num(tr[0],
"proc-utilization");
685 for (
const xml::Element* ee : te->by_tag(
"entry")) {
687 const std::vector<const xml::Element*> er = ee->by_tag(
"result-entry");
688 double entry_tput = nan;
690 entry_tput = detail::attr_num(er[0],
"throughput");
692 raw_.util[eidx] = detail::attr_num(er[0],
"utilization");
693 raw_.phase1util[eidx] = detail::attr_num(er[0],
"phase1-utilization");
694 raw_.phase2util[eidx] = detail::attr_num(er[0],
"phase2-utilization");
695 raw_.phase1svct[eidx] = detail::attr_num(er[0],
"phase1-service-time");
696 raw_.phase2svct[eidx] = detail::attr_num(er[0],
"phase2-service-time");
697 raw_.tput[eidx] = entry_tput;
698 raw_.procutil[eidx] = detail::attr_num(er[0],
"proc-utilization");
703 const std::vector<const xml::Element*> epa =
704 ee->by_tag(
"entry-phase-activities");
705 if (epa.empty())
continue;
706 for (
const xml::Element* ae : epa[0]->by_tag(
"activity")) {
707 read_activity(ae, entry_tput,
true);
711 const std::vector<const xml::Element*> tal = te->by_tag(
"task-activities");
712 if (tal.empty())
continue;
713 for (
const xml::Element* ae : tal[0]->by_tag(
"activity")) {
717 if (ae->parent != tal[0])
continue;
718 read_activity(ae, task_tput,
false);
723 aggregate_entry_procutil();
724 aggregate_entry_svct();
739 void compute_residt() {
740 const std::size_t n = sn_.nidx;
741 for (std::size_t a = 1; a <= sn_.nacts; ++a) {
742 const std::size_t aidx = sn_.ashift + a;
743 if (aidx > n || aidx >= sn_.parent.size())
continue;
744 const std::size_t tidx = sn_.parent[aidx];
745 if (tidx == 0 || tidx > n)
continue;
746 const double xa = raw_.tput[aidx];
747 const double xt = raw_.tput[tidx];
748 const double rv = raw_.residvisit[aidx];
749 if (std::isnan(rv) || std::isnan(xa) || std::isnan(xt))
continue;
752 for (std::size_t t = 1; t <= sn_.ntasks; ++t) {
753 const std::size_t tidx = sn_.tshift + t;
756 bool complete =
true;
757 for (std::size_t a = 1; a <= sn_.nacts; ++a) {
758 const std::size_t aidx = sn_.ashift + a;
759 if (aidx >= sn_.parent.size() || sn_.parent[aidx] != tidx)
continue;
761 if (std::isnan(raw_.residt[aidx])) {
765 sum += raw_.residt[aidx];
767 if (any && complete) raw_.residt[tidx] = sum;
794 void aggregate_entry_svct() {
795 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
796 const std::size_t eidx = sn_.eshift + e;
797 if (eidx >= sn_.actsof.size() || eidx >= raw_.phase1svct.size())
continue;
798 if (!std::isnan(raw_.phase1svct[eidx]))
continue;
799 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
800 if (acts.empty())
continue;
801 bool all_zero =
true;
802 for (std::size_t aidx : acts) {
803 if (aidx >= raw_.phase1svct.size() || std::isnan(raw_.phase1svct[aidx]) ||
804 raw_.phase1svct[aidx] != 0.0) {
809 if (all_zero) raw_.phase1svct[eidx] = 0.0;
826 void aggregate_entry_procutil() {
827 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
828 const std::size_t eidx = sn_.eshift + e;
829 if (eidx >= sn_.actsof.size())
continue;
830 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
831 if (acts.empty())
continue;
833 bool complete =
true;
834 for (std::size_t aidx : acts) {
835 if (aidx >= raw_.procutil.size() || std::isnan(raw_.procutil[aidx])) {
839 sum += raw_.procutil[aidx];
841 if (complete) raw_.procutil[eidx] = sum;
854 void read_activity(
const xml::Element* ae,
double owner_tput,
bool phase_form) {
855 const std::string aname = ae->attr(
"name");
857 const std::vector<const xml::Element*> ar = ae->by_tag(
"result-activity");
858 if (aidx && !ar.empty()) {
859 const xml::Element* r = ar[0];
860 raw_.util[aidx] = detail::attr_num(r,
"utilization");
861 raw_.phase1svct[aidx] = detail::attr_num(r,
"service-time");
862 raw_.procwaiting[aidx] = detail::attr_num(r,
"proc-waiting");
869 const double pw = raw_.procwaiting[aidx];
870 const double st = raw_.phase1svct[aidx];
871 raw_.residvisit[aidx] = (pw < 0 || (!std::isnan(st) && pw > st))
872 ? detail::nan_value()
873 : detail::attr_num(ae,
"host-demand-mean") + pw;
874 const double t = detail::attr_num(r,
"throughput");
878 raw_.tput[aidx] = std::isnan(t) && phase_form ? owner_tput : t;
879 const double pu = detail::attr_num(r,
"proc-utilization");
880 if (!std::isnan(pu)) {
881 raw_.procutil[aidx] = pu;
882 }
else if (phase_form) {
883 const double hd = detail::attr_num(ae,
"host-demand-mean");
884 if (!std::isnan(owner_tput) && !std::isnan(hd)) raw_.procutil[aidx] = owner_tput * hd;
888 read_calls(ae, aname,
"synch-call",
"=>");
889 read_calls(ae, aname,
"asynch-call",
"->");
892 void read_calls(
const xml::Element* ae,
const std::string& aname,
const char* tag,
894 for (
const xml::Element* ce : ae->by_tag(tag)) {
895 const std::size_t cidx = call_index_of(aname + arrow + ce->attr(
"dest"));
896 const std::vector<const xml::Element*> cr = ce->by_tag(
"result-call");
897 if (cidx && !cr.empty()) raw_.callwaiting[cidx] = detail::attr_num(cr[0],
"waiting");
901 lqn::LqnModel<T> model_;
903 lqn::LqnStruct<T> sn_;
905 std::map<std::pair<int, std::string>, std::size_t> byname_;
906 std::map<std::string, std::size_t> bycallname_;
907 double runtime_ = 0.0;
908 bool solved_ =
false;