Skip to content

Commit 1c4833d

Browse files
authored
Add qstat -g d (#30)
* EH: Watch qacct file; improved types * BF: Fixing go.mod file for simulator * Add qstat -g d
1 parent 3a2b094 commit 1c4833d

File tree

5 files changed

+176
-12
lines changed

5 files changed

+176
-12
lines changed

pkg/qacct/v9.0/file_test.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,35 @@ var _ = Describe("File", func() {
2525

2626
It("returns a channel that emits JobDetail objects for 10 jobs", func() {
2727

28+
jobDetailsChan, err := qacct.WatchFile(context.Background(),
29+
qacct.GetDefaultQacctFile(), 0)
30+
Expect(err).NotTo(HaveOccurred())
31+
Expect(jobDetailsChan).NotTo(BeNil())
32+
2833
qs, err := qsub.NewCommandLineQSub(qsub.CommandLineQSubConfig{})
2934
Expect(err).NotTo(HaveOccurred())
3035

3136
jobIDs := make([]int, 10)
3237
for i := 0; i < 10; i++ {
33-
jobID, _, err := qs.Submit(context.Background(), qsub.JobOptions{
34-
Command: "echo",
35-
CommandArgs: []string{fmt.Sprintf("job %d", i+1)},
36-
Binary: qsub.ToPtr(true),
37-
})
38+
jobID, _, err := qs.Submit(context.Background(),
39+
qsub.JobOptions{
40+
Command: "/bin/bash",
41+
CommandArgs: []string{"-c", fmt.Sprintf("echo job %d; sleep 0", i+1)},
42+
Binary: qsub.ToPtr(true),
43+
})
3844
Expect(err).NotTo(HaveOccurred())
3945
log.Printf("jobID: %d", jobID)
4046
jobIDs[i] = int(jobID)
4147
}
4248

43-
jobDetailsChan, err := qacct.WatchFile(context.Background(),
44-
qacct.GetDefaultQacctFile(), 0)
45-
Expect(err).NotTo(HaveOccurred())
46-
Expect(jobDetailsChan).NotTo(BeNil())
47-
4849
receivedJobs := make(map[int]bool)
4950
Eventually(func() bool {
5051
select {
5152
case jd := <-jobDetailsChan:
5253
log.Printf("job: %+v", jd.JobNumber)
5354
// check if jobID is in the jobIDs list
5455
if slices.Contains(jobIDs, int(jd.JobNumber)) {
55-
Expect(jd.SubmitCommandLine).To(ContainSubstring("echo 'job"))
56+
Expect(jd.SubmitCommandLine).To(ContainSubstring("bash"))
5657
Expect(jd.JobUsage.Usage.Memory).To(BeNumerically(">=", 0))
5758
receivedJobs[int(jd.JobNumber)] = true
5859
}

pkg/qstat/v9.0/parser.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,3 +830,111 @@ func ParseClusterQueueSummary(out string) ([]ClusterQueueSummary, error) {
830830

831831
return summaries, nil
832832
}
833+
834+
/*
835+
qstat -g d
836+
job-ID prior name user state submit/start at queue slots ja-task-ID
837+
-----------------------------------------------------------------------------------------------------------------
838+
839+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 1
840+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 3
841+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 5
842+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 7
843+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 25
844+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 27
845+
36 0.60500 sleep root qw 2025-02-10 16:52:21 2
846+
37 0.60500 sleep root qw 2025-02-10 16:52:35 2
847+
38 0.60500 sleep root qw 2025-02-10 16:52:49 2
848+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 1
849+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 2
850+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 3
851+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 8
852+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 9
853+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 10
854+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 29
855+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 31
856+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 99
857+
34 0.50500 sleep root qw 2025-02-10 16:51:51 1
858+
*/
859+
func ParseJobArrayTask(out string) ([]JobArrayTask, error) {
860+
lines := strings.Split(out, "\n")
861+
862+
jobArrayTasks := make([]JobArrayTask, 0, len(lines)-3)
863+
864+
for _, line := range lines[2:] {
865+
fields := strings.Fields(line)
866+
if len(fields) < 8 {
867+
continue
868+
}
869+
jobID, err := strconv.Atoi(fields[0])
870+
if err != nil {
871+
return nil, fmt.Errorf("failed to parse jobID: %v", err)
872+
}
873+
priority, err := strconv.ParseFloat(fields[1], 64)
874+
if err != nil {
875+
return nil, fmt.Errorf("failed to parse priority: %v", err)
876+
}
877+
name := fields[2]
878+
user := fields[3]
879+
state := fields[4]
880+
timeString := fields[5] + " " + fields[6]
881+
jobTime, err := time.Parse("2006-01-02 15:04:05", timeString)
882+
if err != nil {
883+
return nil, fmt.Errorf("failed to parse submit time: %v", err)
884+
}
885+
var submitTime time.Time
886+
var startTime time.Time
887+
if strings.Contains(state, "qw") {
888+
startTime = jobTime
889+
} else {
890+
submitTime = jobTime
891+
}
892+
893+
// if fields[7] is not a number, it is the queue name
894+
var slots int
895+
var taskID int
896+
var queue string
897+
898+
// when waiting there is no queue name
899+
if slotsInt, err := strconv.Atoi(fields[7]); err != nil {
900+
queue = fields[7]
901+
if len(fields) > 8 {
902+
slots, _ = strconv.Atoi(fields[8])
903+
}
904+
if len(fields) > 9 {
905+
taskID, _ = strconv.Atoi(fields[9])
906+
}
907+
} else {
908+
slots = slotsInt
909+
// waiting jobs
910+
if len(fields) > 8 {
911+
slots, _ = strconv.Atoi(fields[8])
912+
}
913+
if len(fields) > 9 {
914+
taskID, err = strconv.Atoi(fields[9])
915+
if err != nil {
916+
// a single job and parallel job has no taskID
917+
taskID = 0
918+
}
919+
}
920+
}
921+
922+
jobInfo := JobInfo{
923+
JobID: jobID,
924+
Priority: priority,
925+
Name: name,
926+
User: user,
927+
State: state,
928+
SubmitTime: submitTime,
929+
StartTime: startTime,
930+
Queue: queue,
931+
Slots: slots,
932+
JaTaskIDs: []int64{int64(taskID)},
933+
}
934+
jobArrayTasks = append(jobArrayTasks, JobArrayTask{
935+
JobInfo: jobInfo,
936+
})
937+
938+
}
939+
return jobArrayTasks, nil
940+
}

pkg/qstat/v9.0/parser_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,4 +452,47 @@ test.q 0.08 0 0 2 2 0 0
452452

453453
})
454454

455+
Describe("JobArrayTask", func() {
456+
457+
It("should parse the output of qstat -g d", func() {
458+
input := `job-ID prior name user state submit/start at queue slots ja-task-ID
459+
-----------------------------------------------------------------------------------------------------------------
460+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 1
461+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 3
462+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 5
463+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 23
464+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 25
465+
33 0.50500 sleep root r 2025-02-10 16:47:18 all.q@master 1 27
466+
36 0.60500 sleep root qw 2025-02-10 16:52:21 2
467+
37 0.60500 sleep root qw 2025-02-10 16:52:35 2
468+
38 0.60500 sleep root qw 2025-02-10 16:52:49 2
469+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 1
470+
39 0.60500 sleep root qw 2025-02-10 16:53:23 2 2
471+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 95
472+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 97
473+
33 0.50500 sleep root qw 2025-02-10 16:47:18 1 99
474+
34 0.50500 sleep root qw 2025-02-10 16:51:51 1
475+
`
476+
jobArrayTasks, err := qstat.ParseJobArrayTask(input)
477+
Expect(err).NotTo(HaveOccurred())
478+
Expect(len(jobArrayTasks)).To(Equal(15))
479+
480+
Expect(jobArrayTasks).To(ContainElement(qstat.JobArrayTask{
481+
JobInfo: qstat.JobInfo{
482+
JobID: 33,
483+
Priority: 0.505,
484+
Name: "sleep",
485+
User: "root",
486+
State: "r",
487+
SubmitTime: time.Date(2025, 2, 10, 16, 47, 18, 0, time.UTC),
488+
StartTime: time.Time{},
489+
Queue: "all.q@master",
490+
Slots: 1,
491+
JaTaskIDs: []int64{1},
492+
},
493+
}))
494+
})
495+
496+
})
497+
455498
})

pkg/qstat/v9.0/qstat.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ type QStat interface {
4545
ShowFullOutputWithResources(resourceAttributes string) ([]JobInfo, error)
4646
// qstat -g c
4747
DisplayClusterQueueSummary() ([]ClusterQueueSummary, error)
48+
// qstat -g d shows all job array tasks individually
4849
DisplayAllJobArrayTasks() ([]JobArrayTask, error)
50+
// qstat -g p shows all parallel job tasks individually
4951
DisplayAllParallelJobTasks() ([]ParallelJobTask, error)
5052
// qstat -help
5153
ShowHelp() (string, error)

pkg/qstat/v9.0/qstat_impl.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,18 @@ func (q *QStatImpl) DisplayClusterQueueSummary() ([]ClusterQueueSummary, error)
178178
return ParseClusterQueueSummary(out)
179179
}
180180

181+
// DisplayAllJobArrayTasks is equivalent to "qstat -g d"
181182
func (q *QStatImpl) DisplayAllJobArrayTasks() ([]JobArrayTask, error) {
182-
return nil, fmt.Errorf("not implemented")
183+
out, err := q.NativeSpecification([]string{"-g", "d"})
184+
if err != nil {
185+
return nil, fmt.Errorf("failed to get output of qstat: %w", err)
186+
}
187+
jobArrayTasks, err := ParseJobArrayTask(out)
188+
if err != nil {
189+
return nil, fmt.Errorf("failed to parse job array tasks: %w", err)
190+
}
191+
192+
return jobArrayTasks, nil
183193
}
184194

185195
func (q *QStatImpl) DisplayAllParallelJobTasks() ([]ParallelJobTask, error) {

0 commit comments

Comments
 (0)