Skip to content

Fix WRR QoS counter convergence check - #6022

Open
AishwaryaSKulakrni wants to merge 2 commits into
openconfig:mainfrom
AishwaryaSKulakrni:aishkulk-fix-wrr-qos-counter-convergence
Open

Fix WRR QoS counter convergence check#6022
AishwaryaSKulakrni wants to merge 2 commits into
openconfig:mainfrom
AishwaryaSKulakrni:aishkulk-fix-wrr-qos-counter-convergence

Conversation

@AishwaryaSKulakrni

Copy link
Copy Markdown
Contributor

Summary

Improve WRR traffic-test reliability by waiting for QoS telemetry counters to converge after traffic stops.

Changes

  • Track unique queues to avoid duplicate telemetry queries.
  • Record the traffic stop time and require fresh counter samples taken afterward.
  • Wait up to 90 seconds for transmit counters to include all packets received by the ATE.
  • Replace the fixed 10-second delay and potentially stale counter snapshot.
  • Add clearer timeout diagnostics with source and receive timestamps.

@AishwaryaSKulakrni
AishwaryaSKulakrni requested a review from a team as a code owner September 11, 2026 06:28
@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 enhances the reliability of WRR traffic tests by implementing a more robust mechanism for verifying QoS telemetry counters. By moving away from static delays and adopting a state-aware convergence check, the test suite now ensures that counter samples are fresh and reflect the actual traffic state, significantly reducing flakiness in performance validation.

Highlights

  • Telemetry Convergence: Replaced the fixed 10-second sleep with a dynamic 90-second wait period to ensure QoS telemetry counters fully converge after traffic stops.
  • Queue Tracking: Introduced a unique queue map to prevent redundant telemetry queries and ensure consistent counter tracking across all active queues.
  • Diagnostic Improvements: Added detailed timeout diagnostics, including source and receive timestamps, to facilitate easier debugging of telemetry convergence issues.
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

OpenConfigBot commented Sep 11, 2026

Copy link
Copy Markdown

Pull Request Functional Test Report for #6022 / 2a00ed6

Virtual Devices

Device Test Test Documentation Job Raw Log
Arista cEOS status
DP-1.9: WRR traffic test
Cisco 8000E status
DP-1.9: WRR traffic test
Cisco XRd status
DP-1.9: WRR traffic test
Juniper ncPTX status
DP-1.9: WRR traffic test
Nokia SR Linux status
DP-1.9: WRR traffic test
Openconfig Lemming status
DP-1.9: WRR traffic test

Hardware Devices

Device Test Test Documentation Raw Log
Arista status
DP-1.9: WRR traffic test
Cisco status
DP-1.9: WRR traffic test
Juniper status
DP-1.9: WRR traffic test
Nokia status
DP-1.9: WRR traffic test

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 WRR traffic test to track unique queue names and wait for QoS counters to converge after traffic stops using gnmi.Watch instead of a static sleep. The review feedback points out two important issues: first, comparing the DUT's source timestamp with the test runner's local time can cause flakiness due to clock skew, so RecvTimestamp should be used instead; second, failing to retrieve initial QoS counters should trigger a t.Fatalf rather than a t.Errorf with a continue to prevent the test from proceeding with uninitialized values and potentially passing falsely.

Comment on lines +918 to +937
awaitCounter := func(counterName, queue string, query ygnmi.SingletonQuery[uint64], before, delta uint64) uint64 {
t.Helper()
want := before + delta
isConverged := func(val *ygnmi.Value[uint64]) bool {
got, present := val.Val()
return present && val.Timestamp.After(trafficStopTime) && got >= want
}
count, ok := gnmi.Watch(t, dut, query, counterConvergenceTimeout, isConverged).Await(t)
if count == nil {
t.Errorf("No %s sample for queue %q on interface %q within %v; want >= %d with source timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, want, trafficStopTime)
return 0
}
got, present := count.Val()
if !ok || !present {
t.Errorf("%s for queue %q on interface %q did not converge within %v: got %d (present=%v), want >= %d; source timestamp %v, receive timestamp %v, want source timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, got, present, want, count.Timestamp, count.RecvTimestamp, trafficStopTime)
return got
}
t.Logf("%s for queue %q converged to %d (want >= %d), source timestamp %v, receive timestamp %v", counterName, queue, got, want, count.Timestamp, count.RecvTimestamp)
return got
}

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.

high

Comparing the DUT's source timestamp (val.Timestamp) with the test runner's local time (trafficStopTime) can lead to flaky or failing tests due to clock skew between the DUT and the test runner. In shared or virtual lab environments, clock synchronization is often imperfect.

Using val.RecvTimestamp instead of val.Timestamp is much safer because RecvTimestamp is generated locally by the gNMI client on the test runner, which shares the same clock domain as trafficStopTime.

Suggested change
awaitCounter := func(counterName, queue string, query ygnmi.SingletonQuery[uint64], before, delta uint64) uint64 {
t.Helper()
want := before + delta
isConverged := func(val *ygnmi.Value[uint64]) bool {
got, present := val.Val()
return present && val.Timestamp.After(trafficStopTime) && got >= want
}
count, ok := gnmi.Watch(t, dut, query, counterConvergenceTimeout, isConverged).Await(t)
if count == nil {
t.Errorf("No %s sample for queue %q on interface %q within %v; want >= %d with source timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, want, trafficStopTime)
return 0
}
got, present := count.Val()
if !ok || !present {
t.Errorf("%s for queue %q on interface %q did not converge within %v: got %d (present=%v), want >= %d; source timestamp %v, receive timestamp %v, want source timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, got, present, want, count.Timestamp, count.RecvTimestamp, trafficStopTime)
return got
}
t.Logf("%s for queue %q converged to %d (want >= %d), source timestamp %v, receive timestamp %v", counterName, queue, got, want, count.Timestamp, count.RecvTimestamp)
return got
}
awaitCounter := func(counterName, queue string, query ygnmi.SingletonQuery[uint64], before, delta uint64) uint64 {
t.Helper()
want := before + delta
isConverged := func(val *ygnmi.Value[uint64]) bool {
got, present := val.Val()
return present && val.RecvTimestamp.After(trafficStopTime) && got >= want
}
count, ok := gnmi.Watch(t, dut, query, counterConvergenceTimeout, isConverged).Await(t)
if count == nil {
t.Errorf("No %s sample for queue %q on interface %q within %v; want >= %d with receive timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, want, trafficStopTime)
return 0
}
got, present := count.Val()
if !ok || !present {
t.Errorf("%s for queue %q on interface %q did not converge within %v: got %d (present=%v), want >= %d; source timestamp %v, receive timestamp %v, want receive timestamp after %v", counterName, queue, dp3.Name(), counterConvergenceTimeout, got, present, want, count.Timestamp, count.RecvTimestamp, trafficStopTime)
return got
}
t.Logf("%s for queue %q converged to %d (want >= %d), source timestamp %v, receive timestamp %v", counterName, queue, got, want, count.Timestamp, count.RecvTimestamp)
return got
}

Comment on lines +870 to 884
for queue := range queueNames {
count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t)
if !ok {
t.Errorf("TransmitPkts count for queue %q on interface %q not available within %v", data.queue, dp3.Name(), timeout)
t.Errorf("TransmitPkts count for queue %q on interface %q not available within %v", queue, dp3.Name(), timeout)
continue
}
dutQosPktsBeforeTraffic[data.queue], _ = count.Val()
dutQosPktsBeforeTraffic[queue], _ = count.Val()

count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(data.queue).DroppedPkts().State(), timeout, isPresent).Await(t)
count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t)
if !ok {
t.Errorf("DroppedPkts count for queue %q on interface %q not available within %v", data.queue, dp3.Name(), timeout)
t.Errorf("DroppedPkts count for queue %q on interface %q not available within %v", queue, dp3.Name(), timeout)
continue
}
dutQosDroppedPktsBeforeTraffic[data.queue], _ = count.Val()
dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val()
}

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

Using t.Errorf and continue when initial QoS counters cannot be retrieved allows the test to proceed with uninitialized (zero) values. This can lead to silent test bypasses or false positives because the subsequent convergence check (got >= want) might be immediately satisfied if the actual counter on the DUT is already non-zero.

Since the test logic depends on accurate initial counters to calculate the expected increments, we should fail fast using t.Fatalf if the initial telemetry is unavailable.

			for queue := range queueNames {
				count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t)
				if !ok {
					t.Fatalf("TransmitPkts count for queue %q on interface %q not available within %v", queue, dp3.Name(), timeout)
				}
				dutQosPktsBeforeTraffic[queue], _ = count.Val()

				count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t)
				if !ok {
					t.Fatalf("DroppedPkts count for queue %q on interface %q not available within %v", queue, dp3.Name(), timeout)
				}
				dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val()
			}
References
  1. In tests, t.Fatalf is preferred over t.Errorf when a failure makes subsequent test steps meaningless, as this fails fast and reduces overall test execution time.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants