github.com/haraldrudell/parl@v0.4.176/mains/process-start-other.go (about)

     1  //go:build !darwin
     2  
     3  /*
     4  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     5  ISC License
     6  */
     7  
     8  package mains
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"os"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/haraldrudell/parl/perrors"
    20  )
    21  
    22  const (
    23  	// format absolute path into /proc directory
    24  	filePrintf           = "/proc/%d/stat"
    25  	rightParenthesisByte = byte(')')
    26  	// the number of clock ticks per second
    27  	//	- getconf CLK_TCK
    28  	//	- 100
    29  	//	- man sysconf, clock ticks
    30  	//	- The  number  of  clock  ticks  per second
    31  	clockTicksPerSecond = 100
    32  	clockTickNs         = time.Second / clockTicksPerSecond
    33  	// starttime index, 1-based, in /proc/[pid]/stat space-separated file.
    34  	//	- man proc, /proc/[pid]/stat, entry (22) 1-based
    35  	//	- starttime  %llu
    36  	//	- The time the process started after system boot
    37  	//	- the value is expressed in clock  ticks  (divide  by sysconf(_SC_CLK_TCK)).
    38  	startTimeIndex = 21 - 2
    39  	// path of /proc/uptime
    40  	//	- This file contains two numbers
    41  	//	- man proc
    42  	//	- values in seconds: the uptime of the system
    43  	//	- cat /proc/uptime
    44  	// 	- 5422217.66 21636302.50
    45  	procUptime      = "/proc/uptime"
    46  	procUptimeField = 0
    47  )
    48  
    49  // ProcessStartTime returns start time for process pid with second resolution
    50  //   - panic on troubles
    51  func ProcessStartTime() (createTime time.Time) {
    52  	var err error
    53  	if createTime, err = ProcessStart(os.Getpid()); err != nil {
    54  		panic(err)
    55  	}
    56  	return
    57  }
    58  
    59  // ProcessStart returns start time for process pid with second resolution
    60  func ProcessStart(pid int) (processStart time.Time, err error) {
    61  
    62  	// get system uptime resolution 10 ms
    63  	var uptime time.Duration
    64  	if uptime, err = systemUptime(); err != nil {
    65  		return
    66  	}
    67  
    68  	// get process start time in clock ticks
    69  	var startTicks int64
    70  	if startTicks, err = processStartTicks(pid); err != nil {
    71  		return
    72  	}
    73  
    74  	// get process duration ± 20 ms
    75  	var processDuration = uptime - time.Duration(startTicks)*clockTickNs
    76  
    77  	// calculate process start time
    78  	processStart = time.Now().Add(-processDuration).Truncate(time.Second)
    79  
    80  	return
    81  }
    82  
    83  // SystemUpSince returns boot time second resolution
    84  func SystemUpSince() (upSince time.Time, err error) {
    85  	var uptime time.Duration
    86  	if uptime, err = systemUptime(); err != nil {
    87  		return
    88  	}
    89  	upSince = time.Now().Add(-uptime).Truncate(time.Second)
    90  	return
    91  }
    92  
    93  // systemUptime returns host up time resolution 10 ms
    94  func systemUptime() (uptime time.Duration, err error) {
    95  
    96  	// read /proc/uptime
    97  	var data []byte
    98  	if data, err = ioutil.ReadFile(procUptime); perrors.Is(&err, "ioutil.ReadFile %w", err) {
    99  		return
   100  	}
   101  
   102  	// extract numeric string
   103  	var fields = bytes.Fields(data)
   104  	if procUptimeField >= len(fields) {
   105  		err = perrors.ErrorfPF("content too short: %q", procUptime)
   106  		return
   107  	}
   108  	// remove the period
   109  	var uptimeS = strings.Replace(string(fields[procUptimeField]), ".", "", 1)
   110  
   111  	// convert from unit 10 ms to time.Duration
   112  	var u64 uint64
   113  	if u64, err = strconv.ParseUint(uptimeS, 10, 64); perrors.Is(&err, "ParseUint %w", err) {
   114  		return
   115  	}
   116  	uptime = time.Duration(u64) * 10 * time.Millisecond
   117  
   118  	return
   119  }
   120  
   121  // processStartTicks returns the time in system clock ticks since boot when the process was started
   122  func processStartTicks(pid int) (clockTicks int64, err error) {
   123  
   124  	// read /proc/n/stat
   125  	var data []byte
   126  	var filename = fmt.Sprintf(filePrintf, pid)
   127  	if data, err = ioutil.ReadFile(filename); perrors.Is(&err, "ioutil.ReadFile %w", err) {
   128  		return
   129  	}
   130  
   131  	// get tick count
   132  	var index int
   133  	if index = bytes.LastIndexByte(data, rightParenthesisByte); index == -1 {
   134  		err = perrors.ErrorfPF("no %q found in %q", rightParenthesisByte, filename)
   135  		return
   136  	}
   137  	var fields = bytes.Fields(data[index+1:])
   138  	if startTimeIndex >= len(fields) {
   139  		err = perrors.ErrorfPF("content too short: %q", filename)
   140  		return
   141  	}
   142  	clockTicks, err = strconv.ParseInt(string(fields[startTimeIndex]), 10, 64)
   143  
   144  	return
   145  }