myxce/src/frontend/apiserver/backend/convert.go
lei.chen 805288d747 E2 Phase 2: external-license-resources 运维面 — stale 标记、last-updated 展示、xceadmin extres status
design.md 3.5 Phase 2(PEN D reason 专用名为可选项,复用 PREASON_NO_RESOURCE
已带资源名,跳过):

① esd stale 标记与 WARN
  - esd_source_t 增 stale_limit/last_ok_time;esd_source_is_stale(): 从未有值
    不算 stale、时钟超前钳到 0、严格大于(interval==limit 不抖动)、
    ESD_STALE_LIMIT_DEFAULT 150;fail-open 保持旧值不变
  - 采集失败首轮一次性 WARN,过限后每轮升级为 STALE WARN(带 age/limit/kept
    值),恢复即静默
  - status 文件 esd_status_write(): daemon/pid/started_at/generated_at/
    pushes ok/fail/sources[{name,last_value,last_ok_time,stale}],tmp+rename
    原子写;写失败仅状态转换时告警一次,绝不拖垮 poll 循环

② `xce resources` 展示 last-updated
  - controller worker.c: EXTERNAL 共享值被接受时盖 last_report——同时盖
    live resmgr 对象与 myconf 配置树副本(apiserver 定义 store 只由 join 时
    config push 供给,串行的是配置树,缺后者 CLI 永远显示 never)
  - scheduler core_events.c: RESOURCES_PUSH 原地更新拷贝 last_report(纯
    metadata,resobj.c 比较本就排除)
  - apiserver: model 带 last_report + 默认列(SelectFields 只投影默认列,
    缺失即 API/CLI 全链不可见);convert cResourceToModel 透传
  - apiserver handler.go: OnResourceLocationUpdated 对 host=="" 且定义带
    EXTERNAL(0x10) 的推送刷新 store 内 LastReport——config push 快照之外
    的活刷新;host 来源值与非 EXTERNAL 资源绝不触碰
  - xcecli: LAST_UPDATED 列(-l verbose 表格/详情/JSON 三态),0 显示 "-"

④ `xceadmin extres status`
  - 本机读 status 文件(远程读取留待 PR-2,需新 wire 协议;LAST_UPDATED
    已覆盖远程新鲜度语义)——--status-file 显式路径或
    /var/run/xce/esd-status.json 默认,无 controller 连接依赖
  - XceExtresStatusError 本地文件类错误;click default=None 装饰期冻结坑
    用调用期解析

TDD:test_extres_resvals +2(配置树盖戳/非 EXTERNAL 不盖戳)、test_esd_core
+4(stale 判式/状态文件)、apiserver models +2(默认列/json tag)、backend
integration +2(EXTERNAL 刷新/非 EXTERNAL 不刷新)、xceadmin +11(渲染/
本地读/容错)。全绿:controller 1360、scheduler 100、apiserver(cgo+nocgo)、
esd 11、xceadmin 385。

t1 E2E 实证:esd join 即推送→API last_report=join 时刻→CLI LAST_UPDATED
"Oct 27 13:52";破坏采集器后 60s 过限转 per-round STALE WARN + status
stale:true + fail-open 保值,恢复脚本即回 false;liccheck 作业提交→运行→
DONE,五 daemon 全健康。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-27 14:00:33 +00:00

588 lines
20 KiB
Go

//go:build cgo
package backend
/*
#include <stdlib.h>
#include "client.h"
#include "val_helpers.h"
// Helper to index into xce_host_alloc_t** (CGO cannot dereference C pointer-to-pointer arrays).
static xce_host_alloc_t *job_alloc_host(xce_job_alloc_t *a, int i) { return a->allocs[i]; }
// Helpers to index into the reason pointer arrays of xce_job_preason_t.
static xce_host_reason_t *job_host_reason(xce_job_preason_t *p, int i) { return p->host_based_reasons[i]; }
static xce_res_reason_t *job_res_reason(xce_job_preason_t *p, int i) { return p->res_based_reasons[i]; }
// Helper to index into xce_alloc_t** (same CGO limitation as above) -- one
// host's per-resource allocation list, scanned for the "slots" entry.
static xce_alloc_t *host_alloc_res(xce_host_alloc_t *h, int i) { return h->allocs[i]; }
// Helpers to index into job_pidinfo_t's pid/pgid arrays (same CGO limitation).
static unsigned int pidinfo_pid(job_pidinfo_t *p, int i) { return p->pids[i]; }
static unsigned int pidinfo_pgid(job_pidinfo_t *p, int i) { return p->pgids[i]; }
*/
import "C"
import (
"apiserver/models"
logger "apiserver/utils"
"unsafe"
"go.uber.org/zap"
)
// goString safely converts a C string to a Go string, handling nil.
func goString(s *C.char) string {
if s == nil {
return ""
}
return C.GoString(s)
}
// cStringOrNil returns a C copy of s, or nil for an empty string so an unset
// field stays NULL in the C struct. The caller frees the result.
func cStringOrNil(s string) *C.char {
if s == "" {
return nil
}
return C.CString(s)
}
// cJobArrayToModels decodes a compact Data.jobArray push descriptor
// (*C.xce_job_submit_t: base jobid, array_size, one shared job_submit_data_t
// spec — the same JobSubmit shape apiserver_push_jobarray() sends,
// job-array-single-wal-record task 5.2) and expands it locally into N
// models.JobInfo entries via rawJobArrayToModels, mirroring cJobToModel's
// single-job field extraction below but for the array-shaped descriptor
// (task 5.4, Req 6.2, 6.5). Decodes the shared submit spec exactly once
// (same field-by-field extraction cJobToModel uses for job.submit) since
// every element shares it by construction — never once per element.
func cJobArrayToModels(jobArray *C.xce_job_submit_t) []*models.JobInfo {
if jobArray == nil || jobArray.submit == nil {
return nil
}
s := jobArray.submit
rd := &rawSubmitData{
queue: goString(s.queue),
user: goString(s.user),
jobName: goString(s.job_name),
projectName: goString(s.project_name),
userGroup: goString(s.user_group),
jobDesc: goString(s.job_description),
resReq: goString(s.resreq),
minTasks: int(s.mintasks),
maxTasks: int(s.maxtasks),
fromHost: goString(s.from_host),
}
if s.start_info != nil {
si := s.start_info
rd.startInfo = &rawStartInfo{
cmdline: goString(si.cmdline),
launchType: goString(si.launch_type),
preExec: goString(si.job_pre_exec),
postExec: goString(si.job_post_exec),
execHome: goString(si.exechome),
execCWD: goString(si.execcwd),
execUser: goString(si.execuser),
exdata: goString(si.exdata),
}
}
return rawJobArrayToModels(uint64(jobArray.jobid), uint64(jobArray.array_size), rd)
}
// cPidInfoToRaw decodes a C job_pidinfo_t — the worker's start report
// (pids/pgids plus exdata, whose {"url": ...} JSON is the interactive
// session URL for ttyd/XJOB jobs) — into the rawPidInfo the pure layer
// consumes. Shared by cJobToModel (the full xce_job_t conversion, where the
// capnp Job wire carries pidinfo at Job.pidinfo@9) and the standalone
// jobPidInfo notify path (NotifyData_data_jobPidInfo), which delivers the
// same struct the moment WAL_JOB_START commits.
func cPidInfoToRaw(pi *C.job_pidinfo_t) *rawPidInfo {
if pi == nil {
return nil
}
p := &rawPidInfo{exdata: goString(pi.exdata)}
for i := 0; i < int(pi.npids); i++ {
p.pids = append(p.pids, int(C.pidinfo_pid(pi, C.int(i))))
}
for i := 0; i < int(pi.npgids); i++ {
p.pgids = append(p.pgids, int(C.pidinfo_pgid(pi, C.int(i))))
}
return p
}
// cJobToModel extracts C xce_job_t fields into a rawJobData and maps it to JobInfo.
func cJobToModel(job *C.xce_job_t) *models.JobInfo {
r := rawJobData{
jobID: int64(job.jobid),
state: int(job.state),
runTime: int(job.run_time),
subTime: int64(job.sub_time),
queueTime: int64(job.queue_time),
startTime: int64(job.start_time),
finishTime: int64(job.finish_time),
}
if job.submit != nil {
s := job.submit
rd := &rawSubmitData{
queue: goString(s.queue),
user: goString(s.user),
jobName: goString(s.job_name),
projectName: goString(s.project_name),
userGroup: goString(s.user_group),
jobDesc: goString(s.job_description),
resReq: goString(s.resreq),
minTasks: int(s.mintasks),
maxTasks: int(s.maxtasks),
fromHost: goString(s.from_host),
}
if s.start_info != nil {
si := s.start_info
rd.startInfo = &rawStartInfo{
cmdline: goString(si.cmdline),
launchType: goString(si.launch_type),
preExec: goString(si.job_pre_exec),
postExec: goString(si.job_post_exec),
execHome: goString(si.exechome),
execCWD: goString(si.execcwd),
execUser: goString(si.execuser),
exdata: goString(si.exdata),
}
}
r.submit = rd
}
if u := job.run_usage; u != nil {
r.runUsage = &rawRunUsage{
maxMem: float64(u.max_mem),
avgMem: float64(u.avg_mem),
usedMem: float64(u.used_mem),
cpuTime: int(u.cpu_time),
peakCPUTime: int(u.peak_cpu_time),
peakCPUDuration: int(u.peak_cpu_duration),
memEfficiency: float64(u.mem_efficiency),
avgCPUEfficiency: float64(u.avg_cpu_efficiency),
peakCPUEfficiency: float64(u.peak_cpu_efficiency),
peakCPU: float64(u.peak_cpu),
}
}
if job.allocs != nil {
n := int(job.allocs.nhosts)
for i := 0; i < n; i++ {
h := C.job_alloc_host(job.allocs, C.int(i))
if h == nil || h.host == nil {
continue
}
// bug-1743-followup: this host's own "slots" resource
// allocation (xce_host_alloc_t.allocs[]), mirroring the C
// scheduler's own get_nslots_in_alloc()/repl_worker_alloc_
// nslots() resname scan -- defaults to 1 when absent, same as
// those.
nslots := 1
nallocs := int(h.nallocs)
for j := 0; j < nallocs; j++ {
res := C.host_alloc_res(h, C.int(j))
if res != nil && res.resname != nil && C.GoString(res.resname) == "slots" {
nslots = int(res.value)
break
}
}
r.execHosts = append(r.execHosts, rawHostSlot{host: C.GoString(h.host), slots: nslots})
}
}
// PEND reason: the scheduler pushes these via setpreason; the controller
// stores them on job->pend_reasons. Maps to models.JobInfo.PendingReason.
// A detail-only record (main_reason 0) gets its overall-reason NAME from
// the first detail row at the pure layer (rawJobToModel); the C record is
// carried through unmutated (the scheduler-framework trace/verify stack
// counts main_reason as its own occurrence and its committed .replay
// baselines encode main_reason=0 detail-only PENDs).
if job.pend_reasons != nil {
pr := job.pend_reasons
// main_reason 0 is detail-only (the allocator's ordinary shortfall
// shape): leave the NAME empty so rawJobToModel's promotion fills it
// from the first detail row. Pre-filling pendReasonName(0) here
// ("unknown(0)") made the promotion unreachable on the live C path —
// the advisor P0 verification caught jobs whose res_reasons named the
// real cause while main_reason_name read unknown(0).
mainName := ""
if pr.main_reason != 0 {
mainName = pendReasonName(int(pr.main_reason))
}
r.pendReason = &rawPendReason{
MainReason: int(pr.main_reason),
MainReasonName: mainName,
}
for i := 0; i < int(pr.nhost_reasons); i++ {
hr := C.job_host_reason(pr, C.int(i))
if hr != nil && hr.host != nil {
r.pendReason.HostReasons = append(r.pendReason.HostReasons,
rawHostReason{Host: goString(hr.host), Reason: int(hr.reason),
ReasonName: pendReasonName(int(hr.reason))})
}
}
for i := 0; i < int(pr.nres_reasons); i++ {
rr := C.job_res_reason(pr, C.int(i))
if rr != nil && rr.resource != nil {
r.pendReason.ResReasons = append(r.pendReason.ResReasons,
rawResReason{Resource: goString(rr.resource),
Host: goString(rr.host), Reason: int(rr.reason),
ReasonName: pendReasonName(int(rr.reason))})
}
}
}
// Pidinfo: the worker's start report — pids/pgids plus exdata, whose
// {"url": ...} JSON is the interactive session URL (ttyd/XJOB). The
// capnp Job wire carries pidinfo (Job.pidinfo@9 / JobPIDInfo.exdata@3)
// but the conversion dropped it, so interactive_job_url never reached
// the API and xcecli's browser flow could never fire.
r.pidInfo = cPidInfoToRaw(job.pidinfo)
return rawJobToModel(r)
}
// cWorkerToModel extracts C xce_worker_t fields into a rawWorkerData and maps it to HostInfo.
func cWorkerToModel(worker *C.xce_worker_t) *models.HostInfo {
workerName := goString(worker.name)
r := rawWorkerData{
name: workerName,
status: int(worker.status),
}
if worker.attrs != nil {
a := worker.attrs
r.hasAttrs = true
r.hostType = goString(a.hosttype)
r.hostModel = goString(a.hostmodel)
r.cpuFactor = float64(a.cpufactor)
r.maxCPUs = int(a.maxcpus)
r.maxMem = int(a.maxmem)
r.maxSwap = int(a.maxswap)
r.maxTmp = int(a.maxtmp)
r.nDisks = int(a.ndisks)
}
n := int(worker.nresvals_to_sched)
if n > 0 {
entries := unsafe.Slice(worker.resvals_to_sched, n)
r.resvals = make([]models.ResourceDistributionInfo, 0, n)
for _, nv := range entries {
if rd := cNameValToWorkerResval(workerName, nv); rd != nil {
r.resvals = append(r.resvals, *rd)
}
}
}
return rawWorkerToModel(r)
}
// cQueueToModel extracts C xce_queue_t fields and maps them to QueueInfo.
func cQueueToModel(queue *C.xce_queue_t) *models.QueueInfo {
return rawQueueToModel(rawQueueData{
name: goString(queue.queue),
status: int(queue.status),
priority: int(queue.priority),
})
}
// cResourceToModel extracts C xce_resource_t fields and maps them to ResourceDefinitionInfo.
func cResourceToModel(res *C.xce_resource_t) *models.ResourceDefinitionInfo {
return rawResourceToModel(rawResourceData{
name: goString(res.name),
desc: goString(res.desc),
valType: int(res.valtype),
ordType: int(res.ordtype),
flags: int(res.flags),
interval: int(res.interval),
lastReport: int64(res.last_report),
})
}
// cClusterConfQueues extracts queue models from a cluster_config_t.
func cClusterConfQueues(conf *C.cluster_config_t) []models.QueueInfo {
n := int(conf.nqueues)
if n == 0 || conf.queues == nil {
return nil
}
qps := unsafe.Slice(conf.queues, n)
out := make([]models.QueueInfo, 0, n)
for _, qp := range qps {
if qp != nil {
out = append(out, *cQueueToModel(qp))
}
}
return out
}
// cClusterConfResources extracts resource definition models from a cluster_config_t.
func cClusterConfResources(conf *C.cluster_config_t) []models.ResourceDefinitionInfo {
n := int(conf.nres)
if n == 0 || conf.resources == nil {
return nil
}
rps := unsafe.Slice(conf.resources, n)
out := make([]models.ResourceDefinitionInfo, 0, n)
for _, rp := range rps {
if rp != nil {
out = append(out, *cResourceToModel(rp))
}
}
return out
}
// cHostConfigToModel extracts C xce_hostconfig_t fields and maps them to HostInfo.
func cHostConfigToModel(hc *C.xce_hostconfig_t) *models.HostInfo {
return rawHostConfigToModel(rawHostConfigData{
name: goString(hc.name),
hostmodel: goString(hc.hostmodel),
hosttype: goString(hc.hosttype),
mxj: int(hc.mxj),
})
}
// cStrList converts an xce_strlist_t to a Go slice.
func cStrList(l *C.xce_strlist_t) []string {
n := int(l.count)
if n == 0 || l.data == nil {
return nil
}
out := make([]string, 0, n)
for _, p := range unsafe.Slice(l.data, n) {
if p != nil {
out = append(out, C.GoString(p))
}
}
return out
}
// cClusterConfUserGroups extracts the user groups from a cluster_config_t
// (bug-1934: the self_ug job scope resolves them).
func cClusterConfUserGroups(conf *C.cluster_config_t) []models.UserGroupInfo {
n := int(conf.nugroups)
if n == 0 || conf.ugroups == nil {
return nil
}
out := make([]models.UserGroupInfo, 0, n)
for _, gp := range unsafe.Slice(conf.ugroups, n) {
if gp == nil || gp.name == nil {
continue
}
out = append(out, models.UserGroupInfo{
Name: C.GoString(gp.name),
Members: cStrList(&gp.members),
ExMembers: cStrList(&gp.ex_members),
})
}
return out
}
// cClusterConfHostConfigs extracts host config models from a cluster_config_t.
func cClusterConfHostConfigs(conf *C.cluster_config_t) []models.HostInfo {
n := int(conf.nhconfigs)
if n == 0 || conf.hconfigs == nil {
return nil
}
hps := unsafe.Slice(conf.hconfigs, n)
out := make([]models.HostInfo, 0, n)
for _, hp := range hps {
if hp != nil {
out = append(out, *cHostConfigToModel(hp))
}
}
return out
}
// cClusterConfToClusterInfo extracts C cluster_config_t cluster-level fields and maps them to ClusterInfo.
// Returns nil when clustername is empty so dispatchConfigPush skips the OnClusterUpdated call.
// C struct field names are snake_case (clustername, heartbeat_interval, etc.) — not Cap'n Proto names.
func cClusterConfToClusterInfo(conf *C.cluster_config_t) *models.ClusterInfo {
name := goString(conf.clustername)
if name == "" {
return nil
}
return rawClusterToModel(rawClusterData{
name: name,
heartbeatInterval: int(conf.heartbeat_interval),
vcpus: 0, // vcpus moved to myconf_t (--vcpus CLI flag); not in cluster_config_t
inactiveHeartbeatCount: int(conf.inactive_heartbeat_count),
})
}
// modelJobToSubmit converts a Go JobInfo to C xce_job_submit_t heap structs.
// The caller must free all returned C memory by calling freeJobSubmit.
func modelJobToSubmit(job *models.JobInfo) (*C.xce_job_submit_t, *C.job_submit_data_t, *C.job_startinfo_t) {
rsi := submitStartInfo(job)
si := (*C.job_startinfo_t)(C.calloc(1, C.sizeof_job_startinfo_t))
si.cmdline = C.CString(rsi.cmdline)
// bug-1500/bug-1780 (same fix both sides): carry the exec user on the
// start_info. Without it the worker receives a NULL/empty execuser and
// runs every job as "nobody".
si.execuser = cStringOrNil(rsi.execUser)
si.launch_type = cStringOrNil(rsi.launchType)
si.job_pre_exec = cStringOrNil(rsi.preExec)
si.job_post_exec = cStringOrNil(rsi.postExec)
si.exechome = cStringOrNil(rsi.execHome)
si.execcwd = cStringOrNil(rsi.execCWD)
si.exdata = cStringOrNil(rsi.exdata)
sd := (*C.job_submit_data_t)(C.calloc(1, C.sizeof_job_submit_data_t))
sd.queue = C.CString(job.QueueName)
sd.user = C.CString(job.SubmitUser)
sd.job_name = C.CString(job.JobName)
if job.ProjectName != "" {
sd.project_name = C.CString(job.ProjectName)
}
if job.UserGroup != "" {
sd.user_group = C.CString(job.UserGroup)
}
if job.JobDescription != "" {
sd.job_description = C.CString(job.JobDescription)
}
if job.ResReq != "" {
sd.resreq = C.CString(job.ResReq)
}
if job.FromHost != "" {
sd.from_host = C.CString(job.FromHost)
}
sd.mintasks = C.uint(job.NumProcessors)
if sd.mintasks == 0 {
sd.mintasks = 1
}
sd.maxtasks = C.uint(job.MaxNumProcessors)
if sd.maxtasks < sd.mintasks {
sd.maxtasks = sd.mintasks
}
sd.start_info = si
js := (*C.xce_job_submit_t)(C.calloc(1, C.sizeof_xce_job_submit_t))
js.jobid = C.uint64_t(job.Unique_JobID)
js.array_size = 1
js.submit = sd
return js, sd, si
}
// freeJobSubmit releases C memory allocated by modelJobToSubmit.
func freeJobSubmit(js *C.xce_job_submit_t, sd *C.job_submit_data_t, si *C.job_startinfo_t) {
if si != nil {
C.free(unsafe.Pointer(si.cmdline))
C.free(unsafe.Pointer(si.execuser))
C.free(unsafe.Pointer(si.launch_type))
C.free(unsafe.Pointer(si.job_pre_exec))
C.free(unsafe.Pointer(si.job_post_exec))
C.free(unsafe.Pointer(si.exechome))
C.free(unsafe.Pointer(si.execcwd))
C.free(unsafe.Pointer(si.exdata))
C.free(unsafe.Pointer(si))
}
if sd != nil {
C.free(unsafe.Pointer(sd.queue))
C.free(unsafe.Pointer(sd.user))
C.free(unsafe.Pointer(sd.job_name))
C.free(unsafe.Pointer(sd.project_name))
C.free(unsafe.Pointer(sd.user_group))
C.free(unsafe.Pointer(sd.job_description))
C.free(unsafe.Pointer(sd.resreq))
C.free(unsafe.Pointer(sd.from_host))
C.free(unsafe.Pointer(sd))
}
if js != nil {
C.free(unsafe.Pointer(js))
}
}
// cNameValToWorkerResval converts a C xce_name_val_t pointer to a ResourceDistributionInfo.
// workerName is set as Location. Returns nil if nv or nv.val is nil.
func cNameValToWorkerResval(workerName string, nv *C.xce_name_val_t) *models.ResourceDistributionInfo {
if nv == nil {
return nil
}
rd := &models.ResourceDistributionInfo{
ResourceName: C.GoString(nv.name),
Location: workerName,
IsDynamic: true,
}
if nv.val == nil {
logger.Warn("worker resval with nil value, skipping",
zap.String("resource", rd.ResourceName),
zap.String("host", workerName))
return nil
}
switch C.xce_val_which(nv.val) {
case C.Value_u_ivalue32:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_i32(nv.val))
case C.Value_u_ivalue64:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_i64(nv.val))
case C.Value_u_uvalue32:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_u32(nv.val))
case C.Value_u_uvalue64:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_u64(nv.val))
case C.Value_u_fvalue:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_fval(nv.val))
case C.Value_u_svalue:
rd.ValueType = int(models.STRING)
rd.StringValue = C.GoString(C.xce_val_str(nv.val))
case C.Value_u_bvalue:
rd.ValueType = int(models.BOOLEAN)
rd.BooleanValue = C.xce_val_bool(nv.val) != 0
default:
logger.Warn("unknown xce_val_t discriminant in worker resval, skipping",
zap.Int("which", int(C.xce_val_which(nv.val))),
zap.String("resource", rd.ResourceName),
zap.String("host", workerName))
return nil
}
return rd
}
// cResvalToDistribution converts a C xce_resval_t pointer to a Go ResourceDistributionInfo.
// Returns nil if vp.val is NULL (nil guard prevents CGO panic).
// vp.val is a *C.xce_val_t pointer field — pass directly, no &.
func cResvalToDistribution(vp *C.xce_resval_t) *models.ResourceDistributionInfo {
rd := &models.ResourceDistributionInfo{
ResourceName: C.GoString(vp.name),
Location: C.GoString(vp.host),
IsDynamic: true, // resource values pushed from workers are always dynamic
}
// Guard: vp.val may be NULL if the controller sends a resval with no value set.
// Dereferencing NULL in xce_val_which() would segfault in C.
if vp.val == nil {
logger.Warn("resval with nil value, skipping",
zap.String("resource", rd.ResourceName),
zap.String("host", rd.Location))
return nil
}
switch C.xce_val_which(vp.val) {
case C.Value_u_ivalue32:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_i32(vp.val))
case C.Value_u_ivalue64:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_i64(vp.val))
case C.Value_u_uvalue32:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_u32(vp.val))
case C.Value_u_uvalue64:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_u64(vp.val))
case C.Value_u_fvalue:
rd.ValueType = int(models.NUMERIC)
rd.NumericValue = float64(C.xce_val_fval(vp.val))
case C.Value_u_svalue:
rd.ValueType = int(models.STRING)
rd.StringValue = C.GoString(C.xce_val_str(vp.val))
case C.Value_u_bvalue:
rd.ValueType = int(models.BOOLEAN)
rd.BooleanValue = C.xce_val_bool(vp.val) != 0
default:
logger.Warn("unknown xce_val_t discriminant, skipping",
zap.Int("which", int(C.xce_val_which(vp.val))),
zap.String("resource", rd.ResourceName),
zap.String("host", rd.Location))
return nil
}
return rd
}