forked from lei.chen/myxce
Collapses a four-step conversion chain (time_t → int64 → time.Time → RFC3339 string → string → time.Time) to a direct int64 pass-through: C time_t → rawJobData int64 → JobInfo int64 → JSON integer → QueryJob int64 → time.Unix() for display Changes: - apiserver/models/jobs.go: SubmitTime, QueueTime, StartTime, EndTime, ReserveTime in JobInfo and JobAdditionalFields: time.Time → int64 - apiserver/models/common.go: ActionControl.SubmitTime: time.Time → int64 - backend/convert_pure.go: remove time.Unix() calls; assign int64 directly - backend/convert_test.go: update time assertions to compare int64 values - xcecli/models/jobs.go: SubmitTime, StartTime, EndTime, ReserveTime in QueryJob: string/time.Time → int64; remove time import - xcecli/common/output/time.go: replace parseJobTime(string) with unixToTime(int64) and fmtUnixTime(int64) helpers - xcecli/common/output/page_print_jobs.go: replace parseJobTime() calls with unixToTime()/fmtUnixTime(); remove !startTime.IsZero() guard (unixToTime returns false for ts<=0, making it redundant) - xcecli/common/output/page_print_array_elements_jobs.go: replace string-split+time.Parse submit-time block with fmtUnixTime(); remove unused time import Side effect: eliminates the "0001-01-01T00:00:00Z" zero-time serialization issue at the source — zero timestamps are now 0 (integer) and unixToTime/fmtUnixTime return false/"-" for ts<=0 without special- casing needed in callers.
348 lines
10 KiB
Go
348 lines
10 KiB
Go
package backend
|
|
|
|
import (
|
|
"apiserver/models"
|
|
"encoding/json"
|
|
)
|
|
|
|
// 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 // 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
|
|
launchType string
|
|
preExec string
|
|
postExec string
|
|
execHome string
|
|
execCWD string
|
|
execUser string
|
|
}
|
|
|
|
// rawSubmitData mirrors the C job_submit_data_t fields used by the Go model.
|
|
type rawSubmitData struct {
|
|
queue string
|
|
user string
|
|
jobName string
|
|
projectName string
|
|
userGroup string
|
|
jobDesc string
|
|
resReq string
|
|
minTasks int
|
|
maxTasks int
|
|
startInfo *rawStartInfo
|
|
}
|
|
|
|
// rawJobData mirrors the C xce_job_t fields used by the Go model.
|
|
type rawJobData struct {
|
|
jobID int64
|
|
state int
|
|
runTime int
|
|
subTime int64
|
|
queueTime int64
|
|
startTime int64
|
|
finishTime int64
|
|
submit *rawSubmitData
|
|
execHosts []string
|
|
}
|
|
|
|
// rawWorkerData mirrors the C xce_worker_t fields used by the Go model.
|
|
type rawWorkerData struct {
|
|
name string
|
|
status int
|
|
hasAttrs bool
|
|
hostType string
|
|
hostModel string
|
|
cpuFactor float64
|
|
maxCPUs int
|
|
maxMem int
|
|
maxSwap int
|
|
maxTmp int
|
|
nDisks int
|
|
}
|
|
|
|
// rawQueueData mirrors the C xce_queue_t fields used by the Go model.
|
|
type rawQueueData struct {
|
|
name string
|
|
status int
|
|
priority int
|
|
}
|
|
|
|
// rawResourceData mirrors the C xce_resource_t fields used by the Go model.
|
|
type rawResourceData struct {
|
|
name string
|
|
desc string
|
|
valType int
|
|
ordType int
|
|
flags int
|
|
interval int
|
|
}
|
|
|
|
// 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: int64(ExtractArrayBaseID(raw)),
|
|
ArrayIdx: ExtractArrayIdx(raw),
|
|
JobStatus: r.state,
|
|
RunTime: r.runTime,
|
|
}
|
|
if r.submit != nil {
|
|
s := r.submit
|
|
info.QueueName = s.queue
|
|
info.SubmitUser = s.user
|
|
info.JobName = s.jobName
|
|
info.ProjectName = s.projectName
|
|
info.UserGroup = s.userGroup
|
|
info.JobDescription = s.jobDesc
|
|
info.ResReq = s.resReq
|
|
info.NumProcessors = s.minTasks
|
|
info.MaxNumProcessors = s.maxTasks
|
|
if s.startInfo != nil {
|
|
si := s.startInfo
|
|
info.Command = si.cmdline
|
|
info.LaunchType = si.launchType
|
|
info.PreExecCmd = si.preExec
|
|
info.PostExecCmd = si.postExec
|
|
info.ExecHome = si.execHome
|
|
info.ExecCWD = si.execCWD
|
|
info.ExecUser = si.execUser
|
|
}
|
|
}
|
|
info.SubmitTime = r.subTime
|
|
info.QueueTime = r.queueTime
|
|
info.StartTime = r.startTime
|
|
info.EndTime = r.finishTime
|
|
if len(r.execHosts) > 0 {
|
|
type hostSlot struct {
|
|
HostName string `json:"host_name"`
|
|
NumSlots int `json:"num_slots"`
|
|
}
|
|
type execHostsJSON struct {
|
|
HostSlots []hostSlot `json:"host_slots"`
|
|
NumHosts int `json:"num_hosts"`
|
|
}
|
|
slots := make([]hostSlot, len(r.execHosts))
|
|
for i, h := range r.execHosts {
|
|
slots[i] = hostSlot{HostName: h, NumSlots: 1}
|
|
}
|
|
data, _ := json.Marshal(execHostsJSON{HostSlots: slots, NumHosts: len(slots)})
|
|
info.ExecHosts = models.Jsonb{String: string(data)}
|
|
}
|
|
return info
|
|
}
|
|
|
|
// rawWorkerToModel maps a rawWorkerData to a models.HostInfo.
|
|
func rawWorkerToModel(r rawWorkerData) *models.HostInfo {
|
|
info := &models.HostInfo{
|
|
HostName: r.name,
|
|
HostStatus: r.status,
|
|
}
|
|
if r.hasAttrs {
|
|
info.HostType = r.hostType
|
|
info.HostModel = r.hostModel
|
|
info.CPUFactor = r.cpuFactor
|
|
info.NumCPUs = r.maxCPUs
|
|
info.MaxMem = r.maxMem
|
|
info.MaxSwap = r.maxSwap
|
|
info.MaxTmp = r.maxTmp
|
|
info.NumDisk = r.nDisks
|
|
}
|
|
return info
|
|
}
|
|
|
|
// rawQueueToModel maps a rawQueueData to a models.QueueInfo.
|
|
func rawQueueToModel(r rawQueueData) *models.QueueInfo {
|
|
return &models.QueueInfo{
|
|
QueueName: r.name,
|
|
QueueStatus: r.status,
|
|
Priority: r.priority,
|
|
}
|
|
}
|
|
|
|
// rawResourceToModel maps a rawResourceData to a models.ResourceDefinitionInfo.
|
|
func rawResourceToModel(r rawResourceData) *models.ResourceDefinitionInfo {
|
|
return &models.ResourceDefinitionInfo{
|
|
ResourceName: r.name,
|
|
Description: r.desc,
|
|
ValueType: r.valType,
|
|
OrderType: r.ordType,
|
|
Flag: r.flags,
|
|
Interval: r.interval,
|
|
}
|
|
}
|
|
|
|
// rawHostConfigData mirrors the C xce_hostconfig_t fields used by the Go model.
|
|
// Only fields that map to models.HostInfo are included:
|
|
// - name → HostName, hostmodel → HostModel, hosttype → HostType, mxj → MaxSlot
|
|
//
|
|
// resources (xce_config_t**) and thresholds (xce_res_threshold_t**) have no HostInfo field and are excluded.
|
|
// dispatchwindow (xce_timewindow_config_t*) is deferred — DisptWindow is left as empty string.
|
|
type rawHostConfigData struct {
|
|
name string
|
|
hostmodel string
|
|
hosttype string
|
|
mxj int
|
|
}
|
|
|
|
// rawHostConfigToModel maps a rawHostConfigData to a models.HostInfo.
|
|
// Config-sourced fields only; worker-sourced fields remain zero.
|
|
func rawHostConfigToModel(r rawHostConfigData) *models.HostInfo {
|
|
return &models.HostInfo{
|
|
HostName: r.name,
|
|
HostModel: r.hostmodel,
|
|
HostType: r.hosttype,
|
|
MaxSlot: r.mxj,
|
|
// TODO(dispatchwindow): xce_hostconfig_t.dispatchwindow is xce_timewindow_config_t*
|
|
// (nsegs int + segs[] with open/close time fields). Serializing it requires a Go
|
|
// mirror struct and json.Marshal. Deferred: no API endpoint currently consumes
|
|
// DisptWindow. Implement when /xce/v1/hosts returns dispatch window data.
|
|
}
|
|
}
|
|
|
|
// rawResvalData mirrors the C xce_resval_t fields used by the Go model.
|
|
// IsDynamic is always true for push-sourced values (controller sends live measurements).
|
|
type rawResvalData struct {
|
|
name string
|
|
host string
|
|
valWhich int // mirrors C xce_val_t.which (Value_u_* discriminant)
|
|
i32 int32
|
|
i64 int64
|
|
u32 uint32
|
|
u64 uint64
|
|
fval float64
|
|
sval string
|
|
bval bool
|
|
valNil bool // true when the C xce_val_t* was NULL
|
|
}
|
|
|
|
// rawResvalToDistribution maps a rawResvalData to a models.ResourceDistributionInfo.
|
|
// Returns nil when valNil is true (C val pointer was NULL) or when valWhich
|
|
// is an unknown discriminant — callers should skip nil results.
|
|
func rawResvalToDistribution(r rawResvalData) *models.ResourceDistributionInfo {
|
|
rd := &models.ResourceDistributionInfo{
|
|
ResourceName: r.name,
|
|
Location: r.host,
|
|
IsDynamic: true, // resource values pushed from workers are always dynamic
|
|
}
|
|
if r.valNil {
|
|
return nil
|
|
}
|
|
switch r.valWhich {
|
|
case 0: // Value_u_ivalue32
|
|
rd.ValueType = int(models.NUMERIC)
|
|
rd.NumericValue = float64(r.i32)
|
|
case 2: // Value_u_ivalue64
|
|
rd.ValueType = int(models.NUMERIC)
|
|
rd.NumericValue = float64(r.i64)
|
|
case 1: // Value_u_uvalue32
|
|
rd.ValueType = int(models.NUMERIC)
|
|
rd.NumericValue = float64(r.u32)
|
|
case 3: // Value_u_uvalue64
|
|
rd.ValueType = int(models.NUMERIC)
|
|
rd.NumericValue = float64(r.u64)
|
|
case 5: // Value_u_fvalue
|
|
rd.ValueType = int(models.NUMERIC)
|
|
rd.NumericValue = r.fval
|
|
case 4: // Value_u_svalue
|
|
rd.ValueType = int(models.STRING)
|
|
rd.StringValue = r.sval
|
|
case 6: // Value_u_bvalue
|
|
rd.ValueType = int(models.BOOLEAN)
|
|
rd.BooleanValue = r.bval
|
|
default:
|
|
return nil
|
|
}
|
|
return rd
|
|
}
|
|
|
|
// rawClusterData mirrors the C cluster_config_t fields used by the Go model.
|
|
// Only config-sourced fields are included (heartbeat_interval, vcpus, inactive_heartbeat_count).
|
|
// Built-in/sourced fields (Version, Status, ControllerName, etc.) come from LoadBuiltinData
|
|
// and are not part of the config push conversion.
|
|
type rawClusterData struct {
|
|
name string
|
|
heartbeatInterval int
|
|
vcpus int
|
|
inactiveHeartbeatCount int
|
|
}
|
|
|
|
// rawClusterToModel maps a rawClusterData to a models.ClusterInfo.
|
|
// Config-sourced fields only; built-in fields remain zero.
|
|
func rawClusterToModel(r rawClusterData) *models.ClusterInfo {
|
|
return &models.ClusterInfo{
|
|
ClusterName: r.name,
|
|
HeartbeatInterval: r.heartbeatInterval,
|
|
Vcpus: r.vcpus,
|
|
InactiveHeartbeatCount: r.inactiveHeartbeatCount,
|
|
}
|
|
}
|