Skip to content

[Optimization] Improve DSCP Transparency test by reducing the number of flows - #5837

Open
RishabhAgarwal-2001 wants to merge 1 commit into
mainfrom
rishabh/improve-dscp-transparency-test
Open

[Optimization] Improve DSCP Transparency test by reducing the number of flows#5837
RishabhAgarwal-2001 wants to merge 1 commit into
mainfrom
rishabh/improve-dscp-transparency-test

Conversation

@RishabhAgarwal-2001

Copy link
Copy Markdown
Contributor

Changes in this PR are to reduce the number of flows used by the DSCP Transparency test. In its current state, the test used 64 flows which consumed a lot of resource on OTG causing it to take a very long time. In our runs, we noticed test running for around 2 hours and ultimately timing out!

With these changes, we use 7 flows on OTG, without impacting the efficacy or leniency of the test. This resulted in a gain of around 80% in performance with total runtime now down to around 25 minutes.

README has also been updated to reflect the new reality of the implementation.

@RishabhAgarwal-2001 RishabhAgarwal-2001 self-assigned this Aug 6, 2026
@RishabhAgarwal-2001
RishabhAgarwal-2001 requested a review from a team as a code owner August 6, 2026 10:02
@RishabhAgarwal-2001 RishabhAgarwal-2001 added the Optimization Test Optimization Efforts label Aug 6, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request optimizes the DSCP Transparency test by significantly reducing the number of generated flows. By grouping DSCP values and streamlining the validation process, the test's resource consumption on the ATE is lowered, leading to a substantial improvement in execution time without compromising test efficacy.

Highlights

  • Performance Optimization: Reduced the number of flows in the DSCP Transparency test from 64 to 7, resulting in an 80% reduction in total runtime.
  • Code Refactoring: Refactored flow creation and validation logic to handle grouped DSCP values and implemented a more efficient metric collection helper.
  • Documentation: Updated the README to accurately describe the updated test implementation and flow configuration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@OpenConfigBot

Copy link
Copy Markdown

Pull Request Functional Test Report for #5837 / b9c90dd

Virtual Devices

Device Test Test Documentation Job Raw Log
Arista cEOS status
DP-1.17: DSCP Transparency with ECN
5a202ca3 Log
Cisco 8000E status
DP-1.17: DSCP Transparency with ECN
3d64a8a8 Log
Cisco XRd status
DP-1.17: DSCP Transparency with ECN
b1e2d4e8 Log
Juniper ncPTX status
DP-1.17: DSCP Transparency with ECN
a53c31f1 Log
Nokia SR Linux status
DP-1.17: DSCP Transparency with ECN
073c5bbd Log
Openconfig Lemming status
DP-1.17: DSCP Transparency with ECN
3908f4dd Log

Hardware Devices

Device Test Test Documentation Raw Log
Arista 7808 status
DP-1.17: DSCP Transparency with ECN
Cisco 8808 status
DP-1.17: DSCP Transparency with ECN
Juniper PTX10008 status
DP-1.17: DSCP Transparency with ECN
Nokia 7250 IXR-10e status
DP-1.17: DSCP Transparency with ECN

Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the DSCP transparency test to map QoS queues to specific DSCP value ranges, reducing the number of generated flows. It also optimizes getQueueCounters by batching gNMI operations to avoid the N+1 query anti-pattern, and introduces a waitForAndGetTaggedMetrics helper to replace custom polling loops. The reviewer feedback highlights an issue in waitForAndGetTaggedMetrics where the check for missing metrics is hardcoded to all 64 DSCP values across both ports, which will cause false-positive errors during timeouts in subtests that only use a subset of these metrics. The reviewer suggests dynamically passing the expected metric IDs to the helper function to ensure accurate error reporting.

Comment on lines +937 to +994
func waitForAndGetTaggedMetrics(t *testing.T, ate *ondatra.ATEDevice, expectedMetricCount int) map[string][]*otgtelemetry.Flow_TaggedMetric {
t.Helper()
deadline := time.Now().Add(5 * time.Minute)
taggedMetricsQuery := gnmi.OTG().FlowAny().TaggedMetricAny().State()

var metricsByTag map[string][]*otgtelemetry.Flow_TaggedMetric
var lastCount int
var lastLogTime time.Time

for time.Now().Before(deadline) {
metricsByTag = make(map[string][]*otgtelemetry.Flow_TaggedMetric)
for _, val := range gnmi.LookupAll(t, ate.OTG(), taggedMetricsQuery) {
if et, ok := val.Val(); ok {
var dscp string
var port string
for _, tag := range et.Tags {
tagName := tag.GetTagName()
if strings.Contains(tagName, "dst-dscp-") {
dscp = tag.GetTagValue().GetValueAsHex()
parts := strings.Split(tagName, "-")
port = parts[len(parts)-1]
}
}
if dscp != "" && port != "" {
dscpInt, err := strconv.ParseInt(strings.TrimPrefix(dscp, "0x"), 16, 64)
if err == nil {
metricID := fmt.Sprintf("dscp-%d-%s", dscpInt, port)
metricsByTag[metricID] = append(metricsByTag[metricID], et)
}
}
}
}

if len(metricsByTag) >= expectedMetricCount {
return metricsByTag
}

if len(metricsByTag) != lastCount || time.Since(lastLogTime) > 10*time.Second {
t.Logf("Waiting for tagged metrics... collected %d/%d distinct metric IDs", len(metricsByTag), expectedMetricCount)
lastCount = len(metricsByTag)
lastLogTime = time.Now()
}

time.Sleep(2 * time.Second)
}

var missing []string
for _, port := range []string{"atePort2", "atePort3"} {
for dscp := 0; dscp < 64; dscp++ {
metricID := fmt.Sprintf("dscp-%d-%s", dscp, port)
if _, ok := metricsByTag[metricID]; !ok {
missing = append(missing, metricID)
}
}
}
t.Fatalf("Timed out waiting for tagged metrics to populate on the ATE. Got %d, expected %d. Missing: %v", len(metricsByTag), expectedMetricCount, missing)
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The waitForAndGetTaggedMetrics helper function currently hardcodes the check for missing metrics to include all 64 DSCP values across both atePort2 and atePort3. However, different subtests expect different subsets of these metrics:

If any of these tests time out, the error message will be cluttered with false-positive "missing" metrics (e.g., all 64 atePort3 metrics for Sub Test #1, or DSCPs 0-47 for Sub Test #3), making debugging extremely difficult.

We can fix this by passing the expected metric IDs directly to waitForAndGetTaggedMetrics so it can dynamically and accurately report only the metrics that were actually expected but missing.

func waitForAndGetTaggedMetrics(t *testing.T, ate *ondatra.ATEDevice, expectedMetricIDs []string) map[string][]*otgtelemetry.Flow_TaggedMetric {
	t.Helper()
	deadline := time.Now().Add(5 * time.Minute)
	taggedMetricsQuery := gnmi.OTG().FlowAny().TaggedMetricAny().State()

	expectedSet := make(map[string]bool)
	for _, id := range expectedMetricIDs {
		expectedSet[id] = true
	}

	var metricsByTag map[string][]*otgtelemetry.Flow_TaggedMetric
	var lastCount int
	var lastLogTime time.Time

	for time.Now().Before(deadline) {
		metricsByTag = make(map[string][]*otgtelemetry.Flow_TaggedMetric)
		for _, val := range gnmi.LookupAll(t, ate.OTG(), taggedMetricsQuery) {
			if et, ok := val.Val(); ok {
				var dscp string
				var port string
				for _, tag := range et.Tags {
					tagName := tag.GetTagName()
					if strings.Contains(tagName, "dst-dscp-") {
						dscp = tag.GetTagValue().GetValueAsHex()
						parts := strings.Split(tagName, "-")
						port = parts[len(parts)-1]
					}
				}
				if dscp != "" && port != "" {
					dscpInt, err := strconv.ParseInt(strings.TrimPrefix(dscp, "0x"), 16, 64)
					if err == nil {
						metricID := fmt.Sprintf("dscp-%d-%s", dscpInt, port)
						if expectedSet[metricID] {
							metricsByTag[metricID] = append(metricsByTag[metricID], et)
						}
					}
				}
			}
		}

		if len(metricsByTag) >= len(expectedMetricIDs) {
			return metricsByTag
		}

		if len(metricsByTag) != lastCount || time.Since(lastLogTime) > 10*time.Second {
			t.Logf("Waiting for tagged metrics... collected %d/%d distinct metric IDs", len(metricsByTag), len(expectedMetricIDs))
			lastCount = len(metricsByTag)
			lastLogTime = time.Now()
		}

		time.Sleep(2 * time.Second)
	}

	var missing []string
	for _, id := range expectedMetricIDs {
		if _, ok := metricsByTag[id]; !ok {
			missing = append(missing, id)
		}
	}
	t.Fatalf("Timed out waiting for tagged metrics to populate on the ATE. Got %d, expected %d. Missing: %v", len(metricsByTag), len(expectedMetricIDs), missing)
	return nil
}
References
  1. Refactor repeated telemetry wait or validation logic into reusable helper functions to improve maintainability and facilitate future optimizations (e.g., batching gNMI lookups) or cross-vendor support.

Comment on lines 591 to +592
// Wait for ALL tagged metrics to populate first
deadline := time.Now().Add(45 * time.Second)
metricsPopulated := false
for time.Now().Before(deadline) {
allFound := true
for dscpValue := 0; dscpValue < 64; dscpValue++ {
etPath := gnmi.OTG().Flow(fmt.Sprintf("dscp-%d-%s", dscpValue, atePort2.Name)).TaggedMetricAny()
vals := gnmi.LookupAll(t, ate.OTG(), etPath.State())
if len(vals) == 0 || !vals[0].IsPresent() {
allFound = false
break
}
}
if allFound {
metricsPopulated = true
break
}
time.Sleep(2 * time.Second)
}
if !metricsPopulated {
t.Fatalf("Timed out waiting for tagged metrics to populate on the ATE")
}
metricsByTag := waitForAndGetTaggedMetrics(t, ate, 64)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the call to waitForAndGetTaggedMetrics to pass the expected metric IDs for this subtest (only atePort2 and DSCPs 0-63).

	// Wait for ALL tagged metrics to populate first
	var expectedIDs []string
	for dscp := 0; dscp < 64; dscp++ {
		expectedIDs = append(expectedIDs, fmt.Sprintf("dscp-%d-%s", dscp, atePort2.Name))
	}
	metricsByTag := waitForAndGetTaggedMetrics(t, ate, expectedIDs)
References
  1. Refactor repeated telemetry wait or validation logic into reusable helper functions to improve maintainability and facilitate future optimizations (e.g., batching gNMI lookups) or cross-vendor support.

}
time.Sleep(2 * time.Second)
}
metricsByTag := waitForAndGetTaggedMetrics(t, ate, 128)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the call to waitForAndGetTaggedMetrics to pass the expected metric IDs for this subtest (both atePort2 and atePort3 for DSCPs 0-63).

	var expectedIDs []string
	for _, sourceAtePort := range []*attrs.Attributes{atePort2, atePort3} {
		for dscp := 0; dscp < 64; dscp++ {
			expectedIDs = append(expectedIDs, fmt.Sprintf("dscp-%d-%s", dscp, sourceAtePort.Name))
		}
	}
	metricsByTag := waitForAndGetTaggedMetrics(t, ate, expectedIDs)
References
  1. Refactor repeated telemetry wait or validation logic into reusable helper functions to improve maintainability and facilitate future optimizations (e.g., batching gNMI lookups) or cross-vendor support.

Comment on lines 843 to +844
// Wait for ALL 48-63 tagged metrics to populate first
deadline := time.Now().Add(45 * time.Second)
metricsPopulated := false
for time.Now().Before(deadline) {
allFound := true
for _, sourceAtePort := range []*attrs.Attributes{atePort2, atePort3} {
for dscpValue := 48; dscpValue < 64; dscpValue++ {
etPath := gnmi.OTG().Flow(fmt.Sprintf("dscp-%d-%s", dscpValue, sourceAtePort.Name)).TaggedMetricAny()
vals := gnmi.LookupAll(t, ate.OTG(), etPath.State())
if len(vals) == 0 || !vals[0].IsPresent() {
allFound = false
break
}
}
if !allFound {
break
}
}
if allFound {
metricsPopulated = true
break
}
time.Sleep(2 * time.Second)
}
metricsByTag := waitForAndGetTaggedMetrics(t, ate, 32)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the call to waitForAndGetTaggedMetrics to pass the expected metric IDs for this subtest (both atePort2 and atePort3 for DSCPs 48-63).

	// Wait for ALL 48-63 tagged metrics to populate first
	var expectedIDs []string
	for _, sourceAtePort := range []*attrs.Attributes{atePort2, atePort3} {
		for dscp := 48; dscp < 64; dscp++ {
			expectedIDs = append(expectedIDs, fmt.Sprintf("dscp-%d-%s", dscp, sourceAtePort.Name))
		}
	}
	metricsByTag := waitForAndGetTaggedMetrics(t, ate, expectedIDs)
References
  1. Refactor repeated telemetry wait or validation logic into reusable helper functions to improve maintainability and facilitate future optimizations (e.g., batching gNMI lookups) or cross-vendor support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Optimization Test Optimization Efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants