optimize redfish post-probe and add eta progress

This commit is contained in:
2026-02-28 15:41:44 +03:00
parent 4c60ebbf1d
commit 8dbbec3610
2 changed files with 106 additions and 2 deletions

View File

@@ -689,9 +689,21 @@ func (c *RedfishConnector) collectRawRedfishTree(ctx context.Context, client *ht
// Some Supermicro BMCs expose NVMe disks at direct Disk.Bay endpoints even when the
// Drives collection returns Members: []. Probe those paths so raw export can be replayed.
driveCollections := make([]string, 0)
for path := range out {
if !strings.HasSuffix(normalizeRedfishPath(path), "/Drives") {
continue
if strings.HasSuffix(normalizeRedfishPath(path), "/Drives") {
driveCollections = append(driveCollections, normalizeRedfishPath(path))
}
}
sort.Strings(driveCollections)
nvmeProbeStart := time.Now()
for i, path := range driveCollections {
if emit != nil && len(driveCollections) > 0 && (i == 0 || i%4 == 0 || i == len(driveCollections)-1) {
emit(Progress{
Status: "running",
Progress: 97,
Message: fmt.Sprintf("Redfish snapshot: post-probe NVMe (%d/%d, ETA≈%s), коллекция=%s", i+1, len(driveCollections), formatETA(estimateProgressETA(nvmeProbeStart, i, len(driveCollections), 2*time.Second)), compactProgressPath(path)),
})
}
for _, bayPath := range directDiskBayCandidates(path) {
doc, err := c.getJSON(ctx, client, req, baseURL, bayPath)
@@ -707,14 +719,38 @@ func (c *RedfishConnector) collectRawRedfishTree(ctx context.Context, client *ht
}
// Some BMCs under-report collection Members for sensors/PSU subresources but still serve
// direct numeric child endpoints. Probe common collections to maximize raw snapshot fidelity.
postProbeCollections := make([]string, 0)
for path := range out {
if shouldPostProbeCollectionPath(path) {
postProbeCollections = append(postProbeCollections, normalizeRedfishPath(path))
}
}
sort.Strings(postProbeCollections)
postProbeStart := time.Now()
addedPostProbe := 0
for i, path := range postProbeCollections {
if emit != nil && len(postProbeCollections) > 0 && (i == 0 || i%8 == 0 || i == len(postProbeCollections)-1) {
emit(Progress{
Status: "running",
Progress: 98,
Message: fmt.Sprintf("Redfish snapshot: post-probe коллекций (%d/%d, ETA≈%s), текущая=%s", i+1, len(postProbeCollections), formatETA(estimateProgressETA(postProbeStart, i, len(postProbeCollections), 3*time.Second)), compactProgressPath(path)),
})
}
for childPath, doc := range c.probeDirectRedfishCollectionChildren(ctx, client, req, baseURL, path) {
if _, exists := out[childPath]; exists {
continue
}
out[childPath] = doc
addedPostProbe++
}
}
if emit != nil && addedPostProbe > 0 {
emit(Progress{
Status: "running",
Progress: 98,
Message: fmt.Sprintf("Redfish snapshot: post-probe добавлено %d документов", addedPostProbe),
})
}
if emit != nil {
emit(Progress{
@@ -887,6 +923,31 @@ func directNumericProbePlan(collectionPath string) (maxItems, startIndex, missBu
}
}
func shouldPostProbeCollectionPath(path string) bool {
path = normalizeRedfishPath(path)
// Restrict expensive post-probe to collections that historically recover
// missing inventory/telemetry on partially implemented BMCs.
switch {
case strings.HasSuffix(path, "/Sensors"),
strings.HasSuffix(path, "/ThresholdSensors"),
strings.HasSuffix(path, "/DiscreteSensors"),
strings.HasSuffix(path, "/Temperatures"),
strings.HasSuffix(path, "/Fans"),
strings.HasSuffix(path, "/Voltages"),
strings.HasSuffix(path, "/PowerSupplies"),
strings.HasSuffix(path, "/EthernetInterfaces"),
strings.HasSuffix(path, "/NetworkPorts"),
strings.HasSuffix(path, "/Ports"),
strings.HasSuffix(path, "/PCIeDevices"),
strings.HasSuffix(path, "/PCIeFunctions"),
strings.HasSuffix(path, "/Drives"),
strings.HasSuffix(path, "/Volumes"):
return true
default:
return false
}
}
func looksLikeRedfishResource(doc map[string]interface{}) bool {
if len(doc) == 0 {
return false
@@ -1145,6 +1206,13 @@ func shouldCrawlPath(path string) bool {
return false
}
normalized := normalizeRedfishPath(path)
if strings.Contains(normalized, "/Chassis/") &&
strings.Contains(normalized, "/PCIeDevices/") &&
strings.Contains(normalized, "/PCIeFunctions/") {
// Chassis-level PCIeFunctions links are frequently noisy/slow on some BMCs
// and duplicate data we already collect from PCIe devices/functions elsewhere.
return false
}
if strings.Contains(normalized, "/Memory/") {
after := strings.SplitN(normalized, "/Memory/", 2)
if len(after) == 2 && strings.Count(after[1], "/") >= 1 {
@@ -2753,6 +2821,24 @@ func estimatePlanBETA(targets int) time.Duration {
return time.Duration(targets) * perTarget
}
func estimateProgressETA(start time.Time, processed, total int, fallbackPerItem time.Duration) time.Duration {
if total <= 0 || processed >= total {
return 0
}
remaining := total - processed
if processed <= 0 {
if fallbackPerItem <= 0 {
fallbackPerItem = time.Second
}
return time.Duration(remaining) * fallbackPerItem
}
elapsed := time.Since(start)
if elapsed <= 0 {
return 0
}
return time.Duration(float64(elapsed) * float64(remaining) / float64(processed))
}
func formatETA(d time.Duration) string {
if d <= 0 {
return "<1s"

View File

@@ -779,4 +779,22 @@ func TestShouldCrawlPath_MemorySubresourcesAreSkipped(t *testing.T) {
if shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0/MemoryMetrics") {
t.Fatalf("expected DIMM metrics subresource to be skipped")
}
if shouldCrawlPath("/redfish/v1/Chassis/1/PCIeDevices/0/PCIeFunctions/1") {
t.Fatalf("expected noisy chassis pciefunctions branch to be skipped")
}
}
func TestShouldPostProbeCollectionPath(t *testing.T) {
if !shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") {
t.Fatalf("expected sensors collection to be post-probed")
}
if !shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives") {
t.Fatalf("expected drives collection to be post-probed")
}
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Boards/BOARD1") {
t.Fatalf("expected board member resource to be skipped from post-probe")
}
if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Assembly/Oem/COMMONb/COMMONbAssembly/1") {
t.Fatalf("expected assembly member resource to be skipped from post-probe")
}
}