220 const std::string& model_name = std::string(
"LQN"),
221 bool use_abstract_names =
false,
222 std::string* out_text = NULL) {
227 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
228 const detail::RawTask<T>& tk = m.
tasks[t];
229 if (!tk.linconrows.empty() || tk.lincon_A.rows() > 0)
231 "' declares an admission constraint, which the LQN XML schema "
232 "cannot express; it travels as JSON `admissionConstraints`");
236 "write_lqnx: a processor declares an admission constraint, which the LQN XML schema "
237 "cannot express; it travels as JSON `admissionConstraints`");
248 std::map<std::string, std::string> nm_host, nm_task, nm_entry, nm_act;
250 std::size_t tctr = 0, ectr = 0, actr = 0;
251 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
253 std::snprintf(buf,
sizeof(buf),
"P%zu", p + 1);
254 nm_host[m.
procs[p].name] = use_abstract_names ? buf : m.
procs[p].name;
255 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
256 if (m.
tasks[t].proc_slot != p)
continue;
257 std::snprintf(buf,
sizeof(buf),
"T%zu", ++tctr);
258 nm_task[m.
tasks[t].name] = use_abstract_names ? buf : m.
tasks[t].name;
259 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
260 if (m.
entries[e].task_slot != t)
continue;
261 std::snprintf(buf,
sizeof(buf),
"E%zu", ++ectr);
262 nm_entry[m.
entries[e].name] = use_abstract_names ? buf : m.
entries[e].name;
264 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
265 if (m.
acts[a].task_slot != t)
continue;
266 std::snprintf(buf,
sizeof(buf),
"A%zu", ++actr);
267 nm_act[m.
acts[a].name] = use_abstract_names ? buf : m.
acts[a].name;
275 auto lookup = [](
const std::map<std::string, std::string>& nm,
const std::string& raw,
276 const char* kind) ->
const std::string& {
277 const std::map<std::string, std::string>::const_iterator it = nm.find(raw);
279 throw InputError(
"write_lqnx: '" + raw +
"' is referenced as a " + kind +
280 " but no " + kind +
" declares it");
283 auto host_name = [&](
const std::string& r) ->
const std::string& {
284 return lookup(nm_host, r,
"processor");
286 auto task_name = [&](
const std::string& r) ->
const std::string& {
287 return lookup(nm_task, r,
"task");
289 auto entry_name = [&](
const std::string& r) ->
const std::string& {
290 return lookup(nm_entry, r,
"entry");
292 auto act_name = [&](
const std::string& r) ->
const std::string& {
293 return lookup(nm_act, r,
"activity");
296 const std::map<std::string, std::vector<std::string>> replies =
297 detail::reply_activities(m,
sn);
302 std::set<std::string> needs_reply;
303 for (std::size_t e = 1; e <=
sn.nentries; ++e) {
304 const std::size_t eidx =
sn.eshift + e;
305 bool wanted =
sn.issynccaller.any_col(eidx);
306 for (std::size_t c = 1; !wanted && c <=
sn.ncalls; ++c)
307 if (
sn.calltype[c] == CallType::FWD &&
sn.callpair_dst[c] == eidx) wanted =
true;
308 if (wanted) needs_reply.insert(
sn.names[eidx]);
312 root.
name =
"lqn-model";
313 root.
set_attr(
"xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance");
314 root.
set_attr(
"xsi:noNamespaceSchemaLocation",
"lqn.xsd");
317 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
318 const detail::RawProc& pr = m.
procs[p];
320 pe.
set_attr(
"name", host_name(pr.name));
322 if (pr.repl > 1.0) pe.
set_attr(
"replication", detail::lqnx_num(pr.repl));
323 if (pr.sched != SchedStrategy::INF) {
326 const double mult = std::isinf(pr.mult) ? 1.0 : pr.mult;
327 pe.
set_attr(
"multiplicity", detail::lqnx_num(mult));
329 if (pr.sched == SchedStrategy::PS && pr.quantum > 0.0)
330 pe.
set_attr(
"quantum", detail::lqnx_num(pr.quantum));
331 pe.
set_attr(
"speed-factor", detail::lqnx_num(pr.speed_factor));
333 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
334 if (m.
tasks[t].proc_slot != p)
continue;
335 const detail::RawTask<T>& tk = m.
tasks[t];
337 te.
set_attr(
"name", task_name(tk.name));
340 if (tk.priority != 0) te.
set_attr(
"priority", std::to_string(tk.priority));
341 if (tk.repl > 1.0) te.
set_attr(
"replication", detail::lqnx_num(tk.repl));
342 if (tk.sched != SchedStrategy::INF)
344 detail::lqnx_num(std::isinf(tk.mult) ? 1.0 : tk.mult));
346 if (tk.sched == SchedStrategy::REF) {
347 te.
set_attr(
"think-time", detail::lqnx_num(tk.thinktime.disabled ? 0.0 : think));
348 }
else if (!tk.thinktime.disabled && think > 0.0) {
349 report.
dropped.push_back(
"task '" + tk.name +
"' has a think time of " +
350 detail::lqnx_num(think) +
351 ", which the schema accepts on reference tasks only");
357 ce.
set_attr(
"items", std::to_string(tk.nitems));
358 ce.
set_attr(
"replacement", detail::replacement_to_lqnx(tk.replacestrat));
359 if (tk.retrieval) ce.
set_attr(
"retrieval",
"true");
360 for (std::size_t lv = 0; lv < tk.itemcap.size(); ++lv)
367 if (!tk.setuptime.disabled &&
373 if (!tk.delayofftime.disabled &&
382 for (std::size_t f = 0; f < tk.fanout.size(); ++f) {
384 fe.
set_attr(
"dest", task_name(tk.fanout[f].first));
385 fe.
set_attr(
"value", detail::lqnx_num(tk.fanout[f].second));
387 for (std::size_t f = 0; f < tk.fanin.size(); ++f) {
389 fe.
set_attr(
"source", task_name(tk.fanin[f].first));
390 fe.
set_attr(
"value", detail::lqnx_num(tk.fanin[f].second));
393 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
394 if (m.
entries[e].task_slot != t)
continue;
395 const detail::RawEntry<T>& en = m.
entries[e];
397 ee.
set_attr(
"name", entry_name(en.name));
399 if (en.has_arrival && !en.arrival.disabled) {
402 ee.
set_attr(
"open-arrival-rate", detail::lqnx_num(1.0 / mean));
406 if (en.cardinality > 0) {
408 ie.
set_attr(
"cardinality", std::to_string(en.cardinality));
409 if (!en.popularity.empty()) {
411 pe.
set_attr(
"name",
"DiscreteSampler");
412 for (std::size_t k = 0; k < en.popularity.size(); ++k)
414 .
set_attr(
"value", detail::lqnx_num_of<T>(en.popularity[k]));
417 for (std::size_t f = 0; f < en.fwd_dest.size(); ++f) {
419 fe.
set_attr(
"dest", entry_name(en.fwd_dest[f]));
420 fe.
set_attr(
"prob", detail::lqnx_num_of<T>(en.fwd_prob[f]));
425 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
426 if (m.
acts[a].task_slot != t)
continue;
427 const detail::RawActivity<T>& ac = m.
acts[a];
430 detail::lqnx_num(ac.hostdem.disabled
434 detail::lqnx_num(ac.hostdem.disabled
437 if (!ac.bound_to_entry.empty())
438 ae.
set_attr(
"bound-to-entry", entry_name(ac.bound_to_entry));
439 ae.
set_attr(
"call-order", ac.call_order);
440 ae.
set_attr(
"name", act_name(ac.name));
443 ae.
set_attr(
"think-time", detail::lqnx_num(athink));
444 for (std::size_t c = 0; c < ac.sync_calls.size(); ++c) {
446 ce.
set_attr(
"dest", entry_name(ac.sync_calls[c].dest));
447 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.sync_calls[c].mean));
449 for (std::size_t c = 0; c < ac.async_calls.size(); ++c) {
451 ce.
set_attr(
"dest", entry_name(ac.async_calls[c].dest));
452 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.async_calls[c].mean));
459 for (std::size_t g = 0; g < ac.call_groups.size(); ++g) {
461 ge.
set_attr(
"strategy", detail::callgroup_to_lqnx(ac.call_groups[g].first));
462 for (std::size_t d = 0; d < ac.call_groups[g].second.size(); ++d)
464 entry_name(ac.call_groups[g].second[d]));
468 for (std::size_t q = 0; q < tk.precedences.size(); ++q) {
469 const detail::RawPrecedence<T>& pc = tk.precedences[q];
473 if (pc.pretype == PrecedenceType::PRE_AND && pc.has_quorum)
474 pre.
set_attr(
"quorum", detail::lqnx_num(
static_cast<double>(pc.quorum)));
475 for (std::size_t i = 0; i < pc.preacts.size(); ++i)
479 if (pc.posttype == PrecedenceType::POST_OR) {
480 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
482 ae.
set_attr(
"name", act_name(pc.postacts[i]));
483 if (i < pc.postparams.size())
484 ae.
set_attr(
"prob", detail::lqnx_num_of<T>(pc.postparams[i]));
486 }
else if (pc.posttype == PrecedenceType::POST_LOOP) {
490 if (pc.postacts.empty())
491 throw InputError(
"write_lqnx: a post-LOOP names no activity");
492 for (std::size_t i = 0; i + 1 < pc.postacts.size(); ++i) {
494 ae.
set_attr(
"name", act_name(pc.postacts[i]));
495 if (i < pc.postparams.size())
496 ae.
set_attr(
"count", detail::lqnx_num_of<T>(pc.postparams[i]));
498 post.
set_attr(
"end", act_name(pc.postacts.back()));
499 }
else if (pc.posttype == PrecedenceType::POST_CACHE) {
504 static const char*
const kResult[2] = {
"hit",
"miss"};
505 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
507 ae.
set_attr(
"name", act_name(pc.postacts[i]));
508 if (i < 2) ae.
set_attr(
"cache-result", kResult[i]);
511 for (std::size_t i = 0; i < pc.postacts.size(); ++i)
516 if (tk.sched != SchedStrategy::REF) {
517 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
518 if (m.
entries[e].task_slot != t)
continue;
519 const std::string& ename = m.
entries[e].name;
520 if (!needs_reply.count(ename))
continue;
521 const std::map<std::string, std::vector<std::string>>::const_iterator it =
523 if (it == replies.end() || it->second.empty())
525 "write_lqnx: entry '" + ename +
526 "' is called synchronously but no activity replies to it, and none "
527 "can be inferred; declare one with replies_to()");
529 re.
set_attr(
"name", entry_name(ename));
530 for (std::size_t r = 0; r < it->second.size(); ++r)
537 if (out_text != NULL)