-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update BrowseDetails data structure #132
Conversation
Refactors the browse functionality to use a new BrowseDetails structure. This change improves the information passed through channels during browsing operations, including task and worker counts. The new structure enhances logging and provides more detailed progress information during node browsing.
WalkthroughThe changes refactor the OPC UA browsing functionality by updating the channel type from Changes
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
@CodeRabbit show your magic by reviewing this PR. Be more strict and get all nitpicks |
🧩 Analysis chain@kanapuli: I'll perform a thorough review of the changes with extra attention to detail and code quality standards. 🏁 Script executed:
Length of output: 9588 @kanapuli: I've reviewed the changes and found several areas for improvement:
// BrowseDetails represents the details of a browse operation.
type BrowseDetails struct {
NodeDef NodeDef
TaskCount int64
WorkerCount int64
} The struct documentation should be more descriptive. Consider: // BrowseDetails encapsulates information about a node browsing operation,
// including the node definition and current operation statistics.
// TaskCount represents the number of pending browse tasks.
// WorkerCount represents the number of active browse operations.
type BrowseDetails struct {
NodeDef NodeDef // The node being browsed
TaskCount int64 // Number of pending browse tasks
WorkerCount int64 // Number of active browse operations
}
opcuaBrowserChan := make(chan BrowseDetails, 100) // in test files
opcuaBrowserChan := make(chan BrowseDetails, 3) // in server_info.go Consider defining these as named constants with clear documentation about their purpose.
func collectNodes(ctx context.Context, nodeBrowserChan chan BrowseDetails, nodeIDMap map[string]*NodeDef, nodes *[]NodeDef, msgChan chan<- string) {
for browseRecord := range nodeBrowserChan {
select {
case <-ctx.Done():
return
default:
// ... existing code
}
}
} Consider handling the context cancellation error: func collectNodes(ctx context.Context, nodeBrowserChan chan BrowseDetails, nodeIDMap map[string]*NodeDef, nodes *[]NodeDef, msgChan chan<- string) error {
for browseRecord := range nodeBrowserChan {
select {
case <-ctx.Done():
return ctx.Err()
default:
// ... existing code
}
}
return nil
}
nodeHeartbeatChan := make(chan NodeDef, 1)
errChanHeartbeat := make(chan error, 1)
opcuaBrowserChanHeartbeat := make(chan BrowseDetails, 1) Consider creating a helper function to create these channel sets with appropriate buffer sizes.
msgChan <- fmt.Sprintf("found node '%s' (%d pending tasks, %d active browse operations)",
browseRecord.NodeDef.BrowseName,
browseRecord.TaskCount,
browseRecord.WorkerCount) Consider using structured logging or a more parseable format for better monitoring integration.
if opcuaBrowserChan == nil {
return fmt.Errorf("opcuaBrowserChan cannot be nil")
}
These changes will improve code quality, maintainability, and reliability. Let me know if you'd like me to elaborate on any of these points. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
opcua_plugin/browse.go (1)
386-386
:⚠️ Potential issueUpdate error messages to reflect the correct timeout duration.
The error messages mention "5 minutes" but the context timeout is set to 1 hour (line 362). This inconsistency could confuse users.
- g.Log.Warn("browse function received timeout signal after 5 minutes. Please select less nodes.") + g.Log.Warn("browse function received timeout signal after 1 hour. Please select less nodes.")Also applies to: 410-410
🧹 Nitpick comments (4)
opcua_plugin/browse.go (4)
64-66
: Update function documentation to reflect the new parameter type.The function documentation should be updated to describe the new
BrowseDetails
channel parameter and its purpose.// Browse is a public wrapper function for the browse function // Avoid using this function directly, use it only for testing +// opcuaBrowserChan receives BrowseDetails containing node information along with task and worker counts
249-254
: Avoid redundant path assignment.The path is being set twice:
- In NodeDef creation (line 235)
- In BrowseDetails creation (line 254)
This is redundant and could lead to confusion.
browserDetails := BrowseDetails{ NodeDef: def, TaskCount: taskWg.Count(), WorkerCount: workerWg.Count(), } -browserDetails.NodeDef.Path = join(task.path, def.BrowseName)
540-541
: Enhance error message with actionable information.The current error message doesn't provide enough context about compatibility requirements or how to resolve the issue.
- g.Log.Errorf("Did not subscribe to any nodes. This can happen if the nodes that are selected are incompatible with this benthos version. Aborting...") + g.Log.Errorf("Did not subscribe to any nodes. This can happen if the selected nodes are incompatible with this benthos version (requires nodes with AttributeIDValue). Please verify node compatibility and refer to documentation for supported node types. Aborting...")
609-628
: Optimize path collision resolution.The current implementation updates paths for both nodes when a collision is found, which could lead to unnecessary updates and potential cascading collisions. Consider updating only the second node's path.
if node.Path == otherNode.Path { - // update only the last element of the path, after the last dot - nodePathSplit := strings.Split(node.Path, ".") - nodePath := strings.Join(nodePathSplit[:len(nodePathSplit)-1], ".") - nodePath = nodePath + "." + sanitize(node.NodeID.String()) - nodes[i].Path = nodePath - otherNodePathSplit := strings.Split(otherNode.Path, ".") otherNodePath := strings.Join(otherNodePathSplit[:len(otherNodePathSplit)-1], ".") otherNodePath = otherNodePath + "." + sanitize(otherNode.NodeID.String()) nodes[j].Path = otherNodePath }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
opcua_plugin/browse.go
(6 hunks)opcua_plugin/browse_frontend.go
(3 hunks)opcua_plugin/opcua_opc-plc_test.go
(1 hunks)opcua_plugin/opcua_unittest_test.go
(3 hunks)opcua_plugin/server_info.go
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**/*`: When reviewing an error message, ensure it meets the...
**/*
: When reviewing an error message, ensure it meets these guidelines. If a message is lacking (e.g., missing clear next steps or context), provide an example of the correct format to help improve clarity and usability.Quick Guidelines for Writing Effective Error Messages
- Structure Your Message with Three Key Parts
• Context:
Explain what the system was doing.
Example: “Couldn’t parse config file: /etc/sample-config.properties.”
• Error Description:
Clearly state what failed without generic wording.
Example: “Invalid snapshot mode ‘nevr’ provided.”
• Mitigation/Next Steps:
Tell the user what to do next. For complex issues, include an error code or documentation link.
Example: “Please update the mode; refer to our docs for valid options.”- Clarity & Conciseness
• Use plain, simple language and avoid unnecessary jargon (unless targeting technical users).
• Aim for short sentences (ideally 8–14 words) for better understanding.- Consistent Tone & Style
• Uniformity: Use the same voice, terminology, and punctuation across all messages.
• Empathy: Maintain a neutral, supportive tone; avoid blaming the user.
• Avoid overly casual language (skip “oops”/“whoops” and excessive exclamation points).- Visibility & Placement (User-Facing Messages)
• Proximity: Place error messages close to the related UI element to reduce cognitive load.
• Design: Use high-contrast text, icons, or subtle animations; follow accessibility guidelines (don’t rely solely on color).- Actionability & Efficiency
• Clearly indicate the next steps (e.g., “Retry later”, “Log in again”, “Check your input”).
• Preserve user input when possible to minimize frustration.
• Where relevant, provide error codes or links for further troubleshooting.- Security & Privacy Considerations
• User-Facing: Avoid exposing internal details or sensitive data.
• Developer/Logging: Include detailed context (like file paths or exception chains) for debugging, but be cautious about sensitive information.- Logging & Exception Best Practices (for Developer/Operations Messages)
• Log the full context—including exception chains—to aid troubleshooting.
• Use structured logging (e.g., JSON) for filtering and analysis.
• Choose a consistent method: either raise an exception or log an error (not both) to avoid redundancy.
• Don’t make error messages an API contract; expose machine-readable error codes or types instead.- Additional Best Practices
• Fail Early: Report errors as soon as possible (e.g., during input validation or startup).
• One Concept, One Term: Use consistent terminology across messages and documentation.
• Localization: For internal logs, use English; if localizing for users, follow a vetted process.
opcua_plugin/server_info.go
opcua_plugin/browse_frontend.go
opcua_plugin/opcua_unittest_test.go
opcua_plugin/opcua_opc-plc_test.go
opcua_plugin/browse.go
`**/*`: Whenever in a new PR a new function or code is intro...
**/*
: Whenever in a new PR a new function or code is introduced, ensure that there is at least somewhere as a code comment, in the README or wherer ever explained the "why" behind the decision. So e.g., why something was preferred over other options, or why a certain loop needs to be executed.
opcua_plugin/server_info.go
opcua_plugin/browse_frontend.go
opcua_plugin/opcua_unittest_test.go
opcua_plugin/opcua_opc-plc_test.go
opcua_plugin/browse.go
🔇 Additional comments (6)
opcua_plugin/browse_frontend.go (2)
14-19
: LGTM! Well-structured type definition.The new
BrowseDetails
type effectively encapsulates both the node information and browsing metrics, providing a clear and maintainable way to track browsing progress.
85-101
: LGTM! Enhanced node collection with progress tracking.The updated
collectNodes
function now provides better visibility into the browsing process by logging both pending tasks and active browse operations.opcua_plugin/server_info.go (1)
36-36
: LGTM! Consistent type usage.The channel type update maintains consistency with the new
BrowseDetails
structure, though the data is not used in this function as noted in the comments.opcua_plugin/opcua_unittest_test.go (2)
69-69
: LGTM! Updated test variable declaration.The test setup correctly uses the new
BrowseDetails
type for the browser channel.
692-712
: LGTM! Well-structured test helper function.The
startBrowsing
helper function maintains its clear structure while adapting to the newBrowseDetails
type.opcua_plugin/opcua_opc-plc_test.go (1)
426-426
: LGTM! Updated test channel type.The test case correctly uses the new
BrowseDetails
type for the browser channel.
Implements AverageResponseTime method for ServerMetrics to provide insights into server performance. Updates BrowseDetails struct and frontend to include this metric, enhancing monitoring capabilities. Improves user feedback during browsing operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
opcua_plugin/browse_frontend.go (1)
62-65
:⚠️ Potential issueReplace sleep-based synchronization with proper channel coordination.
Using
time.Sleep
as a synchronization mechanism is unreliable and could lead to race conditions or missing nodes.Consider using a done channel or wait group to properly coordinate node collection:
- // TODO: Temporary workaround - Adding a timeout ensures all child nodes are properly - // collected in the nodes[] array. Without this timeout, the last children nodes - // may be missing from the results. - time.Sleep(3 * time.Second) + // Create a done channel to signal completion of node collection + done := make(chan struct{}) + go func() { + // Wait for all goroutines to finish + wg.Wait() + // Close channels + close(nodeChan) + close(errChan) + close(opcuaBrowserChan) + // Signal completion + close(done) + }() + + // Wait for completion or context cancellation + select { + case <-done: + // Node collection completed successfully + case <-ctx.Done(): + return rootNode, ctx.Err() + }
🧹 Nitpick comments (4)
opcua_plugin/browse_workers.go (2)
56-66
: Consider maintaining a running average for better performance.The method recalculates the average each time by iterating through all response times. For better performance, consider maintaining a running average and total count.
Here's a suggested implementation:
type ServerMetrics struct { mu sync.Mutex - responseTimes []time.Duration + responseTimes []time.Duration + runningTotal time.Duration currentWorkers int targetLatency time.Duration workerControls map[uuid.UUID]chan struct{} } func (sm *ServerMetrics) recordResponseTime(duration time.Duration) { sm.mu.Lock() defer sm.mu.Unlock() + sm.runningTotal += duration if len(sm.responseTimes) >= SampleSize { + sm.runningTotal -= sm.responseTimes[0] sm.responseTimes = sm.responseTimes[1:] } sm.responseTimes = append(sm.responseTimes, duration) } func (sm *ServerMetrics) AverageResponseTime() time.Duration { sm.mu.Lock() defer sm.mu.Unlock() if len(sm.responseTimes) == 0 { return 0 } - var totalTime time.Duration - for _, t := range sm.responseTimes { - totalTime += t - } - return totalTime / time.Duration(len(sm.responseTimes)) + return sm.runningTotal / time.Duration(len(sm.responseTimes)) }
56-66
: Reduce code duplication in average calculation.The average calculation logic is duplicated between
AverageResponseTime
andadjustWorkers
. Consider extracting this into a private helper method.Here's a suggested implementation:
+func (sm *ServerMetrics) calculateAverage() time.Duration { + if len(sm.responseTimes) == 0 { + return 0 + } + return sm.runningTotal / time.Duration(len(sm.responseTimes)) +} func (sm *ServerMetrics) AverageResponseTime() time.Duration { sm.mu.Lock() defer sm.mu.Unlock() - if len(sm.responseTimes) == 0 { - return 0 - } - var totalTime time.Duration - for _, t := range sm.responseTimes { - totalTime += t - } - return totalTime / time.Duration(len(sm.responseTimes)) + return sm.calculateAverage() } func (sm *ServerMetrics) adjustWorkers(logger Logger) (toAdd, toRemove int) { sm.mu.Lock() defer sm.mu.Unlock() if len(sm.responseTimes) < SampleSize { return 0, 0 } - var totalTime time.Duration - for _, t := range sm.responseTimes { - totalTime += t - } - avgResponse := totalTime / time.Duration(len(sm.responseTimes)) + avgResponse := sm.calculateAverage() oldWorkerCount := sm.currentWorkersAlso applies to: 68-102
opcua_plugin/browse_frontend.go (2)
14-20
: Enhance struct documentation with field descriptions.The struct documentation should explain the purpose of each field to improve code maintainability.
Apply this diff to improve documentation:
-// BrowseDetails represents the details of a browse operation. +// BrowseDetails represents the details of a browse operation, including metrics and node information. +// +// Fields: +// - NodeDef: The node definition containing the node's attributes and relationships +// - TaskCount: Number of pending browse tasks in the queue +// - WorkerCount: Number of active worker goroutines performing browse operations +// - AvgServerResponseTime: Average time taken by the server to respond to browse requests type BrowseDetails struct { NodeDef NodeDef TaskCount int64 WorkerCount int64 AvgServerResponseTime time.Duration }
43-45
: Define channel buffer sizes as constants.The channel buffer sizes are hardcoded. Consider defining them as named constants for better maintainability.
Apply this diff:
+const ( + // ChannelBufferSize defines the buffer size for browse operation channels + ChannelBufferSize = MaxTagsToBrowse +) - nodeChan := make(chan NodeDef, MaxTagsToBrowse) - errChan := make(chan error, MaxTagsToBrowse) - opcuaBrowserChan := make(chan BrowseDetails, MaxTagsToBrowse) + nodeChan := make(chan NodeDef, ChannelBufferSize) + errChan := make(chan error, ChannelBufferSize) + opcuaBrowserChan := make(chan BrowseDetails, ChannelBufferSize)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
opcua_plugin/browse.go
(6 hunks)opcua_plugin/browse_frontend.go
(3 hunks)opcua_plugin/browse_workers.go
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- opcua_plugin/browse.go
🧰 Additional context used
📓 Path-based instructions (2)
`**/*`: When reviewing an error message, ensure it meets the...
**/*
: When reviewing an error message, ensure it meets these guidelines. If a message is lacking (e.g., missing clear next steps or context), provide an example of the correct format to help improve clarity and usability.Quick Guidelines for Writing Effective Error Messages
- Structure Your Message with Three Key Parts
• Context:
Explain what the system was doing.
Example: “Couldn’t parse config file: /etc/sample-config.properties.”
• Error Description:
Clearly state what failed without generic wording.
Example: “Invalid snapshot mode ‘nevr’ provided.”
• Mitigation/Next Steps:
Tell the user what to do next. For complex issues, include an error code or documentation link.
Example: “Please update the mode; refer to our docs for valid options.”- Clarity & Conciseness
• Use plain, simple language and avoid unnecessary jargon (unless targeting technical users).
• Aim for short sentences (ideally 8–14 words) for better understanding.- Consistent Tone & Style
• Uniformity: Use the same voice, terminology, and punctuation across all messages.
• Empathy: Maintain a neutral, supportive tone; avoid blaming the user.
• Avoid overly casual language (skip “oops”/“whoops” and excessive exclamation points).- Visibility & Placement (User-Facing Messages)
• Proximity: Place error messages close to the related UI element to reduce cognitive load.
• Design: Use high-contrast text, icons, or subtle animations; follow accessibility guidelines (don’t rely solely on color).- Actionability & Efficiency
• Clearly indicate the next steps (e.g., “Retry later”, “Log in again”, “Check your input”).
• Preserve user input when possible to minimize frustration.
• Where relevant, provide error codes or links for further troubleshooting.- Security & Privacy Considerations
• User-Facing: Avoid exposing internal details or sensitive data.
• Developer/Logging: Include detailed context (like file paths or exception chains) for debugging, but be cautious about sensitive information.- Logging & Exception Best Practices (for Developer/Operations Messages)
• Log the full context—including exception chains—to aid troubleshooting.
• Use structured logging (e.g., JSON) for filtering and analysis.
• Choose a consistent method: either raise an exception or log an error (not both) to avoid redundancy.
• Don’t make error messages an API contract; expose machine-readable error codes or types instead.- Additional Best Practices
• Fail Early: Report errors as soon as possible (e.g., during input validation or startup).
• One Concept, One Term: Use consistent terminology across messages and documentation.
• Localization: For internal logs, use English; if localizing for users, follow a vetted process.
opcua_plugin/browse_workers.go
opcua_plugin/browse_frontend.go
`**/*`: Whenever in a new PR a new function or code is intro...
**/*
: Whenever in a new PR a new function or code is introduced, ensure that there is at least somewhere as a code comment, in the README or wherer ever explained the "why" behind the decision. So e.g., why something was preferred over other options, or why a certain loop needs to be executed.
opcua_plugin/browse_workers.go
opcua_plugin/browse_frontend.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: create-manifests
- GitHub Check: go-test-opcua-plc
Refactors the browse functionality to use a new BrowseDetails structure. This change improves the information passed through channels during browsing operations, including task and worker counts. The new structure enhances logging and provides more detailed progress information during node browsing.
Summary by CodeRabbit
New Features
Refactor