package format import ( "fmt" "regexp" "strconv" ) // 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 | 5 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 (== N_ARRAY_IDX_BITS) nSequenceBits = 20 // N_JOBID_BITS(64) - N_CLUSTER_ID_BITS(6) - N_APISERVER_ID_BITS(4) - N_TIMESTAMP_BITS(32) - N_TAG_BITS(2) = 20 tagJobArray = 0x01 ) // 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 } // FormatJobID decomposes a 64-bit packed job ID and formats it as a // human-readable string matching the C fmt_jobid() format in src/lib/utils.c. // - Regular job: "{cluster}-{apiserver}-{timestamp}-{sequence}" // - Array child: "{cluster}-{apiserver}-{timestamp}-{base}[{idx}]" func FormatJobID(uniqueJobID int64) string { id := uint64(uniqueJobID) clusterID := extractBits(id, clusterIDShift, nClusterIDBits) apiserverID := extractBits(id, apiserverIDShift, nApiserverIDBits) timestamp := extractBits(id, timestampShift, nTimestampBits) if (extractBits(id, tagShift, nTagBits) & tagJobArray) != 0 { // arrayBase mirrors EXTRACT_ARRAY_JOB: the seq_high bits shifted back to their // original position, matching C's fmt_jobid_ex output exactly. arrayBase := extractBits(id, sequenceHighShift, nSequenceBits-nArrayIdxBits) << uint(sequenceHighShift) arrayIdx := extractBits(id, 0, nArrayIdxBits) return fmt.Sprintf("%d-%d-%d-%d[%d]", clusterID, apiserverID, timestamp, arrayBase, arrayIdx) } sequence := extractBits(id, 0, nSequenceBits) return fmt.Sprintf("%d-%d-%d-%d", clusterID, apiserverID, timestamp, sequence) } // IsArrayJob returns true if the 64-bit packed job ID has the array job tag set. func IsArrayJob(uniqueJobID int64) bool { return extractBits(uint64(uniqueJobID), tagShift, nTagBits)&tagJobArray != 0 } // DecomposeJobID splits a 64-bit packed job ID into its component fields. func DecomposeJobID(uniqueJobID int64) (clusterID, apiserverID, timestamp, sequence uint32, isArray bool, arrayIdx uint32) { id := uint64(uniqueJobID) clusterID = uint32(extractBits(id, clusterIDShift, nClusterIDBits)) apiserverID = uint32(extractBits(id, apiserverIDShift, nApiserverIDBits)) timestamp = uint32(extractBits(id, timestampShift, nTimestampBits)) isArray = extractBits(id, tagShift, nTagBits)&tagJobArray != 0 sequence = uint32(extractBits(id, 0, nSequenceBits)) arrayIdx = uint32(extractBits(id, 0, nArrayIdxBits)) return } // MakeJobID packs the components into a 64-bit job ID, mirroring MAKE_JOBID in src/include/utils.h. func MakeJobID(clusterID, apiserverID, timestamp uint64, tag, sequence uint64) uint64 { return (clusterID << clusterIDShift) | (apiserverID << apiserverIDShift) | (timestamp << timestampShift) | (tag << tagShift) | sequence } var ( // regexJobID matches "{int}-{int}-{int}-{int}" — mirrors REGEX_JOBID in config.h. regexJobID = regexp.MustCompile(`^\s*(\d+)-(\d+)-(\d+)-(\d+)\s*$`) // regexJobArrayID matches "{int}-{int}-{int}-{int}[{int}]" — mirrors REGEX_JOBARRAY_ID in config.h. regexJobArrayID = regexp.MustCompile(`^\s*(\d+)-(\d+)-(\d+)-(\d+)\[(\d+)\]\s*$`) ) // ParseJobID parses a human-readable job ID string into a packed 64-bit job ID, // mirroring parse_jobid() in src/lib/utils.c:482. // Accepts: "0-0-12874529-0" (regular) or "0-0-12874529-0[5]" (array child). func ParseJobID(s string) (uint64, error) { if m := regexJobArrayID.FindStringSubmatch(s); m != nil { clusterID, _ := strconv.ParseUint(m[1], 10, 64) apiserverID, _ := strconv.ParseUint(m[2], 10, 64) timestamp, _ := strconv.ParseUint(m[3], 10, 64) idJob, _ := strconv.ParseUint(m[4], 10, 64) idIdx, _ := strconv.ParseUint(m[5], 10, 64) return MakeJobID(clusterID, apiserverID, timestamp, tagJobArray, idJob+idIdx), nil } if m := regexJobID.FindStringSubmatch(s); m != nil { clusterID, _ := strconv.ParseUint(m[1], 10, 64) apiserverID, _ := strconv.ParseUint(m[2], 10, 64) timestamp, _ := strconv.ParseUint(m[3], 10, 64) idJob, _ := strconv.ParseUint(m[4], 10, 64) return MakeJobID(clusterID, apiserverID, timestamp, 0, idJob), nil } return 0, fmt.Errorf("invalid job ID format: %q", s) } // ExtractBaseJobID clears the tag and array_idx bits, returning the base job ID // that the API server stores in the job_id column. func ExtractBaseJobID(packed uint64) uint64 { id := packed &^ (uint64((1<