diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 8400ec765..d3ba1cc3d 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -806,6 +806,57 @@ voluntary_ctxt_switches: 4742839 nonvoluntary_ctxt_switches: 1727500 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/stat +Lines: 1 +27079 (pthread_load) S 1 27079 1 34816 27079 4194304 113 0 1 0 58125 15 0 0 20 0 5 0 4289574 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 17 2 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task/27079 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/task/27079/stat +Lines: 1 +27079 (pthread_load) S 1 27079 1 34816 27079 4194304 97 0 1 0 0 0 0 0 20 0 5 0 4289574 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 17 2 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task/27080 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/task/27080/stat +Lines: 1 +27080 (pthread_load) R 1 27079 1 34816 27079 4194368 7 0 0 0 34136 3 0 0 20 0 5 0 4289575 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task/27081 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/task/27081/stat +Lines: 1 +27081 (pthread_load) S 1 27079 1 34816 27079 1077936192 3 0 0 0 13680 4 0 0 20 0 5 0 4289575 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 -1 5 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task/27082 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/task/27082/stat +Lines: 1 +27082 (pthread_load) S 1 27079 1 34816 27079 1077936192 3 0 0 0 6859 3 0 0 20 0 5 0 4289575 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 -1 1 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/27079/task/27083 +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/27079/task/27083/stat +Lines: 1 +27083 (pthread_load) S 1 27079 1 34816 27079 1077936192 3 0 0 0 3452 4 0 0 20 0 5 0 4289575 36282368 138 18446744073709551615 94441498279936 94441498282741 140736878632528 0 0 0 0 0 0 0 0 0 -1 4 0 0 0 0 0 94441498291504 94441498292248 94441510707200 140736878639434 140736878639460 140736878639460 140736878641129 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/proc/584 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/thread.go b/thread.go new file mode 100644 index 000000000..f08bfc769 --- /dev/null +++ b/thread.go @@ -0,0 +1,79 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "fmt" + "os" + "strconv" + + fsi "github.com/prometheus/procfs/internal/fs" +) + +// Provide access to /proc/PID/task/TID files, for thread specific values. Since +// such files have the same structure as /proc/PID/ ones, the data structures +// and the parsers for the latter may be reused. + +// AllThreads returns a list of all currently available threads under /proc/PID. +func AllThreads(pid int) (Procs, error) { + fs, err := NewFS(DefaultMountPoint) + if err != nil { + return Procs{}, err + } + return fs.AllThreads(pid) +} + +// AllThreads returns a list of all currently available threads for PID. +func (fs FS) AllThreads(pid int) (Procs, error) { + taskPath := fs.proc.Path(strconv.Itoa(pid), "task") + d, err := os.Open(taskPath) + if err != nil { + return Procs{}, err + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return Procs{}, fmt.Errorf("could not read %q: %w", d.Name(), err) + } + + t := Procs{} + for _, n := range names { + tid, err := strconv.ParseInt(n, 10, 64) + if err != nil { + continue + } + t = append(t, Proc{PID: int(tid), fs: fsi.FS(taskPath)}) + } + + return t, nil +} + +// Thread returns a process for a given PID, TID. +func (fs FS) Thread(pid, tid int) (Proc, error) { + taskPath := fs.proc.Path(strconv.Itoa(pid), "task") + if _, err := os.Stat(taskPath); err != nil { + return Proc{}, err + } + return Proc{PID: tid, fs: fsi.FS(taskPath)}, nil +} + +// Thread returns a process for a given TID of Proc. +func (proc Proc) Thread(tid int) (Proc, error) { + tfs := fsi.FS(proc.path("task")) + if _, err := os.Stat(tfs.Path(strconv.Itoa(tid))); err != nil { + return Proc{}, err + } + return Proc{PID: tid, fs: tfs}, nil +} diff --git a/thread_test.go b/thread_test.go new file mode 100644 index 000000000..503be4f2f --- /dev/null +++ b/thread_test.go @@ -0,0 +1,114 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "reflect" + "sort" + "strconv" + "testing" +) + +var ( + testPID = int(27079) + testTIDS = [...]int{27079, 27080, 27081, 27082, 27083} +) + +func TestAllThreads(t *testing.T) { + fixFS := getProcFixtures(t) + threads, err := fixFS.AllThreads(testPID) + if err != nil { + t.Fatal(err) + } + sort.Sort(threads) + for i, tid := range testTIDS { + if wantTID, haveTID := tid, threads[i].PID; wantTID != haveTID { + t.Errorf("want TID %d, have %d", wantTID, haveTID) + } + wantFS := fixFS.proc.Path(strconv.Itoa(testPID), "task") + haveFS := string(threads[i].fs) + if wantFS != haveFS { + t.Errorf("want fs %q, have %q", wantFS, haveFS) + } + } +} + +func TestThreadStat(t *testing.T) { + // Pull process and thread stats. + proc, err := getProcFixtures(t).Proc(testPID) + if err != nil { + t.Fatal(err) + } + procStat, err := proc.Stat() + if err != nil { + t.Fatal(err) + } + + threads, err := getProcFixtures(t).AllThreads(testPID) + if err != nil { + t.Fatal(err) + } + sort.Sort(threads) + threadStats := make([]*ProcStat, len(threads)) + for i, thread := range threads { + threadStat, err := thread.Stat() + if err != nil { + t.Fatal(err) + } + threadStats[i] = &threadStat + } + + // The following fields should be shared between the process and its thread: + procStatValue := reflect.ValueOf(procStat) + sharedFields := [...]string{ + "PPID", + "PGRP", + "Session", + "TTY", + "TPGID", + "VSize", + "RSS", + } + + for i, thread := range threads { + threadStatValue := reflect.ValueOf(threadStats[i]).Elem() + for _, f := range sharedFields { + if want, have := procStatValue.FieldByName(f), threadStatValue.FieldByName(f); want.Interface() != have.Interface() { + t.Errorf("TID %d, want %s %#v, have %#v", thread.PID, f, want, have) + } + } + } + + // Thread specific fields: + for i, thread := range threads { + if want, have := thread.PID, threadStats[i].PID; want != have { + t.Errorf("TID %d, want PID %d, have %d", thread.PID, want, have) + } + } + + // Finally exemplify the relationship between process and constituent + // threads CPU times: each the former ~ the sum of the corresponding + // latter. Require -v flag. + totalUTime, totalSTime := uint(0), uint(0) + for _, thread := range threads { + threadStat, err := thread.Stat() + if err != nil { + t.Fatal(err) + } + t.Logf("TID %d, UTime %d, STime %d", thread.PID, threadStat.UTime, threadStat.STime) + totalUTime += threadStat.UTime + totalSTime += threadStat.STime + } + t.Logf("PID %d, UTime %d, STime %d, total threads UTime %d, STime %d", proc.PID, procStat.UTime, procStat.STime, totalUTime, totalSTime) +}