195 : model_(model), opt_(
options), sn_(
lqn::lqn_finalize(model)) {
198 "SolverLQNS requires the lqns and lqsim commands on the system path.\n"
199 "Obtain them from their authors at: http://www.sce.carleton.ca/rads/lqns/\n"
200 "LINE ships no LQNS binary and does not redistribute one.\n\n"
201 "Alternatively, point LINE at a host that already runs LQNS by setting\n"
202 "LqnsOptions::remote and LqnsOptions::remote_url.");
203 const std::string m = opt_.method.empty() ? std::string(
"default") : opt_.method;
205 if (std::find(valid.begin(), valid.end(), m) == valid.end())
207 "' is not a method of this solver; it takes default, lqns, srvn, "
208 "exactmva, srvn.exactmva, sim, lqsim and lqnsdefault");
210 multiserver_pragma(opt_.multiserver);
223 std::vector<std::string> m;
224 m.push_back(
"default");
227 m.push_back(
"exactmva");
228 m.push_back(
"srvn.exactmva");
230 m.push_back(
"lqsim");
231 m.push_back(
"lqnsdefault");
237 return method ==
"sim" || method ==
"lqsim";
248 const char* names[] = {
"Sink",
"Source",
"Queue",
249 "Coxian",
"Erlang",
"Exp",
250 "HyperExp",
"Buffer",
"Server",
251 "JobSink",
"RandomSource",
"ServiceTunnel",
252 "SchedStrategy_PS",
"SchedStrategy_FCFS",
"ClosedClass"};
253 return std::vector<std::string>(names, names +
sizeof(names) /
sizeof(names[0]));
275 const std::clock_t t0 = std::clock();
276 const std::string dir = detail::make_temp_dir(
"lqns");
277 const std::string stem = dir +
"/model";
278 const std::string modelfile = stem +
".lqnx";
279 const std::string resultfile = stem +
".lqxo";
284 for (std::size_t i = 0; i < rep.
dropped.size(); ++i)
285 std::fprintf(stderr,
"SolverLQNS: %s\n", rep.
dropped[i].c_str());
287 if (opt_.remote) run_remote(modelfile, resultfile);
288 else run_local(modelfile);
291 parse_lqxo(resultfile);
293 if (!opt_.keep) detail::remove_temp_dir(dir);
296 if (!opt_.keep) detail::remove_temp_dir(dir);
297 else if (opt_.verbose) std::fprintf(stderr,
"SolverLQNS: files kept in %s\n", dir.c_str());
298 runtime_ = double(std::clock() - t0) / double(CLOCKS_PER_SEC);
310 const std::size_t n = sn_.nidx;
313 auto fill = [&](std::vector<T>& v, std::vector<bool>& d,
const std::vector<double>& src) {
315 d.assign(n + 1,
false);
316 for (std::size_t i = 1; i <= n; ++i) {
317 if (std::isnan(src[i]))
continue;
339 void run_analyzer_once() {
344 static std::string multiserver_pragma(
const std::string& policy) {
345 if (policy.empty() || policy ==
"none")
return std::string();
346 if (policy ==
"default")
return "-Pmultiserver=rolia";
347 if (policy ==
"conway" || policy ==
"rolia" || policy ==
"zhou" || policy ==
"suri" ||
348 policy ==
"reiser" || policy ==
"schmidt")
349 return "-Pmultiserver=" + policy;
351 "' is not a multiserver policy of lqns; it takes conway, rolia, zhou, "
352 "suri, reiser, schmidt and default");
369 std::vector<std::string> build_argv(
const std::string& modelfile)
const {
371 std::vector<std::string> a;
372 a.push_back(sim ?
"lqsim" :
"lqns");
380 const std::string ms = multiserver_pragma(opt_.multiserver);
381 if (!ms.empty()) a.push_back(ms);
383 if (opt_.method ==
"srvn" || opt_.method ==
"srvn.exactmva") a.push_back(
"-Playering=srvn");
384 if (opt_.method ==
"exactmva" || opt_.method ==
"srvn.exactmva") a.push_back(
"-Pmva=exact");
385 if (sim && opt_.samples > 0) {
387 std::snprintf(buf,
sizeof(buf),
"%.0f", opt_.samples);
394 if (opt_.method !=
"lqnsdefault") a.push_back(
"-Pstop-on-message-loss=false");
396 a.push_back(modelfile);
400 void run_local(
const std::string& modelfile)
const {
401 const std::vector<std::string> argv = build_argv(modelfile);
404 for (std::size_t i = 0; i < argv.size(); ++i) cmd += (i ?
" " :
"") + argv[i];
405 std::fprintf(stderr,
"SolverLQNS command: %s\n", cmd.c_str());
408 const util::ProcResult r =
util::capture(argv, opt_.timeout_seconds);
410 throw Error(
"SolverLQNS: " + argv[0] +
" did not finish within " +
411 std::to_string(opt_.timeout_seconds) +
"s and was killed");
414 "'; LINE ships no LQNS binary, so it must be installed and on "
416 if (opt_.verbose && !r.out.empty()) std::fprintf(stderr,
"%s", r.out.c_str());
426 void run_remote(
const std::string& modelfile,
const std::string& resultfile)
const {
429 std::ifstream in(modelfile.c_str());
430 if (!in)
throw Error(
"SolverLQNS: cannot read the model it just wrote: " + modelfile);
431 content.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
434 std::string base = opt_.remote_url;
435 while (!base.empty() && base[base.size() - 1] ==
'/') base.erase(base.size() - 1);
436 const std::string url = base + (sim ?
"/api/v1/solve/lqsim" :
"/api/v1/solve/lqns");
439 req[
"model"][
"content"] = content;
440 req[
"model"][
"base64"] =
false;
441 req[
"options"][
"include_raw_output"] =
true;
443 req[
"options"][
"blocks"] = 30;
444 if (opt_.samples > 0) req[
"options"][
"run_time"] = opt_.samples;
446 std::string ms = opt_.multiserver.empty() || opt_.multiserver ==
"default"
447 ? std::string(
"rolia")
449 req[
"options"][
"pragmas"][
"multiserver"] = ms;
450 req[
"options"][
"pragmas"][
"stop_on_message_loss"] =
false;
451 if (opt_.method ==
"srvn" || opt_.method ==
"srvn.exactmva")
452 req[
"options"][
"pragmas"][
"layering"] =
"srvn";
453 if (opt_.method ==
"exactmva" || opt_.method ==
"srvn.exactmva")
454 req[
"options"][
"pragmas"][
"mva"] =
"exact";
457 const int timeout_ms =
458 (opt_.timeout_seconds > 0 ? opt_.timeout_seconds : 300) * 1000;
459 const http::Response resp =
http::post_json(url, req.dump(), timeout_ms);
460 if (resp.status < 200 || resp.status >= 300)
461 throw Error(
"SolverLQNS: remote LQNS at " + url +
" returned HTTP " +
462 std::to_string(resp.status) +
": " + resp.body);
465 out = nlohmann::json::parse(resp.body);
466 }
catch (
const std::exception& e) {
467 throw Error(std::string(
"SolverLQNS: remote LQNS returned a body that is not JSON: ") +
470 const std::string status = out.value(
"status", std::string());
471 if (status ==
"error" || status ==
"failed")
472 throw Error(
"SolverLQNS: remote solver returned an error: " +
473 out.value(
"error", std::string(
"unknown error")));
475 if (out.contains(
"raw_output") && out[
"raw_output"].is_object())
476 lqxo = out[
"raw_output"].value(
"lqxo", std::string());
478 throw Error(
"SolverLQNS: remote solver did not return an LQXO document");
479 std::ofstream f(resultfile.c_str());
480 if (!f)
throw Error(
"SolverLQNS: cannot write the remote result to " + resultfile);
498 std::size_t index_of(
const std::string& name,
lang::LqnElement kind)
const {
499 const std::map<std::pair<int, std::string>, std::size_t>::const_iterator it =
500 byname_.find(std::make_pair(
static_cast<int>(kind), name));
501 return it == byname_.end() ? 0 : it->second;
504 std::size_t call_index_of(
const std::string& name)
const {
505 const std::map<std::string, std::size_t>::const_iterator it = bycallname_.find(name);
506 return it == bycallname_.end() ? 0 : it->second;
509 void init_name_index() {
510 if (!byname_.empty())
return;
512 for (std::size_t i = 1; i <= sn_.nidx; ++i)
513 byname_.insert(std::make_pair(
514 std::make_pair(
static_cast<int>(sn_.type[i]), sn_.names[i]), i));
515 for (std::size_t c = 1; c <= sn_.ncalls; ++c)
516 bycallname_.insert(std::make_pair(sn_.callnames[c], c));
526 void parse_lqxo(
const std::string& resultfile) {
528 const std::size_t n = sn_.nidx;
529 const double nan = detail::nan_value();
531 raw_.util.assign(n + 1, nan);
532 raw_.phase1util.assign(n + 1, nan);
533 raw_.phase2util.assign(n + 1, nan);
534 raw_.phase1svct.assign(n + 1, nan);
535 raw_.phase2svct.assign(n + 1, nan);
536 raw_.tput.assign(n + 1, nan);
537 raw_.procwaiting.assign(n + 1, nan);
538 raw_.procutil.assign(n + 1, nan);
539 raw_.callwaiting.assign(sn_.ncalls + 1, nan);
541 std::unique_ptr<xml::Element> doc;
544 }
catch (
const Error&) {
546 "SolverLQNS: no readable result at " + resultfile +
547 "; the binary ran but wrote no .lqxo, which is what it does when it rejects the "
548 "model (run with verbose to see its diagnostics)");
551 for (
const xml::Element* sp : doc->by_tag(
"solver-params")) {
552 const std::vector<const xml::Element*> g = sp->by_tag(
"result-general");
553 if (!g.empty()) raw_.iterations =
static_cast<int>(detail::attr_num(g[0],
"iterations"));
556 for (
const xml::Element* pe : doc->by_tag(
"processor")) {
558 const std::vector<const xml::Element*> pr = pe->by_tag(
"result-processor");
559 if (pidx && !pr.empty()) raw_.procutil[pidx] = detail::attr_num(pr[0],
"utilization");
561 for (
const xml::Element* te : pe->by_tag(
"task")) {
563 const std::vector<const xml::Element*> tr = te->by_tag(
"result-task");
564 double task_tput = nan;
566 task_tput = detail::attr_num(tr[0],
"throughput");
568 raw_.util[tidx] = detail::attr_num(tr[0],
"utilization");
569 raw_.phase1util[tidx] = detail::attr_num(tr[0],
"phase1-utilization");
570 raw_.phase2util[tidx] = detail::attr_num(tr[0],
"phase2-utilization");
571 raw_.tput[tidx] = task_tput;
572 raw_.procutil[tidx] = detail::attr_num(tr[0],
"proc-utilization");
576 for (
const xml::Element* ee : te->by_tag(
"entry")) {
578 const std::vector<const xml::Element*> er = ee->by_tag(
"result-entry");
579 double entry_tput = nan;
581 entry_tput = detail::attr_num(er[0],
"throughput");
583 raw_.util[eidx] = detail::attr_num(er[0],
"utilization");
584 raw_.phase1util[eidx] = detail::attr_num(er[0],
"phase1-utilization");
585 raw_.phase2util[eidx] = detail::attr_num(er[0],
"phase2-utilization");
586 raw_.phase1svct[eidx] = detail::attr_num(er[0],
"phase1-service-time");
587 raw_.phase2svct[eidx] = detail::attr_num(er[0],
"phase2-service-time");
588 raw_.tput[eidx] = entry_tput;
589 raw_.procutil[eidx] = detail::attr_num(er[0],
"proc-utilization");
594 const std::vector<const xml::Element*> epa =
595 ee->by_tag(
"entry-phase-activities");
596 if (epa.empty())
continue;
597 for (
const xml::Element* ae : epa[0]->by_tag(
"activity")) {
598 read_activity(ae, entry_tput,
true);
602 const std::vector<const xml::Element*> tal = te->by_tag(
"task-activities");
603 if (tal.empty())
continue;
604 for (
const xml::Element* ae : tal[0]->by_tag(
"activity")) {
608 if (ae->parent != tal[0])
continue;
609 read_activity(ae, task_tput,
false);
614 aggregate_entry_procutil();
615 aggregate_entry_svct();
641 void aggregate_entry_svct() {
642 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
643 const std::size_t eidx = sn_.eshift + e;
644 if (eidx >= sn_.actsof.size() || eidx >= raw_.phase1svct.size())
continue;
645 if (!std::isnan(raw_.phase1svct[eidx]))
continue;
646 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
647 if (acts.empty())
continue;
648 bool all_zero =
true;
649 for (std::size_t aidx : acts) {
650 if (aidx >= raw_.phase1svct.size() || std::isnan(raw_.phase1svct[aidx]) ||
651 raw_.phase1svct[aidx] != 0.0) {
656 if (all_zero) raw_.phase1svct[eidx] = 0.0;
673 void aggregate_entry_procutil() {
674 for (std::size_t e = 1; e <= sn_.nentries; ++e) {
675 const std::size_t eidx = sn_.eshift + e;
676 if (eidx >= sn_.actsof.size())
continue;
677 const std::vector<std::size_t>& acts = sn_.actsof[eidx];
678 if (acts.empty())
continue;
680 bool complete =
true;
681 for (std::size_t aidx : acts) {
682 if (aidx >= raw_.procutil.size() || std::isnan(raw_.procutil[aidx])) {
686 sum += raw_.procutil[aidx];
688 if (complete) raw_.procutil[eidx] = sum;
701 void read_activity(
const xml::Element* ae,
double owner_tput,
bool phase_form) {
702 const std::string aname = ae->attr(
"name");
704 const std::vector<const xml::Element*> ar = ae->by_tag(
"result-activity");
705 if (aidx && !ar.empty()) {
706 const xml::Element* r = ar[0];
707 raw_.util[aidx] = detail::attr_num(r,
"utilization");
708 raw_.phase1svct[aidx] = detail::attr_num(r,
"service-time");
709 raw_.procwaiting[aidx] = detail::attr_num(r,
"proc-waiting");
710 const double t = detail::attr_num(r,
"throughput");
714 raw_.tput[aidx] = std::isnan(t) && phase_form ? owner_tput : t;
715 const double pu = detail::attr_num(r,
"proc-utilization");
716 if (!std::isnan(pu)) {
717 raw_.procutil[aidx] = pu;
718 }
else if (phase_form) {
719 const double hd = detail::attr_num(ae,
"host-demand-mean");
720 if (!std::isnan(owner_tput) && !std::isnan(hd)) raw_.procutil[aidx] = owner_tput * hd;
724 read_calls(ae, aname,
"synch-call",
"=>");
725 read_calls(ae, aname,
"asynch-call",
"->");
728 void read_calls(
const xml::Element* ae,
const std::string& aname,
const char* tag,
730 for (
const xml::Element* ce : ae->by_tag(tag)) {
731 const std::size_t cidx = call_index_of(aname + arrow + ce->attr(
"dest"));
732 const std::vector<const xml::Element*> cr = ce->by_tag(
"result-call");
733 if (cidx && !cr.empty()) raw_.callwaiting[cidx] = detail::attr_num(cr[0],
"waiting");
737 lqn::LqnModel<T> model_;
739 lqn::LqnStruct<T> sn_;
741 std::map<std::pair<int, std::string>, std::size_t> byname_;
742 std::map<std::string, std::size_t> bycallname_;
743 double runtime_ = 0.0;
744 bool solved_ =
false;