LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
wfcommons_loader.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_IO_WFCOMMONS_LOADER_H
6#define LINE_IO_WFCOMMONS_LOADER_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Port of `matlab/src/io/WfCommonsLoader.m` (and `jline.io.WfCommonsLoader` /
12 * `WfCommonsOptions`): a WfCommons workflow trace
13 * (https://github.com/wfcommons/workflow-schema, schema 1.3 to 1.5) read into a
14 * `workflow::Workflow<T>`.
15 *
16 * WHAT IS BUILT. One activity per task, named by the task's `id` (its `name`
17 * when there is no `id`, `task_<i>` when neither), with a host demand fitted to
18 * the task's `runtimeInSeconds` from the execution data: embedded in the task
19 * for schema 1.4 and earlier, under `workflow.execution.tasks` for 1.5+. The
20 * edges are the union of the `children` (task -> child) and `parents` (parent ->
21 * task) lists, each pair once and children-derived edges first, so a trace that
22 * lists parents only (Montage dss) gives the same graph. They become precedences
23 * in two passes, exactly as the reference:
24 * a task with several children whose children ALL lead directly to one common
25 * descendant becomes an AND-fork / AND-join pair, and every edge left over
26 * becomes a serial precedence.
27 *
28 * CHILDREN THAT NAME A TASK BY `name` WHEN THE TASKS CARRY AN `id`. The Pegasus
29 * traces of schema 1.4 do this: task `mProject_ID0000001` has
30 * `"id": "ID0000001"` and lists its children as `"mBackground_ID0000013"`. The
31 * reference keys the lookup on the id alone, so EVERY edge of such a trace is
32 * dropped without a word and the workflow reduces to its tasks in series (the
33 * JAR on montage-chameleon-2mass-005d-001.json: 58 activities, 0 precedences).
34 * `WfCommonsOptions::resolve_children_by_name` (default true) looks a child that
35 * matches no id up among the task names; a child matching neither is dropped,
36 * as in the reference. Parent references resolve by the same rule, and one
37 * warning counts the unresolved references of both kinds. Set it false for the
38 * id-only lookup. On a document whose children are ids, which is every 1.5
39 * document, the two agree.
40 *
41 * `loadFromUrl` fetches http:// over `line/util/http.h`, the port's own HTTP/1.1
42 * client, which has no TLS and follows no redirects. An https:// URL, which is
43 * how the WfCommons repositories are served and what MATLAB's `webread` takes,
44 * is handed to `curl` (else `wget`) on PATH, as SolverJMT fetches JMT.jar: no
45 * TLS library becomes a build dependency, and a host with neither tool is told
46 * so by name. The fetch stays on https across redirects.
47 *
48 * WARNINGS go to stderr with the `[LINE] Warning:` prefix the io layer uses;
49 * the port has no `line_warning` channel.
50 */
51
52#include <algorithm>
53#include <cctype>
54#include <cmath>
55#include <cstdlib>
56#include <deque>
57#include <fstream>
58#include <iostream>
59#include <iterator>
60#include <map>
61#include <sstream>
62#include <string>
63#include <vector>
64
65#include "json.hpp"
69#include "line/num/number.h"
70#include "line/util/error.h"
71#include "line/util/http.h"
73#include "line/util/tempdir.h"
74
75namespace line {
76namespace io {
77
78/** `options.distributionType`, the JAR's `WfCommonsOptions.DistributionType`. */
80
81/**
82 * `lower(options.distributionType)` as the reference switches on it: `exp`, `det`,
83 * `aph`, `hyperexp`. Anything else is EXP, the reference's `otherwise` branch.
84 */
86 std::string l(s);
87 for (char& c : l) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
88 if (l == "det") return WfDistributionType::DET;
89 if (l == "aph") return WfDistributionType::APH;
90 if (l == "hyperexp") return WfDistributionType::HYPEREXP;
92}
93
94/** The loader options; the defaults are the reference's `parseOptions`. */
97 double default_scv = 1.0; ///< `defaultSCV`, for APH and HyperExp
98 double default_runtime = 1.0; ///< `defaultRuntime`, when a task has no runtime
99 bool use_execution_data = true; ///< `useExecutionData`
100 bool store_metadata = true; ///< `storeMetadata`, onto `WorkflowActivity::metadata()`
101 /** Resolve a child matching no task id by task name (see the file comment). */
103};
104
106
107using json = nlohmann::json;
108
109/** `WfCommonsLoader.SUPPORTED_SCHEMA_VERSIONS`. */
110inline bool supported_version(const std::string& v) {
111 return v == "1.3" || v == "1.4" || v == "1.5";
112}
113
114inline bool has(const json& o, const char* key) { return o.is_object() && o.contains(key); }
115
116/** `~isempty(x)` on a decoded JSON value. */
117inline bool nonempty(const json& v) {
118 if (v.is_null()) return false;
119 if (v.is_array() || v.is_object() || v.is_string()) return !v.empty();
120 return true;
121}
122
123/** A JSON scalar as the text `strcmp` compares: strings verbatim, numbers as written. */
124inline std::string as_text(const json& v) { return v.is_string() ? v.get<std::string>() : v.dump(); }
125
126/** Port of `validateSchema`. */
127inline void validate_schema(const json& data) {
128 if (!has(data, "schemaVersion"))
129 throw InputError("WfCommonsLoader: Missing schemaVersion field in WfCommons JSON.");
130 const std::string version = as_text(data.at("schemaVersion"));
131 if (!supported_version(version))
132 std::cerr << "[LINE] Warning: WfCommonsLoader: Schema version " << version
133 << " may not be fully supported." << std::endl;
134 if (!has(data, "workflow"))
135 throw InputError("WfCommonsLoader: Missing workflow field in WfCommons JSON.");
136 const json& wf = data.at("workflow");
137 bool has_tasks = false;
138 if (has(wf, "specification")) {
139 const json& spec = wf.at("specification");
140 has_tasks = has(spec, "tasks") && nonempty(spec.at("tasks"));
141 } else if (has(wf, "tasks") && nonempty(wf.at("tasks"))) {
142 has_tasks = true;
143 }
144 if (!has_tasks) throw InputError("WfCommonsLoader: Workflow must have at least one task.");
145}
146
147/** `[~, name, ~] = fileparts(s)`. */
148inline std::string file_stem(const std::string& s) {
149 const std::size_t slash = s.find_last_of("/\\");
150 std::string f = slash == std::string::npos ? s : s.substr(slash + 1);
151 const std::size_t dot = f.find_last_of('.');
152 if (dot != std::string::npos) f = f.substr(0, dot);
153 return f;
154}
155
156/** Port of `extractName`: the document's name, else the default's stem, sanitized. */
157inline std::string extract_name(const json& data, const std::string& default_name) {
158 std::string name;
159 if (has(data, "name") && data.at("name").is_string() && !data.at("name").get<std::string>().empty())
160 name = data.at("name").get<std::string>();
161 else
162 name = file_stem(default_name);
163 for (char& c : name)
164 if (!(std::isalnum(static_cast<unsigned char>(c)) || c == '_')) c = '_';
165 if (name.empty()) name = "Workflow";
166 return name;
167}
168
169/** Port of `getTaskId`. */
170inline std::string task_id(const json& task, std::size_t idx1) {
171 if (has(task, "id")) return as_text(task.at("id"));
172 if (has(task, "name")) return as_text(task.at("name"));
173 return "task_" + std::to_string(idx1);
174}
175
176/** Port of `fitDistribution`. */
177template <class T>
179 typedef lang::Distrib<T> D;
180 if (runtime <= lang::GlobalConstants::FineTol) return D::immediate();
181 const T m = num_traits<T>::from_double(runtime);
182 switch (opt.distribution_type) {
184 return D::det(m);
188 if (opt.default_scv > 1.0)
190 return D::exp_mean(m);
192 default:
193 return D::exp_mean(m);
194 }
195}
196
197/** Port of `extractMetadata`; values are the JSON text of each field. */
198inline std::map<std::string, std::string> extract_metadata(const json& task, const std::string& id,
199 const json* exec) {
200 std::map<std::string, std::string> md;
201 md["taskId"] = json(id).dump();
202 static const char* kTask[] = {"name", "inputFiles", "outputFiles"};
203 for (const char* f : kTask)
204 if (has(task, f)) md[f] = task.at(f).dump();
205 if (exec) {
206 static const char* kExec[] = {"executedAt", "command", "coreCount", "avgCPU",
207 "readBytes", "writtenBytes", "memoryInBytes",
208 "energyInKWh", "avgPowerInW", "priority", "machines"};
209 for (const char* f : kExec)
210 if (has(*exec, f)) md[f] = exec->at(f).dump();
211 }
212 return md;
213}
214
215/** Port of `getReachableNodes`: BFS order, the start excluded. */
216inline std::vector<std::size_t> reachable_nodes(std::size_t start,
217 const std::vector<std::vector<std::size_t>>& adj) {
218 std::vector<bool> visited(adj.size(), false);
219 std::deque<std::size_t> q(1, start);
220 visited[start] = true;
221 std::vector<std::size_t> out;
222 while (!q.empty()) {
223 const std::size_t cur = q.front();
224 q.pop_front();
225 for (std::size_t nx : adj[cur])
226 if (!visited[nx]) {
227 visited[nx] = true;
228 q.push_back(nx);
229 out.push_back(nx);
230 }
231 }
232 return out;
233}
234
235/**
236 * Port of `findCommonJoin`. `intersect` returns its result SORTED, so the first
237 * qualifying join is the one with the smallest task index.
238 *
239 * @return the join's 0-based index, or `adj.size()` when there is none
240 */
241inline std::size_t find_common_join(const std::vector<std::size_t>& children,
242 const std::vector<std::vector<std::size_t>>& adj,
243 const std::vector<std::size_t>& indeg) {
244 const std::size_t none = adj.size();
245 if (children.empty()) return none;
246 std::vector<std::size_t> common = reachable_nodes(children[0], adj);
247 std::sort(common.begin(), common.end());
248 common.erase(std::unique(common.begin(), common.end()), common.end());
249 for (std::size_t c = 1; c < children.size(); ++c) {
250 std::vector<std::size_t> r = reachable_nodes(children[c], adj);
251 std::sort(r.begin(), r.end());
252 std::vector<std::size_t> both;
253 std::set_intersection(common.begin(), common.end(), r.begin(), r.end(),
254 std::back_inserter(both));
255 common.swap(both);
256 }
257 for (std::size_t node : common) {
258 if (indeg[node] < children.size()) continue;
259 bool all_direct = true;
260 for (std::size_t ch : children)
261 if (std::find(adj[ch].begin(), adj[ch].end(), node) == adj[ch].end()) {
262 all_direct = false;
263 break;
264 }
265 if (all_direct) return node;
266 }
267 return none;
268}
269
270/** Port of `buildWorkflow` (with `buildAdjacency` and `addPrecedences`). */
271template <class T>
272workflow::Workflow<T> build_workflow(const json& data, const std::string& wf_name,
273 const WfCommonsOptions& opt) {
274 typedef workflow::Workflow<T> W;
275 W wf(wf_name);
276 const json& jwf = data.at("workflow");
277 const bool legacy = !has(jwf, "specification");
278 const json& tasks = legacy ? jwf.at("tasks") : jwf.at("specification").at("tasks");
279 if (!tasks.is_array())
280 throw InputError("WfCommonsLoader: the workflow's 'tasks' must be an array of task objects");
281 const std::size_t n = tasks.size();
282
283 std::vector<std::string> ids(n);
284 for (std::size_t i = 0; i < n; ++i) ids[i] = task_id(tasks[i], i + 1);
285
286 // Execution data, keyed by task id.
287 std::map<std::string, const json*> exec_map;
288 if (opt.use_execution_data) {
289 if (legacy) {
290 for (std::size_t i = 0; i < n; ++i) exec_map[ids[i]] = &tasks[i];
291 } else if (has(jwf, "execution") && has(jwf.at("execution"), "tasks")) {
292 for (const json& et : jwf.at("execution").at("tasks"))
293 if (has(et, "id")) exec_map[as_text(et.at("id"))] = &et;
294 }
295 }
296
297 // Phase 1: activities.
298 std::map<std::string, std::size_t> idx_of;
299 for (std::size_t i = 0; i < n; ++i) {
300 const std::string& id = ids[i];
301 double runtime = opt.default_runtime;
302 const auto it = exec_map.find(id);
303 const json* exec = it == exec_map.end() ? nullptr : it->second;
304 if (exec && has(*exec, "runtimeInSeconds") && exec->at("runtimeInSeconds").is_number())
305 runtime = exec->at("runtimeInSeconds").get<double>();
306 wf.add_activity(id, fit_distribution<T>(runtime, opt));
307 idx_of[id] = i;
308 if (opt.store_metadata) wf.activity(id).set_metadata(extract_metadata(tasks[i], id, exec));
309 }
310
311 // Children named by task name, for the fallback (a name shared by two tasks is ambiguous).
312 std::map<std::string, std::size_t> name_idx;
313 std::map<std::string, int> name_count;
314 if (opt.resolve_children_by_name)
315 for (std::size_t i = 0; i < n; ++i)
316 if (has(tasks[i], "name") && tasks[i].at("name").is_string()) {
317 const std::string nm = tasks[i].at("name").get<std::string>();
318 name_idx[nm] = i;
319 ++name_count[nm];
320 }
321
322 // Phase 2: adjacency, the union of `children` (task -> child) and `parents` (parent -> task),
323 // each pair once and the children-derived edges first, as WfCommonsLoader.m builds it.
324 std::vector<std::vector<std::size_t>> adj(n);
325 std::vector<std::size_t> indeg(n, 0), outdeg(n, 0);
326 std::vector<std::vector<bool>> seen(n, std::vector<bool>(n, false));
327 std::vector<std::string> unresolved;
328 for (int pass = 0; pass < 2; ++pass) {
329 const char* field = pass == 0 ? "children" : "parents";
330 for (std::size_t i = 0; i < n; ++i) {
331 if (!has(tasks[i], field)) continue;
332 const json& rj = tasks[i].at(field);
333 std::vector<std::string> refs;
334 if (rj.is_string()) refs.push_back(rj.get<std::string>());
335 else if (rj.is_array())
336 for (const json& c : rj) refs.push_back(as_text(c));
337 for (const std::string& k : refs) {
338 std::size_t j = n;
339 const auto a = idx_of.find(k);
340 if (a != idx_of.end()) j = a->second;
341 else if (opt.resolve_children_by_name) {
342 const auto b = name_idx.find(k);
343 if (b != name_idx.end() && name_count[k] == 1) j = b->second;
344 }
345 if (j == n) {
346 unresolved.push_back(k);
347 continue;
348 }
349 const std::size_t from = pass == 0 ? i : j, to = pass == 0 ? j : i;
350 if (seen[from][to]) continue;
351 seen[from][to] = true;
352 adj[from].push_back(to);
353 ++outdeg[from];
354 ++indeg[to];
355 }
356 }
357 }
358 if (!unresolved.empty()) {
359 std::vector<std::string> shown;
360 for (const std::string& u : unresolved)
361 if (std::find(shown.begin(), shown.end(), u) == shown.end()) shown.push_back(u);
362 std::cerr << "[LINE] Warning: WfCommonsLoader: " << unresolved.size()
363 << " child/parent reference(s) match no task id"
364 << (opt.resolve_children_by_name ? " or unique task name" : "") << " and were dropped: ";
365 for (std::size_t u = 0; u < shown.size() && u < 5; ++u) std::cerr << (u ? ", " : "") << shown[u];
366 std::cerr << (shown.size() > 5 ? ", ..." : "") << "\n";
367 }
368
369 // Phase 3: fork-join pairs, then the remaining edges in series.
370 std::vector<std::vector<bool>> processed(n, std::vector<bool>(n, false));
371 for (std::size_t i = 0; i < n; ++i) {
372 if (outdeg[i] <= 1) continue;
373 const std::vector<std::size_t>& children = adj[i];
374 const std::size_t join = find_common_join(children, adj, indeg);
375 if (join == n) continue;
376 std::vector<std::string> posts;
377 for (std::size_t c : children) posts.push_back(ids[c]);
378 wf.add_precedence(W::AndFork(ids[i], posts));
379 wf.add_precedence(W::AndJoin(posts, ids[join]));
380 for (std::size_t c : children) {
381 processed[i][c] = true;
382 processed[c][join] = true;
383 }
384 }
385 for (std::size_t i = 0; i < n; ++i)
386 for (std::size_t j : adj[i])
387 if (!processed[i][j]) wf.add_precedence(W::Serial(ids[i], ids[j]));
388 return wf;
389}
390
391/** Parse a whole file as JSON, naming the file on failure. */
392inline json parse_file(const std::string& path) {
393 std::ifstream in(path.c_str());
394 if (!in) throw InputError("WfCommonsLoader: cannot open '" + path + "'");
395 try {
396 json j;
397 in >> j;
398 return j;
399 } catch (const json::parse_error& e) {
400 throw InputError("WfCommonsLoader: malformed JSON in '" + path + "': " + e.what());
401 }
402}
403
404} // namespace wfcommons_detail
405
406/**
407 * Port of `WfCommonsLoader.loadFromStruct(data, options)`, on a parsed document.
408 * The name defaults to `struct_input`, as in the reference.
409 */
410template <class T>
412 const WfCommonsOptions& options = WfCommonsOptions()) {
414 const std::string nm = wfcommons_detail::extract_name(data, "struct_input");
415 return wfcommons_detail::build_workflow<T>(data, nm, options);
416}
417
418/** Port of `WfCommonsLoader.load(jsonFile, options)`. */
419template <class T>
420workflow::Workflow<T> wfcommons_load(const std::string& path,
421 const WfCommonsOptions& options = WfCommonsOptions()) {
422 const nlohmann::json data = wfcommons_detail::parse_file(path);
424 const std::string nm = wfcommons_detail::extract_name(data, path);
425 return wfcommons_detail::build_workflow<T>(data, nm, options);
426}
427
428namespace wfcommons_detail {
429
430/** The first executable `name` on PATH, empty when there is none. */
431inline std::string which_on_path(const std::string& name) {
432 const char* env = std::getenv("PATH");
433 const std::string path = env ? env : "";
434 std::size_t b = 0;
435 while (b <= path.size()) {
436 const std::size_t e = path.find(':', b);
437 const std::string dir = path.substr(b, e == std::string::npos ? std::string::npos : e - b);
438 if (!dir.empty() && ::access((dir + "/" + name).c_str(), X_OK) == 0) return dir + "/" + name;
439 if (e == std::string::npos) break;
440 b = e + 1;
441 }
442 return std::string();
443}
444
445/**
446 * GET an https:// URL with curl, else wget, into memory. Redirects are followed but only to https, and an HTTP
447 * error status fails the fetch; the tool's own message is quoted in the `InputError`.
448 */
449inline std::string fetch_https(const std::string& url, int timeout_ms) {
450 const int secs = timeout_ms > 0 ? (timeout_ms + 999) / 1000 : 0;
451 util::TempDir dir("wfcommons");
452 const std::string dest = dir.file("workflow.json");
453 std::vector<std::string> argv;
454 const std::string curl = which_on_path("curl");
455 if (!curl.empty()) {
456 argv = {curl, "-sS", "-fL", "--proto", "=https", "--proto-redir", "=https", "--tlsv1.2", "-o", dest};
457 if (secs > 0) {
458 argv.push_back("--max-time");
459 argv.push_back(std::to_string(secs));
460 }
461 } else {
462 const std::string wget = which_on_path("wget");
463 if (wget.empty())
464 throw UnsupportedError("WfCommonsLoader: " + url + " is https, which this port fetches with curl or "
465 "wget, and neither is on PATH. Install one, or download the file and read it "
466 "with wfcommons_load");
467 argv = {wget, "-q", "--https-only", "--tries=1", "-O", dest};
468 if (secs > 0) argv.push_back("--timeout=" + std::to_string(secs));
469 }
470 argv.push_back(url);
471 const util::ProcResult r = util::capture(argv, secs > 0 ? secs + 5 : 0, true);
472 if (r.timedOut) throw InputError("WfCommonsLoader: GET " + url + " did not finish within " +
473 std::to_string(secs) + " s");
474 if (r.exitCode != 0)
475 throw InputError("WfCommonsLoader: GET " + url + " failed (" + argv[0] + " exit " +
476 std::to_string(r.exitCode) + "): " + util::trim(r.out));
477 std::ifstream in(dest.c_str(), std::ios::binary);
478 std::stringstream ss;
479 ss << in.rdbuf();
480 return ss.str();
481}
482
483} // namespace wfcommons_detail
484
485/**
486 * Port of `WfCommonsLoader.loadFromUrl(urlString, options)`.
487 *
488 * http:// goes over `line::http`, where a non-200 answer, including a redirect,
489 * is an `InputError` naming the status. https:// goes through curl or wget
490 * (`fetch_https`), following redirects that stay on https.
491 *
492 * @param timeout_ms read timeout for the fetch
493 */
494template <class T>
496 const WfCommonsOptions& options = WfCommonsOptions(),
497 int timeout_ms = 60000) {
498 std::string body;
499 if (url.compare(0, 8, "https://") == 0) {
500 body = wfcommons_detail::fetch_https(url, timeout_ms);
501 } else {
502 const http::Response resp = http::get(url, timeout_ms);
503 if (resp.status != 200)
504 throw InputError("WfCommonsLoader: GET " + url + " answered HTTP " +
505 std::to_string(resp.status) + " (redirects are not followed)");
506 body = resp.body;
507 }
508 nlohmann::json data;
509 try {
510 data = nlohmann::json::parse(body);
511 } catch (const nlohmann::json::parse_error& e) {
512 throw InputError("WfCommonsLoader: malformed JSON from " + url + ": " + e.what());
513 }
515 const std::string nm = wfcommons_detail::extract_name(data, wfcommons_detail::file_stem(url));
516 return wfcommons_detail::build_workflow<T>(data, nm, options);
517}
518
519/**
520 * Port of `WfCommonsLoader.validateFile(jsonFile)`: true when the file parses and
521 * passes the schema checks. Never throws.
522 */
523inline bool wfcommons_validate(const std::string& path) {
524 try {
526 return true;
527 } catch (...) {
528 return false;
529 }
530}
531
532} // namespace io
533} // namespace line
534
535#endif // LINE_IO_WFCOMMONS_LOADER_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
std::string file(const std::string &name) const
A file inside it.
Definition tempdir.h:127
The moment fitters the reference distributions carry as STATIC FACTORIES: Erlang.fitMeanAndOrder,...
The exception types the port throws.
Minimal HTTP/1.1 client, enough to talk to a line-*-rest service.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Response get(const std::string &url, int timeoutMillis)
GET a URL.
Definition http.h:349
std::string task_id(const json &task, std::size_t idx1)
Port of getTaskId.
std::string fetch_https(const std::string &url, int timeout_ms)
GET an https:// URL with curl, else wget, into memory.
std::string file_stem(const std::string &s)
[~, name, ~] = fileparts(s).
std::map< std::string, std::string > extract_metadata(const json &task, const std::string &id, const json *exec)
Port of extractMetadata; values are the JSON text of each field.
std::string extract_name(const json &data, const std::string &default_name)
Port of extractName: the document's name, else the default's stem, sanitized.
bool nonempty(const json &v)
~isempty(x) on a decoded JSON value.
bool supported_version(const std::string &v)
WfCommonsLoader.SUPPORTED_SCHEMA_VERSIONS.
std::size_t find_common_join(const std::vector< std::size_t > &children, const std::vector< std::vector< std::size_t > > &adj, const std::vector< std::size_t > &indeg)
Port of findCommonJoin.
std::string which_on_path(const std::string &name)
The first executable name on PATH, empty when there is none.
lang::Distrib< T > fit_distribution(double runtime, const WfCommonsOptions &opt)
Port of fitDistribution.
void validate_schema(const json &data)
Port of validateSchema.
workflow::Workflow< T > build_workflow(const json &data, const std::string &wf_name, const WfCommonsOptions &opt)
Port of buildWorkflow (with buildAdjacency and addPrecedences).
std::vector< std::size_t > reachable_nodes(std::size_t start, const std::vector< std::vector< std::size_t > > &adj)
Port of getReachableNodes: BFS order, the start excluded.
bool has(const json &o, const char *key)
std::string as_text(const json &v)
A JSON scalar as the text strcmp compares: strings verbatim, numbers as written.
json parse_file(const std::string &path)
Parse a whole file as JSON, naming the file on failure.
WfDistributionType
options.distributionType, the JAR's WfCommonsOptions.DistributionType.
workflow::Workflow< T > wfcommons_load(const std::string &path, const WfCommonsOptions &options=WfCommonsOptions())
Port of WfCommonsLoader.load(jsonFile, options).
bool wfcommons_validate(const std::string &path)
Port of WfCommonsLoader.validateFile(jsonFile): true when the file parses and passes the schema check...
workflow::Workflow< T > wfcommons_load_from_url(const std::string &url, const WfCommonsOptions &options=WfCommonsOptions(), int timeout_ms=60000)
Port of WfCommonsLoader.loadFromUrl(urlString, options).
WfDistributionType wf_distribution_type_from_string(const std::string &s)
lower(options.distributionType) as the reference switches on it: exp, det, aph, hyperexp.
workflow::Workflow< T > wfcommons_load_from_json(const nlohmann::json &data, const WfCommonsOptions &options=WfCommonsOptions())
Port of WfCommonsLoader.loadFromStruct(data, options), on a parsed document.
Distrib< T > aph_fit_mean_scv(const T &mean, const T &scv)
APH.fitMeanAndSCV(MEAN, SCV), through mam::aph_fit_mean_scv.
Distrib< T > hyperexp_fit_mean_scv(const T &mean, const T &scv)
HyperExp.fitMeanAndSCV(MEAN, SCV), which is map_hyperexp at p = 0.99 read back as (p,...
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Definition subprocess.h:82
std::string trim(const std::string &s)
Trims ASCII whitespace from both ends, as Java's String.trim() does.
Definition subprocess.h:181
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
An HTTP response, with the body already de-chunked.
Definition http.h:60
std::string body
Response body, decoded.
Definition http.h:62
int status
HTTP status code.
Definition http.h:61
The loader options; the defaults are the reference's parseOptions.
bool resolve_children_by_name
Resolve a child matching no task id by task name (see the file comment).
double default_scv
defaultSCV, for APH and HyperExp
double default_runtime
defaultRuntime, when a task has no runtime
bool use_execution_data
useExecutionData
bool store_metadata
storeMetadata, onto WorkflowActivity::metadata()
WfDistributionType distribution_type
distributionType
static constexpr double FineTol
Definition lang_types.h:760
Outcome of a captured command.
Definition subprocess.h:42
int exitCode
Exit status, or -1 when the command could not run.
Definition subprocess.h:43
bool timedOut
True when the deadline expired and the child was killed.
Definition subprocess.h:45
std::string out
Everything the command wrote to stdout.
Definition subprocess.h:44
Running an external command and capturing its output, with a deadline.
A scratch directory for the subprocess wrappers, the port's lineTempName.
An activity workflow reduced to one phase-type law.