From b5e7bb165f30ba0a1575eb44c20a78b3d1fd000f Mon Sep 17 00:00:00 2001
From: "S.H." <sebastian.heiss94@proton.me>
Date: Wed, 16 Apr 2025 10:20:56 +0200
Subject: [PATCH] Change benchmark mechanism, adjust readme to account for
 interactive prompt

---
 applications/rtdt-manager/README.md           |  5 +-
 .../rtdt-manager/benchmark/benchmark.go       | 40 ++++++++++++
 .../rtdt-manager/rtdt-manager/rtdt-manager.go | 62 ++++++++++++++-----
 applications/rtdt-manager/util/util.go        |  2 +-
 applications/rtdt-manager/venv/venv.go        | 45 +++++++++-----
 5 files changed, 120 insertions(+), 34 deletions(-)
 create mode 100644 applications/rtdt-manager/benchmark/benchmark.go

diff --git a/applications/rtdt-manager/README.md b/applications/rtdt-manager/README.md
index 11f756cf2..481a41668 100644
--- a/applications/rtdt-manager/README.md
+++ b/applications/rtdt-manager/README.md
@@ -42,8 +42,9 @@ determine the addresses and configuration of the goSDN Controller, Plugin-Regist
 from the SDN-config file in .json. This is combined into a Containerlab configuration that specifies the entire physical network and once this has been deployed with Containerlab,
 the .json file is applied to the MongoDB database store with the Northbound Interface (NBI) configuration management service.
 
-After this, a NDT is launched by retrieving the Topology information from the Database and recombining it into a Containerlab yaml file with the base Containerlab file passed in
-earlier. The IP addresses are transposed to avoid conflicts in Docker and the Digital Twin network has its own instances of RabbitMQ, MongoDB, etc.
+After this, the user is dropped into a simple CLI. A Network Digital Twin (NDT) can be started with `launch-twin`, which retrieves the Topology information from the Database and recombines
+it into a Containerlab yaml file with the base Containerlab file passed in earlier. The IP addresses are transposed to avoid conflicts in Docker and the Digital Twin network has its own
+instances of RabbitMQ, MongoDB, etc.
 
 The rtdt-manager subscribes to specific events in the physical network and a callback is triggered when these events occur. In this callback, the YANG path of the data that experienced a change
 as well as the new value can be retrieved and are applied to the NDT.
diff --git a/applications/rtdt-manager/benchmark/benchmark.go b/applications/rtdt-manager/benchmark/benchmark.go
new file mode 100644
index 000000000..d3e224f9e
--- /dev/null
+++ b/applications/rtdt-manager/benchmark/benchmark.go
@@ -0,0 +1,40 @@
+package benchmark
+
+import (
+	"fmt"
+	"time"
+)
+
+type Benchmark0 struct {
+	StartTimeRealnet time.Time
+
+	SendChangeRequest     time.Time
+	ReceiveChangeRequest  time.Time
+	ReceiveChangeResponse time.Time
+	ChangeRequestEnd      time.Time
+
+	SendCommitRequest     time.Time
+	ReceiveCommitResponse time.Time
+	CommitEnd             time.Time
+	EndTime               time.Time
+	PropagationDelay      time.Duration
+}
+
+var Current Benchmark0
+
+func (b *Benchmark0) GetDurations() {
+	startToSend := Diff(b.StartTimeRealnet, b.SendChangeRequest).Microseconds()
+	fmt.Println("Start to Send:", startToSend)
+}
+
+func Diff(start, end time.Time) time.Duration {
+	duration := end.Sub(start)
+	return duration
+}
+
+func (b *Benchmark0) Print() {
+	fmt.Println("StartTimeRealnet:")
+	fmt.Println("SendChangeRequest to ReceiveChangeRequest:", b.ReceiveChangeRequest.Sub(b.SendChangeRequest).Microseconds())
+	fmt.Println("SendChangeRequest to ChangeRequestEnd:", b.ChangeRequestEnd.Sub(b.SendChangeRequest).Microseconds())
+	fmt.Println("PropagationDelay:", b.PropagationDelay.Microseconds())
+}
diff --git a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
index 464b42ffd..c237a8186 100644
--- a/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
+++ b/applications/rtdt-manager/rtdt-manager/rtdt-manager.go
@@ -2,6 +2,8 @@ package rtdtmanager
 
 import (
 	"bufio"
+	"crypto/rand"
+	"encoding/hex"
 	"fmt"
 	"os"
 	"os/signal"
@@ -20,6 +22,7 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/sdnconfig"
 
 	// "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/util"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/benchmark"
 	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/venv"
 )
 
@@ -31,6 +34,7 @@ type RtdtManager struct {
 	waitGroup      sync.WaitGroup
 	stopChan       chan os.Signal
 	baseClabConfig *clabconfig.ClabConfig
+	benchmark0     bool
 }
 
 // needs to be passed a running realnet VEnv
@@ -40,6 +44,7 @@ func NewRtdtManager() *RtdtManager {
 		waitGroup:      sync.WaitGroup{},
 		stopChan:       make(chan os.Signal, 1),
 		baseClabConfig: nil,
+		benchmark0:     false,
 	}
 	signal.Notify(rMan.stopChan, os.Interrupt)
 	fmt.Println("Success: RtdtManager created")
@@ -240,27 +245,36 @@ func (r *RtdtManager) ApplyEvents(twinName string) error {
 	return nil
 }
 
-// Performance benchmarks of realnet
-func (r *RtdtManager) RunBenchmark0() error {
+// Performance benchmarks of twin, change hostname to random string
+func (r *RtdtManager) RunBenchmark0(numTests int64) error {
 	var mneid string
+	r.benchmark0 = true
 	for _, node := range r.realnet.GetSdnConfig().Nodes {
 		if strings.HasPrefix(node.Name, "gnmi-target-switch0") {
 			mneid = node.ID
 		}
 	}
-	numTests := int64(200)
-	results := make([]time.Duration, numTests)
+	//results := make([]time.Duration, numTests)
 	// Change a property and measure the time when it's applied
 	for i, value := 0, int64(2001); i < int(numTests); i, value = i+1, value+1 {
-		t1 := time.Now()
-		r.realnet.SetGnmiPath("/interfaces/interface[name=eth2]/config/mtu", strconv.FormatInt(value, 10), mneid, false)
-		timeDiff := time.Now().Sub(t1)
-		results[i] = timeDiff
-	}
+		randBytes := make([]byte, 4)
+		_, err := rand.Read(randBytes)
+		if err != nil {
+			fmt.Println("couldn't create random hostname")
+			return err
+		}
+		randname := hex.EncodeToString(randBytes)
+		//r.realnet.SetGnmiPath("/interfaces/interface[name=eth2]/config/mtu", strconv.FormatInt(value, 10), mneid, false)
+		err = r.realnet.SetGnmiPath("/system/config/hostname", randname, mneid, false)
+		if err != nil {
+			fmt.Printf("Encountered error: %v\n", err)
+		}
 
-	for i, res := range results {
-		fmt.Printf("%d - %v\n", i, res)
 	}
+
+	// for i, res := range results {
+	// 	fmt.Printf("%d - %v\n", i, res)
+	// }
 	return nil
 }
 
@@ -347,8 +361,19 @@ func (r *RtdtManager) Run() error {
 				}
 
 			case "benchmark":
-				fmt.Printf("Launching benchmark!\n")
-				r.RunBenchmark0()
+				var numTests int64
+				var err error
+				if len(tokens) == 2 {
+					numTests, err = strconv.ParseInt(tokens[1], 10, 64)
+					if err != nil {
+						r.RunBenchmark0(numTests)
+					} else {
+						fmt.Printf("Couldn't parse second argument as int64\n")
+						break
+					}
+				} else {
+					r.RunBenchmark0(1)
+				}
 			case "exit", "quit":
 				close(r.stopChan)
 				return
@@ -356,7 +381,7 @@ func (r *RtdtManager) Run() error {
 				fmt.Println("Available commands:")
 				fmt.Println("   launch-twin                         Launch a twin with default options")
 				fmt.Println("   launch-twin <IPv4> <IPv6> <name>    Launch a twin with specified network ranges and name")
-                fmt.Println("   playback                            Play changes recorded by twin back to realnet")
+				fmt.Println("   playback                            Play changes recorded by twin back to realnet")
 				fmt.Println("   benchmark                           Measure propagation delay of twin")
 				fmt.Println("   exit / quit                         Exit the program")
 			}
@@ -453,13 +478,16 @@ func (r *RtdtManager) updateMNECallbackTwin(event *event.Event) {
 		r.rtdt_twins[0].SavedEvents = append(r.rtdt_twins[0].SavedEvents, event)
 		fmt.Printf("Saved change with path %s and value %s\n", path, value)
 	}
-
 }
 
 func (r *RtdtManager) updateMNECallbackRealnet(event *event.Event) {
 	if !r.realnet.SyncBack {
 		return
 	}
+	var b0 benchmark.Benchmark0
+	if r.benchmark0 {
+		b0.StartTimeRealnet = time.Now()
+	}
 	fmt.Println("--------------------------------")
 	fmt.Println("---------- MNE EVENT -----------")
 	fmt.Println("EventID: ", event.ID.ID())
@@ -509,7 +537,7 @@ func (r *RtdtManager) updateMNECallbackRealnet(event *event.Event) {
 		if strings.HasPrefix(path, prefix) && strings.HasSuffix(path, suffixMTU) {
 			fmt.Println("--- CHANGE MTU TRIGGERED ---")
 			fmt.Println("Value of new MTU: ", value)
-			twin.SetGnmiPath(path, value, twinEntityID, false)
+			twin.SetGnmiPath(path, value, twinEntityID, r.benchmark0)
 			// Set Hostname
 		} else if strings.HasPrefix(path, prefixHostname) {
 			// Hostname change
@@ -520,7 +548,7 @@ func (r *RtdtManager) updateMNECallbackRealnet(event *event.Event) {
 					continue
 				}
 				fmt.Println("ENTERING SETGNMIPATH, value: ", value, "path:", path)
-				err = twin.SetGnmiPath(path, value, twinEntityID, false)
+				err = twin.SetGnmiPath(path, value, twinEntityID, r.benchmark0)
 				if err != nil {
 					fmt.Println("Callback failed:", err)
 					return
diff --git a/applications/rtdt-manager/util/util.go b/applications/rtdt-manager/util/util.go
index 48cc23af6..3e0b4a5b4 100644
--- a/applications/rtdt-manager/util/util.go
+++ b/applications/rtdt-manager/util/util.go
@@ -9,7 +9,7 @@ import (
 )
 
 func Now() int64 {
-	return int64(time.Now().UnixNano())
+	return time.Now().UnixNano()
 }
 
 func GenerateGosdnPath() (string, error) {
diff --git a/applications/rtdt-manager/venv/venv.go b/applications/rtdt-manager/venv/venv.go
index 26839ba93..8d300079c 100644
--- a/applications/rtdt-manager/venv/venv.go
+++ b/applications/rtdt-manager/venv/venv.go
@@ -16,6 +16,7 @@ import (
 	topoPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
+	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/benchmark"
 	clab "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
 	clabconfig "code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/clab-config"
 	"code.fbi.h-da.de/danet/gosdn/applications/rtdt-manager/gosdnutil"
@@ -273,7 +274,7 @@ func getTypedValue(value, ytype string) (*gnmi.TypedValue, error) {
 				return nil, err
 			}
 		}
-	case "uint16":
+	case "uint16", "uint32", "uint64":
 		{
 			uintVal, err := strconv.ParseUint(value, 10, 64)
 			if err == nil {
@@ -315,25 +316,18 @@ func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
 		return fmt.Errorf("Encountered error while trying to parse string path into gnmi path: %w", err)
 	}
 	//ytypes.GetOrCreateNode(gnmitargetygot.Schema(), ,gnmiPath)
-	fmt.Println("GETTING SCHEMA")
 	schema, nil := gnmitargetygot.Schema()
-	fmt.Println("GETTING ROOT SCHEMA")
 	rootSchema := schema.RootSchema()
-	fmt.Println("GET OR CREATE NODE")
 	_, entry, err := ytypes.GetOrCreateNode(rootSchema, &gnmitargetygot.Gnmitarget{}, gnmiPath)
 	if err != nil {
 		return fmt.Errorf("SetGnnmiPath Error: %w", err)
 	}
-	//gosdnAddr := v.auth.GetAddress()
-	fmt.Println("GET TYPED VALUE")
 	yangType := entry.Type
 	gnmiVal, err := getTypedValue(value, yangType.Kind.String())
 	if err != nil {
 		fmt.Println("The given type is not supported yet!")
 	}
-	// fmt.Println("YangType Name is ", yangType.Name)
-	// fmt.Println("YangType Type is ", yangType.Type)
-	// fmt.Println("YangType Kind is ", yangType.Kind.String())
+    fmt.Println("gnmiVal:", gnmiVal)
 
 	changeRequest := &networkelement.ChangeRequest{
 		Mneid: mneid,
@@ -345,11 +339,18 @@ func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
 	changeRequests := []*networkelement.ChangeRequest{changeRequest}
 
 	pid := v.pnd.Id
-	setPathResponse, err := mneService.SetPathList(ctx, &networkelement.SetPathListRequest{
+	if save {
+		benchmark.Current.SendChangeRequest = time.Now()
+	}
+	setPathListReq := &networkelement.SetPathListRequest{
 		Timestamp:     util.Now(),
 		Pid:           pid,
 		ChangeRequest: changeRequests,
-	})
+	}
+	setPathResponse, err := mneService.SetPathList(ctx, setPathListReq)
+	if save {
+		benchmark.Current.ChangeRequestEnd = time.Now()
+	}
 	if err != nil {
 		fmt.Printf("Error: %v\n", err)
 		return err
@@ -368,15 +369,31 @@ func (v *VEnv) SetGnmiPath(path, value, mneid string, save bool) error {
 		Pid:       pid,
 	}
 	clResponse, err := mneService.SetChangeList(ctx, &setChangeListRequest)
+	if save {
+		benchmark.Current.CommitEnd = time.Now()
+	}
 	if err != nil {
 		fmt.Println("Error, failed to commit changes:", err)
 		return err
 	} else {
 		fmt.Println("Successfully applied changes:", clResponse)
+		if save {
+			benchmark.Current.CommitEnd = time.Now()
+		}
+	}
+
+	if save {
+		benchmark.Current.SendChangeRequest = time.Unix(0, setPathListReq.Timestamp)     // send req in realnet
+		benchmark.Current.ReceiveChangeRequest = time.Unix(0, setPathResponse.Timestamp) // rcv in twin
+
+		benchmark.Current.SendCommitRequest = time.Unix(0, setChangeListRequest.Timestamp)
+		benchmark.Current.ReceiveCommitResponse = time.Unix(0, clResponse.Timestamp)
+
+		benchmark.Current.PropagationDelay = benchmark.Current.StartTimeRealnet.Sub(benchmark.Current.EndTime)
+
+		fmt.Println("---Measurement finished---")
+		benchmark.Current.Print()
 	}
-	// if save {
-	// 	v.SavedChanges = append(v.SavedChanges, changeRequest)
-	// }
 	return nil
 }
 
-- 
GitLab