199 const std::string& model_name = std::string(
"LQN"),
200 bool use_abstract_names =
false) {
205 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
206 const detail::RawTask<T>& tk = m.
tasks[t];
209 "' is a CacheTask, which the LQN XML schema cannot express; "
210 "solve it with SolverLN, which models the cache directly");
211 if (!tk.linconrows.empty() || tk.lincon_A.rows() > 0)
213 "' declares an admission constraint, which the LQN XML schema "
214 "cannot express; it travels as JSON `admissionConstraints`");
216 for (std::size_t e = 0; e < m.
entries.size(); ++e)
217 if (m.
entries[e].cardinality > 0)
219 "' is an ItemEntry, which the LQN XML schema cannot express");
222 "write_lqnx: a processor declares an admission constraint, which the LQN XML schema "
223 "cannot express; it travels as JSON `admissionConstraints`");
234 std::map<std::string, std::string> nm_host, nm_task, nm_entry, nm_act;
236 std::size_t tctr = 0, ectr = 0, actr = 0;
237 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
239 std::snprintf(buf,
sizeof(buf),
"P%zu", p + 1);
240 nm_host[m.
procs[p].name] = use_abstract_names ? buf : m.
procs[p].name;
241 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
242 if (m.
tasks[t].proc_slot != p)
continue;
243 std::snprintf(buf,
sizeof(buf),
"T%zu", ++tctr);
244 nm_task[m.
tasks[t].name] = use_abstract_names ? buf : m.
tasks[t].name;
245 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
246 if (m.
entries[e].task_slot != t)
continue;
247 std::snprintf(buf,
sizeof(buf),
"E%zu", ++ectr);
248 nm_entry[m.
entries[e].name] = use_abstract_names ? buf : m.
entries[e].name;
250 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
251 if (m.
acts[a].task_slot != t)
continue;
252 std::snprintf(buf,
sizeof(buf),
"A%zu", ++actr);
253 nm_act[m.
acts[a].name] = use_abstract_names ? buf : m.
acts[a].name;
261 auto lookup = [](
const std::map<std::string, std::string>& nm,
const std::string& raw,
262 const char* kind) ->
const std::string& {
263 const std::map<std::string, std::string>::const_iterator it = nm.find(raw);
265 throw InputError(
"write_lqnx: '" + raw +
"' is referenced as a " + kind +
266 " but no " + kind +
" declares it");
269 auto host_name = [&](
const std::string& r) ->
const std::string& {
270 return lookup(nm_host, r,
"processor");
272 auto task_name = [&](
const std::string& r) ->
const std::string& {
273 return lookup(nm_task, r,
"task");
275 auto entry_name = [&](
const std::string& r) ->
const std::string& {
276 return lookup(nm_entry, r,
"entry");
278 auto act_name = [&](
const std::string& r) ->
const std::string& {
279 return lookup(nm_act, r,
"activity");
282 const std::map<std::string, std::vector<std::string>> replies =
283 detail::reply_activities(m,
sn);
288 std::set<std::string> needs_reply;
289 for (std::size_t e = 1; e <=
sn.nentries; ++e) {
290 const std::size_t eidx =
sn.eshift + e;
291 bool wanted =
sn.issynccaller.any_col(eidx);
292 for (std::size_t c = 1; !wanted && c <=
sn.ncalls; ++c)
293 if (
sn.calltype[c] == CallType::FWD &&
sn.callpair_dst[c] == eidx) wanted =
true;
294 if (wanted) needs_reply.insert(
sn.names[eidx]);
298 root.
name =
"lqn-model";
299 root.
set_attr(
"xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance");
300 root.
set_attr(
"xsi:noNamespaceSchemaLocation",
"lqn.xsd");
303 for (std::size_t p = 0; p < m.
procs.size(); ++p) {
304 const detail::RawProc& pr = m.
procs[p];
306 pe.
set_attr(
"name", host_name(pr.name));
308 if (pr.repl > 1.0) pe.
set_attr(
"replication", detail::lqnx_num(pr.repl));
309 if (pr.sched != SchedStrategy::INF) {
312 const double mult = std::isinf(pr.mult) ? 1.0 : pr.mult;
313 pe.
set_attr(
"multiplicity", detail::lqnx_num(mult));
315 if (pr.sched == SchedStrategy::PS && pr.quantum > 0.0)
316 pe.
set_attr(
"quantum", detail::lqnx_num(pr.quantum));
317 pe.
set_attr(
"speed-factor", detail::lqnx_num(pr.speed_factor));
319 for (std::size_t t = 0; t < m.
tasks.size(); ++t) {
320 if (m.
tasks[t].proc_slot != p)
continue;
321 const detail::RawTask<T>& tk = m.
tasks[t];
323 te.
set_attr(
"name", task_name(tk.name));
325 if (tk.repl > 1.0) te.
set_attr(
"replication", detail::lqnx_num(tk.repl));
326 if (tk.sched != SchedStrategy::INF)
328 detail::lqnx_num(std::isinf(tk.mult) ? 1.0 : tk.mult));
330 if (tk.sched == SchedStrategy::REF) {
331 te.
set_attr(
"think-time", detail::lqnx_num(tk.thinktime.disabled ? 0.0 : think));
332 }
else if (!tk.thinktime.disabled && think > 0.0) {
333 report.
dropped.push_back(
"task '" + tk.name +
"' has a think time of " +
334 detail::lqnx_num(think) +
335 ", which the schema accepts on reference tasks only");
341 if (!tk.setuptime.disabled &&
347 if (!tk.delayofftime.disabled &&
356 for (std::size_t f = 0; f < tk.fanout.size(); ++f) {
358 fe.
set_attr(
"dest", task_name(tk.fanout[f].first));
359 fe.
set_attr(
"value", detail::lqnx_num(tk.fanout[f].second));
361 for (std::size_t f = 0; f < tk.fanin.size(); ++f) {
363 fe.
set_attr(
"source", task_name(tk.fanin[f].first));
364 fe.
set_attr(
"value", detail::lqnx_num(tk.fanin[f].second));
367 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
368 if (m.
entries[e].task_slot != t)
continue;
369 const detail::RawEntry<T>& en = m.
entries[e];
371 ee.
set_attr(
"name", entry_name(en.name));
373 if (en.has_arrival && !en.arrival.disabled) {
376 ee.
set_attr(
"open-arrival-rate", detail::lqnx_num(1.0 / mean));
378 for (std::size_t f = 0; f < en.fwd_dest.size(); ++f) {
380 fe.
set_attr(
"dest", entry_name(en.fwd_dest[f]));
381 fe.
set_attr(
"prob", detail::lqnx_num_of<T>(en.fwd_prob[f]));
386 for (std::size_t a = 0; a < m.
acts.size(); ++a) {
387 if (m.
acts[a].task_slot != t)
continue;
388 const detail::RawActivity<T>& ac = m.
acts[a];
391 detail::lqnx_num(ac.hostdem.disabled
395 detail::lqnx_num(ac.hostdem.disabled
398 if (!ac.bound_to_entry.empty())
399 ae.
set_attr(
"bound-to-entry", entry_name(ac.bound_to_entry));
400 ae.
set_attr(
"call-order",
"STOCHASTIC");
401 ae.
set_attr(
"name", act_name(ac.name));
404 ae.
set_attr(
"think-time", detail::lqnx_num(athink));
405 for (std::size_t c = 0; c < ac.sync_calls.size(); ++c) {
407 ce.
set_attr(
"dest", entry_name(ac.sync_calls[c].dest));
408 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.sync_calls[c].mean));
410 for (std::size_t c = 0; c < ac.async_calls.size(); ++c) {
412 ce.
set_attr(
"dest", entry_name(ac.async_calls[c].dest));
413 ce.
set_attr(
"calls-mean", detail::lqnx_num_of<T>(ac.async_calls[c].mean));
420 for (std::size_t g = 0; g < ac.call_groups.size(); ++g) {
422 ge.
set_attr(
"strategy", detail::callgroup_to_lqnx(ac.call_groups[g].first));
423 for (std::size_t d = 0; d < ac.call_groups[g].second.size(); ++d)
425 entry_name(ac.call_groups[g].second[d]));
429 for (std::size_t q = 0; q < tk.precedences.size(); ++q) {
430 const detail::RawPrecedence<T>& pc = tk.precedences[q];
434 if (pc.pretype == PrecedenceType::PRE_AND && pc.has_quorum)
435 pre.
set_attr(
"quorum", detail::lqnx_num(
static_cast<double>(pc.quorum)));
436 for (std::size_t i = 0; i < pc.preacts.size(); ++i)
440 if (pc.posttype == PrecedenceType::POST_OR) {
441 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
443 ae.
set_attr(
"name", act_name(pc.postacts[i]));
444 if (i < pc.postparams.size())
445 ae.
set_attr(
"prob", detail::lqnx_num_of<T>(pc.postparams[i]));
447 }
else if (pc.posttype == PrecedenceType::POST_LOOP) {
451 if (pc.postacts.empty())
452 throw InputError(
"write_lqnx: a post-LOOP names no activity");
453 for (std::size_t i = 0; i + 1 < pc.postacts.size(); ++i) {
455 ae.
set_attr(
"name", act_name(pc.postacts[i]));
456 if (i < pc.postparams.size())
457 ae.
set_attr(
"count", detail::lqnx_num_of<T>(pc.postparams[i]));
459 post.
set_attr(
"end", act_name(pc.postacts.back()));
460 }
else if (pc.posttype == PrecedenceType::POST_CACHE) {
465 static const char*
const kResult[2] = {
"hit",
"miss"};
466 for (std::size_t i = 0; i < pc.postacts.size(); ++i) {
468 ae.
set_attr(
"name", act_name(pc.postacts[i]));
469 if (i < 2) ae.
set_attr(
"cache-result", kResult[i]);
472 for (std::size_t i = 0; i < pc.postacts.size(); ++i)
477 if (tk.sched != SchedStrategy::REF) {
478 for (std::size_t e = 0; e < m.
entries.size(); ++e) {
479 if (m.
entries[e].task_slot != t)
continue;
480 const std::string& ename = m.
entries[e].name;
481 if (!needs_reply.count(ename))
continue;
482 const std::map<std::string, std::vector<std::string>>::const_iterator it =
484 if (it == replies.end() || it->second.empty())
486 "write_lqnx: entry '" + ename +
487 "' is called synchronously but no activity replies to it, and none "
488 "can be inferred; declare one with replies_to()");
490 re.
set_attr(
"name", entry_name(ename));
491 for (std::size_t r = 0; r < it->second.size(); ++r)