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.

338 {
339  switch (type) {
340  case ArtifactType::Checkpoint: return "checkpoint";
341  case ArtifactType::Plotfile2D: return "plotfile_2d";
342  case ArtifactType::Plotfile3D: return "plotfile_3d";
343  }
344  return "checkpoint";
345 }

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.

319 {
320  return format_utc(std::time(nullptr));
321 }
std::string format_utc(std::time_t time_value)
Definition: ERF_Provenance.cpp:305

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.

306 {
307  std::tm utc{};
308 #if defined(_WIN32)
309  gmtime_s(&utc, &time_value);
310 #else
311  gmtime_r(&time_value, &utc);
312 #endif
313  std::ostringstream out;
314  out << std::put_time(&utc, "%Y-%m-%dT%H:%M:%SZ");
315  return out.str();
316 }
struct @28 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.

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

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.

563 {
564  static bool initialized = false;
565  static ExecutionProvenance invocation;
566  if (!initialized) {
567  std::string execution_uuid;
568  std::string execution_start_utc;
569  if (amrex::ParallelDescriptor::IOProcessor()) {
570  execution_uuid = generate_uuid_v4();
571  execution_start_utc = current_utc();
572  }
573  broadcast_string(execution_uuid);
574  broadcast_string(execution_start_utc);
575  invocation = make_cold_start_provenance(execution_uuid, execution_start_utc);
576  initialized = true;
577  }
578  return invocation;
579 }
std::string current_utc()
Definition: ERF_Provenance.cpp:318
std::string generate_uuid_v4()
Definition: ERF_Provenance.cpp:273
ExecutionProvenance make_cold_start_provenance(const std::string &execution_uuid, const std::string &execution_start_utc)
Definition: ERF_Provenance.cpp:511
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.

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

◆ lineage_status_token()

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

Return the stable persisted token for a lineage status.

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

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.

513 {
514  ExecutionProvenance result;
515  result.simulation_uuid = execution_uuid;
516  result.execution_uuid = execution_uuid;
517  result.execution_lineage = {execution_uuid};
518  result.execution_start_utc = execution_start_utc;
519  return result;
520 }

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.

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

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.

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

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.

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

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.

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

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.

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

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.

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

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().