Search for processes by start time
Python
We used the psutil [4] library for an attempt with Python. psutil provides a large number of functions and delivers bundles of information about processes (e.g., a process's PID, run time, owner, and memory requirements). As was revealed when we read the library's source code, psutil also ultimately accesses information from the /proc
filesystem.
The script in Listing 8 includes two functions. The first function, getListOfProcesses()
, scans the process list and returns a list of the individual processes. Each list entry contains four data fields: PID, program or call name, time of creation, and username. The second function, calculateTimestamp()
, calculates the time, which serves as a limit filter to filter out irrelevant processes later.
Listing 8
Python Variant
01 import psutil 02 import datetime 03 04 # Define global variables 05 listOfProcessNames = [] 06 07 def getListOfProcesses(createTime=10): 08 # Deliver list of running processes as dictionary 09 # PID, program name, creation time and process owner 10 11 # Define upper limit of interval 12 intervalTime = calculateTimestamp(createTime) 13 14 for proc in psutil.process_iter(): 15 pInfoDict = proc.as_dict(attrs=['pid', 'name', 'create_time', 'username']) 16 # Create time values from 17 currentCreateTime = pInfoDict["create_time"] 18 19 # Is process outside of time interval? 20 if currentCreateTime < intervalTime: 21 listOfProcessNames.append(pInfoDict) 22 return 23 24 def calculateTimestamp(daysValue=10): 25 # Compute time interval (default: ten days) 26 27 # Determine current timestamp 28 currentTimestamp = datetime.datetime.now() 29 30 # Compute time interval 31 dateRange = datetime.timedelta(days=daysValue) 32 targetTimestamp = currentTimestamp - dateRange 33 unixTime = targetTimestamp.timestamp() 34 35 # Return as UNIX timestamp 36 return unixTime 37 38 getListOfProcesses() 39 40 # Sort list by create time and PID 41 listOfProcessNames = sorted( 42 listOfProcessNames, 43 key = lambda i: (i['create_time'], i['pid']) 44 ) 45 46 # Process list values from 47 for currentProcess in listOfProcessNames: 48 # Extract process details 49 username = currentProcess["username"] 50 pid = currentProcess["pid"] 51 creationTime = currentProcess["create_time"] 52 creationTimeString = datetime.datetime.fromtimestamp(creationTime).strftime('%d.%m.%Y %H:%M:%S') 53 processName = currentProcess["name"] 54 55 # Output process information 56 print( 57 "User name: %s, PID: %8i, Program: %s" % (username, pid, processName), 58 ", created on", 59 creationTimeString 60 )
The main program first calls the getListOfProcesses()
function and then sorts the list of processes by their creation times and PIDs. This results in the output shown in Listing 9, which contains all the processes identified with their owners, PIDs, program names, and creation times. If you want to search for all Bash processes in the results, grep
can help you filter the output.
Listing 9
Python Script Output
$ python3 list-processes2.py | grep bash User name: frank, PID: 3428, Program: bash , created on 08.03.2020 21:49:09 User name: frank, PID: 10438, Program: bash , created on 16.03.2020 21:12:18 User name: frank, PID: 5919, Program: bash , created on 25.03.2020 12:13:29
Perl
As with Python, you would not want to program everything yourself in Perl, although this would certainly be possible by browsing the /proc
filesystem. Instead, you should first look at the Comprehensive Perl Archive Network (CPAN) [5], since there may already be a Perl module for accessing the process table. And, lo and behold, there is: Proc::ProcessTable [6].
In Perl, you first create an instance of Proc::ProcessTable and retrieve a reference to a data structure with the entire process table in it. You could certainly iterate through the table with loops. However, if you like functional programming (à la Lisp), you can use a Schwartzian transform [7]. This works almost like a pipe at the command line or in shell scripts, only backwards: The data source is at the end (Listing 10).
Listing 10
Perl Variant
01 #!/usr/bin/perl 02 03 # Boiler plate to avoid bugs 04 use strict; 05 use warnings; 06 07 # Use modern "say" instead of "print" 08 use 5.010; 09 10 # Minimal parameter parsing: If a number is passed as parameter 11 # output this number of processes, otherwise 10. 12 my $max = @ARGV ? $ARGV[0] : 10; 13 14 # Use the Proc::ProcessTable module 15 use Proc::ProcessTable; 16 17 # Create a disposable object and save the process table it generated 18 my $table = Proc::ProcessTable->new->table; 19 20 # Schwartzian transform of table 21 my @result = 22 # Sort the list, first by start time and then by PID 23 sort { ($a->[0] <=> $b->[0]) or ($a->[1] <=> $b->[1]) } 24 # Use only the start time, PID and UID of the process 25 map { [ $_->start, $_->pid, $_->uid ] } 26 # The array following the dereferenced scalar is the data source 27 @$table; 28 29 # Output the results by classical iteration 30 foreach my $p (@result[0..$max-1]) { 31 say sprintf('PID: %6i | Start: %s | UID: %s', 32 $p->[1], ''.localtime($p->[0]), $p->[2]); 33 }
Listing 11 shows a more compact Perl variant without comments, boiler plate, or command-line parsing (it outputs all processes, sorted) – all of this in just one Schwartzian transform.
Listing 11
Compact Perl Variant
01 #!/usr/bin/perl 02 03 use Proc::ProcessTable; 04 05 print 06 map { sprintf("PID: %6i | Start: %s | UID: %s\n", 07 $_->[1], ''.localtime($_->[0]), $_->[2]) } 08 sort { ($a->[0] <=> $b->[0]) or ($a->[1] <=> $b->[1]) } 09 map { [ $_->start, $_->pid, $_->uid ] } 10 @{ Proc::ProcessTable->new->table };
Go
The Go programming language has recently gained in popularity among developers [8], which is why we offer an appropriate solution in Go. Our solution is based on two modules, go-ps [9] and go-sysconf [10], which provide functions for reading processes and system information. Further information from the /proc
filesystem, which neither of the two modules currently support, is used.
Our Go script has about 150 lines; we have split it into several listings for clarity. The first step (Listing 12) contains the package definition and imports the required modules. The following steps, which are part of the main
function, include the variable definitions and parameters (Listing 13), time frame and boot time (Listing 14), CLK_TCK
(Listing 15), and routines for retrieving (Listing 16) and evaluating the process list (Listing 17).
Listing 12
Import Required Modules
01 package main 02 03 import ( 04 // import standard modules 05 "bufio" 06 "fmt" 07 "io/ioutil" 08 "log" 09 "os" 10 "strconv" 11 "strings" 12 "time" 13 // import additional modules 14 ps "github.com/mitchellh/go-ps" 15 "github.com/tklauser/go-sysconf" 16 ) 17 18 func main () { 19 ... 20 }
Listing 13
Variables
01 var bootTime string 02 var userId string 03 04 // Suppress date and time output in log. 05 log.SetFlags(0) 06 07 // Set default value of ten days 08 timeLimit64 := int64(10) 09 10 // Read command line parameters 11 args := os.Args[1:] 12 if len(args) > 0 { 13 // Convert string to number 14 timeLimitArg, err := strconv.ParseInt(args[0], 10, 64) 15 if err != nil { 16 log.Fatalf("Error: %v\n", err) 17 } 18 timeLimit64 = timeLimitArg 19 } 20 log.Printf("Set time limit to %d days\n", timeLimit64)
Listing 13 covers the definition of the required variables and evaluation of the command-line parameters. If nothing else is specified, the program sets the default value to 10
.
With the data already determined, the code sets the time frame and consequently defines the relevant processes. It then determines the boot time: the number of seconds since January 1, 1970 (Listing 14). To evaluate time stamps correctly, the clock ticks are determined with the sysconf module as shown in Listing 15.
Listing 14
Time Frame
01 // Compute time frame 02 // Current time - days * 24h * 60min * 60s 03 timeBoundary := time.Now().Unix() - timeLimit64*24*60*60 04 05 // Determine boot time from /proc/stat in seconds since 1.1.1970 06 // available in /proc/stat in the line starting with btime 07 fileHandle, err := os.Open("/proc/stat") 08 if err != nil { 09 log.Fatalf("Error calling os.Open(): %v\n", err) 10 } 11 defer fileHandle.Close() 12 13 scanner := bufio.NewScanner(fileHandle) 14 for scanner.Scan() { 15 currentLine := scanner.Text() 16 if strings.HasPrefix(currentLine, "btime") { 17 dataFields := strings.Fields(currentLine) 18 bootTime = dataFields[1] 19 break 20 } 21 } 22 23 // Convert string to numeric value 24 bootTime64, err := strconv.ParseInt(bootTime, 10, 64) 25 if err != nil { 26 log.Fatalf("Error: %v\n", err) 27 }
Listing 15
CLK_TCK
01 // Reference value stored for CLK_TCK 02 // Values per second 03 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) 04 if err != nil { 05 log.Fatalf("Error calling Sysconf") 06 }
In the next step, the user scans the processes and creates a list (Listing 16). A for
loop then browses this list and analyzes each process with regard to the user and the process run time. If a process is within the period under consideration, information to that effect is displayed (Listing 17).
Listing 16
Process List
01 // Get process list 02 processList, err := ps.Processes() 03 if err != nil { 04 log.Fatalf("Error in call to ps.Processes()") 05 }
Listing 17
Analyze Processes
01 // Iterate through process list 02 for _, process := range processList { 03 // Read process list 04 // Extract PID and executed program 05 pid := process.Pid() 06 exec := process.Executable() 07 08 // Read user ID from /proc/<pid>/status 09 // Available in column 2 of the line starting with Uid 10 // Go counts with an index of 0; therefore data field 1 11 statusPath := fmt.Sprintf("/proc/%d/status", pid) 12 fileHandle, err := os.Open(statusPath) 13 if err != nil { 14 log.Fatalf("Error calling os.Open(): %v\n", err) 15 } 16 defer fileHandle.Close() 17 18 scanner = bufio.NewScanner(fileHandle) 19 for scanner.Scan() { 20 currentLine := scanner.Text() 21 if strings.HasPrefix(currentLine, "Uid") { 22 uidFields := strings.Fields(currentLine) 23 userId = uidFields[1] 24 break 25 } 26 } 27 28 // Read process status from /proc/<pid>/stat 29 procPath := fmt.Sprintf("/proc/%d/stat", pid) 30 dataBytes, err := ioutil.ReadFile(procPath) 31 if err != nil { 32 log.Fatalf("Error: %v\n", err) 33 } 34 // Break line down into data fields 35 dataFields := strings.Fields(string(dataBytes)) 36 37 // Compute process start time 38 // Read number of clock ticks since the system booted 39 // Available in column 22 of /proc/<pid>/stat 40 // Go counts with an index of 0; therefore data field 21 41 executionTime := dataFields[21] 42 executionTime64, err := strconv.ParseInt(executionTime, 10, 64) 43 if err != nil { 44 log.Fatalf("Error: %v\n", err) 45 } 46 47 // Divide the number of clock ticks passed by the stored kernel value 48 // Gives you seconds since booting 49 // And add the boot time 50 executionTime64 = (executionTime64 / clkTck) + bootTime64 51 52 // Check time frame 53 if executionTime64 < timeBoundary { 54 // Compute the start time as a date 55 startDate := time.Unix(executionTime64, 0) 56 57 // Output the information for the process 58 fmt.Printf("User ID: %s, Process ID: %8d, Program name: %s, Started on %s\n", userId, pid, exec, startDate) 59 } 60}
Listing 18 shows the output, where the script was called with the parameter 1
and then processed via a pipe with grep
to find all Bash instances called in the process list.
Listing 18
Go Script Output
$ ./list-processes 1 | grep bash User ID: 1000, Process ID: 604, Program name: bash, Started on 2020-04-14 11:31:51 +0200 CEST User ID: 1000, Process ID: 5318, Program name: bash, Started on 2020-04-16 16:15:21 +0200 CEST User ID: 1000, Process ID: 6984, Program name: bash, Started on 2020-04-16 19:04:52 +0200 CEST User ID: 1000, Process ID: 6998, Program name: bash, Started on 2020-04-16 19:08:57 +0200 CEST
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.
-
Rhino Linux Announces Latest "Quick Update"
If you prefer your Linux distribution to be of the rolling type, Rhino Linux delivers a beautiful and reliable experience.