///|
/// Deterministic Fabric load-profile fixture for W5B.
///|
pub(all) enum FabricLoadClass {
ProfinetRtLoad
BackgroundBestEffortLoad
AcyclicRecordLoad
NetloadPlaceholderLoad
} derive(Eq, Debug)
///|
pub fn FabricLoadClass::label(self : FabricLoadClass) -> String {
match self {
ProfinetRtLoad => "profinet-rt"
BackgroundBestEffortLoad => "background-best-effort"
AcyclicRecordLoad => "acyclic-record"
NetloadPlaceholderLoad => "netload-placeholder"
}
}
///|
pub(all) struct FabricBackgroundFlow {
flow_id : Int
source : @core.EndpointId
target : @core.EndpointId
label : String
frame_size_octets : Int
period_ns : Int64
load_class : FabricLoadClass
} derive(Eq, Debug)
///|
pub fn FabricBackgroundFlow::is_rt(self : FabricBackgroundFlow) -> Bool {
self.load_class == ProfinetRtLoad
}
///|
pub fn FabricBackgroundFlow::is_background(self : FabricBackgroundFlow) -> Bool {
self.load_class == BackgroundBestEffortLoad ||
self.load_class == AcyclicRecordLoad
}
///|
pub(all) struct FabricLoadProfile {
name : String
flows : Array[FabricBackgroundFlow]
netload_semantics : String
complete_switched_fabric : Bool
real_netload_evidence : Bool
art_pi_certification_evidence : Bool
} derive(Eq, Debug)
///|
pub fn FabricLoadProfile::background_load_enabled(
self : FabricLoadProfile,
) -> Bool {
self.flows.any(fn(flow) { flow.is_background() })
}
///|
pub fn FabricLoadProfile::declared_fixture_octets(
self : FabricLoadProfile,
) -> Int {
let mut total = 0
for flow in self.flows {
total += flow.frame_size_octets
}
total
}
///|
pub(all) struct FabricLoadProfileRun {
seed : Int
profile : FabricLoadProfile
fabric_event_count : Int
rt_event_count : Int
background_event_count : Int
netload_placeholder_event_count : Int
trace : @trace.TraceLog
digest : @core.SimDigest
} derive(Debug)
///|
pub fn FabricLoadProfileRun::passes(self : FabricLoadProfileRun) -> Bool {
self.profile.background_load_enabled() &&
self.fabric_event_count > 0 &&
self.rt_event_count >= 2 &&
self.background_event_count >= 2 &&
self.netload_placeholder_event_count == 1 &&
self.trace.len() ==
self.fabric_event_count +
self.rt_event_count +
self.background_event_count +
self.netload_placeholder_event_count &&
!self.profile.complete_switched_fabric &&
!self.profile.real_netload_evidence &&
!self.profile.art_pi_certification_evidence
}
///|
pub(all) struct ProfinetRtLoadTraceReport {
trace_event_count : Int
replay_count : Int
same_seed_stable : Bool
digest : @core.SimDigest
rt_event_count : Int
background_event_count : Int
netload_placeholder_event_count : Int
complete_switched_fabric : Bool
real_netload_evidence : Bool
art_pi_certification_evidence : Bool
} derive(Eq, Debug)
///|
pub fn ProfinetRtLoadTraceReport::passes(
self : ProfinetRtLoadTraceReport,
) -> Bool {
self.trace_event_count > 0 &&
self.replay_count == self.trace_event_count &&
self.same_seed_stable &&
self.rt_event_count >= 2 &&
self.background_event_count >= 2 &&
self.netload_placeholder_event_count == 1 &&
!self.complete_switched_fabric &&
!self.real_netload_evidence &&
!self.art_pi_certification_evidence
}
///|
pub fn profinet_rt_load_profile(seed? : Int = 601) -> FabricLoadProfile {
{
name: "profinet-rt-load-profile-\{normalized_seed(seed)}",
flows: [
{
flow_id: 1,
source: @core.EndpointId(1),
target: @core.EndpointId(2),
label: "profinet.rt.cycle",
frame_size_octets: 64,
period_ns: 1_000_000L,
load_class: ProfinetRtLoad,
},
{
flow_id: 2,
source: @core.EndpointId(3),
target: @core.EndpointId(1),
label: "background.snmp.poll",
frame_size_octets: 96,
period_ns: 100_000_000L,
load_class: BackgroundBestEffortLoad,
},
{
flow_id: 3,
source: @core.EndpointId(1),
target: @core.EndpointId(4),
label: "background.dcp.identify",
frame_size_octets: 128,
period_ns: 500_000_000L,
load_class: AcyclicRecordLoad,
},
{
flow_id: 4,
source: @core.EndpointId(0),
target: @core.EndpointId(0),
label: "fabric.netload.placeholder",
frame_size_octets: 0,
period_ns: 0L,
load_class: NetloadPlaceholderLoad,
},
],
netload_semantics: "placeholder-only: deterministic background-flow vocabulary, not measured network load",
complete_switched_fabric: false,
real_netload_evidence: false,
art_pi_certification_evidence: false,
}
}
///|
pub fn run_profinet_rt_load_profile(seed? : Int = 601) -> FabricLoadProfileRun {
let profile = profinet_rt_load_profile(seed~)
let baseline = run_fabric_partition_heal(seed~)
let log = @trace.TraceLog::new()
let mut event_id = 1
let mut rt_event_count = 0
let mut background_event_count = 0
let mut netload_placeholder_event_count = 0
for event in baseline.trace.events {
append_baseline_fabric_event(log, event, event_id~, seed~)
event_id += 1
}
for flow in profile.flows {
match flow.load_class {
ProfinetRtLoad => {
append_load_flow_event(log, event_id~, flow, @trace.Tx, seed~)
event_id += 1
append_load_flow_event(log, event_id~, flow, @trace.Rx, seed~)
event_id += 1
rt_event_count += 2
}
BackgroundBestEffortLoad | AcyclicRecordLoad => {
append_load_flow_event(log, event_id~, flow, @trace.Probe, seed~)
event_id += 1
background_event_count += 1
}
NetloadPlaceholderLoad => {
append_load_flow_event(log, event_id~, flow, @trace.Probe, seed~)
event_id += 1
netload_placeholder_event_count += 1
}
}
}
{
seed,
profile,
fabric_event_count: baseline.trace.len(),
rt_event_count,
background_event_count,
netload_placeholder_event_count,
trace: log,
digest: log.portable_digest(seed~),
}
}
///|
pub fn profinet_rt_load_trace_fixture(seed? : Int = 601) -> @trace.TraceLog {
run_profinet_rt_load_profile(seed~).trace
}
///|
pub fn profinet_rt_load_profile_report(
seed? : Int = 601,
) -> ProfinetRtLoadTraceReport {
let run = run_profinet_rt_load_profile(seed~)
let same_seed = run.digest == run_profinet_rt_load_profile(seed~).digest
{
trace_event_count: run.trace.len(),
replay_count: replay_count(run.trace),
same_seed_stable: same_seed,
digest: run.digest,
rt_event_count: run.rt_event_count,
background_event_count: run.background_event_count,
netload_placeholder_event_count: run.netload_placeholder_event_count,
complete_switched_fabric: run.profile.complete_switched_fabric,
real_netload_evidence: run.profile.real_netload_evidence,
art_pi_certification_evidence: run.profile.art_pi_certification_evidence,
}
}
///|
fn append_baseline_fabric_event(
log : @trace.TraceLog,
event : @trace.TraceEvent,
event_id~ : Int,
seed~ : Int,
) -> Unit {
let direction = match event.direction {
@trace.Fault => @trace.Probe
other => other
}
log.append(
@trace.TraceEvent::make(
event_id~,
parent_id=event.parent_id,
vtime=event.vtime,
clock_domain="sim",
raw_ns=event.raw_ns,
node_id="fabric",
medium_id="fabric-load-min",
channel_id=event.channel_id,
direction~,
payload_digest=event.payload_digest,
rng_step=event_id,
seed~,
backend=@core.SimNative,
label="fabric.load.baseline." + event.label,
),
)
}
///|
fn append_load_flow_event(
log : @trace.TraceLog,
event_id~ : Int,
flow : FabricBackgroundFlow,
direction : @trace.TraceDirection,
seed~ : Int,
) -> Unit {
let time_ns = 200L + Int64::from_int(event_id) * 10L
log.append(
@trace.TraceEvent::make(
event_id~,
parent_id=None,
vtime=@core.VTime::from_ns(time_ns),
clock_domain="sim",
raw_ns=time_ns,
node_id="fabric-load",
medium_id="fabric-load-min",
channel_id=Some(@core.ChannelId(flow.flow_id)),
direction~,
payload_digest=Some(
flow.frame_size_octets + flow.load_class.code() * 1000,
),
rng_step=event_id,
seed~,
backend=@core.SimNative,
label="fabric.load." + flow.label + "." + direction.label(),
),
)
}
///|
fn FabricLoadClass::code(self : FabricLoadClass) -> Int {
match self {
ProfinetRtLoad => 1
BackgroundBestEffortLoad => 2
AcyclicRecordLoad => 3
NetloadPlaceholderLoad => 4
}
}
///|
fn replay_count(log : @trace.TraceLog) -> Int {
let replay = @trace.Replay::from_log(log)
let mut count = 0
while !replay.is_exhausted() {
ignore(replay.next_event())
count += 1
}
count
}