ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
erf_provenance Namespace Reference

Classes

struct  ExecutionProvenance
 
struct  ArtifactProvenance
 
struct  ProvenanceRecord
 
struct  ProvenanceParseResult
 

Typedefs

using ProvenanceReadResult = ProvenanceParseResult
 

Enumerations

enum class  LineageStatus {
  Complete , IncompleteAncestor , MissingJobInfo , MissingProvenanceBlock ,
  MalformedProvenance , UnsupportedSchema , ArtifactTypeMismatch
}
 
enum class  ArtifactType { Checkpoint , Plotfile2D , Plotfile3D }
 
enum class  ProvenanceReadStatus {
  Valid , MissingJobInfo , MissingProvenanceBlock , MalformedProvenance ,
  UnsupportedSchema , ArtifactTypeMismatch
}
 

Functions

std::string uuid_v4_from_bytes (const std::array< std::uint8_t, 16 > &bytes)
 
bool is_valid_uuid_v4 (std::string_view uuid)
 
std::string generate_uuid_v4 ()
 
std::string format_utc (std::time_t time_value)
 
std::string current_utc ()
 
const char * lineage_status_token (LineageStatus status) noexcept
 
const char * artifact_type_token (ArtifactType type) noexcept
 
std::string serialize_provenance_block (const ProvenanceRecord &record)
 
ProvenanceParseResult parse_provenance_block (std::string_view text)
 
ProvenanceReadResult read_job_info_file (const std::string &path)
 
ExecutionProvenance make_cold_start_provenance (const std::string &execution_uuid, const std::string &execution_start_utc)
 
ExecutionProvenance make_restart_provenance (const ExecutionProvenance &current_invocation, const ProvenanceRecord &parent_checkpoint, const std::string &checkpoint_path)
 
ExecutionProvenance make_incomplete_restart_provenance (const ExecutionProvenance &current_invocation, ProvenanceReadStatus failure_status, const std::string &checkpoint_path)
 
ExecutionProvenance initialize_execution_provenance ()
 

Variables

constexpr int schema_version = 1
 
constexpr const char * provenance_begin = "ERF_PROVENANCE_BEGIN"
 
constexpr const char * provenance_end = "ERF_PROVENANCE_END"
 

Typedef Documentation

◆ ProvenanceReadResult

Enumeration Type Documentation

◆ ArtifactType

Enumerator
Checkpoint 
Plotfile2D 
Plotfile3D 

◆ LineageStatus

The reason a lineage is incomplete is part of the persisted contract. Missing or invalid metadata never prevents ERF from reading physical state.

Enumerator
Complete 
IncompleteAncestor 
MissingJobInfo 
MissingProvenanceBlock 
MalformedProvenance 
UnsupportedSchema 
ArtifactTypeMismatch 

◆ ProvenanceReadStatus

Enumerator
Valid 
MissingJobInfo 
MissingProvenanceBlock 
MalformedProvenance 
UnsupportedSchema 
ArtifactTypeMismatch 

Function Documentation

◆ artifact_type_token()

const char * erf_provenance::artifact_type_token ( ArtifactType  type)
noexcept

Return the stable persisted token for an artifact type.

335 {
336  switch (type) {
337  case ArtifactType::Checkpoint: return "checkpoint";
338  case ArtifactType::Plotfile2D: return "plotfile_2d";
339  case ArtifactType::Plotfile3D: return "plotfile_3d";
340  }
341  return "checkpoint";
342 }

Referenced by ERF::ReadCheckpointFile(), serialize_provenance_block(), and ERF::writeJobInfo().

Here is the caller graph for this function:

◆ current_utc()

std::string erf_provenance::current_utc ( )

Return the current time in the persisted UTC format.

316 {
317  return format_utc(std::time(nullptr));
318 }
std::string format_utc(std::time_t time_value)
Definition: ERF_Provenance.cpp:302

Referenced by initialize_execution_provenance(), and ERF::writeJobInfo().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ format_utc()

std::string erf_provenance::format_utc ( std::time_t  time_value)

Format a supplied clock value as an ISO 8601 UTC timestamp.

303 {
304  std::tm utc{};
305 #if defined(_WIN32)
306  gmtime_s(&utc, &time_value);
307 #else
308  gmtime_r(&time_value, &utc);
309 #endif
310  std::ostringstream out;
311  out << std::put_time(&utc, "%Y-%m-%dT%H:%M:%SZ");
312  return out.str();
313 }
struct @29 out

Referenced by current_utc().

Here is the caller graph for this function:

◆ generate_uuid_v4()

std::string erf_provenance::generate_uuid_v4 ( )

Generate a UUID without using AMReX's scientific random-number stream.

271 {
272  std::array<std::uint8_t, 16> bytes{};
273  bool generated = false;
274  try {
275  std::random_device random;
276  for (auto& byte : bytes) byte = static_cast<std::uint8_t>(random());
277  generated = true;
278  } catch (...) {
279  generated = false;
280  }
281  const auto now = std::chrono::high_resolution_clock::now().time_since_epoch().count();
282  const auto steady = std::chrono::steady_clock::now().time_since_epoch().count();
283  const auto address = reinterpret_cast<std::uintptr_t>(&bytes);
284  if (!generated) {
285  for (std::size_t i = 0; i < bytes.size(); ++i) {
286  bytes[i] = static_cast<std::uint8_t>((now >> ((i % 8) * 8)) ^
287  (steady >> (((i + 3) % 8) * 8)) ^
288  (address >> ((i % sizeof(std::uintptr_t)) * 8)));
289  }
290  } else {
291  for (std::size_t i = 0; i < bytes.size(); ++i) {
292  bytes[i] ^= static_cast<std::uint8_t>((now >> ((i % 8) * 8)) ^
293  (steady >> (((i + 3) % 8) * 8)) ^
294  (address >> ((i % sizeof(std::uintptr_t)) * 8)));
295  }
296  }
297  bytes[6] = static_cast<std::uint8_t>((bytes[6] & 0x0f) | 0x40);
298  bytes[8] = static_cast<std::uint8_t>((bytes[8] & 0x3f) | 0x80);
299  return uuid_v4_from_bytes(bytes);
300 }
std::string uuid_v4_from_bytes(const std::array< std::uint8_t, 16 > &bytes)
Definition: ERF_Provenance.cpp:249

Referenced by initialize_execution_provenance(), and ERF::writeJobInfo().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ initialize_execution_provenance()

ExecutionProvenance erf_provenance::initialize_execution_provenance ( )

Initialize one process-wide invocation record and share it across multiblock ERF objects.

560 {
561  static bool initialized = false;
562  static ExecutionProvenance invocation;
563  if (!initialized) {
564  std::string execution_uuid;
565  std::string execution_start_utc;
566  if (amrex::ParallelDescriptor::IOProcessor()) {
567  execution_uuid = generate_uuid_v4();
568  execution_start_utc = current_utc();
569  }
570  broadcast_string(execution_uuid);
571  broadcast_string(execution_start_utc);
572  invocation = make_cold_start_provenance(execution_uuid, execution_start_utc);
573  initialized = true;
574  }
575  return invocation;
576 }
std::string current_utc()
Definition: ERF_Provenance.cpp:315
std::string generate_uuid_v4()
Definition: ERF_Provenance.cpp:270
ExecutionProvenance make_cold_start_provenance(const std::string &execution_uuid, const std::string &execution_start_utc)
Definition: ERF_Provenance.cpp:508
bool initialized
Definition: ERF_RRTMGP_Interface.cpp:24

Referenced by ERF::ERF_shared().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_valid_uuid_v4()

bool erf_provenance::is_valid_uuid_v4 ( std::string_view  uuid)

Validate a lowercase or uppercase canonical RFC UUIDv4 string.

263 {
264  if (!is_canonical_uuid_layout(uuid) || uuid[14] != '4') return false;
265  const char variant = uuid[19];
266  return variant == '8' || variant == '9' || variant == 'a' || variant == 'b' ||
267  variant == 'A' || variant == 'B';
268 }

◆ lineage_status_token()

const char * erf_provenance::lineage_status_token ( LineageStatus  status)
noexcept

Return the stable persisted token for a lineage status.

321 {
322  switch (status) {
323  case LineageStatus::Complete: return "complete";
324  case LineageStatus::IncompleteAncestor: return "incomplete_ancestor";
325  case LineageStatus::MissingJobInfo: return "missing_job_info";
326  case LineageStatus::MissingProvenanceBlock: return "missing_provenance_block";
327  case LineageStatus::MalformedProvenance: return "malformed_provenance";
328  case LineageStatus::UnsupportedSchema: return "unsupported_schema";
329  case LineageStatus::ArtifactTypeMismatch: return "artifact_type_mismatch";
330  }
331  return "malformed_provenance";
332 }

Referenced by serialize_provenance_block().

Here is the caller graph for this function:

◆ make_cold_start_provenance()

ExecutionProvenance erf_provenance::make_cold_start_provenance ( const std::string &  execution_uuid,
const std::string &  execution_start_utc 
)

Create the root record for one cold-start invocation.

510 {
511  ExecutionProvenance result;
512  result.simulation_uuid = execution_uuid;
513  result.execution_uuid = execution_uuid;
514  result.execution_lineage = {execution_uuid};
515  result.execution_start_utc = execution_start_utc;
516  return result;
517 }

Referenced by initialize_execution_provenance().

Here is the caller graph for this function:

◆ make_incomplete_restart_provenance()

ExecutionProvenance erf_provenance::make_incomplete_restart_provenance ( const ExecutionProvenance current_invocation,
ProvenanceReadStatus  failure,
const std::string &  checkpoint_path 
)

Start a new known lineage segment after a nonfatal provenance read failure.

546 {
547  ExecutionProvenance result = current_invocation;
548  result.simulation_uuid = current_invocation.execution_uuid;
549  result.parent_execution_uuid.clear();
550  result.execution_lineage = {current_invocation.execution_uuid};
551  result.restart_generation = -1;
552  result.lineage_complete = false;
553  result.lineage_status = failure_lineage_status(failure_status);
554  result.source_checkpoint_uuid.clear();
555  result.source_checkpoint_path = checkpoint_path;
556  return result;
557 }

Referenced by ERF::ReadCheckpointFile().

Here is the caller graph for this function:

◆ make_restart_provenance()

ExecutionProvenance erf_provenance::make_restart_provenance ( const ExecutionProvenance current_invocation,
const ProvenanceRecord parent_checkpoint,
const std::string &  checkpoint_path 
)

Create a child record from a valid checkpoint record, including its artifact UUID.

522 {
523  ExecutionProvenance result = current_invocation;
524  const auto& parent = parent_checkpoint.execution;
525  result.simulation_uuid = parent.simulation_uuid;
526  result.parent_execution_uuid = parent.execution_uuid;
527  result.execution_lineage = parent.execution_lineage;
528  result.execution_lineage.push_back(current_invocation.execution_uuid);
529  result.source_checkpoint_uuid = parent_checkpoint.artifact.artifact_uuid;
530  result.source_checkpoint_path = checkpoint_path;
531  if (parent.lineage_complete) {
532  result.restart_generation = parent.restart_generation + 1;
533  result.lineage_complete = true;
534  result.lineage_status = LineageStatus::Complete;
535  } else {
536  result.restart_generation = -1;
537  result.lineage_complete = false;
538  result.lineage_status = LineageStatus::IncompleteAncestor;
539  }
540  return result;
541 }

Referenced by ERF::ReadCheckpointFile().

Here is the caller graph for this function:

◆ parse_provenance_block()

ProvenanceParseResult erf_provenance::parse_provenance_block ( std::string_view  job_info_text)

Parse only delimited provenance markers from a human-readable job_info.

375 {
376  enum class BlockState { BeforeBlock, InsideBlock, AfterBlock };
377  BlockState state = BlockState::BeforeBlock;
378  std::vector<std::string> block_lines;
379  std::istringstream input{std::string(text)};
380  std::string line;
381  while (std::getline(input, line)) {
382  line = strip_cr(std::move(line));
383  const bool is_begin = line == provenance_begin;
384  const bool is_end = line == provenance_end;
385  if (state == BlockState::BeforeBlock) {
386  if (is_end) {
387  return failure(ProvenanceReadStatus::MalformedProvenance,
388  "provenance end marker precedes begin marker");
389  }
390  if (is_begin) state = BlockState::InsideBlock;
391  } else if (state == BlockState::InsideBlock) {
392  if (is_begin) {
393  return failure(ProvenanceReadStatus::MalformedProvenance,
394  "provenance block has a nested begin marker");
395  }
396  if (is_end) {
397  state = BlockState::AfterBlock;
398  } else {
399  block_lines.push_back(line);
400  }
401  } else if (is_begin || is_end) {
402  return failure(ProvenanceReadStatus::MalformedProvenance,
403  "job_info has multiple provenance blocks");
404  }
405  }
406  if (state == BlockState::BeforeBlock) {
407  return failure(ProvenanceReadStatus::MissingProvenanceBlock,
408  "job_info has no exact ERF provenance block");
409  }
410  if (state == BlockState::InsideBlock) {
411  return failure(ProvenanceReadStatus::MalformedProvenance,
412  "provenance block has no matching end marker");
413  }
414 
415  std::unordered_map<std::string, std::string> fields;
416  static const std::unordered_set<std::string> required = {
417  "schema_version", "simulation_uuid", "execution_uuid", "parent_execution_uuid",
418  "execution_lineage", "restart_generation", "lineage_complete", "lineage_status",
419  "source_checkpoint_uuid", "source_checkpoint_path", "execution_start_utc",
420  "artifact_uuid", "artifact_type", "artifact_step", "artifact_time_seconds",
421  "artifact_created_utc"
422  };
423  for (const auto& block_line : block_lines) {
424  if (block_line.empty()) continue;
425  const std::size_t equals = block_line.find('=');
426  if (equals == std::string::npos || equals == 0) {
427  return failure(ProvenanceReadStatus::MalformedProvenance,
428  "provenance line is not a key=value pair");
429  }
430  const std::string key = block_line.substr(0, equals);
431  if (required.count(key) != 0 && fields.count(key) != 0) {
432  return failure(ProvenanceReadStatus::MalformedProvenance,
433  "duplicate required provenance key: " + key);
434  }
435  if (required.count(key) != 0) fields.emplace(key, block_line.substr(equals + 1));
436  }
437  ProvenanceParseResult result;
438  int schema = 0;
439  if (fields.count("schema_version") == 0 ||
440  !parse_int(fields.at("schema_version"), schema)) {
441  return failure(ProvenanceReadStatus::MalformedProvenance, "invalid provenance schema_version");
442  }
443  if (schema != erf_provenance::schema_version) {
444  return failure(ProvenanceReadStatus::UnsupportedSchema, "unsupported provenance schema_version");
445  }
446  for (const auto& key : required) {
447  if (fields.count(key) == 0) {
448  return failure(ProvenanceReadStatus::MalformedProvenance,
449  "missing required provenance key: " + key);
450  }
451  }
452  result.record.execution.schema_version = schema;
453  result.record.execution.simulation_uuid = fields.at("simulation_uuid");
454  result.record.execution.execution_uuid = fields.at("execution_uuid");
455  result.record.execution.parent_execution_uuid = fields.at("parent_execution_uuid");
456  result.record.execution.execution_lineage = split_lineage(fields.at("execution_lineage"));
457  if (result.record.execution.execution_lineage.empty()) {
458  return failure(ProvenanceReadStatus::MalformedProvenance, "invalid execution_lineage");
459  }
460  if (!parse_int(fields.at("restart_generation"), result.record.execution.restart_generation) ||
461  !parse_bool(fields.at("lineage_complete"), result.record.execution.lineage_complete) ||
462  !parse_lineage_status(fields.at("lineage_status"), result.record.execution.lineage_status) ||
463  !parse_artifact_type(fields.at("artifact_type"), result.record.artifact.artifact_type) ||
464  !parse_int(fields.at("artifact_step"), result.record.artifact.artifact_step) ||
465  !parse_double(fields.at("artifact_time_seconds"), result.record.artifact.artifact_time_seconds)) {
466  return failure(ProvenanceReadStatus::MalformedProvenance, "invalid typed provenance field");
467  }
468  result.record.execution.source_checkpoint_uuid = fields.at("source_checkpoint_uuid");
469  result.record.execution.source_checkpoint_path = fields.at("source_checkpoint_path");
470  result.record.execution.execution_start_utc = fields.at("execution_start_utc");
471  result.record.artifact.artifact_uuid = fields.at("artifact_uuid");
472  result.record.artifact.artifact_created_utc = fields.at("artifact_created_utc");
473 
474  std::string diagnostic;
475  if (!validate_record(result.record, diagnostic)) {
476  return failure(ProvenanceReadStatus::MalformedProvenance, std::move(diagnostic));
477  }
478  result.status = ProvenanceReadStatus::Valid;
479  return result;
480 }
constexpr int schema_version
Definition: ERF_Provenance.H:14
constexpr const char * provenance_end
Definition: ERF_Provenance.H:16
constexpr const char * provenance_begin
Definition: ERF_Provenance.H:15

Referenced by read_job_info_file().

Here is the caller graph for this function:

◆ read_job_info_file()

ProvenanceReadResult erf_provenance::read_job_info_file ( const std::string &  job_info_path)

Read and broadcast one job_info before parsing the same bytes on every rank.

483 {
484  int read_status = static_cast<int>(ProvenanceReadStatus::Valid);
485  std::string contents;
486  if (amrex::ParallelDescriptor::IOProcessor()) {
487  std::ifstream input(path, std::ios::in | std::ios::binary);
488  if (!input.good()) {
489  read_status = static_cast<int>(ProvenanceReadStatus::MissingJobInfo);
490  } else {
491  contents.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
492  if (!input.good() && !input.eof()) {
493  read_status = static_cast<int>(ProvenanceReadStatus::MalformedProvenance);
494  }
495  }
496  }
497  const int io_rank = amrex::ParallelDescriptor::IOProcessorNumber();
498  amrex::ParallelDescriptor::Bcast(&read_status, 1, io_rank);
499  broadcast_string(contents);
500  if (read_status != static_cast<int>(ProvenanceReadStatus::Valid)) {
501  return failure(static_cast<ProvenanceReadStatus>(read_status),
502  read_status == static_cast<int>(ProvenanceReadStatus::MissingJobInfo)
503  ? "job_info does not exist" : "job_info could not be read");
504  }
505  return parse_provenance_block(contents);
506 }
struct @29 in
ProvenanceParseResult parse_provenance_block(std::string_view text)
Definition: ERF_Provenance.cpp:374
ProvenanceReadStatus
Definition: ERF_Provenance.H:41

Referenced by ERF::ReadCheckpointFile().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ serialize_provenance_block()

std::string erf_provenance::serialize_provenance_block ( const ProvenanceRecord record)

Serialize exactly one schema-1 machine-readable block.

345 {
346  std::ostringstream out;
347  out << provenance_begin << '\n';
348  out << "schema_version=" << record.execution.schema_version << '\n';
349  out << "simulation_uuid=" << record.execution.simulation_uuid << '\n';
350  out << "execution_uuid=" << record.execution.execution_uuid << '\n';
351  out << "parent_execution_uuid=" << record.execution.parent_execution_uuid << '\n';
352  out << "execution_lineage=";
353  for (std::size_t i = 0; i < record.execution.execution_lineage.size(); ++i) {
354  if (i != 0) out << ',';
355  out << record.execution.execution_lineage[i];
356  }
357  out << '\n';
358  out << "restart_generation=" << record.execution.restart_generation << '\n';
359  out << "lineage_complete=" << (record.execution.lineage_complete ? "true" : "false") << '\n';
360  out << "lineage_status=" << lineage_status_token(record.execution.lineage_status) << '\n';
361  out << "source_checkpoint_uuid=" << record.execution.source_checkpoint_uuid << '\n';
362  out << "source_checkpoint_path=" << record.execution.source_checkpoint_path << '\n';
363  out << "execution_start_utc=" << record.execution.execution_start_utc << '\n';
364  out << "artifact_uuid=" << record.artifact.artifact_uuid << '\n';
365  out << "artifact_type=" << artifact_type_token(record.artifact.artifact_type) << '\n';
366  out << "artifact_step=" << record.artifact.artifact_step << '\n';
367  out << "artifact_time_seconds=" << std::setprecision(17)
368  << record.artifact.artifact_time_seconds << '\n';
369  out << "artifact_created_utc=" << record.artifact.artifact_created_utc << '\n';
370  out << provenance_end << '\n';
371  return out.str();
372 }
const char * lineage_status_token(LineageStatus status) noexcept
Definition: ERF_Provenance.cpp:320
const char * artifact_type_token(ArtifactType type) noexcept
Definition: ERF_Provenance.cpp:334

Referenced by ERF::writeJobInfo().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ uuid_v4_from_bytes()

std::string erf_provenance::uuid_v4_from_bytes ( const std::array< std::uint8_t, 16 > &  bytes)

Format supplied bytes as a lowercase canonical RFC UUIDv4 string.

250 {
251  static constexpr char digits[] = "0123456789abcdef";
252  std::string result;
253  result.reserve(36);
254  for (std::size_t i = 0; i < bytes.size(); ++i) {
255  if (i == 4 || i == 6 || i == 8 || i == 10) result.push_back('-');
256  result.push_back(digits[(bytes[i] >> 4) & 0x0f]);
257  result.push_back(digits[bytes[i] & 0x0f]);
258  }
259  return result;
260 }

Referenced by generate_uuid_v4().

Here is the caller graph for this function:

Variable Documentation

◆ provenance_begin

constexpr const char* erf_provenance::provenance_begin = "ERF_PROVENANCE_BEGIN"
constexpr

◆ provenance_end

constexpr const char* erf_provenance::provenance_end = "ERF_PROVENANCE_END"
constexpr

◆ schema_version

constexpr int erf_provenance::schema_version = 1
constexpr

Referenced by parse_provenance_block().