fix job array display issue
This commit is contained in:
parent
d9b5c361c2
commit
960ef62342
5 changed files with 290 additions and 15 deletions
|
|
@ -250,7 +250,8 @@ func SubmitJob(job *models.JobInfo, arraySize int) error {
|
|||
}
|
||||
if outJobID != 0 {
|
||||
job.Unique_JobID = int64(outJobID)
|
||||
job.JobID = int64(outJobID)
|
||||
job.JobID = int64(ExtractArrayBaseID(uint64(outJobID)))
|
||||
job.ArrayIdx = ExtractArrayIdx(uint64(outJobID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,76 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// JobID bit layout constants — mirrors src/include/config.h.
|
||||
//
|
||||
// |63 62|61 56|55 52|51 20|19 15|14 0|
|
||||
// | TAG | CLUSTER_ID | APISERVER_ID| TIMESTAMP | SEQ_HIGH | ARRAY_IDX |
|
||||
// | 2 bits | 6 bits | 4 bits | 32 bits | 15 bits | 15 bits |
|
||||
const (
|
||||
nTagBits = 2
|
||||
tagShift = 62
|
||||
nClusterIDBits = 6
|
||||
clusterIDShift = 56
|
||||
nApiserverIDBits = 4
|
||||
apiserverIDShift = 52
|
||||
nTimestampBits = 32
|
||||
timestampShift = 20
|
||||
nArrayIdxBits = 15
|
||||
sequenceHighShift = 15 // mirrors SEQUENCE_HIGH_SHIFT in config.h
|
||||
nSequenceBits = 30 // mirrors N_SEQUENCE_BITS in config.h
|
||||
tagJobArray = 0x01 // mirrors TAG_JOB_ARRAY in config.h
|
||||
)
|
||||
|
||||
// extractBits extracts a bit field from id at the given shift position.
|
||||
// Mirrors EXTRACT_BITS(id, shift, bits) in src/include/utils.h.
|
||||
func extractBits(id uint64, shift, bits int) uint64 {
|
||||
mask := uint64((1 << bits) - 1)
|
||||
return (id >> shift) & mask
|
||||
}
|
||||
|
||||
// extractIDTag extracts the 2-bit tag field (bits 62-63).
|
||||
// Mirrors EXTRACT_ID_TAG in src/include/utils.h.
|
||||
func extractIDTag(id uint64) uint64 {
|
||||
return extractBits(id, tagShift, nTagBits)
|
||||
}
|
||||
|
||||
// ExtractArrayBaseID extracts the base (family) job ID from a 64-bit encoded
|
||||
// jobid. Mirrors C's array_jobid() in src/lib/utils.c and EXTRACT_ARRAY_BASEID
|
||||
// in src/include/utils.h.
|
||||
// - For non-array jobs (tag=0x00): returns id unchanged.
|
||||
// - For array children (tag=0x01): returns the shared base ID (tag=0, idx=0).
|
||||
func ExtractArrayBaseID(id uint64) uint64 {
|
||||
if extractIDTag(id) == 0x00 {
|
||||
return id
|
||||
}
|
||||
clusterID := extractBits(id, clusterIDShift, nClusterIDBits)
|
||||
apiserverID := extractBits(id, apiserverIDShift, nApiserverIDBits)
|
||||
timestamp := extractBits(id, timestampShift, nTimestampBits)
|
||||
// EXTRACT_ARRAY_JOB: high bits of sequence (mirrors config.h SEQUENCE_HIGH_SHIFT)
|
||||
arrayJob := extractBits(id, sequenceHighShift, nSequenceBits-nArrayIdxBits) << sequenceHighShift
|
||||
return (clusterID << clusterIDShift) |
|
||||
(apiserverID << apiserverIDShift) |
|
||||
(timestamp << timestampShift) |
|
||||
arrayJob // tag=0, idx=0
|
||||
}
|
||||
|
||||
// ExtractArrayIdx extracts the array index from a 64-bit encoded jobid.
|
||||
// Mirrors C's array_idx() in src/lib/utils.c and EXTRACT_ARRAY_IDX in
|
||||
// src/include/utils.h.
|
||||
// Returns 0 for non-array jobs (tag=0x00).
|
||||
func ExtractArrayIdx(id uint64) int {
|
||||
if extractIDTag(id) == 0x00 {
|
||||
return 0
|
||||
}
|
||||
return int(extractBits(id, 0, nArrayIdxBits))
|
||||
}
|
||||
|
||||
// IsArrayJob reports whether the 64-bit encoded jobid belongs to an array job
|
||||
// (tag == TAG_JOB_ARRAY, 0x01). Returns false for non-array jobs (tag=0x00).
|
||||
func IsArrayJob(id uint64) bool {
|
||||
return extractIDTag(id) == tagJobArray
|
||||
}
|
||||
|
||||
// rawStartInfo mirrors the C job_startinfo_t fields used by the Go model.
|
||||
type rawStartInfo struct {
|
||||
cmdline string
|
||||
|
|
@ -77,9 +147,11 @@ type rawResourceData struct {
|
|||
// rawJobToModel maps a rawJobData to a models.JobInfo.
|
||||
// This pure-Go function contains the field-mapping logic and is tested without CGO.
|
||||
func rawJobToModel(r rawJobData) *models.JobInfo {
|
||||
raw := uint64(r.jobID)
|
||||
info := &models.JobInfo{
|
||||
Unique_JobID: r.jobID,
|
||||
JobID: r.jobID,
|
||||
JobID: int64(ExtractArrayBaseID(raw)),
|
||||
ArrayIdx: ExtractArrayIdx(raw),
|
||||
JobStatus: r.state,
|
||||
RunTime: r.runTime,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,3 +197,193 @@ func TestRawResourceToModel_BasicFields(t *testing.T) {
|
|||
t.Errorf("Interval: got %d, want 60", info.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
// makeJobID mirrors the C MAKE_JOBID macro in src/include/utils.h.
|
||||
// Used to construct known 64-bit job IDs for testing.
|
||||
func makeJobID(cluster, apiserver, timestamp, tag, sequence uint64) uint64 {
|
||||
return (cluster << clusterIDShift) |
|
||||
(apiserver << apiserverIDShift) |
|
||||
(timestamp << timestampShift) |
|
||||
(tag << tagShift) |
|
||||
sequence
|
||||
}
|
||||
|
||||
func TestExtractBits(t *testing.T) {
|
||||
// 0b1111 shifted left by 4 = 0b11110000 = 240
|
||||
id := uint64(0xF0)
|
||||
if got := extractBits(id, 4, 4); got != 0xF {
|
||||
t.Errorf("extractBits(0xF0, 4, 4): got %d, want 15", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIDTag_NonArray(t *testing.T) {
|
||||
id := makeJobID(1, 0, 1700000000, 0, 5)
|
||||
if tag := extractIDTag(id); tag != 0x00 {
|
||||
t.Errorf("tag for non-array job: got %d, want 0", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIDTag_Array(t *testing.T) {
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, 5)
|
||||
if tag := extractIDTag(id); tag != tagJobArray {
|
||||
t.Errorf("tag for array job: got %d, want %d", tag, tagJobArray)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractArrayBaseID_NonArray(t *testing.T) {
|
||||
// Non-array job: base ID should equal the original ID
|
||||
id := makeJobID(1, 0, 1700000000, 0, 5)
|
||||
base := ExtractArrayBaseID(id)
|
||||
if base != id {
|
||||
t.Errorf("non-array base ID should equal original: got %d, want %d", base, id)
|
||||
}
|
||||
idx := ExtractArrayIdx(id)
|
||||
if idx != 0 {
|
||||
t.Errorf("non-array index should be 0: got %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractArrayBaseID_ArrayChild(t *testing.T) {
|
||||
// Array job: cluster=1, apiserver=0, timestamp=1700000000
|
||||
// The sequence for array jobs has the "job number" in high bits.
|
||||
// sequence = jobNumber << sequenceHighShift + arrayIdx
|
||||
// e.g. jobNumber=2, arrayIdx=3 → sequence = 2<<15 + 3 = 65539
|
||||
jobNumber := uint64(2)
|
||||
arrayIdx := uint64(3)
|
||||
sequence := jobNumber<<sequenceHighShift + arrayIdx
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
|
||||
base := ExtractArrayBaseID(id)
|
||||
// Base ID should have tag=0, same cluster/apiserver/timestamp, and arrayJob in sequence
|
||||
expectedBase := makeJobID(1, 0, 1700000000, 0, jobNumber<<sequenceHighShift)
|
||||
if base != expectedBase {
|
||||
t.Errorf("array base ID mismatch: got %d, want %d", base, expectedBase)
|
||||
}
|
||||
|
||||
idx := ExtractArrayIdx(id)
|
||||
if idx != int(arrayIdx) {
|
||||
t.Errorf("array index: got %d, want %d", idx, arrayIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractArrayIdx_MaxIndex(t *testing.T) {
|
||||
// Maximum array index = 32767 (2^15 - 1)
|
||||
maxIdx := uint64((1 << nArrayIdxBits) - 1)
|
||||
sequence := uint64(1)<<sequenceHighShift + maxIdx
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
|
||||
idx := ExtractArrayIdx(id)
|
||||
if idx != int(maxIdx) {
|
||||
t.Errorf("max array index: got %d, want %d", idx, maxIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractArrayBaseID_RoundTrip(t *testing.T) {
|
||||
// ExtractArrayBaseID and ExtractArrayIdx together should preserve enough
|
||||
// information to reconstruct the original ID.
|
||||
// Reconstruction requires re-encoding with tag=TAG_JOB_ARRAY.
|
||||
jobNumber := uint64(7)
|
||||
arrayIdx := uint64(42)
|
||||
sequence := jobNumber<<sequenceHighShift + arrayIdx
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
|
||||
base := ExtractArrayBaseID(id)
|
||||
idx := ExtractArrayIdx(id)
|
||||
|
||||
// Reconstruct: base ID sequence + array index, with array tag set
|
||||
reconstructedSeq := (extractBits(base, sequenceHighShift, nSequenceBits-nArrayIdxBits) << sequenceHighShift) + uint64(idx)
|
||||
reconstructed := makeJobID(
|
||||
extractBits(base, clusterIDShift, nClusterIDBits),
|
||||
extractBits(base, apiserverIDShift, nApiserverIDBits),
|
||||
extractBits(base, timestampShift, nTimestampBits),
|
||||
tagJobArray,
|
||||
reconstructedSeq,
|
||||
)
|
||||
if reconstructed != id {
|
||||
t.Errorf("round-trip failed: reconstructed %d, want %d", reconstructed, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawJobToModel_NonArrayJob(t *testing.T) {
|
||||
// Non-array job: JobID == Unique_JobID, ArrayIdx == 0
|
||||
id := makeJobID(1, 0, 1700000000, 0, 5)
|
||||
r := rawJobData{jobID: int64(id), state: 2}
|
||||
info := rawJobToModel(r)
|
||||
|
||||
if info.Unique_JobID != int64(id) {
|
||||
t.Errorf("Unique_JobID: got %d, want %d", info.Unique_JobID, id)
|
||||
}
|
||||
if info.JobID != int64(id) {
|
||||
t.Errorf("JobID for non-array should equal Unique_JobID: got %d, want %d", info.JobID, id)
|
||||
}
|
||||
if info.ArrayIdx != 0 {
|
||||
t.Errorf("ArrayIdx for non-array should be 0: got %d", info.ArrayIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawJobToModel_ArrayChild(t *testing.T) {
|
||||
// Array child: JobID == baseID, ArrayIdx == arrayIdx
|
||||
jobNumber := uint64(2)
|
||||
arrayIdx := uint64(5)
|
||||
sequence := jobNumber<<sequenceHighShift + arrayIdx
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
baseID := makeJobID(1, 0, 1700000000, 0, jobNumber<<sequenceHighShift)
|
||||
|
||||
r := rawJobData{jobID: int64(id), state: 2}
|
||||
info := rawJobToModel(r)
|
||||
|
||||
if info.Unique_JobID != int64(id) {
|
||||
t.Errorf("Unique_JobID: got %d, want %d", info.Unique_JobID, id)
|
||||
}
|
||||
if info.JobID != int64(baseID) {
|
||||
t.Errorf("JobID for array child should be base ID: got %d, want %d", info.JobID, baseID)
|
||||
}
|
||||
if info.ArrayIdx != int(arrayIdx) {
|
||||
t.Errorf("ArrayIdx for array child: got %d, want %d", info.ArrayIdx, arrayIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawJobToModel_ArrayChildrenShareBaseID(t *testing.T) {
|
||||
// All array children should share the same base JobID
|
||||
jobNumber := uint64(3)
|
||||
baseID := makeJobID(1, 0, 1700000000, 0, jobNumber<<sequenceHighShift)
|
||||
|
||||
for idx := 0; idx < 5; idx++ {
|
||||
sequence := jobNumber<<sequenceHighShift + uint64(idx)
|
||||
id := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
r := rawJobData{jobID: int64(id)}
|
||||
info := rawJobToModel(r)
|
||||
|
||||
if info.JobID != int64(baseID) {
|
||||
t.Errorf("array child %d: JobID %d should equal base ID %d", idx, info.JobID, baseID)
|
||||
}
|
||||
if info.ArrayIdx != idx {
|
||||
t.Errorf("array child %d: ArrayIdx got %d, want %d", idx, info.ArrayIdx, idx)
|
||||
}
|
||||
if info.Unique_JobID != int64(id) {
|
||||
t.Errorf("array child %d: Unique_JobID should be unique per child", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsArrayJob(t *testing.T) {
|
||||
// Non-array job (tag=0x00): IsArrayJob returns false
|
||||
nonArrayID := makeJobID(1, 0, 1700000000, 0, 5)
|
||||
if IsArrayJob(nonArrayID) {
|
||||
t.Errorf("non-array job should not be an array job")
|
||||
}
|
||||
|
||||
// Array child (tag=0x01): IsArrayJob returns true
|
||||
arrayID := makeJobID(1, 0, 1700000000, tagJobArray, 5)
|
||||
if !IsArrayJob(arrayID) {
|
||||
t.Errorf("array job child should be an array job")
|
||||
}
|
||||
|
||||
// Array child with idx=0: IsArrayJob returns true
|
||||
jobNumber := uint64(2)
|
||||
sequence := jobNumber<<sequenceHighShift + 0 // arrayIdx=0
|
||||
arrayIdx0ID := makeJobID(1, 0, 1700000000, tagJobArray, sequence)
|
||||
if !IsArrayJob(arrayIdx0ID) {
|
||||
t.Errorf("array child with idx=0 should still be an array job")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1192,8 +1192,8 @@ func (j JobController) RestartJobByID(ctx *gin.Context) {
|
|||
// Use a concrete type for job
|
||||
newJob := models.JobInfo{
|
||||
Unique_JobID: int64(jobID),
|
||||
JobID: int64(jobID),
|
||||
ArrayIdx: 0,
|
||||
JobID: int64(backend.ExtractArrayBaseID(jobID)),
|
||||
ArrayIdx: backend.ExtractArrayIdx(jobID),
|
||||
SubmitUser: restarter,
|
||||
JobStatus: models.JOBSTAT["PEND"],
|
||||
}
|
||||
|
|
@ -1453,7 +1453,8 @@ func (j JobController) CreateJob(ctx *gin.Context) {
|
|||
// Build the single JobInfo to submit
|
||||
job := models.JobInfo{
|
||||
Unique_JobID: primaryJobID,
|
||||
JobID: primaryJobID,
|
||||
JobID: int64(backend.ExtractArrayBaseID(uint64(primaryJobID))),
|
||||
ArrayIdx: backend.ExtractArrayIdx(uint64(primaryJobID)),
|
||||
//SubmitTime: time.Now().Unix(), // backend will fill the submit time;
|
||||
SubmitUser: submitUser,
|
||||
JobStatus: models.JOBSTAT["PEND"], // new job is pend.
|
||||
|
|
@ -3097,12 +3098,20 @@ func processJobData(rows []utils.OrderMap, pagetoken string) utils.QueryResult {
|
|||
}
|
||||
}
|
||||
|
||||
// Update job_name if array_idx is not 0
|
||||
// Update job_name if this is an array job child (including idx=0)
|
||||
if jobName, exist := row.Data["job_name"]; exist {
|
||||
if arrayIdx, ok := row.Data["array_idx"].(int); ok && arrayIdx > 0 {
|
||||
row.Data["job_name"] = fmt.Sprintf("%s[%d]", jobName, arrayIdx)
|
||||
} else if arrayIdx64, ok := row.Data["array_idx"].(int64); ok && arrayIdx64 > 0 {
|
||||
row.Data["job_name"] = fmt.Sprintf("%s[%d]", jobName, arrayIdx64)
|
||||
// Use IsArrayJob to detect array children, which covers
|
||||
// the first child (idx=0) that the old array_idx > 0 check missed.
|
||||
var isArray bool
|
||||
if uid, ok := row.Data["unique_job_id"].(int64); ok {
|
||||
isArray = backend.IsArrayJob(uint64(uid))
|
||||
}
|
||||
if isArray {
|
||||
if arrayIdx, ok := row.Data["array_idx"].(int); ok {
|
||||
row.Data["job_name"] = fmt.Sprintf("%s[%d]", jobName, arrayIdx)
|
||||
} else if arrayIdx64, ok := row.Data["array_idx"].(int64); ok {
|
||||
row.Data["job_name"] = fmt.Sprintf("%s[%d]", jobName, arrayIdx64)
|
||||
}
|
||||
} else {
|
||||
row.Data["job_name"] = jobName
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,11 @@ func (p *JobsPagePrinter) initColumnWidths(jobs *models.JobsResponse) {
|
|||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
width = len(fmt.Sprintf("%d", job.JobID))
|
||||
if job.UniqueJobID != job.JobID {
|
||||
width = len(fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx))
|
||||
} else {
|
||||
width = len(fmt.Sprintf("%d", job.JobID))
|
||||
}
|
||||
case models.HEADER_JOB_USER:
|
||||
width = len(job.SubmitUser)
|
||||
case models.HEADER_JOB_STATUS:
|
||||
|
|
@ -174,7 +178,7 @@ func (p *JobsPagePrinter) formatRows(jobs *models.JobsResponse, truncatFlag bool
|
|||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
var arrayJobIDStr string
|
||||
if job.ArrayIdx != 0 {
|
||||
if job.UniqueJobID != job.JobID {
|
||||
arrayJobIDStr = fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
||||
} else {
|
||||
arrayJobIDStr = fmt.Sprintf("%d", job.JobID)
|
||||
|
|
@ -274,9 +278,8 @@ func printJobsVerboseOutput(jobs *models.JobsResponse, filter []string) {
|
|||
}
|
||||
// generate jobId for two format: 111 or 111[10]
|
||||
jobId := fmt.Sprintf("%d", job.JobID)
|
||||
index := strings.Index(job.JobName, "[")
|
||||
if index != -1 {
|
||||
jobId = fmt.Sprintf("%s%s", jobId, job.JobName[index:])
|
||||
if job.UniqueJobID != job.JobID {
|
||||
jobId = fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
||||
}
|
||||
|
||||
// (1) print the job baisc info when sending "jobs list -l" command
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue