Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use query builder #25

Merged
merged 11 commits into from
Jan 5, 2024
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ test-e2e: build pkg/collector/fixtures/sys/.unpacked pkg/collector/fixtures/proc
./scripts/e2e-test.sh -s stats-jobid-query
./scripts/e2e-test.sh -s stats-jobuuid-jobid-query
./scripts/e2e-test.sh -s stats-admin-query
./scripts/e2e-test.sh -s stats-admin-query-all
endif

.PHONY: skip-test-e2e
Expand Down
3 changes: 2 additions & 1 deletion cmd/batchjob_stats_server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ func TestBatchjobStatsExecutable(t *testing.T) {
}

jobstats := exec.Command(
binary, "--path.data", tmpDir, "--slurm.sacct.path", tmpSacctPath,
binary, "--path.data", tmpDir,
"--slurm.sacct.path", tmpSacctPath,
"--batch.scheduler.slurm",
"--web.listen-address", address,
)
Expand Down
44 changes: 40 additions & 4 deletions internal/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helpers

import (
"context"
"os"
"os/exec"
"strings"
"syscall"
Expand All @@ -22,13 +23,24 @@ func GetUuidFromString(stringSlice []string) (string, error) {
}

// Execute command and return stdout/stderr
func Execute(cmd string, args []string, logger log.Logger) ([]byte, error) {
func Execute(cmd string, args []string, env []string, logger log.Logger) ([]byte, error) {
level.Debug(logger).Log("msg", "Executing", "command", cmd, "args", strings.Join(args, " "))

execCmd := exec.Command(cmd, args...)

// If env is not nil pointer, add env vars into subprocess cmd
if env != nil {
execCmd.Env = append(os.Environ(), env...)
}

// Attach a separate terminal less session to the subprocess
// This is to avoid prompting for password when we run command with sudo
// Ref: https://stackoverflow.com/questions/13432947/exec-external-program-script-and-detect-if-it-requests-user-input
if cmd == "sudo" {
execCmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
}

// Execute command
out, err := execCmd.CombinedOutput()
if err != nil {
level.Error(logger).
Expand All @@ -38,12 +50,21 @@ func Execute(cmd string, args []string, logger log.Logger) ([]byte, error) {
}

// Execute command as a given UID and GID and return stdout/stderr
func ExecuteAs(cmd string, args []string, uid int, gid int, logger log.Logger) ([]byte, error) {
func ExecuteAs(cmd string, args []string, uid int, gid int, env []string, logger log.Logger) ([]byte, error) {
level.Debug(logger).
Log("msg", "Executing as user", "command", cmd, "args", strings.Join(args, " "), "uid", uid, "gid", gid)
execCmd := exec.Command(cmd, args...)

// Set uid and gid for process
execCmd.SysProcAttr = &syscall.SysProcAttr{}
execCmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}

// If env is not nil pointer, add env vars into subprocess cmd
if env != nil {
execCmd.Env = append(os.Environ(), env...)
}

// Execute command
out, err := execCmd.CombinedOutput()
if err != nil {
level.Error(logger).
Expand All @@ -53,7 +74,7 @@ func ExecuteAs(cmd string, args []string, uid int, gid int, logger log.Logger) (
}

// Execute command with timeout and return stdout/stderr
func ExecuteWithTimeout(cmd string, args []string, timeout int, logger log.Logger) ([]byte, error) {
func ExecuteWithTimeout(cmd string, args []string, timeout int, env []string, logger log.Logger) ([]byte, error) {
level.Debug(logger).
Log("msg", "Executing with timeout", "command", cmd, "args", strings.Join(args, " "), "timeout", timeout)

Expand All @@ -65,6 +86,12 @@ func ExecuteWithTimeout(cmd string, args []string, timeout int, logger log.Logge
}

execCmd := exec.CommandContext(ctx, cmd, args...)

// If env is not nil pointer, add env vars into subprocess cmd
if env != nil {
execCmd.Env = append(os.Environ(), env...)
}

// Attach a separate terminal less session to the subprocess
// This is to avoid prompting for password when we run command with sudo
// Ref: https://stackoverflow.com/questions/13432947/exec-external-program-script-and-detect-if-it-requests-user-input
Expand All @@ -75,6 +102,7 @@ func ExecuteWithTimeout(cmd string, args []string, timeout int, logger log.Logge
// The signal to send to the children when parent receives a kill signal
// execCmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM}

// Execute command
out, err := execCmd.CombinedOutput()
if err != nil {
level.Error(logger).
Expand All @@ -84,7 +112,7 @@ func ExecuteWithTimeout(cmd string, args []string, timeout int, logger log.Logge
}

// Execute command with timeout as a given UID and GID and return stdout/stderr
func ExecuteAsWithTimeout(cmd string, args []string, uid int, gid int, timeout int, logger log.Logger) ([]byte, error) {
func ExecuteAsWithTimeout(cmd string, args []string, uid int, gid int, timeout int, env []string, logger log.Logger) ([]byte, error) {
level.Debug(logger).
Log("msg", "Executing with timeout as user", "command", cmd, "args", strings.Join(args, " "), "uid", uid, "gid", gid, "timout")

Expand All @@ -96,9 +124,17 @@ func ExecuteAsWithTimeout(cmd string, args []string, uid int, gid int, timeout i
}

execCmd := exec.CommandContext(ctx, cmd, args...)

// If env is not nil pointer, add env vars into subprocess cmd
if env != nil {
execCmd.Env = append(os.Environ(), env...)
}

// Set uid and gid for the process
execCmd.SysProcAttr = &syscall.SysProcAttr{}
execCmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}

// Execute command
out, err := execCmd.CombinedOutput()
if err != nil {
level.Error(logger).
Expand Down
10 changes: 5 additions & 5 deletions internal/helpers/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ func TestGetUuid(t *testing.T) {

func TestExecute(t *testing.T) {
// Test successful command execution
out, err := Execute("echo", []string{"test"}, log.NewNopLogger())
out, err := Execute("bash", []string{"-c", "echo ${VAR1} ${VAR2}"}, []string{"VAR1=1", "VAR2=2"}, log.NewNopLogger())
if err != nil {
t.Errorf("Failed to execute command %s", err)
}
if strings.TrimSpace(string(out)) != "test" {
t.Errorf("Expected output \"test\". Got \"%s\"", string(out))
if strings.TrimSpace(string(out)) != "1 2" {
t.Errorf("Expected output \"1 2\". Got \"%s\"", string(out))
}

// Test failed command execution
out, err = Execute("exit", []string{"1"}, log.NewNopLogger())
out, err = Execute("exit", []string{"1"}, nil, log.NewNopLogger())
if err == nil {
t.Errorf("Expected to fail command execution. Got output %s", out)
}
}

func TestExecuteWithTimeout(t *testing.T) {
// Test successful command execution
_, err := ExecuteWithTimeout("sleep", []string{"5"}, 2, log.NewNopLogger())
_, err := ExecuteWithTimeout("sleep", []string{"5"}, 2, nil, log.NewNopLogger())
if err == nil {
t.Errorf("Expected command timeout")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/collector/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func GetNvidiaGPUDevices(nvidiaSmiPath string, logger log.Logger) (map[int]Devic

// Execute nvidia-smi command to get available GPUs
args := []string{"--query-gpu=index,name,uuid", "--format=csv"}
nvidiaSmiOutput, err := helpers.Execute(nvidiaSmiPath, args, logger)
nvidiaSmiOutput, err := helpers.Execute(nvidiaSmiPath, args, nil, logger)
if err != nil {
level.Error(logger).
Log("msg", "nvidia-smi command to get list of devices failed", "err", err)
Expand Down
12 changes: 6 additions & 6 deletions pkg/collector/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,23 @@ func NewIPMICollector(logger log.Logger) (Collector, error) {
cmdSlice := strings.Split(*ipmiDcmiCmd, " ")

// Verify if running ipmiDcmiCmd works
if _, err := helpers.Execute(cmdSlice[0], cmdSlice[1:], logger); err == nil {
if _, err := helpers.Execute(cmdSlice[0], cmdSlice[1:], nil, logger); err == nil {
execMode = "native"
goto outside
}

// If ipmiDcmiCmd failed to run and if sudo is not already present in command,
// add sudo to command and execute. If current user has sudo rights it will be a success
if cmdSlice[0] != "sudo" {
if _, err := helpers.ExecuteWithTimeout("sudo", cmdSlice, 2, logger); err == nil {
if _, err := helpers.ExecuteWithTimeout("sudo", cmdSlice, 2, nil, logger); err == nil {
execMode = "sudo"
goto outside
}
}

// As last attempt, run the command as root user by forking subprocess
// as root. If there is setuid cap on the process, it will be a success
if _, err := helpers.ExecuteAs(cmdSlice[0], cmdSlice[1:], 0, 0, logger); err == nil {
if _, err := helpers.ExecuteAs(cmdSlice[0], cmdSlice[1:], 0, 0, nil, logger); err == nil {
execMode = "cap"
goto outside
}
Expand Down Expand Up @@ -152,11 +152,11 @@ func (c *impiCollector) Update(ch chan<- prometheus.Metric) error {
// Execute ipmi-dcmi command
cmdSlice := strings.Split(*ipmiDcmiCmd, " ")
if c.execMode == "cap" {
stdOut, err = helpers.ExecuteAs(cmdSlice[0], cmdSlice[1:], 0, 0, c.logger)
stdOut, err = helpers.ExecuteAs(cmdSlice[0], cmdSlice[1:], 0, 0, nil, c.logger)
} else if c.execMode == "sudo" {
stdOut, err = helpers.ExecuteWithTimeout("sudo", cmdSlice, 1, c.logger)
stdOut, err = helpers.ExecuteWithTimeout("sudo", cmdSlice, 1, nil, c.logger)
} else if c.execMode == "native" {
stdOut, err = helpers.Execute(cmdSlice[0], cmdSlice[1:], c.logger)
stdOut, err = helpers.Execute(cmdSlice[0], cmdSlice[1:], nil, c.logger)
} else {
err = fmt.Errorf("Current process do not have permissions to execute %s", *ipmiDcmiCmd)
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/jobstats/base/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type BatchJob struct {
Jobid string `json:"jobid"`
Jobuuid string `json:"id"`
Partition string `json:"partition"`
QoS string `jsob:"qos"`
QoS string `json:"qos"`
Account string `json:"account"`
Grp string `json:"group"`
Gid string `json:"gid"`
Expand All @@ -29,6 +29,9 @@ type BatchJob struct {
Submit string `json:"submit"`
Start string `json:"start"`
End string `json:"end"`
SubmitTS string `json:"submitts"`
StartTS string `json:"startts"`
EndTS string `json:"endts"`
Elapsed string `json:"elapsed"`
Exitcode string `json:"exitcode"`
State string `json:"state"`
Expand Down
6 changes: 3 additions & 3 deletions pkg/jobstats/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"time"
)

func queryExporter(address string) error {
resp, err := http.Get(fmt.Sprintf("http://%s/api/jobs", address))
func queryServer(address string) error {
resp, err := http.Get(fmt.Sprintf("http://%s/api/health", address))
if err != nil {
return err
}
Expand Down Expand Up @@ -42,7 +42,7 @@ func TestBatchStatsServerMain(t *testing.T) {

// Query exporter
for i := 0; i < 10; i++ {
if err := queryExporter("localhost:9020"); err == nil {
if err := queryServer("localhost:9020"); err == nil {
break
}
time.Sleep(500 * time.Millisecond)
Expand Down
20 changes: 15 additions & 5 deletions pkg/jobstats/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func (j *jobStatsDB) getJobStats(startTime, endTime time.Time) error {

// Set pragma statements
j.setPragmaDirectives()
stmt, err := j.prepareInsertStatement(tx, len(jobs))
stmt, err := j.prepareInsertStatement(tx)
if err != nil {
level.Error(j.logger).Log("msg", "Failed to prepare insert job statement in DB", "err", err)
return err
Expand Down Expand Up @@ -401,21 +401,26 @@ func (j *jobStatsDB) vacuumDB() error {

// Delete old entries in DB
func (j *jobStatsDB) deleteOldJobs(tx *sql.Tx) error {
deleteSQLCmd := fmt.Sprintf(
deleteRowQuery := fmt.Sprintf(
"DELETE FROM %s WHERE Start <= date('now', '-%d day')",
j.jobstatDBTable,
int(j.retentionPeriod.Hours()/24),
)
_, err := tx.Exec(deleteSQLCmd)
_, err := tx.Exec(deleteRowQuery)
if err != nil {
level.Error(j.logger).Log("msg", "Failed to delete old jobs", "err", err)
return err
}

// Get changes
var rowsDeleted int
_ = tx.QueryRow("SELECT changes();").Scan(&rowsDeleted)
level.Debug(j.logger).Log("msg", "Queried for changes after deletion", "rowsDeleted", rowsDeleted)
return nil
}

// Make and return prepare statement for inserting entries
func (j *jobStatsDB) prepareInsertStatement(tx *sql.Tx, numJobs int) (*sql.Stmt, error) {
func (j *jobStatsDB) prepareInsertStatement(tx *sql.Tx) (*sql.Stmt, error) {
placeHolderString := fmt.Sprintf(
"(%s)",
strings.Join(strings.Split(strings.Repeat("?", len(base.BatchJobFieldNames)), ""), ","),
Expand All @@ -436,13 +441,15 @@ func (j *jobStatsDB) prepareInsertStatement(tx *sql.Tx, numJobs int) (*sql.Stmt,

// Insert job stat into DB
func (j *jobStatsDB) insertJobs(statement *sql.Stmt, jobStats []base.BatchJob) {
var err error
for _, jobStat := range jobStats {
// Empty job
if jobStat == (base.BatchJob{}) {
continue
}

// level.Debug(j.logger).Log("msg", "Inserting job", "jobid", jobStat.Jobid)
_, err := statement.Exec(
_, err = statement.Exec(
jobStat.Jobid,
jobStat.Jobuuid,
jobStat.Partition,
Expand All @@ -455,6 +462,9 @@ func (j *jobStatsDB) insertJobs(statement *sql.Stmt, jobStats []base.BatchJob) {
jobStat.Submit,
jobStat.Start,
jobStat.End,
jobStat.SubmitTS,
jobStat.StartTS,
jobStat.EndTS,
jobStat.Elapsed,
jobStat.Exitcode,
jobStat.State,
Expand Down
4 changes: 2 additions & 2 deletions pkg/jobstats/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func prepareMockConfig(tmpDir string) *Config {

func populateDBWithMockData(db *sql.DB, j *jobStatsDB) {
tx, _ := db.Begin()
stmt, _ := j.prepareInsertStatement(tx, len(mockJobs))
stmt, _ := j.prepareInsertStatement(tx)
j.insertJobs(stmt, mockJobs)
tx.Commit()
}
Expand Down Expand Up @@ -261,7 +261,7 @@ func TestJobStatsDeleteOldJobs(t *testing.T) {
},
}
tx, _ := j.db.Begin()
stmt, err := j.prepareInsertStatement(tx, len(jobs))
stmt, err := j.prepareInsertStatement(tx)
if err != nil {
t.Errorf("Failed to prepare SQL statements")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"status":"success","errorType":"","error":"","warnings":null,"data":[{"jobid":"1481508","id":"baee651d-df44-af2c-fa09-50f5523b5e19","partition":"part1","qos":"qos1","account":"acc2","group":"grp2","gid":"1002","user":"usr2","uid":"1002","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:08:17","exitcode":"0:0","state":"CANCELLED by 1002","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr2"},{"jobid":"1481510","id":"b76ecf69-4d2f-076b-047d-2bcc8503b4cb","partition":"part1","qos":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:00:17","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr3"},{"jobid":"147973","id":"d8b28c2c-2011-d572-de94-8ec4facb4a2a","partition":"part1","qos":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T14:37:02+0100","start":"2023-02-21T14:37:07+0100","end":"2023-02-21T15:26:29+0100","submitts":"1676986622000","startts":"1676986627000","endts":"1676989589000","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr3"},{"jobid":"14508","id":"88a46e84-ffce-52ea-37e9-47b39d9ccfb3","partition":"part1","qos":"qos1","account":"acc4","group":"grp4","gid":"1004","user":"usr4","uid":"1004","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:08:17","exitcode":"0:0","state":"CANCELLED by 1004","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr4"},{"jobid":"1479763","id":"a04088e8-2699-2a9b-bc27-30282679ebb3","partition":"part1","qos":"qos1","account":"acc1","group":"grp8","gid":"1008","user":"usr8","uid":"1008","submit":"2023-02-21T14:37:02+0100","start":"2023-02-21T14:37:07+0100","end":"2023-02-21T15:26:29+0100","submitts":"1676986622000","startts":"1676986627000","endts":"1676989589000","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1008","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr8"},{"jobid":"11508","id":"d4956307-af17-870a-2fa0-38375105d257","partition":"part1","qos":"qos1","account":"acc1","group":"grp15","gid":"1015","user":"usr15","uid":"1015","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:08:17","exitcode":"0:0","state":"CANCELLED by 1015","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr15"},{"jobid":"81510","id":"938832b4-33b4-3303-b002-8150f737de7e","partition":"part1","qos":"qos1","account":"acc1","group":"grp15","gid":"1015","user":"usr15","uid":"1015","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:00:17","exitcode":"0:0","state":"CANCELLED by 1015","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr23"}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"status":"success","errorType":"","error":"","warnings":null,"data":[{"jobid":"147973","id":"d8b28c2c-2011-d572-de94-8ec4facb4a2a","partition":"part1","QoS":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T14:37:02","start":"2023-02-21T14:37:07","end":"2023-02-21T15:26:29","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr3"},{"jobid":"1481510","id":"b76ecf69-4d2f-076b-047d-2bcc8503b4cb","partition":"part1","QoS":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T15:48:20","start":"2023-02-21T15:49:06","end":"2023-02-21T15:57:23","elapsed":"00:00:17","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr3"}]}
{"status":"success","errorType":"","error":"","warnings":null,"data":[{"jobid":"147973","id":"d8b28c2c-2011-d572-de94-8ec4facb4a2a","partition":"part1","qos":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T14:37:02+0100","start":"2023-02-21T14:37:07+0100","end":"2023-02-21T15:26:29+0100","submitts":"1676986622000","startts":"1676986627000","endts":"1676989589000","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr3"},{"jobid":"1481510","id":"b76ecf69-4d2f-076b-047d-2bcc8503b4cb","partition":"part1","qos":"qos1","account":"acc3","group":"grp3","gid":"1003","user":"usr3","uid":"1003","submit":"2023-02-21T15:48:20+0100","start":"2023-02-21T15:49:06+0100","end":"2023-02-21T15:57:23+0100","submitts":"1676990900000","startts":"1676990946000","endts":"1676991443000","elapsed":"00:00:17","exitcode":"0:0","state":"CANCELLED by 1003","nnodes":"2","ncpus":"16","nodelist":"compute-[0-2]","nodelistexp":"compute-0|compute-1|compute-2","jobname":"test_script2","workdir":"/home/usr3"}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"status":"success","errorType":"","error":"","warnings":null,"data":[{"jobid":"1479763","id":"a04088e8-2699-2a9b-bc27-30282679ebb3","partition":"part1","QoS":"qos1","account":"acc1","group":"grp8","gid":"1008","user":"usr8","uid":"1008","submit":"2023-02-21T14:37:02","start":"2023-02-21T14:37:07","end":"2023-02-21T15:26:29","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1008","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr8"}]}
{"status":"success","errorType":"","error":"","warnings":null,"data":[{"jobid":"1479763","id":"a04088e8-2699-2a9b-bc27-30282679ebb3","partition":"part1","qos":"qos1","account":"acc1","group":"grp8","gid":"1008","user":"usr8","uid":"1008","submit":"2023-02-21T14:37:02+0100","start":"2023-02-21T14:37:07+0100","end":"2023-02-21T15:26:29+0100","submitts":"1676986622000","startts":"1676986627000","endts":"1676989589000","elapsed":"00:49:22","exitcode":"0:0","state":"CANCELLED by 1008","nnodes":"1","ncpus":"8","nodelist":"compute-0","nodelistexp":"compute-0","jobname":"test_script1","workdir":"/home/usr8"}]}
Loading