package backend import ( "apiserver/models" "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 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 } // 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 } } if r.subTime > 0 { info.SubmitTime = time.Unix(r.subTime, 0) } if r.queueTime > 0 { info.QueueTime = time.Unix(r.queueTime, 0) } if r.startTime > 0 { info.StartTime = time.Unix(r.startTime, 0) } if r.finishTime > 0 { info.EndTime = time.Unix(r.finishTime, 0) } 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, } }