feat(apiserver): populate hosts num_run from the job store
num_run was declared in the host model but never set anywhere — the controller's worker push carries no running-job count, so hosts always showed 0 regardless of running jobs. Compute it live in the hosts list handler by counting RUN jobs per exec host from store.Jobs — the same source the jobs list uses, so the two stay consistent. Verified: t1 with 5 RUN jobs shows num_run 5, matching the jobs list. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
125a8b283e
commit
b29886a000
2 changed files with 102 additions and 0 deletions
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"apiserver/store"
|
"apiserver/store"
|
||||||
"apiserver/utils"
|
"apiserver/utils"
|
||||||
logger "apiserver/utils"
|
logger "apiserver/utils"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -188,6 +189,21 @@ func (h *HostHandler) GetHostsInfo(ctx *gin.Context) {
|
||||||
ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUERY_HOSTINFO_MSG+": "+err.Error(), nil)
|
ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUERY_HOSTINFO_MSG+": "+err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// num_run is not populated by the controller's worker push, so compute it
|
||||||
|
// here from the apiserver's own job store — the same source the jobs list
|
||||||
|
// uses — keeping the hosts list consistent with the jobs list.
|
||||||
|
runningPerHost := countRunningJobsPerHost()
|
||||||
|
for i := range hqueryReply {
|
||||||
|
host, ok := hqueryReply[i].Data["host_name"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n, ok2 := runningPerHost[host]; ok2 {
|
||||||
|
hqueryReply[i].Data["num_run"] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.Debug("the host info query reply:", zap.Any("hqueryReply", hqueryReply), zap.String("pagetoken", pagetoken))
|
logger.Debug("the host info query reply:", zap.Any("hqueryReply", hqueryReply), zap.String("pagetoken", pagetoken))
|
||||||
//convert to readable string.
|
//convert to readable string.
|
||||||
queryResult := processHostData(hqueryReply, pagetoken)
|
queryResult := processHostData(hqueryReply, pagetoken)
|
||||||
|
|
@ -195,6 +211,31 @@ func (h *HostHandler) GetHostsInfo(ctx *gin.Context) {
|
||||||
ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, queryResult)
|
ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, queryResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// countRunningJobsPerHost returns the number of RUN jobs per exec host,
|
||||||
|
// derived live from the in-memory job store (store.Jobs). RUN is the bitmask
|
||||||
|
// JOBSTAT["RUN"] (0x02); exec hosts are read from each job's exec_hosts JSONB.
|
||||||
|
func countRunningJobsPerHost() map[string]int {
|
||||||
|
counts := make(map[string]int)
|
||||||
|
store.Jobs.Range(func(_ uint64, job models.JobInfo) bool {
|
||||||
|
if job.JobStatus&models.JOBSTAT["RUN"] == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var eh struct {
|
||||||
|
HostSlots []struct {
|
||||||
|
HostName string `json:"host_name"`
|
||||||
|
} `json:"host_slots"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(job.ExecHosts.String), &eh); err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, hs := range eh.HostSlots {
|
||||||
|
counts[hs.HostName]++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return counts
|
||||||
|
}
|
||||||
|
|
||||||
func selectFields(host interface{}, fields []string) map[string]interface{} {
|
func selectFields(host interface{}, fields []string) map[string]interface{} {
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]interface{})
|
||||||
v := reflect.ValueOf(host)
|
v := reflect.ValueOf(host)
|
||||||
|
|
|
||||||
61
src/frontend/apiserver/controllers/hosts_numrun_test.go
Normal file
61
src/frontend/apiserver/controllers/hosts_numrun_test.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apiserver/models"
|
||||||
|
"apiserver/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountRunningJobsPerHost(t *testing.T) {
|
||||||
|
store.InitStores()
|
||||||
|
|
||||||
|
// RUN job on t1.
|
||||||
|
store.Jobs.Put(1, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["RUN"],
|
||||||
|
ExecHosts: models.Jsonb{String: `{"host_slots":[{"host_name":"t1","num_slots":1}],"num_hosts":1}`},
|
||||||
|
})
|
||||||
|
// RUN|USUSP job on t2 — the RUN bit must still count it.
|
||||||
|
store.Jobs.Put(2, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["RUN"] | models.JOBSTAT["USUSP"],
|
||||||
|
ExecHosts: models.Jsonb{String: `{"host_slots":[{"host_name":"t2","num_slots":1}],"num_hosts":1}`},
|
||||||
|
})
|
||||||
|
// Multi-host RUN job: one job on t1 + t3 counts on both.
|
||||||
|
store.Jobs.Put(3, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["RUN"],
|
||||||
|
ExecHosts: models.Jsonb{String: `{"host_slots":[{"host_name":"t1","num_slots":1},{"host_name":"t3","num_slots":1}],"num_hosts":2}`},
|
||||||
|
})
|
||||||
|
// PEND and DONE jobs must NOT count.
|
||||||
|
store.Jobs.Put(4, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["PEND"],
|
||||||
|
ExecHosts: models.Jsonb{String: `{"host_slots":[{"host_name":"t3","num_slots":1}],"num_hosts":1}`},
|
||||||
|
})
|
||||||
|
store.Jobs.Put(5, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["DONE"],
|
||||||
|
ExecHosts: models.Jsonb{String: `{"host_slots":[{"host_name":"t1","num_slots":1}],"num_hosts":1}`},
|
||||||
|
})
|
||||||
|
// RUN job with empty/bad exec_hosts must not crash or count.
|
||||||
|
store.Jobs.Put(6, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["RUN"],
|
||||||
|
ExecHosts: models.Jsonb{String: ``},
|
||||||
|
})
|
||||||
|
store.Jobs.Put(7, models.JobInfo{
|
||||||
|
JobStatus: models.JOBSTAT["RUN"],
|
||||||
|
ExecHosts: models.Jsonb{String: `not-json`},
|
||||||
|
})
|
||||||
|
|
||||||
|
counts := countRunningJobsPerHost()
|
||||||
|
|
||||||
|
if counts["t1"] != 2 { // jobs 1 + 3
|
||||||
|
t.Errorf("t1 running = %d, want 2", counts["t1"])
|
||||||
|
}
|
||||||
|
if counts["t2"] != 1 {
|
||||||
|
t.Errorf("t2 running = %d, want 1", counts["t2"])
|
||||||
|
}
|
||||||
|
if counts["t3"] != 1 { // job 3 only (job 4 is PEND)
|
||||||
|
t.Errorf("t3 running = %d, want 1", counts["t3"])
|
||||||
|
}
|
||||||
|
if _, ok := counts["nohost"]; ok {
|
||||||
|
t.Error("unexpected host 'nohost' in counts")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue