fix(frontend): count host job-state columns and EXEC_HOST by real slots
NJOBS/RUN/SSUSP/USUSP (xcecli hosts list -l) and EXEC_HOST (xcecli jobs list) always showed a hardcoded 1 slot per host regardless of a job's actual per-host allocation -- rawJobToModel()'s ExecHosts JSONB builder never read the real per-host "slots" resource from the C job allocation, so the already-correct downstream slot-counting logic (computeHostSlotUsage, bug-1743) and EXEC_HOST's "N*host" formatter never had real data to work with. convert.go now extracts each host's real slots allocation via a new CGO indexing helper, mirroring the scheduler's own get_nslots_in_alloc() convention. Also wires hosts list -l's "CURRENT LOAD USED FOR SCHEDULING" table (tmp/ swap/mem/slots and friends) to the host's real live resource values -- previously entirely hardcoded placeholder strings, never connected to any backend data.
This commit is contained in:
parent
f55b49f1a7
commit
e395eaa3ad
5 changed files with 223 additions and 28 deletions
|
|
@ -2,10 +2,66 @@ package output
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// formatHostResourceBytesHuman renders a raw byte count the same way the C
|
||||
// scheduler's own repl_format_bytes_human()/xce_test's format_bytes_human()
|
||||
// do -- successive /1024 until under 1024, B/KB/MB/GB/TB/PB.
|
||||
func formatHostResourceBytesHuman(bytesVal float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
||||
val := bytesVal
|
||||
i := 0
|
||||
for val >= 1024.0 && i < len(units)-1 {
|
||||
val /= 1024.0
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", val, units[i])
|
||||
}
|
||||
|
||||
// hostResourceValue looks up one named resource's raw numeric wire value
|
||||
// from the host's live ResourceValues (populated from the worker's own
|
||||
// resval pushes -- see apiserver/backend/convert.go's cNameValToWorkerResval).
|
||||
func hostResourceValue(host *models.XCEHost, name string) (float64, bool) {
|
||||
for _, rv := range host.ResourceValues {
|
||||
if rv.ResourceName == name {
|
||||
return rv.NumericValue, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// formatHostLoadValue formats one "CURRENT LOAD USED FOR SCHEDULING" column
|
||||
// value per this codebase's established per-resource unit conventions
|
||||
// (mirroring the scheduler REPL's repl_is_bytes_valued_resource()/is_swp
|
||||
// special case, repl_fmt.c/repl_info.c): mem/tmp arrive already in bytes;
|
||||
// swp arrives in MB (node_information.c's node_info_get_swap()) and needs
|
||||
// the same *1024*1024 conversion the REPL applies before byte-formatting;
|
||||
// slots is a plain integer; every other (load-average/utilization-style)
|
||||
// metric is printed as a bare decimal, matching the REPL's own Load Metrics
|
||||
// section (no unit suffix, no percent sign).
|
||||
func formatHostLoadValue(host *models.XCEHost, name string) string {
|
||||
val, ok := hostResourceValue(host, name)
|
||||
if !ok {
|
||||
return "-"
|
||||
}
|
||||
switch name {
|
||||
case models.HEADER_HOST_MEM, models.HEADER_HOST_TMP:
|
||||
return formatHostResourceBytesHuman(val)
|
||||
case models.HEADER_HOST_SWP:
|
||||
return formatHostResourceBytesHuman(val * 1024.0 * 1024.0)
|
||||
case models.HEADER_HOST_SLOTS:
|
||||
return fmt.Sprintf("%.0f", val)
|
||||
default:
|
||||
if val == math.Trunc(val) {
|
||||
return fmt.Sprintf("%.0f", val)
|
||||
}
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
}
|
||||
}
|
||||
|
||||
// HostsPagePrinter
|
||||
type HostsPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
|
|
@ -284,13 +340,30 @@ func printHostsVerboseOutput(hosts *models.HostsResponse) {
|
|||
|
||||
// table2
|
||||
headers2 := append([]string{""}, models.HostLoadSchedulingHeaders...)
|
||||
var totalVals, reserveVals []string
|
||||
if isUnavail {
|
||||
totalVals = []string{"Total", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
reserveVals = []string{"Reserved", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
} else {
|
||||
totalVals = []string{"Total", "0.0", "0.0", "0.0", "1%", "0.0", "33", "1", "1137", "51G", "3.9G", "6.8G", "8", "0.0"}
|
||||
reserveVals = []string{"Reserved", "0.0", "0.0", "0.0", "0%", "0.0", "0", "0", "0", "0M", "0M", "0M", "-", "-"}
|
||||
totalVals := make([]string, 0, len(headers2))
|
||||
reserveVals := make([]string, 0, len(headers2))
|
||||
totalVals = append(totalVals, "Total")
|
||||
reserveVals = append(reserveVals, "Reserved")
|
||||
for _, h := range models.HostLoadSchedulingHeaders {
|
||||
if isUnavail {
|
||||
totalVals = append(totalVals, "-")
|
||||
reserveVals = append(reserveVals, "-")
|
||||
continue
|
||||
}
|
||||
totalVals = append(totalVals, formatHostLoadValue(&host, h))
|
||||
// Reserved/in-use is only a meaningful concept for a CONSUMABLE
|
||||
// resource (mirrors the scheduler REPL's own Consumable/
|
||||
// Capacity-vs-Load-Metrics split, repl_info.c) -- slots is the
|
||||
// only one the Go apiserver currently tracks in-use counts for
|
||||
// (computeHostSlotUsage(), same live counts the RUN/SSUSP/USUSP
|
||||
// columns above show); mem/tmp/swp in-use and every load metric
|
||||
// are honestly "-" rather than a fabricated number.
|
||||
if h == models.HEADER_HOST_SLOTS {
|
||||
reserveVals = append(reserveVals,
|
||||
fmt.Sprintf("%d", host.NumRun+host.NumSSusp+host.NumUSusp))
|
||||
} else {
|
||||
reserveVals = append(reserveVals, "-")
|
||||
}
|
||||
}
|
||||
|
||||
widths2 := make([]int, len(headers2))
|
||||
|
|
|
|||
|
|
@ -98,6 +98,24 @@ type XCEHost struct {
|
|||
GPUTopo interface{} `json:"gpu_topo"` // JSONB
|
||||
SchedStop SchedStop `json:"sched_stop"` // JSONB
|
||||
RealLoad interface{} `json:"real_load"`
|
||||
|
||||
// ResourceValues carries this host's live per-resource wire values
|
||||
// (r15s/r1m/r15m/ut/pg/io/ls/it/tmp/swp/mem/slots, among others), the
|
||||
// same set the C scheduler's own "info worker" REPL command shows --
|
||||
// populated by the apiserver from the worker's own resval pushes.
|
||||
ResourceValues []ResourceDistributionInfo `json:"resource_values"`
|
||||
}
|
||||
|
||||
// ResourceDistributionInfo mirrors apiserver/models.ResourceDistributionInfo's
|
||||
// JSON shape -- one resource's current value for one host/worker.
|
||||
type ResourceDistributionInfo struct {
|
||||
ResourceName string `json:"res_name"`
|
||||
IsDynamic bool `json:"is_dynamic"`
|
||||
ValueType int `json:"value_type"`
|
||||
BooleanValue bool `json:"boolean_value"`
|
||||
StringValue string `json:"string_value"`
|
||||
NumericValue float64 `json:"numeric_value"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
var HostFieldsMap = map[string]string{
|
||||
|
|
@ -135,20 +153,20 @@ type HostControlResponseItem struct {
|
|||
HostName string `json:"host_name"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (h *XCEHost) UnmarshalJSON(data []byte) error {
|
||||
type Alias XCEHost
|
||||
var a Alias
|
||||
if err := UnmarshalOrderMapRow(data, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*h = XCEHost(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResReqSelectRequest struct {
|
||||
Select string `json:"select"`
|
||||
Order string `json:"order,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
PageToken string `json:"page_token,omitempty"`
|
||||
}
|
||||
|
||||
func (h *XCEHost) UnmarshalJSON(data []byte) error {
|
||||
type Alias XCEHost
|
||||
var a Alias
|
||||
if err := UnmarshalOrderMapRow(data, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*h = XCEHost(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResReqSelectRequest struct {
|
||||
Select string `json:"select"`
|
||||
Order string `json:"order,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
PageToken string `json:"page_token,omitempty"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue