221 const std::string& model_name = std::string(
"LQN"),
222 bool use_abstract_names =
false,
223 std::string* out_text = NULL) {
228 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
229 const detail::RawTask<T>& tk = m.
tasks[t];
230 if (!tk.linconrows.empty() || tk.lincon_A.rows() > 0)
232 "' declares an admission constraint, which the LQN XML schema "
233 "cannot express; it travels as JSON `admissionConstraints`");
237 "write_lqnx: a processor declares an admission constraint, which the LQN XML schema "
238 "cannot express; it travels as JSON `admissionConstraints`");
242 const auto dropped_server = [&](
const std::string& kind,
const std::string& name,
bool pools,
bool rate) {
244 report.
dropped.push_back(kind +
" " + name +
" declares heterogeneous server pools with a "
245 "class-compatibility graph, which the LQN XML schema cannot represent: they are "
246 "omitted from " + path +
" and the " + (kind ==
"Host" ?
"host" :
"task") +
247 " reads back as a homogeneous multiserver.");
249 report.
dropped.push_back(kind +
" " + name +
" declares a service-rate dependence, which the LQN XML "
250 "schema cannot represent: it is omitted from " + path +
".");
252 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
257 dropped_server(
"Host", m.
procs[p].name, pl != m.
proc_pools.end() && !pl->second.empty(),
259 (cd != m.
proc_cdscaling.end() &&
static_cast<bool>(cd->second)) ||
262 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
263 const detail::RawTask<T>& tk = m.
tasks[t];
264 dropped_server(
"Task", tk.name, !tk.pools.empty(),
265 !tk.lldscaling.empty() ||
static_cast<bool>(tk.cdscaling) ||
static_cast<bool>(tk.jdscaling));
277 std::map<std::string, std::string> nm_host, nm_task, nm_entry, nm_act;
279 std::size_t tctr = 0, ectr = 0, actr = 0;
280 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
282 std::snprintf(buf,
sizeof(buf),
"P%zu", p + 1);
283 nm_host[m.
procs[p].name] = use_abstract_names ? buf : m.
procs[p].name;
284 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
285 if (m.
tasks[t].proc_slot != p)
continue;
286 std::snprintf(buf,
sizeof(buf),
"T%zu", ++tctr);
287 nm_task[m.
tasks[t].name] = use_abstract_names ? buf : m.
tasks[t].name;
288 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
289 if (m.
entries[e].task_slot != t)
continue;
290 std::snprintf(buf,
sizeof(buf),
"E%zu", ++ectr);
291 nm_entry[m.
entries[e].name] = use_abstract_names ? buf : m.
entries[e].name;
293 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
294 if (m.
acts[a].task_slot != t)
continue;
295 std::snprintf(buf,
sizeof(buf),
"A%zu", ++actr);
296 nm_act[m.
acts[a].name] = use_abstract_names ? buf : m.
acts[a].name;
304 auto lookup = [](
const std::map<std::string, std::string>& nm,
const std::string& raw,
305 const char* kind) ->
const std::string& {
306 const std::map<std::string, std::string>::const_iterator it = nm.find(raw);
308 throw InputError(
"write_lqnx: '" + raw +
"' is referenced as a " + kind +
309 " but no " + kind +
" declares it");
312 auto host_name = [&](
const std::string& r) ->
const std::string& {
313 return lookup(nm_host, r,
"processor");
315 auto task_name = [&](
const std::string& r) ->
const std::string& {
316 return lookup(nm_task, r,
"task");
318 auto entry_name = [&](
const std::string& r) ->
const std::string& {
319 return lookup(nm_entry, r,
"entry");
321 auto act_name = [&](
const std::string& r) ->
const std::string& {
322 return lookup(nm_act, r,
"activity");
325 const std::map<std::string, std::vector<std::string>> replies =
326 detail::reply_activities(m,
sn);
331 std::set<std::string> needs_reply;
332 for (std::size_t e = 1; e <=
sn.nentries; ++e) {
333 const std::size_t eidx =
sn.eshift + e;
334 bool wanted =
sn.issynccaller.any_col(eidx);
335 for (std::size_t c = 1; !wanted && c <=
sn.ncalls; ++c)
336 if (
sn.calltype[c] == CallType::FWD &&
sn.callpair_dst[c] == eidx) wanted =
true;
337 if (wanted) needs_reply.insert(
sn.names[eidx]);
341 auto write_activity = [&](
xml::Element& parent,
const detail::RawActivity<T>& ac,
bool in_phase) {
344 detail::lqnx_num(ac.hostdem.disabled
348 detail::lqnx_num(ac.hostdem.disabled
351 if (!in_phase && !ac.bound_to_entry.empty())
352 ae.
set_attr(
"bound-to-entry", entry_name(ac.bound_to_entry));
353 ae.
set_attr(
"call-order", ac.call_order);
354 ae.
set_attr(
"name", act_name(ac.name));
355 if (in_phase) ae.
set_attr(
"phase", std::to_string(ac.phase));
358 ae.
set_attr(
"think-time", detail::lqnx_num(athink));
359 for (std::size_t c = 0; c < ac.sync_calls.size(); ++c) {
361 ce.
set_attr(
"dest", entry_name(ac.sync_calls[c].dest));
362 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.sync_calls[c].mean));
364 for (std::size_t c = 0; c < ac.async_calls.size(); ++c) {
366 ce.
set_attr(
"dest", entry_name(ac.async_calls[c].dest));
367 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.async_calls[c].mean));
374 for (std::size_t g = 0; g < ac.call_groups.size(); ++g) {
376 ge.
set_attr(
"strategy", detail::callgroup_to_lqnx(ac.call_groups[g].first));
377 for (std::size_t d = 0; d < ac.call_groups[g].second.size(); ++d)
379 entry_name(ac.call_groups[g].second[d]));
384 root.
name =
"lqn-model";
385 root.
set_attr(
"xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance");
386 root.
set_attr(
"xsi:noNamespaceSchemaLocation",
"lqn.xsd");
389 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
390 const detail::RawProc& pr = m.
procs[p];
392 pe.
set_attr(
"name", host_name(pr.name));
394 if (pr.repl > 1.0) pe.
set_attr(
"replication", detail::lqnx_num(pr.repl));
395 if (pr.sched != SchedStrategy::INF) {
398 const double mult = std::isinf(pr.mult) ? 1.0 : pr.mult;
399 pe.
set_attr(
"multiplicity", detail::lqnx_num(mult));
402 if (pr.sched == SchedStrategy::PS || pr.sched == SchedStrategy::PSPRIO)
403 pe.
set_attr(
"quantum", detail::lqnx_num(pr.quantum));
404 pe.
set_attr(
"speed-factor", detail::lqnx_num(pr.speed_factor));
406 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
407 if (m.
tasks[t].proc_slot != p)
continue;
408 const detail::RawTask<T>& tk = m.
tasks[t];
410 te.
set_attr(
"name", task_name(tk.name));
413 if (tk.priority != 0) te.
set_attr(
"priority", std::to_string(tk.priority));
414 if (tk.repl > 1.0) te.
set_attr(
"replication", detail::lqnx_num(tk.repl));
415 if (tk.sched != SchedStrategy::INF)
417 detail::lqnx_num(std::isinf(tk.mult) ? 1.0 : tk.mult));
419 if (tk.sched == SchedStrategy::REF) {
420 te.
set_attr(
"think-time", detail::lqnx_num(tk.thinktime.disabled ? 0.0 : think));
421 }
else if (!tk.thinktime.disabled && think > 0.0) {
422 report.
dropped.push_back(
"task '" + tk.name +
"' has a think time of " +
423 detail::lqnx_num(think) +
424 ", which the schema accepts on reference tasks only");
430 ce.
set_attr(
"items", std::to_string(tk.nitems));
431 ce.
set_attr(
"replacement", detail::replacement_to_lqnx(tk.replacestrat));
432 if (tk.retrieval) ce.
set_attr(
"retrieval",
"true");
433 for (std::size_t lv = 0; lv < tk.itemcap.size(); ++lv)
440 if (!tk.setuptime.disabled &&
446 if (!tk.delayofftime.disabled &&
455 for (std::size_t f = 0; f < tk.fanout.size(); ++f) {
457 fe.
set_attr(
"dest", task_name(tk.fanout[f].first));
458 fe.
set_attr(
"value", detail::lqnx_num(tk.fanout[f].second));
460 for (std::size_t f = 0; f < tk.fanin.size(); ++f) {
462 fe.
set_attr(
"source", task_name(tk.fanin[f].first));
463 fe.
set_attr(
"value", detail::lqnx_num(tk.fanin[f].second));
470 std::map<std::string, std::vector<std::size_t>> phase_chains;
471 std::set<std::size_t> phase_acts, phase_precs;
473 std::map<std::string, std::size_t> slot_of;
474 for (std::size_t a = 0; a < m.
acts.size(); ++a)
475 if (m.
acts[a].task_slot == t) slot_of[m.
acts[a].name] = a;
476 auto is_seq = [&](
const detail::RawPrecedence<T>& pc) {
477 return pc.pretype == PrecedenceType::PRE_SEQ && pc.posttype == PrecedenceType::POST_SEQ &&
478 pc.preacts.size() == 1 && pc.postacts.size() == 1;
480 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
481 if (m.
entries[e].task_slot != t)
continue;
482 const detail::RawEntry<T>& en = m.
entries[e];
483 std::vector<std::size_t> bound;
484 for (std::size_t a = 0; a < m.
acts.size(); ++a)
485 if (m.
acts[a].task_slot == t && m.
acts[a].bound_to_entry == en.name) bound.push_back(a);
486 if (bound.size() != 1)
continue;
487 std::vector<std::size_t> chain(1, bound[0]);
488 std::set<std::size_t> chain_precs;
489 for (
bool grew =
true; grew;) {
491 for (std::size_t q = 0; q < tk.precedences.size(); ++q) {
492 const detail::RawPrecedence<T>& pc = tk.precedences[q];
493 if (chain_precs.count(q) || !is_seq(pc) || pc.preacts[0] != m.
acts[chain.back()].name)
495 const std::map<std::string, std::size_t>::const_iterator nx = slot_of.find(pc.postacts[0]);
496 if (nx == slot_of.end() || std::find(chain.begin(), chain.end(), nx->second) != chain.end())
498 chain.push_back(nx->second);
499 chain_precs.insert(q);
504 bool ok = m.
acts[chain.back()].phase >= 2 && m.
acts[chain.back()].phase <= 3;
505 for (std::size_t i = 1; ok && i < chain.size(); ++i)
506 ok = m.
acts[chain[i]].phase > m.
acts[chain[i - 1]].phase &&
507 m.
acts[chain[i]].bound_to_entry.empty();
508 std::set<std::string> names;
509 for (std::size_t a : chain) names.insert(m.
acts[a].name);
510 for (std::size_t q = 0; ok && q < tk.precedences.size(); ++q) {
511 if (chain_precs.count(q))
continue;
512 const detail::RawPrecedence<T>& pc = tk.precedences[q];
513 for (
const std::string& nmv : pc.preacts) ok = ok && !names.count(nmv);
514 for (
const std::string& nmv : pc.postacts) ok = ok && !names.count(nmv);
517 bool replied =
false;
518 for (std::size_t e2 = 0; ok && e2 < m.
entries.size(); ++e2)
519 for (
const std::string& r : m.
entries[e2].reply_activities) {
520 if (!names.count(r) && e2 != e)
continue;
521 if (e2 != e || r != m.
acts[chain[0]].name) ok =
false;
524 if (ok && !replied && tk.sched != SchedStrategy::REF && m.
acts[chain[0]].phase == 1) ok =
false;
526 phase_chains[en.name] = chain;
527 phase_acts.insert(chain.begin(), chain.end());
528 phase_precs.insert(chain_precs.begin(), chain_precs.end());
532 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
533 if (m.
entries[e].task_slot != t)
continue;
534 const detail::RawEntry<T>& en = m.
entries[e];
536 ee.
set_attr(
"name", entry_name(en.name));
537 ee.
set_attr(
"type", phase_chains.count(en.name) ?
"PH1PH2" :
"NONE");
538 if (en.has_arrival && !en.arrival.disabled) {
541 ee.
set_attr(
"open-arrival-rate", detail::lqnx_num(1.0 / mean));
545 if (en.cardinality > 0) {
547 ie.
set_attr(
"cardinality", std::to_string(en.cardinality));
548 if (!en.popularity.empty()) {
550 pe.
set_attr(
"name",
"DiscreteSampler");
551 for (std::size_t k = 0; k < en.popularity.size(); ++k)
553 .
set_attr(
"value", detail::lqnx_num_of<T>(en.popularity[k]));
556 for (std::size_t f = 0; f < en.fwd_dest.size(); ++f) {
558 fe.
set_attr(
"dest", entry_name(en.fwd_dest[f]));
559 fe.
set_attr(
"prob", detail::lqnx_num_of<T>(en.fwd_prob[f]));
561 const typename std::map<std::string, std::vector<std::size_t>>::const_iterator ch =
562 phase_chains.find(en.name);
563 if (ch != phase_chains.end()) {
565 for (std::size_t a : ch->second) write_activity(epa, m.
acts[a],
true);
570 bool any_task_act =
false;
571 for (std::size_t a = 0; a < m.
acts.size(); ++a)
572 if (m.
acts[a].task_slot == t && !phase_acts.count(a)) any_task_act =
true;
573 for (std::size_t e = 0; !any_task_act && e < m.
entries.size(); ++e)
574 if (m.
entries[e].task_slot == t && !phase_chains.count(m.
entries[e].name)) any_task_act =
true;
575 if (!any_task_act)
continue;
577 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
578 if (m.
acts[a].task_slot != t || phase_acts.count(a))
continue;
579 write_activity(ta, m.
acts[a],
false);
582 for (std::size_t q = 0; q < tk.precedences.size(); ++q) {
583 if (phase_precs.count(q))
continue;
584 const detail::RawPrecedence<T>& pc = tk.precedences[q];
589 if (pc.pretype == PrecedenceType::PRE_AND && pc.has_quorum && pc.quorum >= 1 && pc.quorum < pc.preacts.size())
590 pre.
set_attr(
"quorum", detail::lqnx_num(
static_cast<double>(pc.quorum)));
591 for (std::size_t i = 0; i < pc.preacts.size(); ++i)
595 if (pc.posttype == PrecedenceType::POST_OR) {
596 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
598 ae.
set_attr(
"name", act_name(pc.postacts[i]));
599 if (i < pc.postparams.size())
600 ae.
set_attr(
"prob", detail::lqnx_num_of<T>(pc.postparams[i]));
602 }
else if (pc.posttype == PrecedenceType::POST_LOOP) {
606 if (pc.postacts.empty())
607 throw InputError(
"write_lqnx: a post-LOOP names no activity");
608 for (std::size_t i = 0; i + 1 < pc.postacts.size(); ++i) {
610 ae.
set_attr(
"name", act_name(pc.postacts[i]));
611 if (i < pc.postparams.size())
612 ae.
set_attr(
"count", detail::lqnx_num_of<T>(pc.postparams[i]));
614 post.
set_attr(
"end", act_name(pc.postacts.back()));
615 }
else if (pc.posttype == PrecedenceType::POST_CACHE) {
620 static const char*
const kResult[2] = {
"hit",
"miss"};
621 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
623 ae.
set_attr(
"name", act_name(pc.postacts[i]));
624 if (i < 2) ae.
set_attr(
"cache-result", kResult[i]);
627 for (std::size_t i = 0; i < pc.postacts.size(); ++i)
632 if (tk.sched != SchedStrategy::REF) {
633 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
634 if (m.
entries[e].task_slot != t)
continue;
635 const std::string& ename = m.
entries[e].name;
636 if (!needs_reply.count(ename) || phase_chains.count(ename))
continue;
637 const std::map<std::string, std::vector<std::string>>::const_iterator it =
639 if (it == replies.end() || it->second.empty())
641 "write_lqnx: entry '" + ename +
642 "' is called synchronously but no activity replies to it, and none "
643 "can be inferred; declare one with replies_to()");
645 re.set_attr(
"name", entry_name(ename));
646 for (std::size_t r = 0; r < it->second.size(); ++r)
647 re.add_child(
"reply-activity").set_attr(
"name", act_name(it->second[r]));
653 if (out_text != NULL)