├── tests ├── .gitignore ├── test_posix.nim ├── test_windows.nim ├── test_linux.nim └── quick_test.nim ├── scripts ├── .gitignore ├── free.nim ├── meminfo.nim ├── disk_usage.nim └── ifconfig.nim ├── .gitignore ├── src ├── psutil │ ├── arch │ │ ├── osx │ │ │ ├── types.nim │ │ │ ├── process_info.nim │ │ │ └── socket.nim │ │ └── bsd_osx.c │ ├── common.nim │ ├── psutil_posix.nim │ ├── psutil_windows.nim │ └── psutil_linux.nim └── psutil.nim ├── doc ├── linux │ ├── functions │ │ ├── uptime.md │ │ ├── try_pid_users.md │ │ ├── pid_parents.md │ │ ├── process_exists.md │ │ ├── pid_cmdline.md │ │ ├── pids_cmdline.md │ │ ├── pids.md │ │ ├── pid_parent.md │ │ ├── pids_with_names.md │ │ ├── pid_users.md │ │ ├── boot_time.md │ │ ├── pid_paths.md │ │ ├── pid_names.md │ │ ├── try_pid_paths.md │ │ ├── try_pid_path.md │ │ ├── pid_path.md │ │ ├── pid_user.md │ │ ├── cpu_times.md │ │ ├── pid_name.md │ │ ├── per_cpu_times.md │ │ ├── try_pid_user.md │ │ ├── cpu_stats.md │ │ ├── pid_exists.md │ │ ├── users.md │ │ ├── get_partitions.md │ │ ├── cpu_count_physical.md │ │ ├── cpu_count_logical.md │ │ ├── net_connections.md │ │ ├── per_nic_net_io_counters.md │ │ ├── disk_partions.md │ │ ├── swap_memory.md │ │ ├── per_disk_io_counters.md │ │ └── virtual_memory.md │ └── types │ │ ├── Users.md │ │ ├── DiskIO.md │ │ ├── DiskUsage.md │ │ ├── NetIO.md │ │ ├── SwapMemory.md │ │ ├── Connection.md │ │ ├── VirtualMemory.md │ │ └── CPUTimes.md └── windows │ ├── functions │ ├── uptime.md │ ├── cpu_count_logical.md │ ├── pid_exists.md │ ├── pids_with_names.md │ ├── pid_parents.md │ ├── pid_names.md │ ├── boot_time.md │ ├── pid_paths.md │ ├── try_pid_users.md │ ├── pid_users.md │ ├── try_pid_paths.md │ ├── disk_usage.md │ ├── pid_parent.md │ ├── swap_memory.md │ ├── process_exists.md │ ├── virtual_memory.md │ ├── pid_path.md │ ├── users.md │ ├── pids.md │ ├── try_pid_path.md │ ├── cpu_count_physical.md │ ├── psutil_get_drive_type.md │ ├── toUnixTime.md │ ├── getnativearch.md │ ├── pid_name.md │ ├── pid_arch.md │ ├── cpu_times.md │ ├── pid_domain.md │ ├── pid_user.md │ ├── try_pid_user.md │ ├── per_cpu_times.md │ ├── pid_domain_user.md │ └── disk_partitions.md │ └── types │ ├── Users.md │ ├── DiskUsage.md │ ├── SwapMemory.md │ ├── VirtualMemory.md │ └── CPUTimes.md ├── psutil.nimble ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── LICENSE └── README.md /tests/.gitignore: -------------------------------------------------------------------------------- 1 | quick_test 2 | test_posix 3 | test_linux 4 | *.exe -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | disk_usage 2 | free 3 | meminfo 4 | ifconfig 5 | *.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !**/ 3 | !*.* 4 | nimcache/ 5 | nimsuggest.log 6 | temp.c 7 | temp.nim 8 | -------------------------------------------------------------------------------- /src/psutil/arch/osx/types.nim: -------------------------------------------------------------------------------- 1 | type gid_t* = distinct uint32 2 | 3 | 4 | type uid_t* = distinct uint32 5 | 6 | 7 | type off_t* {.importc: "off_t", header: "".} = object # https://stackoverflow.com/questions/9073667/where-to-find-the-complete-definition-of-off-t-type 8 | -------------------------------------------------------------------------------- /doc/linux/functions/uptime.md: -------------------------------------------------------------------------------- 1 | # uptime 2 | 3 | uptime returns the system uptime expressed in seconds, Integer type. 4 | 5 | # The function 6 | ```nim 7 | proc uptime*(): int = 8 | ## Return the system uptime expressed in seconds, Integer type. 9 | epochTime().int - boot_time() 10 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/uptime.md: -------------------------------------------------------------------------------- 1 | # uptime 2 | 3 | uptime the system uptime expressed in seconds, Integer type. 4 | 5 | # The function 6 | ```nim 7 | proc uptime*(): int = 8 | ## Return the system uptime expressed in seconds, Integer type. 9 | int(GetTickCount64().float / 1000.float) 10 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/cpu_count_logical.md: -------------------------------------------------------------------------------- 1 | # cpu_count_logical 2 | 3 | This function runs GetActiveProcesserCount with the argument ALL_PROCESSOR_GROUPS. 4 | Which returns the number of active processors in the system. 5 | 6 | 7 | # The function 8 | ```nim 9 | proc cpu_count_logical*(): int = 10 | return cast[int](GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)) 11 | 12 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_exists.md: -------------------------------------------------------------------------------- 1 | # pid_exists 2 | 3 | pid_exists returns a boolean which is true if the given pid exists, else false 4 | 5 | # The function 6 | ```nim 7 | proc pid_exists*(pid: int): bool = 8 | 9 | var p = OpenProcess(SYNCHRONIZE, FALSE, cast[DWORD](pid)); 10 | var r = WaitForSingleObject(p, 0); 11 | CloseHandle(p); 12 | return r == WAIT_TIMEOUT 13 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pids_with_names.md: -------------------------------------------------------------------------------- 1 | # pids_with_names 2 | 3 | This function with return a tuple of a sequence of integers (pids), and a sequence of strings (names) 4 | 5 | # The function 6 | 7 | ```nim 8 | proc pids_with_names*(): (seq[int], seq[string]) = 9 | 10 | var pids_seq = pids() 11 | var names_seq = process_names(pids_seq) 12 | 13 | return (pids_seq, names_seq) 14 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_parents.md: -------------------------------------------------------------------------------- 1 | # pid_parents 2 | 3 | pid_parents returns a sequence of integers, each being the parent pid of each corresponding 4 | pid given as a sequence of integers 5 | 6 | # The function 7 | ```nim 8 | proc pid_parents*(pids: int): seq[int] = 9 | 10 | var ret: string[int] 11 | for pid in pids: 12 | ret.add(pid_parent(pid)) 13 | 14 | return ret 15 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/try_pid_users.md: -------------------------------------------------------------------------------- 1 | # try_pid_users 2 | 3 | try_pid_users will attempt to get the user running the corresponding pid in the specified sequence, but instead of raising an exception if failed. It'll instead return "" for the pid that failed. 4 | 5 | # The function 6 | ```nim 7 | proc try_pid_users*(pids: seq[int]): seq[string] = 8 | 9 | for pid in pids: 10 | result.add(try_pid_user(pid)) 11 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_parents.md: -------------------------------------------------------------------------------- 1 | # pid_parents 2 | 3 | pid_parents will attempt to get the parent pids of the corresponding pids specified, and return them as a sequence of integers. 4 | 5 | # The function 6 | ```nim 7 | proc pid_parents*(pids: seq[int]): seq[int] = 8 | 9 | ## Function for getting the parent pids of the corresponding pids specified. 10 | for pid in pids: 11 | result.add(pid_parent(pid)) 12 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/process_exists.md: -------------------------------------------------------------------------------- 1 | # process_exists 2 | 3 | process_exists returns a boolean which is true if the given processName (like "firefox") 4 | exists else false 5 | 6 | # The function 7 | ```nim 8 | proc process_exists*(processName: string): bool = 9 | 10 | let names_seq = process_names(pids()) 11 | for name in names_seq: 12 | if processName == name: 13 | return true 14 | 15 | return false 16 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_names.md: -------------------------------------------------------------------------------- 1 | # pids_names 2 | 3 | This function returns a sequence of strings (names) given a sequence of integers (pids) 4 | 5 | # The function 6 | 7 | '''nim 8 | proc pid_names*(pids: seq[int]): seq[string] = 9 | 10 | #[ 11 | function for getting the process name of pid 12 | ]# 13 | var ret: seq[string] 14 | for pid in pids: 15 | ret.add(pid_name(pid)) 16 | 17 | return ret 18 | 19 | ''' -------------------------------------------------------------------------------- /doc/linux/functions/pid_cmdline.md: -------------------------------------------------------------------------------- 1 | # pid_cmdline 2 | 3 | pid_cmdline gets the command that the pid is ran as. Reads /proc/pid/cmdline 4 | 5 | Note: linux only 6 | 7 | # The function 8 | ```nim 9 | proc pid_cmdline*(pid: int): string = 10 | 11 | ## Function for getting the cmdline of a pid 12 | ## this gets path of command and arguments 13 | 14 | let cmdline_path = PROCFS_PATH / $pid / "cmdline" 15 | return cmdline_path.readFile() 16 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pids_cmdline.md: -------------------------------------------------------------------------------- 1 | # pids_cmdline 2 | 3 | pids_cmdline returns a sequence of strings. Each string being the command the pid is run as. 4 | 5 | # The function 6 | ```nim 7 | proc pids_cmdline*(pids: seq[int]): seq[string] = 8 | 9 | ## function for getting the cmdline of a sequence of pids 10 | ## this gets path of command and arguments 11 | var ret: seq[string] 12 | for pid in pids: 13 | ret.add(pid_cmdline(pid)) 14 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pids.md: -------------------------------------------------------------------------------- 1 | # pids 2 | 3 | pids returns a sequence of integers (PIDs) currently running on the system 4 | 5 | Note: isnumber is defined in psutil_linux.nim 6 | 7 | # The function 8 | ```nim 9 | proc pids*(): seq[int] = 10 | ## Returns a list of PIDs currently running on the system. 11 | let all_files = toSeq( walkDir(PROCFS_PATH, relative=true) ) 12 | 13 | return mapIt( filterIt( all_files, isnumber(it.path) ), parseInt( it.path ) ) 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /doc/windows/functions/boot_time.md: -------------------------------------------------------------------------------- 1 | # boot_time 2 | 3 | boot_time returns the system boot time expressed in seconds since the epoch 4 | 5 | # The function 6 | ```nim 7 | proc boot_time*(): float = 8 | ## Return the system boot time expressed in seconds since the epoch 9 | var fileTime : FILETIME 10 | GetSystemTimeAsFileTime(addr fileTime) 11 | 12 | let pt = toUnixTime(fileTime) 13 | let uptime = int(GetTickCount64()) / 1000 14 | 15 | return pt - uptime 16 | ``` -------------------------------------------------------------------------------- /psutil.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.6.1" 3 | author = "Juan Carlos, John Scillieri, Nim community" 4 | description = "Psutil is a cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network)" 5 | license = "MIT" 6 | srcDir = "src" 7 | skipDirs = @["tests"] 8 | 9 | # Dependencies 10 | requires "nim >= 1.2.6" 11 | requires "unpack" 12 | 13 | when defined(windows): 14 | requires "winim" 15 | -------------------------------------------------------------------------------- /doc/linux/functions/pid_parent.md: -------------------------------------------------------------------------------- 1 | # pid_parent 2 | 3 | pid_parent will get the parent pid of the specified pid. 4 | 5 | # The function 6 | ```nim 7 | proc pid_parent*(pid: int): int = 8 | 9 | ## Function for getting the parent pid of the specified pid 10 | var p_path = PROCFS_PATH / $pid / "status" 11 | var data = p_path.readFile() 12 | for line in data.split("\n"): 13 | if "PPid:" in line: 14 | result = parseInt(line.split("PPid:")[^1].strip()) 15 | 16 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_paths.md: -------------------------------------------------------------------------------- 1 | # pid_paths 2 | 3 | pid_paths returns a sequence of strings. Each string being the path to the corresponding pid in the sequence of integers (pids) given. 4 | 5 | Note: this will raise an exception if an exception occurs for any of the pids specified. 6 | 7 | # The function 8 | ```nim 9 | proc pid_paths*(pids: seq[int]): seq[string] = 10 | 11 | var ret: seq[string] 12 | for pid in pids: 13 | ret.add(pid_path(pid)) 14 | 15 | return ret 16 | ``` -------------------------------------------------------------------------------- /doc/linux/types/Users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | User type holds information regarding users on the system 4 | 5 | # The type 6 | ```nim 7 | type User* = object 8 | name*: string 9 | terminal*: string 10 | host*: string 11 | started*: float 12 | ``` 13 | 14 | # Information 15 | 16 | name : The username 17 | terminal : unused 18 | host : address of session (like for RDP, ssh, etc.) 19 | started : the login time 20 | 21 | # Functions 22 | 23 | - [users](../functions/users.md) -------------------------------------------------------------------------------- /doc/windows/types/Users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | User type holds information regarding users on the system 4 | 5 | # The type 6 | ```nim 7 | type User* = object 8 | name*: string 9 | terminal*: string 10 | host*: string 11 | started*: float 12 | ``` 13 | 14 | # Information 15 | 16 | name : The username 17 | terminal : unused 18 | host : address of session (like for RDP, ssh, etc.) 19 | started : the login time 20 | 21 | # Functions 22 | 23 | - [users](../functions/users.md) -------------------------------------------------------------------------------- /doc/windows/functions/try_pid_users.md: -------------------------------------------------------------------------------- 1 | # try_pid_users 2 | 3 | try_pid_users will return a sequence of strings (users), corresponding to the pids specified, but 4 | instead of raising an exception if an exception occurs for any of the pids. The result will be "" for the corresponding pid. 5 | 6 | # The function 7 | ```nim 8 | proc try_pid_users*(pids: seq[int]): seq[string] = 9 | 10 | ## Function for getting users of specified pids 11 | for pid in pids: 12 | result.add(try_pid_user(pid)) 13 | ``` -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://liberapay.com/juancarlospaco/donate # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /doc/linux/functions/pids_with_names.md: -------------------------------------------------------------------------------- 1 | # pids_with_names 2 | 3 | pids_with_names returns a tuple of sequences. The first sequence being the pids (integers), and the second being the process names of each of the corresponding pids. 4 | 5 | # The function 6 | ```nim 7 | proc pids_with_names*(): (seq[int], seq[string]) = 8 | 9 | ## Function for returning tuple of pids and names 10 | 11 | var pids_seq = pids() 12 | var names_seq = process_names(pids_seq) 13 | 14 | return (pids_seq, names_seq) 15 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_users.md: -------------------------------------------------------------------------------- 1 | # pid_users 2 | 3 | pid_users will attempt to return the user running the corresponding pid, in the specified sequence, and return the users as a sequence of strings. 4 | 5 | Note: If an exception occurs for any of the pids it will be raised. If you don't want this 6 | look at try_pid_users 7 | 8 | - [try_pid_users](./try_pid_users.md) 9 | 10 | 11 | # The function 12 | ```nim 13 | proc pid_users*(pids: seq[int]): seq[string] = 14 | 15 | for pid in pids: 16 | result.add(pid_user(pid)) 17 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_users.md: -------------------------------------------------------------------------------- 1 | # pid_users 2 | 3 | pid_users will return a sequence of strings (users), corresponding to the pids specified 4 | 5 | Note: an exception will be raised if an error occurs for any of the pids specified. 6 | If you don't want this, then look at the try_pid_users function. 7 | 8 | - [try_pid_users]("./try_pid_users.md") 9 | - 10 | # The function 11 | ```nim 12 | proc pid_users*(pids: seq[int]): seq[string] = 13 | 14 | ## Function for getting a sequence of users 15 | for pid in pids: 16 | result.add(pid_user(pid)) 17 | ``` -------------------------------------------------------------------------------- /tests/test_posix.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import osproc 3 | import sequtils 4 | import strutils 5 | 6 | import ../src/psutil 7 | 8 | 9 | # test "test users": 10 | # let output = execProcess("who") 11 | # let lines = output.strip().splitlines() 12 | 13 | # let users = mapIt( lines, it.splitWhitespace()[0] ) 14 | # check( len(users) == len(psutil.users()) ) 15 | 16 | # let terminals = mapIt( lines, it.splitWhitespace()[1] ) 17 | # for u in psutil.users(): 18 | # check( u.name in users ) 19 | # check( u.terminal in terminals ) 20 | -------------------------------------------------------------------------------- /doc/linux/types/DiskIO.md: -------------------------------------------------------------------------------- 1 | # DiskIO 2 | 3 | # The type 4 | ```nim 5 | type DiskIO* = object of RootObj 6 | read_count*: int 7 | write_count*: int 8 | read_bytes*: int 9 | write_bytes*: int 10 | read_time*: int 11 | write_time*: int 12 | when defined(linux): 13 | read_merged_count*: int 14 | write_merged_count*: int 15 | busy_time*: int 16 | ``` 17 | 18 | # Information 19 | 20 | read_count : amount read 21 | write_count : amount written 22 | read_bytes : amount of bytes read 23 | write_bytes : amount of byte written 24 | -------------------------------------------------------------------------------- /doc/linux/functions/boot_time.md: -------------------------------------------------------------------------------- 1 | # boot_time 2 | 3 | boot_time returns the system boot time expressed in seconds since the epoch (time of boot), Integer type 4 | 5 | # The function 6 | ```nim 7 | proc boot_time*(): int = 8 | ## Return the system boot time expressed in seconds since the epoch, Integer type. 9 | let stat_path = PROCFS_PATH / "stat" 10 | for line in stat_path.lines: 11 | if line.strip.startswith("btime"): 12 | return line.strip.split()[1].parseInt() 13 | 14 | raise newException(OSError, "line 'btime' not found in $1" % stat_path) 15 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_paths.md: -------------------------------------------------------------------------------- 1 | # pid_paths 2 | 3 | pid_paths will attempt to get the elf path for each of the specified pids and return the paths as a sequence of strings. 4 | 5 | Note: This will raise an exception if this fails for any of the specified pids. If you don't want an error to be raised then look at try_pid_paths 6 | 7 | - [try_pid_paths](./try_pid_paths.md) 8 | 9 | # The function 10 | ```nim 11 | proc pid_paths*(pids: seq[int]): seq[string] = 12 | 13 | ## Function for getting the elf paths of the specified pids 14 | for pid in pids: 15 | result.add(pid_path(pid)) 16 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/try_pid_paths.md: -------------------------------------------------------------------------------- 1 | # try_pid_paths 2 | 3 | try_pid_paths returns a sequence of strings. Each string being the path to the corresponding pid in the sequence of integers (pids) given, but instead of raising an exception if an exception occurs for any of the pids, the result will instead be "" for the corresponding pid in the specified sequence. 4 | 5 | # The function 6 | ```nim 7 | proc try_pid_paths*(pid: seq[int]): seq[string] = 8 | 9 | ## Function to return the paths of the exes (sequence of strings) of the running pids. 10 | for pid in pids: 11 | result.add(try_pid_path(pid)) 12 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_names.md: -------------------------------------------------------------------------------- 1 | # pid_names 2 | 3 | 4 | pid_names returns a sequence of strings (process names) for every given pid 5 | in the sequence pids. 6 | 7 | * Note: please read carefully. pid_names is not pid_name 8 | 9 | # The function 10 | ```nim 11 | proc pid_names*(pids: seq[int]): seq[string] = 12 | ## Function for getting the process name of a sequence of pids 13 | ## not to be mmixed with pids_cmdline. This only gets the 14 | ## program name. Not the path and arguments. 15 | var ret: seq[string] 16 | for pid in pids: 17 | ret.add(pid_name(pid)) 18 | 19 | return ret 20 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/try_pid_paths.md: -------------------------------------------------------------------------------- 1 | # try_pid_paths 2 | 3 | try_pid_paths attempts to get the elf paths of the specified pids, and return them as a sequence of 4 | strings. 5 | 6 | Note: If an error occurs for any of the pids. The result of the corresponding pid will be "" 7 | 8 | # The function 9 | ```nim 10 | proc try_pid_paths*(pids: seq[int]): seq[string] = 11 | 12 | ## Function for getting the paths of the specified pids 13 | ## Note: If an error occurs for any of the pids. The result for the corresponding 14 | ## pid will be "" 15 | for pid in pids: 16 | result.add(try_pid_path(pid)) 17 | ``` -------------------------------------------------------------------------------- /doc/linux/types/DiskUsage.md: -------------------------------------------------------------------------------- 1 | # DiskUsage 2 | 3 | DiskUsage is a type that holds information regarding disk usage on specified path 4 | 5 | # The type 6 | 7 | ```nim 8 | type DiskUsage* = object of RootObj 9 | total*: int 10 | used*: int 11 | free*:int 12 | percent*: float 13 | ``` 14 | 15 | # Information 16 | 17 | total : the amount of disk on specified path 18 | used : the amount of used disk space on specified path 19 | free : the amount of free disk space on the specified path 20 | percent : percent of disk used on specified path 21 | 22 | # Functions 23 | 24 | - [disk_usage](../functions/disk_usage.md) -------------------------------------------------------------------------------- /doc/windows/types/DiskUsage.md: -------------------------------------------------------------------------------- 1 | # DiskUsage 2 | 3 | DiskUsage is a type that holds information regarding disk usage on specified path 4 | 5 | # The type 6 | 7 | ```nim 8 | type DiskUsage* = object of RootObj 9 | total*: int 10 | used*: int 11 | free*:int 12 | percent*: float 13 | ``` 14 | 15 | # Information 16 | 17 | total : the amount of disk on specified path 18 | used : the amount of used disk space on specified path 19 | free : the amount of free disk space on the specified path 20 | percent : percent of disk used on specified path 21 | 22 | # Functions 23 | 24 | - [disk_usage](../functions/disk_usage.md) -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macOS-latest 14 | - windows-latest 15 | nim: 16 | - 'stable' 17 | # - devel 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Setup nim 21 | uses: jiro4989/setup-nim-action@v1 22 | with: 23 | nim-version: ${{ matrix.nim }} 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | - run: nimble test -Y 26 | -------------------------------------------------------------------------------- /doc/linux/functions/try_pid_path.md: -------------------------------------------------------------------------------- 1 | # try_pid_path 2 | 3 | try_pid_path will attempt to get the path of the elf of the running pid, but instead of raising 4 | an exception upon error. It will instead return "". 5 | 6 | # The function 7 | ```nim 8 | proc try_pid_path*(pid: int): string = 9 | 10 | ## Function for getting the path of the elf of the running pid 11 | ## Note: Instead of raising an error. It will instread return "" 12 | var p_path: cstring = PROCFS_PATH / $pid / "exe" 13 | var buf: array[PATH_MAX, char] 14 | if readlink(p_path, buf, PATH_MAX) == -1: 15 | result = "" 16 | else: 17 | result = buf.join("").strip() 18 | ``` -------------------------------------------------------------------------------- /tests/test_windows.nim: -------------------------------------------------------------------------------- 1 | import ../src/psutil 2 | 3 | when defined(windows): 4 | import ../src/psutil/psutil_windows 5 | import winim 6 | echo "arch",getnativearch() 7 | let ids = pids() 8 | echo ids 9 | # echo pids_with_names() 10 | # for pid in ids: 11 | # echo pid_path(pid) 12 | let pid = GetCurrentProcess() 13 | echo pid_arch(pid) 14 | echo pid_path(pid) 15 | # echo pid_user(pid) 16 | # echo pid_domain(pid) 17 | # echo pid_domain_user(pid) 18 | 19 | var du = disk_usage("C:\\") 20 | echo du.total 21 | # echo_proc pid_paths(ids) 22 | echo pid_parent(pid) 23 | echo psutil_get_drive_type("C:\\") 24 | echo process_exists("test_windows.exe") 25 | -------------------------------------------------------------------------------- /doc/linux/types/NetIO.md: -------------------------------------------------------------------------------- 1 | # NetIO 2 | 3 | # The type 4 | ```nim 5 | type NetIO* = object of RootObj 6 | bytes_sent*: int 7 | bytes_recv*: int 8 | packets_sent*: int 9 | packets_recv*: int 10 | errin*: int 11 | errout*: int 12 | dropin*: int 13 | dropout*: int 14 | ``` 15 | 16 | # Information 17 | bytes_sent : total amount of bytes sent 18 | bytes_recv : total amount of bytes received 19 | packets_sent : total amount of packets sent 20 | packets_recv : total amount of packets received 21 | errin : total amount of errors in 22 | errout : total amount of errors out 23 | dropin : total amount or dropped packets coming in -------------------------------------------------------------------------------- /doc/linux/functions/pid_path.md: -------------------------------------------------------------------------------- 1 | # pid_path 2 | 3 | pid_path will attempt to get the link path of the specified pid. 4 | 5 | Note: This will raise an exception if this fails. If you don't want this then look at 6 | try_pid_path 7 | 8 | - [try_pid_path](./try_pid_path.md) 9 | 10 | # The function 11 | ```nim 12 | proc pid_path*(pid: int): string = 13 | 14 | ## Function for getting the path of the elf of the running pid 15 | var p_path: cstring = PROCFS_PATH / $pid / "exe" 16 | var buf: array[PATH_MAX, char] 17 | if readlink(p_path, buf, PATH_MAX) == -1: 18 | raise newException(IOError, "Cannot read /proc/$1/exe | $2" % [$pid, $strerror(errno)]) 19 | result = buf.join("").strip() 20 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_user.md: -------------------------------------------------------------------------------- 1 | # pid_user 2 | 3 | pid_user will attempt to get the 4 | 5 | # The function 6 | ```nim 7 | proc pid_user*(pid: int): string = 8 | 9 | ## Function for getting the username running the specified pid 10 | var p_path = PROCFS_PATH / $pid / "status" 11 | var uid = -1 12 | var data = p_path.readFile() 13 | for line in data.split("\n"): 14 | if "Uid:" in line: 15 | uid = parseInt(line.split("Uid:")[1].strip().split("\t")[0]) 16 | debugEcho fmt"Uid: {uid}" 17 | 18 | var pws = getpwuid(cast[Uid](uid)) 19 | if pws.isNil: 20 | raise newException(OSError, "UID $1 not found" % [$uid]) 21 | result = $pws.pw_name 22 | ``` -------------------------------------------------------------------------------- /doc/linux/types/SwapMemory.md: -------------------------------------------------------------------------------- 1 | # SwapMemory 2 | 3 | SwapMemory is a type that hold information regarding swap memory on the system 4 | 5 | # The type 6 | 7 | ```nim 8 | type SwapMemory* = object of RootObj 9 | total*: int 10 | used*: int 11 | free*: int 12 | percent*: float 13 | sin*: int 14 | sout*: int 15 | ``` 16 | 17 | # Information 18 | 19 | total : total amount of swap memory on the system 20 | used : amount of swap memory used on the system 21 | free : amount of swap memory free on the system 22 | percent : percent of swap memory being used on the system 23 | sin : swap in 24 | sout : swap out 25 | 26 | # Functions 27 | 28 | - [swap_memory](../functions/swap_memory.md) -------------------------------------------------------------------------------- /doc/windows/types/SwapMemory.md: -------------------------------------------------------------------------------- 1 | # SwapMemory 2 | 3 | SwapMemory is a type that hold information regarding swap memory on the system 4 | 5 | # The type 6 | 7 | ```nim 8 | type SwapMemory* = object of RootObj 9 | total*: int 10 | used*: int 11 | free*: int 12 | percent*: float 13 | sin*: int 14 | sout*: int 15 | ``` 16 | 17 | # Information 18 | 19 | total : total amount of swap memory on the system 20 | used : amount of swap memory used on the system 21 | free : amount of swap memory free on the system 22 | percent : percent of swap memory being used on the system 23 | sin : unused 24 | sout : unused 25 | 26 | # Functions 27 | 28 | - [swap_memory](../functions/swap_memory.md) -------------------------------------------------------------------------------- /doc/linux/functions/cpu_times.md: -------------------------------------------------------------------------------- 1 | # cpu_times 2 | 3 | cpu_times retrieves system CPU timing information. 4 | On a multiprocessor system, the values returned are the 5 | sum of the designated times across all processors. 6 | 7 | 8 | # The function 9 | ```nim 10 | proc cpu_times*(): CPUTimes = 11 | # Return a tuple representing the following system-wide CPU times: 12 | # (user, nice, system, idle, iowait, irq, softirq [steal, [guest, 13 | # [guest_nice]]]) 14 | # Last 3 fields may not be available on all Linux kernel versions. 15 | for line in lines( PROCFS_PATH / "stat" ): 16 | result = parse_cpu_time_line(line) 17 | break 18 | ``` 19 | 20 | # The type 21 | 22 | - [CPUTimes](../types/CPUTimes.md) -------------------------------------------------------------------------------- /doc/windows/functions/disk_usage.md: -------------------------------------------------------------------------------- 1 | # disk_usage 2 | 3 | This function returns a type DiskUsage. 4 | 5 | # The function 6 | 7 | ```nim 8 | proc disk_usage*( path: string ): DiskUsage = 9 | ## Return disk usage associated with path. 10 | var total, free: ULARGE_INTEGER 11 | 12 | let ret_code = GetDiskFreeSpaceExW( path, nil, addr total, addr free ) 13 | if ret_code != 1: raiseError() 14 | 15 | let used = total.QuadPart - free.QuadPart 16 | let percent = usage_percent( used.int, total.QuadPart.int, places=1 ) 17 | return DiskUsage( total:total.QuadPart.int, used:used.int, 18 | free:free.QuadPart.int, percent:percent ) 19 | ``` 20 | 21 | # The type 22 | 23 | - [DiskUsage](../types/DiskUsage.md) -------------------------------------------------------------------------------- /doc/windows/functions/pid_parent.md: -------------------------------------------------------------------------------- 1 | # pid_parent 2 | 3 | pid_parent returns the pid of the process that called the given pid. The processes that called the 4 | given pid is the parent. 5 | 6 | # The function 7 | ```nim 8 | proc pid_parent*(pid: int): int = 9 | 10 | var h: HANDLE 11 | var pe: PROCESSENTRY32 12 | var ppid = cast[DWORD](0) 13 | pe.dwSize = cast[DWORD](sizeof(PROCESSENTRY32)) 14 | h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 15 | if Process32First(h, pe.addr): 16 | while Process32Next(h, pe.addr): 17 | if cast[int](pe.th32ParentProcessID) == pid: 18 | ppid = pe.th32ParentProcessID 19 | break 20 | 21 | CloseHandle(h); 22 | return cast[int](ppid) 23 | 24 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_name.md: -------------------------------------------------------------------------------- 1 | # pid_name 2 | 3 | pid_name returns the process name of a pid. 4 | 5 | This is not the same as pid_name!!! 6 | This function only gets the name of the process. 7 | Not the path of the program, and arguments. 8 | This reads and sanitizes /proc/pid/comm 9 | 10 | # The function 11 | ```nim 12 | proc pid_name*(pid: int): string = 13 | ## Function for getting the process name of a pid 14 | ## not to be mixed with pid_cmdline. This only gets the 15 | ## program name. Not the path and arguments 16 | let p_path = PROCFS_PATH / $pid / "status" 17 | var data = p_path.readFile() 18 | for line in data.split("\n"): 19 | if "Name:" in line: 20 | var name = line.split("Name:")[1].strip() 21 | result = name 22 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/per_cpu_times.md: -------------------------------------------------------------------------------- 1 | # per_cpu_times 2 | 3 | per_cpu_times returns system per-CPU times as a sequence of CPUTimes. CPUTimes holds information 4 | regarding cpu times on the system 5 | 6 | # The function 7 | ```nim 8 | proc per_cpu_times*(): seq[CPUTimes] = 9 | ## Return a list of tuples representing the CPU times for every 10 | ## CPU available on the system. 11 | result = newSeq[CPUTimes]() 12 | for line in lines( PROCFS_PATH / "stat" ): 13 | if not line.startswith("cpu"): continue 14 | let entry = parse_cpu_time_line( line ) 15 | result.add( entry ) 16 | # get rid of the first line which refers to system wide CPU stats 17 | result.delete(0) 18 | return result 19 | ``` 20 | 21 | # The type 22 | 23 | - [CPUTimes](../functions/per_cpu_times.md) -------------------------------------------------------------------------------- /doc/linux/types/Connection.md: -------------------------------------------------------------------------------- 1 | # Connection 2 | 3 | # The type 4 | ```nim 5 | type Connection* = object of RootObj 6 | fd*: int 7 | family*: int 8 | `type`*: int 9 | laddr*: string 10 | lport*: Port 11 | raddr*: string 12 | rport*: Port 13 | status*: string 14 | pid*: int 15 | ``` 16 | 17 | # Information 18 | 19 | fd : the file descriptor the connection is on 20 | family : the family type of connection (i.e. AF_INET and so on) 21 | 'type' : socket type 22 | laddr : interface address on the machine for the connection 23 | lport : port open on the machine for the connection 24 | raddr : remote address the connection is made with 25 | rport : port on the remote machine for connection 26 | status : status of connection 27 | pid : pid of process making connection -------------------------------------------------------------------------------- /doc/linux/functions/try_pid_user.md: -------------------------------------------------------------------------------- 1 | # try_pid_user 2 | 3 | try_pid_user will attempt to get the user running the specified pid, but instead of raising an 4 | exception if it fails. It'll simply return "" 5 | 6 | # The function 7 | ```nim 8 | proc try_pid_user*(pid: int): string = 9 | 10 | ## Function for getting the username running the specified pid 11 | var p_path = PROCFS_PATH / $pid / "status" 12 | var uid = -1 13 | var data = p_path.readFile() 14 | for line in data.split("\n"): 15 | if "Uid:" in line: 16 | uid = parseInt(line.split("Uid:")[1].strip().split("\t")[0]) 17 | debugEcho fmt"Uid: {uid}" 18 | 19 | var pws = getpwuid(cast[Uid](uid)) 20 | if pws.isNil: 21 | result = "" 22 | else: 23 | result = $pws.pw_name 24 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/swap_memory.md: -------------------------------------------------------------------------------- 1 | # swap_memory 2 | 3 | This function will return a SwapMemory type. Which holds information regarding system swap memory 4 | 5 | # The function 6 | 7 | ```nim 8 | proc swap_memory*(): SwapMemory = 9 | ## Swap system memory as a (total, used, free, sin, sout) 10 | var memInfo: MEMORYSTATUSEX 11 | memInfo.dwLength = sizeof(MEMORYSTATUSEX).DWORD 12 | 13 | if GlobalMemoryStatusEx( addr memInfo ) == 0: 14 | raiseError() 15 | 16 | let total = memInfo.ullTotalPageFile.int 17 | let free = memInfo.ullAvailPageFile.int 18 | let used = total - free 19 | let percent = usage_percent(used, total, places=1) 20 | return SwapMemory(total:total, used:used, free:free, percent:percent, sin:0, sout:0) 21 | ``` 22 | 23 | # The type 24 | 25 | - [SwapMemory](../types/SwapMemory.md) -------------------------------------------------------------------------------- /doc/linux/functions/cpu_stats.md: -------------------------------------------------------------------------------- 1 | # cpu_stats 2 | 3 | cpu_stats will get the cpu status of system, and return the ctx switches, interrupts, soft_interrupts, and syscalls. 4 | 5 | * Note: linux only 6 | 7 | # The function 8 | ```nim 9 | proc cpu_stats*(): tuple[ctx_switches, interrupts, soft_interrupts, syscalls: int] = 10 | var ctx_switches, interrupts, soft_interrupts = 0 11 | for line in lines( PROCFS_PATH / "stat" ): 12 | if line.startswith("ctxt"): 13 | ctx_switches = parseint( line.split()[1] ) 14 | elif line.startswith("intr"): 15 | interrupts = parseint( line.split()[1] ) 16 | elif line.startswith("softirq"): 17 | soft_interrupts = parseint( line.split()[1] ) 18 | if ctx_switches != 0 and soft_interrupts != 0 and interrupts != 0: 19 | break 20 | # syscalls = 0 21 | return (ctx_switches, interrupts, soft_interrupts, 0) 22 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/pid_exists.md: -------------------------------------------------------------------------------- 1 | # pid_exists 2 | 3 | pid_exists returns a boolean which is true if the given pid (integer) exists on the system else false. 4 | 5 | # The function 6 | ```nim 7 | proc pid_exists*( pid: int ): bool = 8 | ## Check For the existence of a unix pid 9 | 10 | let exists = psutil_posix.pid_exists( pid ) 11 | if not exists: return false 12 | 13 | try: 14 | # Note: already checked that this is faster than using a regular expr. 15 | # Also (a lot) faster than doing "return pid in pids()" 16 | let status_path = PROCFS_PATH / $pid / "status" 17 | for line in status_path.lines: 18 | if line.startswith( "Tgid:" ): 19 | let tgid = parseInt( line.split()[1] ) 20 | return tgid == pid 21 | 22 | raise newException(OSError, "Tgid line not found in " & status_path) 23 | except: 24 | return pid in pids() 25 | ``` 26 | -------------------------------------------------------------------------------- /scripts/free.nim: -------------------------------------------------------------------------------- 1 | ## A clone of 'free' cmdline utility. 2 | ## $ nim c -r free.nim 3 | ## total used free shared buffers cache 4 | ## Mem: 9198316 4714076 374348 406372 363140 3746752 5 | ## Swap: 9424892 1091644 8333248 6 | 7 | 8 | import strformat 9 | import psutil 10 | 11 | proc main() = 12 | let virt = psutil.virtual_memory() 13 | let swap = psutil.swap_memory() 14 | echo(&"""{"total":>17} {"used":>10} {"free":>10} {"shared":>10} """ & 15 | &"""{"buffers":>10} {"cache":>10}""") 16 | echo(&"Mem: {int(virt.total / 1024):>12} {int(virt.used / 1024):>10} " & 17 | &"{int(virt.free / 1024):>10} {int(virt.shared / 1024):>10} " & 18 | &"{int(virt.buffers / 1024):>10} {int(virt.cached / 1024):>10}") 19 | echo(&"Swap: {int(swap.total / 1024):>11} {int(swap.used / 1024):>10} " & 20 | &"{int(swap.free / 1024):>10} ") 21 | 22 | when isMainModule: 23 | main() 24 | -------------------------------------------------------------------------------- /tests/test_linux.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import osproc 3 | import strutils 4 | 5 | import ../src/psutil 6 | 7 | template echo_proc(x: untyped) = 8 | echo "\n\n", astToStr(x), "\n", x 9 | 10 | 11 | proc vmstat(stat: string): int = 12 | for line in splitlines(execProcess("vmstat -s")): 13 | if stat in line: 14 | return parseInt(line.splitWhitespace()[0]) 15 | raise newException(ValueError, "can't find $1 in 'vmstat' output" % stat) 16 | 17 | 18 | test "test boot time": 19 | let vmstat_value = vmstat("boot time") 20 | let psutil_value = psutil.boot_time() 21 | check(vmstat_value == int(psutil_value)) 22 | 23 | 24 | 25 | 26 | quit(0) 27 | #[ 28 | echo_proc pid_name(1) 29 | echo_proc process_exists("Discord") 30 | echo_proc pids_with_names() 31 | for pid in pids(): 32 | try: 33 | echo_proc pid_path(pid) 34 | debugEcho try_pid_user(pid) 35 | discard pid_parent(pid) 36 | except: 37 | discard 38 | 39 | ]# 40 | -------------------------------------------------------------------------------- /doc/windows/functions/process_exists.md: -------------------------------------------------------------------------------- 1 | # process_exists 2 | 3 | process_exists returns a boolean which returns true if the given processName is running, else false. 4 | 5 | # Example 6 | 7 | ```nim 8 | echo process_exists("cmd.exe") 9 | ``` 10 | 11 | # The function 12 | ```nim 13 | proc process_exists*(processName: string): bool = 14 | 15 | var exists = false 16 | var entry: PROCESSENTRY32 17 | entry.dwSize = cast[DWORD](PROCESSENTRY32.sizeof) 18 | 19 | var snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 20 | 21 | if Process32First(snapshot, entry.addr): 22 | while Process32Next(snapshot, entry.addr): 23 | var name: string 24 | for c in entry.szExeFile: 25 | if cast[char](c) == '\0': 26 | break 27 | 28 | name.add(cast[char](c)) 29 | 30 | if name == processName: 31 | exists = true 32 | 33 | CloseHandle(snapshot) 34 | return exists 35 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/virtual_memory.md: -------------------------------------------------------------------------------- 1 | # virtual_memory 2 | 3 | This function will return type VirtualMemory which contains information on the virtual memory of the 4 | system 5 | 6 | # The function 7 | 8 | ```nim 9 | proc virtual_memory*(): VirtualMemory = 10 | ## System virtual memory 11 | var memInfo: MEMORYSTATUSEX 12 | memInfo.dwLength = sizeof(MEMORYSTATUSEX).DWORD 13 | 14 | if GlobalMemoryStatusEx( addr memInfo ) == 0: 15 | raiseError() 16 | 17 | let used = int(memInfo.ullTotalPhys - memInfo.ullAvailPhys) 18 | let percent = usage_percent( used, memInfo.ullTotalPhys.int, places=1 ) 19 | return VirtualMemory( total: memInfo.ullTotalPhys.int, 20 | avail: memInfo.ullAvailPhys.int, 21 | percent: percent, 22 | used: used, 23 | free: memInfo.ullAvailPhys.int ) 24 | 25 | ``` 26 | 27 | # The type 28 | 29 | - [VirtualMemory](../types/VirtualMemory.md) 30 | -------------------------------------------------------------------------------- /doc/linux/functions/users.md: -------------------------------------------------------------------------------- 1 | # users 2 | 3 | users() returns a sequence of the type User. User type holds information on the users on the system. 4 | 5 | # The functions 6 | ```nim 7 | proc users*(): seq[User] = 8 | result = newSeq[User]() 9 | 10 | setutent() 11 | 12 | var ut = getutent() 13 | while ut != nil: 14 | let is_user_proc = ut.ut_type == USER_PROCESS 15 | if not is_user_proc: 16 | ut = getutent() 17 | continue 18 | 19 | var hostname = $ut.ut_host 20 | if hostname == ":0.0" or hostname == ":0": 21 | hostname = "localhost" 22 | 23 | let user_tuple = User( name:($ut.ut_user.join().strip.replace("\x00", "")), 24 | terminal:($ut.ut_line.join().strip.replace("\x00", "")), 25 | started:ut.ut_tv.tv_sec.float ) 26 | result.add( user_tuple ) 27 | ut = getutent() 28 | 29 | endutent() 30 | ``` 31 | 32 | # The type 33 | 34 | - [Users](../types/Users.md) -------------------------------------------------------------------------------- /tests/quick_test.nim: -------------------------------------------------------------------------------- 1 | import ../psutil 2 | 3 | template echo_proc(x: untyped) = 4 | echo "\n\n", astToStr(x), "\n", x 5 | 6 | echo_proc net_if_addrs() 7 | echo_proc boot_time() 8 | echo_proc uptime() 9 | echo_proc users() 10 | echo_proc pids() 11 | echo_proc cpu_times() 12 | echo_proc per_cpu_times() 13 | echo_proc cpu_stats() 14 | echo_proc pid_exists(1) 15 | echo_proc pid_exists(999) 16 | echo_proc cpu_count(logical = true) 17 | echo_proc cpu_count(logical = false) 18 | echo_proc cpu_percent(interval = 0.0) 19 | echo_proc cpu_percent(interval = 1.0) 20 | echo_proc per_cpu_percent(interval = 0.0) 21 | echo_proc per_cpu_percent(interval = 1.0) 22 | echo_proc disk_usage(".") 23 | echo_proc virtual_memory() 24 | echo_proc swap_memory() 25 | echo_proc disk_partitions(all = false) 26 | echo_proc disk_partitions(all = true) 27 | echo_proc net_io_counters() 28 | echo_proc per_nic_net_io_counters() 29 | echo_proc net_if_stats() 30 | #echo_proc disk_io_counters() 31 | #echo_proc per_disk_io_counters() 32 | echo_proc net_connections() 33 | -------------------------------------------------------------------------------- /doc/linux/functions/get_partitions.md: -------------------------------------------------------------------------------- 1 | # get_partitions 2 | 3 | get_partition returns a sequence of strings (the partitions on the system) 4 | 5 | # The functions 6 | ```nim 7 | proc get_partitions*(): seq[string] = 8 | # Determine partitions to look for 9 | result = newSeq[string]() 10 | var lines = toSeq( lines( PROCFS_PATH / "partitions" ) ) 11 | for line in reversed( lines[2..10.1f} %" 36 | else: 37 | n = &"{n:<10}:{formatSize(v.int, prefix=bpColloquial, includeSpace=true):>12}" 38 | echo n 39 | 40 | proc main() = 41 | echo("MEMORY\n------") 42 | pprint_object(psutil.virtual_memory()) 43 | echo("\nSWAP\n----") 44 | pprint_object(psutil.swap_memory()) 45 | 46 | 47 | when isMainModule: 48 | main() 49 | -------------------------------------------------------------------------------- /doc/windows/functions/cpu_count_physical.md: -------------------------------------------------------------------------------- 1 | # cpu_count_physical 2 | 3 | cpu_count_physical returns the amount of physical cpus are on the system 4 | 5 | # The function 6 | ```nim 7 | proc cpu_count_physical*(): int = 8 | var length: DWORD = 0 9 | var rc = GetLogicalProcessorInformationEx(relationAll, NULL, addr length) 10 | 11 | var buffer = cast[PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX](alloc0(length)) 12 | rc = GetLogicalProcessorInformationEx(relationAll, buffer, addr length) 13 | 14 | if rc == 0: 15 | dealloc(buffer) 16 | raiseError() 17 | 18 | var currentPtr = buffer 19 | var offset = 0 20 | var prevProcessorInfoSize = 0 21 | while offset < length: 22 | # Advance ptr by the size of the previous SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct. 23 | currentPtr = cast[PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX](cast[int](currentPtr) + prevProcessorInfoSize) 24 | 25 | if currentPtr.Relationship == relationProcessorCore: 26 | result += 1 27 | 28 | # When offset == length, we've reached the last processor info struct in the buffer. 29 | offset += currentPtr.Size 30 | prevProcessorInfoSize = currentPtr.Size 31 | 32 | dealloc(buffer) 33 | 34 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/per_nic_net_io_counters.md: -------------------------------------------------------------------------------- 1 | # per_nic_net_io_counters 2 | 3 | Return network I/O statistics (NetIO) for every network interface 4 | installed on the system as a dict of raw tuples. 5 | 6 | # The function 7 | ```nim 8 | proc per_nic_net_io_counters*(): TableRef[string, NetIO] = 9 | ## Return network I/O statistics for every network interface 10 | ## installed on the system as a dict of raw tuples. 11 | result = newTable[string, NetIO]() 12 | for line in lines( PROCFS_PATH / "net/dev" ): 13 | if not( ":" in line ): continue 14 | let colon = line.rfind(':') 15 | let name = line[..colon].strip() 16 | let lst = line[(colon + 1)..len(line) - 1].strip.replace("\x00", "").splitWhitespace 17 | let fields = mapIt(lst, parseInt(it)) 18 | 19 | result[name] = NetIO( bytes_sent: fields[8], 20 | bytes_recv: fields[0], 21 | packets_sent: fields[9], 22 | packets_recv: fields[1], 23 | errin: fields[2], 24 | errout: fields[10], 25 | dropin: fields[3], 26 | dropout: fields[11] ) 27 | 28 | ``` 29 | 30 | # The type 31 | 32 | - [NetIO](../types/NetIO.md) -------------------------------------------------------------------------------- /doc/windows/functions/psutil_get_drive_type.md: -------------------------------------------------------------------------------- 1 | # psutil_get_drive_type 2 | 3 | There are two version of this function the first will give you the drive_type if given the uint. This is 4 | normally returned by GetDriveTypeA. 5 | 6 | # The first function 7 | 8 | ```nim 9 | proc psutil_get_drive_type*( drive_type: UINT ): string = 10 | case drive_type 11 | of DRIVE_FIXED: "fixed" 12 | of DRIVE_CDROM: "cdrom" 13 | of DRIVE_REMOVABLE: "removable" 14 | of DRIVE_UNKNOWN: "unknown" 15 | of DRIVE_NO_ROOT_DIR: "unmounted" 16 | of DRIVE_REMOTE: "remote" 17 | of DRIVE_RAMDISK: "ramdisk" 18 | else: "?" 19 | ``` 20 | 21 | # The second function 22 | 23 | The second version of this function will give you the drive_type if given the drive name. This will run GetDriveTypeA 24 | automatically. 25 | 26 | ```nim 27 | proc psutil_get_drive_type*(drive: string): string = 28 | 29 | var drive_type = GetDriveTypeA(cast[LPCSTR](drive)) 30 | case drive_type 31 | of DRIVE_FIXED: "fixed" 32 | of DRIVE_CDROM: "cdrom" 33 | of DRIVE_REMOVABLE: "removable" 34 | of DRIVE_UNKNOWN: "unknown" 35 | of DRIVE_NO_ROOT_DIR: "unmounted" 36 | of DRIVE_REMOTE: "remote" 37 | of DRIVE_RAMDISK: "ramdisk" 38 | else: "?" 39 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/toUnixTime.md: -------------------------------------------------------------------------------- 1 | # toUnixTime 2 | 3 | toUnixTime converts the FILETIME structure to the 32 bit Unix time structure. 4 | The time_t is a 32-bit value for the number of seconds since January 1, 1970. 5 | A FILETIME is a 64-bit for the number of 100-nanosecond periods since January 1, 1601. 6 | Convert by subtracting the number of 100-nanosecond period between 01-01-1970 7 | and 01-01-1601, from time_t then divide by 1e+7 to get to the same base granularity. 8 | 9 | Note: FILETIME is defined in windef 10 | 11 | # The function 12 | ```nim 13 | proc toUnixTime(ft: FILETIME): float = 14 | # HUGE thanks to: 15 | # http://johnstewien.spaces.live.com/blog/cns!E6885DB5CEBABBC8!831.entry 16 | # This function converts the FILETIME structure to the 32 bit 17 | # Unix time structure. 18 | # The time_t is a 32-bit value for the number of seconds since 19 | # January 1, 1970. A FILETIME is a 64-bit for the number of 20 | # 100-nanosecond periods since January 1, 1601. Convert by 21 | # subtracting the number of 100-nanosecond period between 01-01-1970 22 | # and 01-01-1601, from time_t then divide by 1e+7 to get to the same 23 | # base granularity. 24 | let ll = (int64(ft.dwHighDateTime) shl 32) + int64(ft.dwLowDateTime) 25 | result = int(ll - 116444736000000000) / 10000000 26 | 27 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/getnativearch.md: -------------------------------------------------------------------------------- 1 | # getnativearch 2 | 3 | This function returns a string that shows the native architecture of the system 4 | "x64" is 64 bit 5 | "x86" is 32 bit 6 | "unknown" if we can't get the architecture. (Permissions or other reasons) 7 | 8 | # The function 9 | ```nim 10 | proc getnativearch*(): string = 11 | ## Get the native architecture of the system we are running on 12 | var pGetNativeSystemInfo: SYSTEM_INFO 13 | var nativeArch = "unknown" 14 | 15 | GetNativeSystemInfo(pGetNativeSystemInfo.addr) 16 | 17 | if pGetNativeSystemInfo.isNil: 18 | raiseError() 19 | 20 | 21 | case pGetNativeSystemInfo.union1.struct1.wProcessorArchitecture 22 | of PROCESSOR_ARCHITECTURE_AMD64: 23 | ## 64 bit (x64) 24 | # dwNativeArch = PROCESSOR_ARCHITECTURE_AMD64 25 | nativeArch = "x64" 26 | 27 | of PROCESSOR_ARCHITECTURE_IA64: 28 | # dwNativeArch = PROCESSOR_ARCHITECTURE_IA64 29 | nativeArch = "x64" 30 | 31 | of PROCESSOR_ARCHITECTURE_INTEL: 32 | # 32 bit (x86) 33 | # dwNativeArch = PROCESSOR_ARCHITECTURE_INTEL 34 | nativeArch = "x86" 35 | 36 | else: 37 | # dwNativeArch = PROCESSOR_ARCHITECTURE_UNKNOWN 38 | nativeArch = "unknown" 39 | 40 | return nativeArch 41 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_name.md: -------------------------------------------------------------------------------- 1 | # pid_name 2 | 3 | This function will return the process name of the given pid. 4 | This is not the same as pid_name!!! 5 | This function only gets the name of the process. 6 | Not the path of the program, and arguments. 7 | This reads and sanitizes /proc/pid/comm 8 | 9 | # The function 10 | 11 | ```nim 12 | proc pid_name*(processID: int): string = 13 | 14 | #[ 15 | function for getting the process name of pid 16 | ]# 17 | var szProcessName: array[MAX_PATH, TCHAR] 18 | 19 | # szProcessName[0] = cast[TCHAR]("") 20 | 21 | # Get a handle to the process. 22 | 23 | var hProcess = OpenProcess( cast[DWORD](PROCESS_QUERY_INFORMATION or PROCESS_VM_READ), FALSE, cast[DWORD](processID) ) 24 | 25 | # Get the process name. 26 | 27 | if hProcess.addr != nil: 28 | 29 | var hMod: HMODULE 30 | var cbNeeded: DWORD 31 | 32 | if EnumProcessModules( hProcess, hMod.addr, cast[DWORD](sizeof(hMod)), cbNeeded.addr): 33 | 34 | GetModuleBaseName( hProcess, hMod, szProcessName, 35 | cast[DWORD](szProcessName.len) ) 36 | 37 | # Release the handle to the process. 38 | 39 | CloseHandle( hProcess ) 40 | 41 | # return the process name 42 | var ret: string 43 | for c in szProcessName: 44 | if cast[char](c) == '\0': 45 | break 46 | 47 | ret.add(cast[char](c)) 48 | 49 | return ret 50 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_arch.md: -------------------------------------------------------------------------------- 1 | # pid_arch 2 | 3 | This function returns an integer saying the architecture of the pid 4 | PROCESS_ARCH_X64 is 64 bit 5 | PROCESS_ARCH_X86 is 32 bit 6 | PROCESS_ARCH_UNKNOWN if we can't get the architecture. (Permissions or other reasons) 7 | 8 | # The function 9 | ```nim 10 | proc pid_arch*(pid: int) : int = 11 | 12 | ## function for getting the architecture of the pid running 13 | var bIsWow64: BOOL 14 | var nativeArch = static PROCESS_ARCH_UNKNOWN 15 | var hProcess: HANDLE 16 | # var pIsWow64Process: ISWOW64PROCESS 17 | var dwPid = cast[DWORD](pid) 18 | # now we must default to an unknown architecture as the process may be either x86/x64 and we may not have the rights to open it 19 | result = PROCESS_ARCH_UNKNOWN 20 | 21 | ## grab the native systems architecture the first time we use this function we use this funciton. 22 | if nativeArch == PROCESS_ARCH_UNKNOWN: 23 | nativeArch = getnativearch() 24 | 25 | 26 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid) 27 | defer: CloseHandle(hProcess) 28 | if hProcess == cast[HANDLE](-1): 29 | hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPid) 30 | if hProcess == cast[HANDLE](-1): 31 | raiseError() 32 | 33 | if IsWow64Process(hProcess, bIsWow64.addr) == FALSE: 34 | return 35 | 36 | if bIsWow64: 37 | result = PROCESS_ARCH_X86 38 | else: 39 | result = nativeArch 40 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/disk_partions.md: -------------------------------------------------------------------------------- 1 | # disk_partions 2 | 3 | This function returns a type DiskUsage. 4 | 5 | # The function 6 | ```nim 7 | proc disk_partitions*(all=false): seq[DiskPartition] = 8 | ## Return mounted disk partitions as a sequence of DiskPartitions 9 | var fstypes = initHashSet[string]() 10 | for raw_line in lines( PROCFS_PATH / "filesystems" ): 11 | let line = raw_line.strip() 12 | if not line.startswith("nodev"): 13 | fstypes.incl( line ) 14 | else: 15 | # ignore all lines starting with "nodev" except "nodev zfs" 16 | if line.split("\t")[1] == "zfs": 17 | fstypes.incl( "zfs" ) 18 | 19 | result = newSeq[DiskPartition]() 20 | 21 | let file = setmntent(MOUNTED, "r"); 22 | var entry = getmntent( file ) 23 | while entry != nil: 24 | let device = if entry.mnt_fsname == "none": "" else: $entry.mnt_fsname 25 | let mountpoint = $entry.mnt_dir 26 | let fstype = $entry.mnt_type 27 | let opts = $entry.mnt_opts 28 | 29 | if not all: 30 | if device == "" or not( fstype in fstypes ): 31 | entry = getmntent( file ) 32 | continue 33 | let partition = DiskPartition( device:device, mountpoint:mountpoint, 34 | fstype:fstype, opts:opts ) 35 | result.add( partition ) 36 | entry = getmntent( file ) 37 | 38 | discard endmntent( file ) 39 | 40 | ``` 41 | 42 | # The type 43 | 44 | - [DiskUsage](../types/DiskUsage.md) -------------------------------------------------------------------------------- /scripts/disk_usage.nim: -------------------------------------------------------------------------------- 1 | ## List all mounted disk partitions a-la "df -h" command. 2 | ## $ nim c -r disk_usage.nim 3 | ## Device Total Used Free Use % Type Mount 4 | ## /dev/sdb3 18.9GB 14.7GB 3.3GB 77% ext4 / 5 | ## /dev/sda6 345.9GB 83.8GB 244.5GB 24% ext4 /home 6 | ## /dev/sda1 296.0MB 43.1MB 252.9MB 14% vfat /boot/efi 7 | ## /dev/sda2 600.0MB 312.4MB 287.6MB 52% fuseblk /media/Recovery 8 | 9 | import sequtils 10 | import strutils 11 | import strformat 12 | import psutil 13 | 14 | proc main() = 15 | var n: int = max(mapIt(psutil.disk_partitions(all = false), len(it.device))) 16 | 17 | echo(&"""{alignString("Device", n)} {"Total":>10} {"Used":>10} """ & 18 | &"""{"Free":>10} {"Use":>5}% {"Type":>9} Mount""") 19 | for part in psutil.disk_partitions(all = false): 20 | when defined(windows): 21 | if "cdrom" in part.opts or part.fstype == "": 22 | # skip cd-rom drives with no disk in it; they may raise 23 | # ENOENT, pop-up a Windows GUI error for a non-ready 24 | # partition or just hang. 25 | continue 26 | let usage = psutil.disk_usage(part.mountpoint) 27 | echo(&"{alignString(part.device, n)} " & 28 | &"{formatSize(usage.total, prefix=bpColloquial):>10} " & 29 | &"{formatSize(usage.used, prefix=bpColloquial ):>10} " & 30 | &"{formatSize(usage.free, prefix=bpColloquial ):>10} " & 31 | &"{usage.percent:>5}% {part.fstype:>9} {part.mountpoint:9}") 32 | 33 | when isMainModule: 34 | main() 35 | -------------------------------------------------------------------------------- /doc/windows/functions/cpu_times.md: -------------------------------------------------------------------------------- 1 | # cpu_times 2 | 3 | cpu_times retrieves system CPU timing information. 4 | On a multiprocessor system, the values returned are the 5 | sum of the designated times across all processors. 6 | 7 | # The function 8 | 9 | ```nim 10 | proc cpu_times*(): CPUTimes = 11 | ## Retrieves system CPU timing information . On a multiprocessor system, 12 | ## the values returned are the 13 | ## sum of the designated times across all processors. 14 | 15 | var idle_time: FILETIME 16 | var kernel_time: FILETIME 17 | var user_time: FILETIME 18 | 19 | if GetSystemTimes(addr idle_time, addr kernel_time, addr user_time).bool == false: 20 | raiseError() 21 | 22 | let idle = (HI_T * idle_time.dwHighDateTime.float) + (LO_T * idle_time.dwLowDateTime.float) 23 | let user = (HI_T * user_time.dwHighDateTime.float) + (LO_T * user_time.dwLowDateTime.float) 24 | let kernel = (HI_T * kernel_time.dwHighDateTime.float) + (LO_T * kernel_time.dwLowDateTime.float) 25 | 26 | # Kernel time includes idle time. 27 | # We return only busy kernel time subtracting idle time from kernel time. 28 | let system = kernel - idle 29 | 30 | # Internally, GetSystemTimes() is used, and it doesn't return interrupt and dpc times. 31 | # per_cpu_times() does, so we rely on it to get those only. 32 | let per_times = per_cpu_times() 33 | let interrupt_sum = sum(per_times.mapIt(it.interrupt)) 34 | let dpc_sum = sum(per_times.mapIt(it.dpc)) 35 | return CPUTimes(user:user, system:system, idle:idle, interrupt:interrupt_sum, dpc:dpc_sum) 36 | ``` 37 | 38 | # The type 39 | 40 | - [CPUTimes](../types/CPUTimes.md) -------------------------------------------------------------------------------- /doc/linux/functions/swap_memory.md: -------------------------------------------------------------------------------- 1 | # swap_memory 2 | 3 | This function will return a SwapMemory type. 4 | Which holds information regarding system swap memory. 5 | 6 | # The function 7 | ```nim 8 | proc swap_memory*(): SwapMemory = 9 | var si: SysInfo 10 | if sysinfo( si ) == -1: 11 | echo( "Error calling sysinfo in swap_memory(): ", errno ) 12 | return 13 | 14 | let total = si.totalswap * si.mem_unit 15 | let free = si.freeswap * si.mem_unit 16 | let used = total - free 17 | let percent = usage_percent(used.int, total.int, places=1) 18 | 19 | result = SwapMemory( total:total.int, used:used.int, free:free.int, 20 | percent:percent, sin:0, sout:0 ) 21 | 22 | # try to get pgin/pgouts 23 | if not existsFile( PROCFS_PATH / "vmstat" ): 24 | # see https://github.com/giampaolo/psutil/issues/722 25 | echo( "'sin' and 'sout' swap memory stats couldn't be determined ", 26 | "and were set to 0" ) 27 | return result 28 | 29 | for line in lines( PROCFS_PATH / "vmstat" ): 30 | # values are expressed in 4 kilo bytes, we want bytes instead 31 | if line.startswith("pswpin"): 32 | result.sin = parseInt(line.split()[1]) * 4 * 1024 33 | elif line.startswith("pswpout"): 34 | result.sout = parseInt(line.split()[1]) * 4 * 1024 35 | if result.sin != 0 and result.sout != 0: 36 | return result 37 | 38 | # we might get here when dealing with exotic Linux flavors, see: 39 | # https://github.com/giampaolo/psutil/issues/313 40 | echo( "'sin' and 'sout' swap memory stats couldn't be determined ", 41 | "and were set to 0" ) 42 | 43 | ``` 44 | 45 | # The type 46 | 47 | - [SwapMemory](../types/SwapMemory.md) -------------------------------------------------------------------------------- /doc/windows/functions/pid_domain.md: -------------------------------------------------------------------------------- 1 | # pid_domain 2 | 3 | pid_domain will attempt to get the domain that the pid is running on 4 | 5 | # The function 6 | ```nim 7 | proc pid_domain*(pid: int): string = 8 | 9 | ## Attempt to get the domain associated with the given pid. 10 | var hProcess: HANDLE 11 | var hToken: HANDLE 12 | var pUser: TOKEN_USER 13 | var peUse: SID_NAME_USE 14 | var dwUserLength = cast[DWORD](512) 15 | var dwDomainLength = cast[DWORD](512) 16 | var dwLength: DWORD 17 | var dwPid = cast[DWORD](pid) 18 | var wcUser: array[512, TCHAR] 19 | var wcDomain: array[512, TCHAR] 20 | 21 | 22 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid) 23 | defer: CloseHandle(hProcess) 24 | if hProcess == cast[DWORD](-1) or hProcess == cast[DWORD](NULL): 25 | raiseError() 26 | 27 | if OpenProcessToken(hProcess, TOKEN_QUERY, cast[PHANDLE](hToken.addr)) == FALSE: 28 | raiseError() 29 | 30 | defer: CloseHandle(hToken) 31 | 32 | if hToken == cast[HANDLE](-1) or hToken == cast[HANDLE](NULL): 33 | raiseError() 34 | 35 | ## Get required buffer size and allocate the TOKEN_USER buffer 36 | GetTokenInformation(hToken, tokenUser, cast[LPVOID](pUser.addr), cast[DWORD](0), cast[PDWORD](dwLength.addr)) 37 | 38 | GetTokenInformation(hToken, tokenUser, pUser.addr, cast[DWORD](dwLength), cast[PDWORD](dwLength.addr)) 39 | 40 | if LookupAccountSidW(cast[LPCWSTR](NULL), pUser.User.Sid, wcUser, dwUserLength.addr, wcDomain, dwDomainLength.addr, peUse.addr) == FALSE: 41 | raiseError() 42 | 43 | let domain = wcDomain[0..^1] 44 | var retd: string 45 | for c in domain: 46 | if cast[char](c) != '\0': 47 | retd.add(cast[char](c)) 48 | else: 49 | break 50 | 51 | return retd 52 | 53 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/pid_user.md: -------------------------------------------------------------------------------- 1 | # pid_user 2 | 3 | pid_user will attempt to get the username running the specified pid 4 | 5 | # The function 6 | ```nim 7 | proc pid_user*(pid: int): string = 8 | 9 | ## Attempt to get the username associated with the given pid. 10 | var hProcess: HANDLE 11 | var hToken: HANDLE 12 | var pUser: TOKEN_USER 13 | var peUse: SID_NAME_USE 14 | var dwUserLength = cast[DWORD](512) 15 | var dwDomainLength = cast[DWORD](0) 16 | var dwLength: DWORD 17 | var dwPid = cast[DWORD](pid) 18 | var wcUser: array[512, TCHAR] 19 | var wcDomain: array[512, TCHAR] 20 | 21 | 22 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid) 23 | defer: CloseHandle(hProcess) 24 | if hProcess == cast[DWORD](-1) or hProcess == cast[DWORD](NULL): 25 | raiseError() 26 | 27 | if OpenProcessToken(hProcess, TOKEN_QUERY, cast[PHANDLE](hToken.addr)) == FALSE: 28 | raiseError() 29 | 30 | defer: CloseHandle(hToken) 31 | 32 | if hToken == cast[HANDLE](-1) or hToken == cast[HANDLE](NULL): 33 | raiseError() 34 | 35 | ## Get required buffer size and allocate the TOKEN_USER buffer 36 | GetTokenInformation(hToken, tokenUser, cast[LPVOID](pUser.addr), cast[DWORD](0), cast[PDWORD](dwLength.addr)) 37 | 38 | GetTokenInformation(hToken, tokenUser, pUser.addr, cast[DWORD](dwLength), cast[PDWORD](dwLength.addr)) 39 | 40 | 41 | if LookupAccountSidW(cast[LPCWSTR](NULL), pUser.User.Sid, wcUser, dwUserLength.addr, wcDomain, dwDomainLength.addr, peUse.addr) == FALSE: 42 | raiseError() 43 | 44 | let user = wcUser[0..^1] 45 | var retu: string 46 | for c in user: 47 | if cast[char](c) != '\0': 48 | retu.add(cast[char](c)) 49 | else: 50 | break 51 | 52 | return retu 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /doc/windows/functions/try_pid_user.md: -------------------------------------------------------------------------------- 1 | # try_pid_user 2 | 3 | try_pid_user will attempt to get the username running the specified pid, but if an exception occurs 4 | it will return "". Instead of raising it. 5 | 6 | # The function 7 | ```nim 8 | proc try_pid_user*(pid: int): string = 9 | 10 | ## Attempt to get the username associated with the given pid. 11 | var hProcess: HANDLE 12 | var hToken: HANDLE 13 | var pUser: TOKEN_USER 14 | var peUse: SID_NAME_USE 15 | var dwUserLength = cast[DWORD](512) 16 | var dwDomainLength = cast[DWORD](0) 17 | var dwLength: DWORD 18 | var dwPid = cast[DWORD](pid) 19 | var wcUser: array[512, TCHAR] 20 | var wcDomain: array[512, TCHAR] 21 | 22 | 23 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid) 24 | defer: CloseHandle(hProcess) 25 | if hProcess == cast[DWORD](-1) or hProcess == cast[DWORD](NULL): 26 | return "" 27 | 28 | if OpenProcessToken(hProcess, TOKEN_QUERY, cast[PHANDLE](hToken.addr)) == FALSE: 29 | return "" 30 | 31 | defer: CloseHandle(hToken) 32 | 33 | if hToken == cast[HANDLE](-1) or hToken == cast[HANDLE](NULL): 34 | return "" 35 | 36 | ## Get required buffer size and allocate the TOKEN_USER buffer 37 | GetTokenInformation(hToken, tokenUser, cast[LPVOID](pUser.addr), cast[DWORD](0), cast[PDWORD](dwLength.addr)) 38 | 39 | GetTokenInformation(hToken, tokenUser, pUser.addr, cast[DWORD](dwLength), cast[PDWORD](dwLength.addr)) 40 | 41 | 42 | if LookupAccountSidW(cast[LPCWSTR](NULL), pUser.User.Sid, wcUser, dwUserLength.addr, wcDomain, dwDomainLength.addr, peUse.addr) == FALSE: 43 | return "" 44 | 45 | let user = wcUser[0..^1] 46 | var retu: string 47 | for c in user: 48 | if cast[char](c) != '\0': 49 | retu.add(cast[char](c)) 50 | else: 51 | break 52 | 53 | return retu 54 | ``` -------------------------------------------------------------------------------- /doc/windows/functions/per_cpu_times.md: -------------------------------------------------------------------------------- 1 | # per_cpu_times 2 | 3 | per_cpu_times returns system per-CPU times as a sequence of CPUTimes. CPUTimes holds information 4 | regarding cpu times on the system 5 | 6 | # The function 7 | ```nim 8 | proc per_cpu_times*(): seq[CPUTimes] = 9 | ## Return system per-CPU times as a sequence of CPUTimes. 10 | 11 | let ncpus = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS) 12 | if ncpus == 0: 13 | return result 14 | 15 | # allocates an array of _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION structures, one per processor 16 | var sppi = newSeq[SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION](ncpus) 17 | let buffer_size = ULONG(ncpus * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)) 18 | 19 | # gets cpu time informations 20 | let status = NtQuerySystemInformation(systemProcessorPerformanceInformation, addr sppi[0], buffer_size, NULL) 21 | if status != 0: 22 | raiseError() 23 | 24 | # computes system global times summing each processor value 25 | for i in 0 ..< ncpus: 26 | let user = (HI_T * sppi[i].UserTime.HighPart.float) + 27 | (LO_T * sppi[i].UserTime.LowPart.float) 28 | let idle = (HI_T * sppi[i].IdleTime.HighPart.float) + 29 | (LO_T * sppi[i].IdleTime.LowPart.float) 30 | let kernel = (HI_T * sppi[i].KernelTime.HighPart.float) + 31 | (LO_T * sppi[i].KernelTime.LowPart.float) 32 | let interrupt = (HI_T * sppi[i].InterruptTime.HighPart.float) + 33 | (LO_T * sppi[i].InterruptTime.LowPart.float) 34 | let dpc = (HI_T * sppi[i].DpcTime.HighPart.float) + 35 | (LO_T * sppi[i].DpcTime.LowPart.float) 36 | 37 | # kernel time includes idle time on windows 38 | # we return only busy kernel time subtracting idle time from kernel time 39 | let system = kernel - idle 40 | 41 | result.add(CPUTimes(user:user, system:system, idle:idle, interrupt:interrupt, dpc:dpc)) 42 | 43 | ``` 44 | 45 | # The type 46 | 47 | - [CPUTimes](../functions/per_cpu_times.md) -------------------------------------------------------------------------------- /doc/windows/functions/pid_domain_user.md: -------------------------------------------------------------------------------- 1 | # pid_domain_user 2 | 3 | pid_domain_user will attempt to return the domain and the user of the specified pid as a tuple 4 | 5 | # The function 6 | ```nim 7 | proc pid_domain_user*(pid: int): (string, string) = 8 | 9 | ## Attempt to get the domain and username associated with the given pid. 10 | var hProcess: HANDLE 11 | var hToken: HANDLE 12 | var pUser: TOKEN_USER 13 | var peUse: SID_NAME_USE 14 | var dwUserLength = cast[DWORD](512) 15 | var dwDomainLength = cast[DWORD](512) 16 | var dwLength: DWORD 17 | var dwPid = cast[DWORD](pid) 18 | var wcUser: array[512, TCHAR] 19 | var wcDomain: array[512, TCHAR] 20 | 21 | 22 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid) 23 | defer: CloseHandle(hProcess) 24 | if hProcess == cast[DWORD](-1) or hProcess == cast[DWORD](NULL): 25 | raiseError() 26 | 27 | if OpenProcessToken(hProcess, TOKEN_QUERY, cast[PHANDLE](hToken.addr)) == FALSE: 28 | raiseError() 29 | 30 | defer: CloseHandle(hToken) 31 | 32 | if hToken == cast[HANDLE](-1) or hToken == cast[HANDLE](NULL): 33 | raiseError() 34 | 35 | ## Get required buffer size and allocate the TOKEN_USER buffer 36 | GetTokenInformation(hToken, tokenUser, cast[LPVOID](pUser.addr), cast[DWORD](0), cast[PDWORD](dwLength.addr)) #== FALSE: 37 | # raiseError() 38 | 39 | GetTokenInformation(hToken, tokenUser, pUser.addr, cast[DWORD](dwLength), cast[PDWORD](dwLength.addr)) #== FALSE: 40 | # raiseError() 41 | 42 | if LookupAccountSidW(cast[LPCWSTR](NULL), pUser.User.Sid, wcUser, dwUserLength.addr, wcDomain, dwDomainLength.addr, peUse.addr) == FALSE: 43 | raiseError() 44 | 45 | let user = wcUser[0..^1] 46 | var retu: string 47 | for c in user: 48 | if cast[char](c) != '\0': 49 | retu.add(cast[char](c)) 50 | else: 51 | break 52 | 53 | let domain = wcDomain[0..^1] 54 | var retd: string 55 | for c in domain: 56 | if cast[char](c) != '\0': 57 | retd.add(cast[char](c)) 58 | else: 59 | break 60 | 61 | return (retd, retu) 62 | ``` -------------------------------------------------------------------------------- /doc/linux/functions/per_disk_io_counters.md: -------------------------------------------------------------------------------- 1 | # per_disk_io_counters 2 | 3 | per_disk_io_counters returns a table of strings to DiskIO 4 | 5 | # The function 6 | ```nim 7 | proc per_disk_io_counters*(): TableRef[string, DiskIO] = 8 | result = newTable[string, DiskIO]() 9 | for line in lines( PROCFS_PATH / "diskstats" ): 10 | # OK, this is a bit confusing. The format of /proc/diskstats can 11 | # have 3 variations. 12 | # On Linux 2.4 each line has always 15 fields, e.g.: 13 | # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 14 | # On Linux 2.6+ each line *usually* has 14 fields, and the disk 15 | # name is in another position, like this: 16 | # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 17 | # ...unless (Linux 2.6) the line refers to a partition instead 18 | # of a disk, in which case the line has less fields (7): 19 | # "3 1 hda1 8 8 8 8" 20 | # See: 21 | # https://www.kernel.org/doc/Documentation/iostats.txt 22 | # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats 23 | let fields = line.splitWhitespace() 24 | let fields_len = len(fields) 25 | var name: string 26 | var reads, reads_merged, rbytes, rtime, writes, writes_merged, 27 | wbytes, wtime, busy_time, ignore1, ignore2 = 0 28 | if fields_len == 15: 29 | # Linux 2.4 30 | name = fields[3] 31 | reads = parseInt( fields[2] ) 32 | (reads_merged, rbytes, rtime, writes, writes_merged, 33 | wbytes, wtime, ignore1, busy_time, ignore2) = map( fields[4..<14], parseInt ) 34 | elif fields_len == 14: 35 | # Linux 2.6+, line referring to a disk 36 | name = fields[2] 37 | (reads, reads_merged, rbytes, rtime, writes, writes_merged, 38 | wbytes, wtime, ignore1, busy_time, ignore2) = map(fields[3..<14], parseInt) 39 | elif fields_len == 7: 40 | # Linux 2.6+, line referring to a partition 41 | name = fields[2] 42 | ( reads, rbytes, writes, wbytes ) = map(fields[3..<7], parseInt) 43 | else: 44 | raise newException( ValueError, "not sure how to interpret line $1" % line ) 45 | 46 | if name in get_partitions(): 47 | rbytes = rbytes * SECTOR_SIZE 48 | wbytes = wbytes * SECTOR_SIZE 49 | result[name] = DiskIO( read_count:reads, write_count:writes, 50 | read_bytes:rbytes, write_bytes:wbytes, 51 | read_time:rtime, write_time:wtime, 52 | read_merged_count:reads_merged, write_merged_count:writes_merged, 53 | busy_time:busy_time ) 54 | ``` 55 | 56 | # The type 57 | 58 | - [DiskIO](../types/DiskIO.md) -------------------------------------------------------------------------------- /src/psutil/common.nim: -------------------------------------------------------------------------------- 1 | import math, nativesockets, posix 2 | 3 | # type TSa_Family* {.importc: "sa_family_t", header: "".} = cint 4 | type Address* = object of RootObj 5 | family*: posix.TSa_Family # int 6 | address*: string 7 | netmask*: string 8 | broadcast*: string 9 | ptp*: string 10 | 11 | type User* = object 12 | name*: string 13 | terminal*: string 14 | host*: string 15 | started*: float 16 | 17 | type CPUTimes* = object of RootObj 18 | user*: float 19 | system*: float 20 | idle*: float 21 | when defined(windows): 22 | interrupt*: float 23 | dpc*: float 24 | when defined(posix): 25 | nice*: float 26 | iowait*: float 27 | irq*: float 28 | softirq*: float 29 | steal*: float 30 | guest*: float 31 | guest_nice*: float 32 | 33 | 34 | type DiskUsage* = object of RootObj 35 | total*: int 36 | used*: int 37 | free*: int 38 | percent*: float 39 | 40 | type DiskPartition* = object of RootObj 41 | device*: string 42 | mountpoint*: string 43 | fstype*: string 44 | opts*: string 45 | 46 | type VirtualMemory* = object of RootObj 47 | total*: int 48 | avail*: int 49 | percent*: float 50 | used*: int 51 | free*: int 52 | active*: int 53 | inactive*: int 54 | buffers*: int 55 | cached*: int 56 | shared*: int 57 | 58 | type SwapMemory* = object of RootObj 59 | total*: int 60 | used*: int 61 | free*: int 62 | percent*: float 63 | sin*: int 64 | sout*: int 65 | 66 | type NetIO* = object of RootObj 67 | bytes_sent*: int 68 | bytes_recv*: int 69 | packets_sent*: int 70 | packets_recv*: int 71 | errin*: int 72 | errout*: int 73 | dropin*: int 74 | dropout*: int 75 | 76 | type NicDuplex* = enum 77 | NIC_DUPLEX_UNKNOWN, NIC_DUPLEX_HALF, NIC_DUPLEX_FULL 78 | 79 | type NICStats* = object of RootObj 80 | isup*: bool 81 | duplex*: NicDuplex 82 | speed*: int 83 | mtu*: int 84 | 85 | type DiskIO* = object of RootObj 86 | read_count*: int 87 | write_count*: int 88 | read_bytes*: int 89 | write_bytes*: int 90 | read_time*: int 91 | write_time*: int 92 | when defined(linux) or defined(macosx): 93 | reads_merged*: int 94 | read_merged_count*: int 95 | write_merged_count*: int 96 | busy_time*: int 97 | 98 | type Connection* = object of RootObj 99 | fd*: int 100 | family*: int 101 | `type`*: int 102 | laddr*: string 103 | lport*: Port 104 | raddr*: string 105 | rport*: Port 106 | status*: string 107 | pid*: int 108 | 109 | 110 | proc usage_percent*[T](used: T, total: T, places = 0): float = 111 | ## Calculate percentage usage of 'used' against 'total'. 112 | try: 113 | result = (used.int / total.int) * 100 114 | except DivByZeroError: 115 | result = if used is float or total is float: 0.0 else: 0 116 | if places != 0: 117 | return round(result, places) 118 | else: 119 | return result 120 | -------------------------------------------------------------------------------- /src/psutil/arch/osx/process_info.nim: -------------------------------------------------------------------------------- 1 | import ./types 2 | import ./socket 3 | 4 | const 5 | MAXPATHLEN = 1024 6 | PROC_PIDLISTFDS* = 1.cint 7 | PROC_PIDLISTFD_SIZE* = 8.cint 8 | 9 | # defns of process file desc type https://opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/proc_info.h.auto.html 10 | const 11 | PROX_FDTYPE_ATALK* = 0.cint 12 | PROX_FDTYPE_VNODE* = 1.cint 13 | PROX_FDTYPE_SOCKET* = 2.cint 14 | PROX_FDTYPE_PSHM* = 3.cint 15 | PROX_FDTYPE_PSEM* = 4.cint 16 | PROX_FDTYPE_KQUEUE* = 5.cint 17 | PROX_FDTYPE_PIPE* = 6.cint 18 | PROX_FDTYPE_FSEVENTS* = 7.cint 19 | PROC_PIDFDVNODEPATHINFO* = 2.cint 20 | PROC_PIDFDSOCKETINFO* = 3.cint 21 | 22 | 23 | type proc_fdinfo* {.importc: "struct proc_fdinfo", header: "", 24 | pure.} = object 25 | proc_fdtype*: cint 26 | proc_fd*: cint 27 | 28 | 29 | type vnode_fdinfowithpath* {.importc: "struct vnode_fdinfowithpath", 30 | header: "".} = object 31 | 32 | 33 | type proc_fileinfo* {.importc: "struct proc_fileinfo", 34 | header: "", pure.} = object 35 | fi_openflags*: uint32 36 | fi_status*: uint32 37 | fi_offset*: off_t 38 | fi_guardflags*: uint32 39 | 40 | 41 | type socket_fdinfo* {.importc: "struct socket_fdinfo", 42 | header: "", pure.} = object 43 | pfi*: proc_fileinfo 44 | psi*: socket_info 45 | 46 | 47 | # https://opensource.apple.com/source/xnu/xnu-2422.1.72/libsyscall/wrappers/libproc/libproc.h.auto.html 48 | proc proc_pidinfo*(pid: cint, flavor: cint, arg: uint64, buffer: pointer, 49 | buffer_size: cint): cint {.importc: "proc_pidinfo", header: "".} 50 | 51 | 52 | proc proc_pidfdinfo*(pid: cint, flavor: cint, fd: cint, buffer: pointer, 53 | buffer_size: cint): cint {.importc: "proc_pidfdinfo", 54 | header: "".} 55 | 56 | 57 | when isMainModule: 58 | var x {.importc: "PROC_PIDLISTFDS", header: "".}: cint 59 | assert x == PROC_PIDLISTFDS 60 | 61 | var y {.importc: "PROX_FDTYPE_ATALK", header: "".}: cint 62 | assert y == PROX_FDTYPE_ATALK 63 | 64 | var z {.importc: "PROX_FDTYPE_VNODE", header: "".}: cint 65 | assert z == PROX_FDTYPE_VNODE 66 | 67 | var z1 {.importc: "PROX_FDTYPE_SOCKET", header: "".}: cint 68 | assert z1 == PROX_FDTYPE_SOCKET 69 | 70 | var z2 {.importc: "PROX_FDTYPE_PSHM", header: "".}: cint 71 | assert z2 == PROX_FDTYPE_PSHM 72 | 73 | var z3 {.importc: "PROX_FDTYPE_PSEM", header: "".}: cint 74 | assert z3 == PROX_FDTYPE_PSEM 75 | 76 | var z4 {.importc: "PROX_FDTYPE_KQUEUE", header: "".}: cint 77 | assert z4 == PROX_FDTYPE_KQUEUE 78 | 79 | var z5 {.importc: "PROX_FDTYPE_PIPE", header: "".}: cint 80 | assert z5 == PROX_FDTYPE_PIPE 81 | 82 | var z6 {.importc: "PROX_FDTYPE_FSEVENTS", header: "".}: cint 83 | assert z6 == PROX_FDTYPE_FSEVENTS 84 | 85 | var z7 {.importc: "PROC_PIDFDVNODEPATHINFO", 86 | header: "".}: cint 87 | assert z7 == PROC_PIDFDVNODEPATHINFO 88 | -------------------------------------------------------------------------------- /doc/windows/functions/disk_partitions.md: -------------------------------------------------------------------------------- 1 | # disk_partitions 2 | 3 | This function will return a sequence of a type DiskPartition. 4 | 5 | # The function 6 | 7 | ```nim 8 | proc disk_partitions*( all=false ): seq[DiskPartition] = 9 | result = newSeq[DiskPartition]() 10 | 11 | # avoid to visualize a message box in case something goes wrong 12 | # see https://github.com/giampaolo/psutil/issues/264 13 | discard SetErrorMode( SEM_FAILCRITICALERRORS ) 14 | 15 | var drive_strings = newWString( 256 ) 16 | let returned_len = GetLogicalDriveStringsW( 256, drive_strings ) 17 | if returned_len == 0: 18 | raiseError() 19 | return 20 | 21 | let letters = split( strip( $drive_strings, chars={'\0'} ), '\0' ) 22 | for drive_letter in letters: 23 | let drive_type = GetDriveType( drive_letter ) 24 | 25 | # by default we only show hard drives and cd-roms 26 | if not all: 27 | if drive_type == DRIVE_UNKNOWN or 28 | drive_type == DRIVE_NO_ROOT_DIR or 29 | drive_type == DRIVE_REMOTE or 30 | drive_type == DRIVE_RAMDISK: continue 31 | 32 | # floppy disk: skip it by default as it introduces a considerable slowdown. 33 | if drive_type == DRIVE_REMOVABLE and drive_letter == "A:\\": 34 | continue 35 | 36 | 37 | var fs_type: LPWSTR = newString( 256 ) 38 | var pflags: DWORD = 0 39 | var lpdl: LPCWSTR = drive_letter 40 | let gvi_ret = GetVolumeInformationW( lpdl, 41 | NULL, 42 | DWORD( drive_letter.len ), 43 | NULL, 44 | NULL, 45 | addr pflags, 46 | fs_type, 47 | DWORD( 256 ) ) 48 | var opts = "" 49 | if gvi_ret == 0: 50 | # We might get here in case of a floppy hard drive, in 51 | # which case the error is ( 21, "device not ready"). 52 | # Let's pretend it didn't happen as we already have 53 | # the drive name and type ('removable'). 54 | SetLastError( 0 ) 55 | else: 56 | opts = if ( pflags and FILE_READ_ONLY_VOLUME ) != 0: "ro" else: "rw" 57 | 58 | if ( pflags and FILE_VOLUME_IS_COMPRESSED ) != 0: 59 | opts &= ",compressed" 60 | 61 | if len( opts ) > 0: 62 | opts &= "," 63 | opts &= psutil_get_drive_type( drive_type ) 64 | 65 | result.add( DiskPartition( mountpoint: drive_letter, 66 | device: drive_letter, 67 | fstype: $fs_type, # either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS 68 | opts: opts ) ) 69 | discard SetErrorMode( 0 ) 70 | ``` 71 | 72 | # Type DiskPartition 73 | 74 | ```nim 75 | type DiskPartition* = object of RootObj 76 | device*: string 77 | mountpoint*: string 78 | fstype*: string 79 | opts*: string 80 | 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Psutil-Nim 2 | 3 | > Port of Python [psutil](https://github.com/giampaolo/psutil) to Nim. 4 | 5 | ![](https://img.shields.io/github/languages/top/juancarlospaco/psutil-nim?style=for-the-badge) 6 | ![](https://img.shields.io/github/languages/count/juancarlospaco/psutil-nim?logoColor=green&style=for-the-badge) 7 | ![](https://img.shields.io/github/stars/juancarlospaco/psutil-nim?style=for-the-badge "Star psutil-nim on GitHub!") 8 | ![](https://img.shields.io/maintenance/yes/2020?style=for-the-badge) 9 | ![](https://img.shields.io/github/languages/code-size/juancarlospaco/psutil-nim?style=for-the-badge) 10 | ![](https://img.shields.io/github/issues-raw/juancarlospaco/psutil-nim?style=for-the-badge "Bugs") 11 | ![](https://img.shields.io/github/issues-pr-raw/juancarlospaco/psutil-nim?style=for-the-badge "PRs") 12 | ![](https://img.shields.io/github/commit-activity/y/juancarlospaco/psutil-nim?style=for-the-badge) 13 | ![](https://img.shields.io/github/last-commit/juancarlospaco/psutil-nim?style=for-the-badge "Commits") 14 | 15 | **Pull requests with bug fixes and support for other platforms are welcome!** 16 | 17 | 18 | ## Table of Contents 19 | 20 | - [Background](#background) 21 | - [Installation](#installation) 22 | - [Usage](#usage) 23 | - [Contribute](#contribute) 24 | - [License](#license) 25 | 26 | ## Background 27 | 28 | I love Python and [psutil](https://github.com/giampaolo/psutil) but bundling a Python app is a pain. 29 | Having a psutil library in Nim seemed like the logical next step. 30 | 31 | ## Install 32 | 33 | - ` nimble install psutil` 34 | 35 | This package works Ok as of Nim `1.0.6`. Pull requests welcome. 36 | 37 | 38 | ## Usage 39 | 40 | Just some basic usage below until I get the example apps working and can mirror 41 | what's in psutil's documentation. Take a look at the scripts folder for some 42 | basic examples as well. 43 | 44 | ```nim 45 | import psutil 46 | 47 | echo net_if_addrs() 48 | echo boot_time() 49 | echo users() 50 | ``` 51 | 52 | 53 | ## Troubleshooting 54 | 55 | If you are running on CentOS or RedHat you may or may not find errors with the Network related functions, 56 | complaining about missing Linux C Headers `sockios.h` to Compile, 57 | this is not a Bug on the code but that Distro not having development libraries or having too old versions of it. 58 | 59 | You can try installing the package `kernel-headers` for CentOS/RedHat, 60 | to see if that fixes the problem about missing libraries. 61 | 62 | If you know how to fix that Distro-specific detail feel free to send pull requests. 63 | 64 | The failing functions are: 65 | 66 | ```nim 67 | net_io_counters() 68 | per_nic_net_io_counters() 69 | net_if_stats() 70 | net_connections() 71 | ``` 72 | 73 | You can workaround by using [Distros module](https://nim-lang.org/docs/distros.html#Distribution): 74 | 75 | ```nim 76 | when not detectOs(CentOS): 77 | # Do something here with the Network functions. 78 | echo net_io_counters() 79 | echo per_nic_net_io_counters() 80 | echo net_if_stats() 81 | echo net_connections() 82 | ``` 83 | 84 | 85 | # Stars 86 | 87 | ![Star psutil-nim on GitHub](https://starchart.cc/juancarlospaco/psutil-nim.svg "Star psutil-nim on GitHub!") 88 | 89 | 90 | ## Contribute 91 | 92 | PRs accepted! Adding a single function to any platform is a huge help and can usually be done with less than an hour of work. 93 | 94 | #### License 95 | 96 | - MIT 97 | -------------------------------------------------------------------------------- /scripts/ifconfig.nim: -------------------------------------------------------------------------------- 1 | ## A clone of "ifconfig" on UNIX. 2 | ## $ nim c -r ifconfig.nim 3 | ## lo: 4 | ## stats : speed=0MB, duplex=?, mtu=65536, up=yes 5 | ## incoming : bytes=6889336, pkts=84032, errs=0, drops=0 6 | ## outgoing : bytes=6889336, pkts=84032, errs=0, drops=0 7 | ## IPv4 address : 127.0.0.1 8 | ## netmask : 255.0.0.0 9 | ## IPv6 address : ::1 10 | ## netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 11 | ## MAC address : 00:00:00:00:00:00 12 | ## vboxnet0: 13 | ## stats : speed=10MB, duplex=full, mtu=1500, up=yes 14 | ## incoming : bytes=0, pkts=0, errs=0, drops=0 15 | ## outgoing : bytes=1622766, pkts=9102, errs=0, drops=0 16 | ## IPv4 address : 192.168.33.1 17 | ## broadcast : 192.168.33.255 18 | ## netmask : 255.255.255.0 19 | ## IPv6 address : fe80::800:27ff:fe00:0%vboxnet0 20 | ## netmask : ffff:ffff:ffff:ffff:: 21 | ## MAC address : 0a:00:27:00:00:00 22 | ## broadcast : ff:ff:ff:ff:ff:ff 23 | ## eth0: 24 | ## stats : speed=0MB, duplex=?, mtu=1500, up=yes 25 | ## incoming : bytes=18905596301, pkts=15178374, errs=0, drops=21 26 | ## outgoing : bytes=1913720087, pkts=9543981, errs=0, drops=0 27 | ## IPv4 address : 10.0.0.3 28 | ## broadcast : 10.255.255.255 29 | ## netmask : 255.0.0.0 30 | ## IPv6 address : fe80::7592:1dcf:bcb7:98d6%wlp3s0 31 | ## netmask : ffff:ffff:ffff:ffff:: 32 | ## MAC address : 48:45:20:59:a4:0c 33 | ## broadcast : ff:ff:ff:ff:ff:ff 34 | import sequtils 35 | import tables 36 | when defined(posix): 37 | import posix 38 | else: 39 | import winlean 40 | 41 | import stringinterpolation 42 | 43 | import psutil 44 | 45 | 46 | var af_map = { 47 | AF_INET: "IPv4", 48 | AF_INET6: "IPv6", 49 | AF_PACKET.cint: "MAC", 50 | }.toTable() 51 | 52 | var duplex_map = { 53 | NIC_DUPLEX_FULL: "full", 54 | NIC_DUPLEX_HALF: "half", 55 | NIC_DUPLEX_UNKNOWN: "?", 56 | }.toTable() 57 | 58 | 59 | proc main() = 60 | let stats = psutil.net_if_stats() 61 | let io_counters = psutil.per_nic_net_io_counters() 62 | 63 | for nic, addrs in psutil.net_if_addrs(): 64 | echo("%s:".format(nic)) 65 | if nic in stats: 66 | let st = stats[nic] 67 | stdout.write(" stats : ") 68 | echo("speed=%sMB, duplex=%s, mtu=%s, up=%s".format( 69 | st.speed, duplex_map[st.duplex], st.mtu, 70 | if st.isup: "yes" else: "no")) 71 | if nic in io_counters: 72 | let io = io_counters[nic] 73 | stdout.write(" incoming : ") 74 | echo("bytes=%s, pkts=%s, errs=%s, drops=%s".format( 75 | io.bytes_recv, io.packets_recv, io.errin, io.dropin)) 76 | stdout.write(" outgoing : ") 77 | echo("bytes=%s, pkts=%s, errs=%s, drops=%s".format( 78 | io.bytes_sent, io.packets_sent, io.errout, io.dropout)) 79 | for addr in addrs: 80 | if addr.address != nil: 81 | stdout.write(" %-4s".format(af_map.mgetOrPut(addr.family.cint, 82 | $addr.family))) 83 | echo(" address : %s".format(addr.address)) 84 | if addr.broadcast != nil: 85 | echo(" broadcast : %s".format(addr.broadcast)) 86 | if addr.netmask != nil: 87 | echo(" netmask : %s".format(addr.netmask)) 88 | if addr.ptp != nil: 89 | echo(" p2p : %s".format(addr.ptp)) 90 | echo("") 91 | 92 | 93 | when isMainModule: 94 | main() 95 | -------------------------------------------------------------------------------- /doc/linux/functions/virtual_memory.md: -------------------------------------------------------------------------------- 1 | # virtual_memory 2 | 3 | This function will return type VirtualMemory which contains information on the virtual memory of the 4 | system 5 | 6 | 7 | # The function 8 | ```nim 9 | proc virtual_memory*(): VirtualMemory = 10 | ## Report virtual memory stats. 11 | ## This implementation matches "free" and "vmstat -s" cmdline 12 | ## utility values and procps-ng-3.3.12 source was used as a reference 13 | ## (2016-09-18): 14 | ## https://gitlab.com/procps-ng/procps/blob/ 15 | ## 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c 16 | ## For reference, procps-ng-3.3.10 is the version available on Ubuntu 17 | ## 16.04. 18 | ## Note about "available" memory: up until psutil 4.3 it was 19 | ## calculated as "avail = (free + buffers + cached)". Now 20 | ## "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as 21 | ## it's more accurate. 22 | ## That matches "available" column in newer versions of "free". 23 | 24 | var missing_fields = newSeq[string]() 25 | var mems = newTable[string, int]() 26 | for line in lines( PROCFS_PATH / "meminfo" ): 27 | let fields = line.splitWhitespace() 28 | mems[fields[0]] = parseInt(fields[1]) * 1024 29 | 30 | # /proc doc states that the available fields in /proc/meminfo vary 31 | # by architecture and compile options, but these 3 values are also 32 | # returned by sysinfo(2); as such we assume they are always there. 33 | let total = mems["MemTotal:"] 34 | let free = mems["MemFree:"] 35 | let buffers = mems["Buffers:"] 36 | 37 | var cached = 0 38 | try: 39 | cached = mems["Cached:"] 40 | # "free" cmdline utility sums reclaimable to cached. 41 | # Older versions of procps used to add slab memory instead. 42 | # This got changed in: 43 | # https://gitlab.com/procps-ng/procps/commit/ 44 | # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 45 | cached += mems.getOrDefault("SReclaimable:") # since kernel 2.6.19 46 | except KeyError: 47 | missing_fields.add("cached") 48 | 49 | var shared = 0 50 | try: 51 | shared = mems["Shmem:"] # since kernel 2.6.32 52 | except KeyError: 53 | try: 54 | shared = mems["MemShared:"] # kernels 2.4 55 | except KeyError: 56 | missing_fields.add("shared") 57 | 58 | var active = 0 59 | try: 60 | active = mems["Active:"] 61 | except KeyError: 62 | missing_fields.add("active") 63 | 64 | var inactive = 0 65 | try: 66 | inactive = mems["Inactive:"] 67 | except KeyError: 68 | try: 69 | inactive = mems["Inact_dirty:"] + mems["Inact_clean:"] + mems["Inact_laundry:"] 70 | except KeyError: 71 | missing_fields.add("inactive") 72 | 73 | var used = total - free - cached - buffers 74 | if used < 0: 75 | # May be symptomatic of running within a LCX container where such 76 | # values will be dramatically distorted over those of the host. 77 | used = total - free 78 | 79 | # - starting from 4.4.0 we match free's "available" column. 80 | # Before 4.4.0 we calculated it as (free + buffers + cached) 81 | # which matched htop. 82 | # - free and htop available memory differs as per: 83 | # http://askubuntu.com/a/369589 84 | # http://unix.stackexchange.com/a/65852/168884 85 | # - MemAvailable has been introduced in kernel 3.14 86 | var avail = 0 87 | try: 88 | avail = mems["MemAvailable:"] 89 | except KeyError: 90 | avail = calculate_avail_vmem(mems) 91 | 92 | if avail < 0: 93 | avail = 0 94 | missing_fields.add("available") 95 | 96 | # If avail is greater than total or our calculation overflows, 97 | # that's symptomatic of running within a LCX container where such 98 | # values will be dramatically distorted over those of the host. 99 | # https://gitlab.com/procps-ng/procps/blob/ 100 | # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 101 | if avail > total: 102 | avail = free 103 | 104 | let percent = usage_percent( (total - avail), total, places=1 ) 105 | 106 | # Warn about missing metrics which are set to 0. 107 | if len( missing_fields ) > 0: 108 | echo( missing_fields.join( ", " ), 109 | " memory stats couldn't be determined and ", 110 | if len(missing_fields) == 1: "was" else: "were", 111 | " set to 0" ) 112 | 113 | return VirtualMemory( total:total, avail:avail, percent:percent, used:used, 114 | free:free, active:active, inactive:inactive, 115 | buffers:buffers, cached:cached, shared:shared ) 116 | 117 | ``` 118 | 119 | # The type 120 | 121 | - [VirtualMemory](../types/VirtualMemory.md) 122 | -------------------------------------------------------------------------------- /src/psutil/arch/bsd_osx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int psutil_get_nic_speed(int ifm_active) { 9 | // Determine NIC speed. Taken from: 10 | // http://www.i-scream.org/libstatgrab/ 11 | // Assuming only ETHER devices 12 | switch(IFM_TYPE(ifm_active)) { 13 | case IFM_ETHER: 14 | switch(IFM_SUBTYPE(ifm_active)) { 15 | #if defined(IFM_HPNA_1) && ((!defined(IFM_10G_LR)) \ 16 | || (IFM_10G_LR != IFM_HPNA_1)) 17 | // HomePNA 1.0 (1Mb/s) 18 | case(IFM_HPNA_1): 19 | return 1; 20 | #endif 21 | // 10 Mbit 22 | case(IFM_10_T): // 10BaseT - RJ45 23 | case(IFM_10_2): // 10Base2 - Thinnet 24 | case(IFM_10_5): // 10Base5 - AUI 25 | case(IFM_10_STP): // 10BaseT over shielded TP 26 | case(IFM_10_FL): // 10baseFL - Fiber 27 | return 10; 28 | // 100 Mbit 29 | case(IFM_100_TX): // 100BaseTX - RJ45 30 | case(IFM_100_FX): // 100BaseFX - Fiber 31 | case(IFM_100_T4): // 100BaseT4 - 4 pair cat 3 32 | case(IFM_100_VG): // 100VG-AnyLAN 33 | case(IFM_100_T2): // 100BaseT2 34 | return 100; 35 | // 1000 Mbit 36 | case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber 37 | case(IFM_1000_LX): // 1000baseLX - single-mode fiber 38 | case(IFM_1000_CX): // 1000baseCX - 150ohm STP 39 | #if defined(IFM_1000_TX) && !defined(PSUTIL_OPENBSD) 40 | // FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h 41 | case(IFM_1000_TX): 42 | #endif 43 | #ifdef IFM_1000_FX 44 | case(IFM_1000_FX): 45 | #endif 46 | #ifdef IFM_1000_T 47 | case(IFM_1000_T): 48 | #endif 49 | return 1000; 50 | #if defined(IFM_10G_SR) || defined(IFM_10G_LR) || defined(IFM_10G_CX4) \ 51 | || defined(IFM_10G_T) 52 | #ifdef IFM_10G_SR 53 | case(IFM_10G_SR): 54 | #endif 55 | #ifdef IFM_10G_LR 56 | case(IFM_10G_LR): 57 | #endif 58 | #ifdef IFM_10G_CX4 59 | case(IFM_10G_CX4): 60 | #endif 61 | #ifdef IFM_10G_TWINAX 62 | case(IFM_10G_TWINAX): 63 | #endif 64 | #ifdef IFM_10G_TWINAX_LONG 65 | case(IFM_10G_TWINAX_LONG): 66 | #endif 67 | #ifdef IFM_10G_T 68 | case(IFM_10G_T): 69 | #endif 70 | return 10000; 71 | #endif 72 | #if defined(IFM_2500_SX) 73 | #ifdef IFM_2500_SX 74 | case(IFM_2500_SX): 75 | #endif 76 | return 2500; 77 | #endif // any 2.5GBit stuff... 78 | // We don't know what it is 79 | default: 80 | return 0; 81 | } 82 | break; 83 | 84 | #ifdef IFM_TOKEN 85 | case IFM_TOKEN: 86 | switch(IFM_SUBTYPE(ifm_active)) { 87 | case IFM_TOK_STP4: // Shielded twisted pair 4m - DB9 88 | case IFM_TOK_UTP4: // Unshielded twisted pair 4m - RJ45 89 | return 4; 90 | case IFM_TOK_STP16: // Shielded twisted pair 16m - DB9 91 | case IFM_TOK_UTP16: // Unshielded twisted pair 16m - RJ45 92 | return 16; 93 | #if defined(IFM_TOK_STP100) || defined(IFM_TOK_UTP100) 94 | #ifdef IFM_TOK_STP100 95 | case IFM_TOK_STP100: // Shielded twisted pair 100m - DB9 96 | #endif 97 | #ifdef IFM_TOK_UTP100 98 | case IFM_TOK_UTP100: // Unshielded twisted pair 100m - RJ45 99 | #endif 100 | return 100; 101 | #endif 102 | // We don't know what it is 103 | default: 104 | return 0; 105 | } 106 | break; 107 | #endif 108 | 109 | #ifdef IFM_FDDI 110 | case IFM_FDDI: 111 | switch(IFM_SUBTYPE(ifm_active)) { 112 | // We don't know what it is 113 | default: 114 | return 0; 115 | } 116 | break; 117 | #endif 118 | case IFM_IEEE80211: 119 | switch(IFM_SUBTYPE(ifm_active)) { 120 | case IFM_IEEE80211_FH1: // Frequency Hopping 1Mbps 121 | case IFM_IEEE80211_DS1: // Direct Sequence 1Mbps 122 | return 1; 123 | case IFM_IEEE80211_FH2: // Frequency Hopping 2Mbps 124 | case IFM_IEEE80211_DS2: // Direct Sequence 2Mbps 125 | return 2; 126 | case IFM_IEEE80211_DS5: // Direct Sequence 5Mbps 127 | return 5; 128 | case IFM_IEEE80211_DS11: // Direct Sequence 11Mbps 129 | return 11; 130 | case IFM_IEEE80211_DS22: // Direct Sequence 22Mbps 131 | return 22; 132 | // We don't know what it is 133 | default: 134 | return 0; 135 | } 136 | break; 137 | 138 | default: 139 | return 0; 140 | } 141 | } -------------------------------------------------------------------------------- /src/psutil/arch/osx/socket.nim: -------------------------------------------------------------------------------- 1 | import ./types 2 | 3 | const TSI_T_NTIMERS = 4 4 | 5 | 6 | type vinfo_stat* = object # https://github.com/alecmocatta/socketstat/blob/062fae4f10d673fb447bfc9c5748f14dbd86c46d/src/mac.rs 7 | vst_dev*: uint32 # [XSI] ID of device containing file 8 | vst_mode*: uint16 # [XSI] Mode of file (see below) 9 | vst_nlink*: uint16 # [XSI] Number of hard links 10 | vst_ino*: uint64 # [XSI] File serial number 11 | vst_uid*: uid_t # [XSI] User ID of the file 12 | vst_gid*: gid_t # [XSI] Group ID of the file 13 | vst_atime*: int64 # [XSI] Time of last access 14 | vst_atimensec*: int64 # nsec of last access 15 | vst_mtime*: int64 # [XSI] Last data modification time 16 | vst_mtimensec*: int64 # last data modification nsec 17 | vst_ctime*: int64 # [XSI] Time of last status change 18 | vst_ctimensec*: int64 # nsec of last status change 19 | vst_birthtime*: int64 # File creation time(birth) 20 | vst_birthtimensec*: int64 # nsec of File creation time 21 | vst_size*: off_t # [XSI] file size, in bytes 22 | vst_blocks*: int64 # [XSI] blocks allocated for file 23 | vst_blksize*: int32 # [XSI] optimal blocksize for I/O 24 | vst_flags*: uint32 # user defined flags for file 25 | vst_gen*: uint32 # file generation number 26 | vst_rdev*: uint32 # [XSI] Device ID 27 | vst_qspare*: array[2, int64] # RESERVED: DO NOT USE! 28 | 29 | 30 | type sockbuf_info* = object 31 | sbi_cc*: uint32 32 | sbi_hiwat*: uint32 # SO_RCVBUF, SO_SNDBUF 33 | sbi_mbcnt*: uint32 34 | sbi_mbmax*: uint32 35 | sbi_lowat*: uint32 36 | sbi_flags*: cshort 37 | sbi_timeo*: cshort 38 | 39 | 40 | type insi_v4* = object 41 | in4_tos*: cuchar # u_char type of service 42 | 43 | 44 | type insi_v6* = object 45 | in6_hlim*: uint8 46 | in6_cksum*: cint 47 | in6_ifindex*: cushort 48 | in6_hops*: cshort 49 | 50 | type in_addr = object 51 | s_addr: culong # load with inet_pton() 52 | 53 | 54 | type in4in6_addr* {.importc: "struct in4in6_addr", 55 | header: "".} = object 56 | i46a_pad32*: array[3, uint32] 57 | i46a_addr4*: in_addr 58 | 59 | 60 | type in6_addr* {.importc: "struct in6_addr", header: "".} = object 61 | 62 | 63 | type address* {.importc: "struct addr", header: "".} = object 64 | ina_46*: in4in6_addr 65 | ina_6*: in6_addr 66 | 67 | 68 | type in_sockinfo* {.importc: "struct in_sockinfo", 69 | header: "".} = object 70 | insi_fport*: cint # foreign port 71 | insi_lport*: cint # local port 72 | insi_gencnt*: uint64 # generation count of this instance 73 | insi_flags*: uint32 # generic IP/datagram flags 74 | insi_flow*: uint32 75 | insi_vflag*: uint8 # ini_IPV4 or ini_IPV6 76 | insi_ip_ttl*: uint8 # time to live proto 77 | rfu_1*: uint32 # reserved 78 | # protocol dependent part 79 | insi_faddr*: address # foreign host table entry 80 | insi_laddr*: address # local host table entry 81 | insi_v4*: insi_v4 82 | insi_v6*: insi_v6 83 | 84 | 85 | type tcp_sockinfo* = object 86 | tcpsi_ini*: in_sockinfo 87 | tcpsi_state*: cint 88 | tcpsi_timer*: array[TSI_T_NTIMERS, cint] 89 | tcpsi_mss*: cint 90 | tcpsi_flags*: uint32 91 | rfu_1*: uint32 # reserved */ 92 | tcpsi_tp*: uint64 # opaque handle of TCP protocol control block */ 93 | 94 | 95 | type sockaddr_un* {.importc: "struct sockaddr_un", header: "", 96 | incompleteStruct, nodecl.} = object 97 | sun_path*: ptr char 98 | 99 | 100 | const SOCK_MAXADDRLEN = 0xff 101 | 102 | 103 | type union_unsi_caddr = object 104 | ua_sun*: sockaddr_un 105 | ua_dummy*: array[SOCK_MAXADDRLEN, char] 106 | 107 | 108 | type un_sockinfo* {.importc: "struct sockaddr_un", header: "", 109 | incompleteStruct, nodecl.} = object 110 | unsi_conn_so*: uint64 # opaque handle of connected socket 111 | unsi_conn_pcb*: uint64 # opaque handle of connected protocol control block 112 | unsi_addr*: union_unsi_caddr 113 | unsi_caddr*: union_unsi_caddr 114 | # union { 115 | # struct sockaddr_un ua_sun; 116 | # char ua_dummy[SOCK_MAXADDRLEN]; 117 | # } unsi_addr; # bound address */ 118 | # union { 119 | # struct sockaddr_un ua_sun; 120 | # char ua_dummy[SOCK_MAXADDRLEN]; 121 | # } unsi_caddr; # address of socket connected to */ 122 | 123 | 124 | type pri* = object 125 | pri_in*: in_sockinfo # SOCKINFO_IN */ 126 | pri_tcp*: tcp_sockinfo # SOCKINFO_TCP */ 127 | pri_un*: un_sockinfo # SOCKINFO_UN */ 128 | # pri_ndrv*: ndrv_info # SOCKINFO_NDRV */ 129 | # pri_kern_event*: kern_event_info # SOCKINFO_KERN_EVENT */ 130 | # pri_kern_ctl*: kern_ctl_info # SOCKINFO_KERN_CTL */ 131 | hack_to_avoid_copying_more_structs*: array[524, uint8] 132 | 133 | 134 | type socket_info* {.importc: "struct socket_info", 135 | header: "".} = object 136 | soi_stat*: vinfo_stat 137 | soi_so*: uint64 # opaque handle of socket 138 | soi_pcb*: uint64 # opaque handle of protocol control block 139 | soi_type*: cint 140 | soi_protocol*: cint 141 | soi_family*: cint 142 | soi_options*: cshort 143 | soi_linger*: cshort 144 | soi_state*: cshort 145 | soi_qlen*: cshort 146 | soi_incqlen*: cshort 147 | soi_qlimit*: cshort 148 | soi_timeo*: cshort 149 | soi_error*: uint16 # ushort 150 | soi_oobmark*: uint32 151 | soi_rcv*: sockbuf_info 152 | soi_snd*: sockbuf_info 153 | soi_kind*: cint 154 | rfu_1*: uint32 # reserved 155 | soi_proto*: pri 156 | -------------------------------------------------------------------------------- /src/psutil.nim: -------------------------------------------------------------------------------- 1 | ##[ 2 | Linux To Do - 3 | cpu_times_percent(interval=None, percpu=False) 4 | process_iter() 5 | wait_procs(procs, timeout=None, callback=None) 6 | ]## 7 | import math 8 | import os 9 | import sequtils 10 | import strutils 11 | import tables 12 | 13 | import psutil/common 14 | 15 | when defined(posix): 16 | import psutil/psutil_posix 17 | 18 | when defined(linux): 19 | import psutil/psutil_linux as platform 20 | 21 | when defined(windows): 22 | import psutil/psutil_windows as platform 23 | 24 | when defined(macosx): 25 | import psutil/psutil_macosx as platform 26 | ################################################################################ 27 | var g_last_cpu_times: CPUTimes 28 | var g_last_per_cpu_times: seq[CPUTimes] 29 | try: 30 | g_last_cpu_times = cpu_times() 31 | g_last_per_cpu_times = per_cpu_times() 32 | except IOError: 33 | discard 34 | 35 | var g_total_phymem: int 36 | 37 | 38 | ################################################################################ 39 | proc pid_exists*(pid: int): bool = 40 | ## Return True if given PID exists in the current process list. 41 | ## This is faster than doing "pid in psutil.pids()" and should be preferred. 42 | 43 | if pid < 0: 44 | return false 45 | 46 | elif pid == 0 and defined(posix): 47 | # On POSIX we use os.kill() to determine PID existence. 48 | # According to "man 2 kill" PID 0 has a special meaning 49 | # though: it refers to <> and that is not we want 51 | # to do here. 52 | return pid in pids() 53 | 54 | else: 55 | return platform.pid_exists(pid) 56 | 57 | 58 | proc cpu_count*(logical = true): int = 59 | # Return the number of logical CPUs in the system. 60 | # If logical is False return the number of physical cores only 61 | # (e.g. hyper thread CPUs are excluded). 62 | # Return 0 if undetermined. 63 | if logical: platform.cpu_count_logical().int 64 | else: platform.cpu_count_physical() 65 | 66 | 67 | proc calculate(t1, t2: CPUTimes): float = 68 | when defined(windows): 69 | let t1_all = t1.user + t1.system + t1.idle + t1.interrupt + t1.dpc 70 | else: 71 | let t1_all = t1.user + t1.nice + t1.system + t1.idle + t1.iowait + 72 | t1.irq + t1.softirq + t1.steal + t1.guest + t1.guest_nice 73 | let t1_busy = t1_all - t1.idle 74 | 75 | when defined(windows): 76 | let t2_all = t2.user + t2.system + t2.idle + t2.interrupt + t2.dpc 77 | else: 78 | let t2_all = t2.user + t2.nice + t2.system + t2.idle + t2.iowait + 79 | t2.irq + t2.softirq + t2.steal + t2.guest + t2.guest_nice 80 | let t2_busy = t2_all - t2.idle 81 | 82 | # this usually indicates a float precision issue 83 | if t2_busy <= t1_busy: 84 | return 0.0 85 | 86 | let busy_delta = t2_busy - t1_busy 87 | let all_delta = t2_all - t1_all 88 | let busy_perc = (busy_delta / all_delta) * 100 89 | return round(busy_perc, 1) 90 | 91 | 92 | proc cpu_percent*(interval = 0.0): float = 93 | ## Return a float representing the current system-wide CPU utilization as a percentage. 94 | ## 95 | ## When interval is > 0.0 compares system CPU times elapsed before 96 | ## and after the interval (blocking). 97 | ## 98 | ## When interval is == 0.0 compares system CPU times elapsed since last 99 | ## call or module import, returning immediately (non 100 | ## blocking). That means the first time this is called it will 101 | ## return a meaningless 0.0 value which you should ignore. 102 | ## In this case is recommended for accuracy that this function be 103 | ## called with at least 0.1 seconds between calls. 104 | ## When percpu is True returns a list of floats representing the 105 | ## utilization as a percentage for each CPU. 106 | ## First element of the list refers to first CPU, second element 107 | ## to second CPU and so on. 108 | ## The order of the list is consistent across calls. 109 | ## Examples: 110 | ## >>> # blocking, system-wide 111 | ## >>> psutil.cpu_percent(interval=1) 112 | ## 2.0 113 | ## >>> 114 | ## >>> # blocking, per-cpu 115 | ## >>> psutil.cpu_percent(interval=1, percpu=True) 116 | ## [2.0, 1.0] 117 | ## >>> 118 | ## >>> # non-blocking (percentage since last call) 119 | ## >>> psutil.cpu_percent(interval=None) 120 | ## 2.9 121 | ## >>> 122 | 123 | let blocking = interval > 0.0 124 | if interval < 0: 125 | raise newException(ValueError, "interval is not positive (got $1)" % $interval) 126 | 127 | # system-wide usage 128 | var t1 = g_last_cpu_times 129 | if blocking: 130 | t1 = cpu_times() 131 | sleep(int(interval * 1000)) 132 | else: 133 | var empty: CPUTimes 134 | if t1 == empty: 135 | # Something bad happened at import time. We'll 136 | # get a meaningful result on the next call. See: 137 | # https://github.com/giampaolo/psutil/pull/715 138 | t1 = cpu_times() 139 | g_last_cpu_times = cpu_times() 140 | return calculate(t1, g_last_cpu_times) 141 | 142 | 143 | proc per_cpu_percent*(interval = 0.0): seq[float] = 144 | let blocking = interval > 0.0 145 | if interval < 0: 146 | raise newException(ValueError, "interval is not positive (got $1)" % $interval) 147 | 148 | result = newSeq[float]() 149 | var tot1 = g_last_per_cpu_times 150 | if blocking: 151 | tot1 = per_cpu_times() 152 | sleep(int(interval * 1000)) 153 | else: 154 | if not tot1.len > 0: 155 | # Something bad happened at import time. We'll 156 | # get a meaningful result on the next call. See: 157 | # https://github.com/giampaolo/psutil/pull/715 158 | tot1 = per_cpu_times() 159 | 160 | g_last_per_cpu_times = per_cpu_times() 161 | for pair in zip(tot1, g_last_per_cpu_times): 162 | result.add(calculate(pair[0], pair[1])) 163 | return result 164 | 165 | 166 | proc virtual_memory*(): VirtualMemory = 167 | ## Return statistics about system memory usage as a namedtuple 168 | ## including the following fields, expressed in bytes: 169 | ## - total: 170 | ## total physical memory available. 171 | ## - available: 172 | ## the memory that can be given instantly to processes without the 173 | ## system going into swap. 174 | ## This is calculated by summing different memory values depending 175 | ## on the platform and it is supposed to be used to monitor actual 176 | ## memory usage in a cross platform fashion. 177 | ## - percent: 178 | ## the percentage usage calculated as (total - available) / total * 100 179 | ## - used: 180 | ## memory used, calculated differently depending on the platform and 181 | ## designed for informational purposes only: 182 | ## OSX: active + inactive + wired 183 | ## BSD: active + wired + cached 184 | ## LINUX: total - free 185 | ## - free: 186 | ## memory not being used at all (zeroed) that is readily available; 187 | ## note that this doesn't reflect the actual memory available 188 | ## (use 'available' instead) 189 | ## Platform-specific fields: 190 | ## - active (UNIX): 191 | ## memory currently in use or very recently used, and so it is in RAM. 192 | ## - inactive (UNIX): 193 | ## memory that is marked as not used. 194 | ## - buffers (BSD, Linux): 195 | ## cache for things like file system metadata. 196 | ## - cached (BSD, OSX): 197 | ## cache for various things. 198 | ## - wired (OSX, BSD): 199 | ## memory that is marked to always stay in RAM. It is never moved to disk. 200 | ## - shared (BSD): 201 | ## memory that may be simultaneously accessed by multiple processes. 202 | ## The sum of 'used' and 'available' does not necessarily equal total. 203 | ## On Windows 'available' and 'free' are the same. 204 | 205 | result = platform.virtual_memory() 206 | # cached for later use in Process.memory_percent() 207 | g_total_phymem = result.total 208 | 209 | 210 | proc net_io_counters*(): NetIO = 211 | ## Return total network I/O statistics including the following fields: 212 | ## - bytes_sent: number of bytes sent 213 | ## - bytes_recv: number of bytes received 214 | ## - packets_sent: number of packets sent 215 | ## - packets_recv: number of packets received 216 | ## - errin: total number of errors while receiving 217 | ## - errout: total number of errors while sending 218 | ## - dropin: total number of incoming packets which were dropped 219 | ## - dropout: total number of outgoing packets which were dropped 220 | ## (always 0 on OSX and BSD) 221 | 222 | let raw_counters = platform.per_nic_net_io_counters() 223 | if len(raw_counters) == 0: 224 | raise newException(Exception, "couldn't find any network interface") 225 | 226 | for _, counter in raw_counters: 227 | result.bytes_sent += counter.bytes_sent 228 | result.bytes_recv += counter.bytes_recv 229 | result.packets_sent += counter.packets_sent 230 | result.packets_recv += counter.packets_recv 231 | result.errin += counter.errin 232 | result.errout += counter.errout 233 | result.dropin += counter.dropin 234 | result.dropout += counter.dropout 235 | 236 | 237 | proc disk_io_counters: DiskIO = 238 | ## Return system disk I/O statistics as a namedtuple including 239 | ## the following fields: 240 | ## - read_count: number of reads 241 | ## - write_count: number of writes 242 | ## - read_bytes: number of bytes read 243 | ## - write_bytes: number of bytes written 244 | ## - read_time: time spent reading from disk (in milliseconds) 245 | ## - write_time: time spent writing to disk (in milliseconds) 246 | ## If perdisk is True return the same information for every 247 | ## physical disk installed on the system as a dictionary 248 | ## with partition names as the keys and the namedtuple 249 | ## described above as the values. 250 | ## On recent Windows versions 'diskperf -y' command may need to be 251 | ## executed first otherwise this function won't find any disk. 252 | 253 | let counters = per_disk_io_counters() 254 | if len(counters) == 0: 255 | raise newException(Exception, "couldn't find any physical disk") 256 | 257 | for counter in counters.values(): 258 | result.read_count += counter.read_count 259 | result.write_count += counter.write_count 260 | result.read_bytes += counter.read_bytes 261 | result.write_bytes += counter.write_bytes 262 | result.read_time += counter.read_time 263 | result.write_time += counter.write_time 264 | 265 | when defined(linux): 266 | result.read_merged_count += counter.read_merged_count 267 | result.write_merged_count += counter.write_merged_count 268 | result.busy_time += counter.busy_time 269 | 270 | 271 | ################################################################################ 272 | export tables 273 | 274 | export NicDuplex 275 | export AF_PACKET 276 | 277 | export net_if_addrs 278 | export boot_time 279 | export uptime 280 | export users 281 | export pids 282 | export cpu_times 283 | export per_cpu_times 284 | export cpu_stats 285 | export cpu_count 286 | export disk_usage 287 | export swap_memory 288 | export disk_partitions 289 | export net_io_counters 290 | export per_nic_net_io_counters 291 | 292 | export disk_io_counters 293 | export per_disk_io_counters 294 | export net_if_stats 295 | export net_connections 296 | -------------------------------------------------------------------------------- /src/psutil/psutil_posix.nim: -------------------------------------------------------------------------------- 1 | import posix, strutils, tables 2 | import common 3 | 4 | const bsdPlatform = defined(macosx) or defined(freebsd) or 5 | defined(netbsd) or defined(openbsd) or 6 | defined(dragonfly) 7 | 8 | const IFHWADDRLEN* = 6 9 | const IF_NAMESIZE* = 16 10 | const IFNAMSIZ* = IF_NAMESIZE 11 | 12 | var AF_PACKET* {.header: "".}: cint 13 | var IFF_BROADCAST* {.header: "".}: uint 14 | var IFF_POINTOPOINT* {.header: "".}: uint 15 | 16 | var NI_MAXHOST* {.header: "".}: cint 17 | when bsdPlatform: 18 | var IFF_UP {.header: "".}: uint 19 | var SIOCGIFFLAGS {.header: "".}: uint 20 | var SIOCGIFMTU {.header: "".}: uint 21 | 22 | else: 23 | var IFF_UP {.header: "".}: uint 24 | var SIOCGIFFLAGS {.header: "".}: uint 25 | var SIOCGIFMTU {.header: "".}: uint 26 | 27 | type ifaddrs = object 28 | pifaddrs: ptr ifaddrs # Next item in list 29 | ifa_name: cstring # Name of interface 30 | ifa_flags: uint # Flags from SIOCGIFFLAGS 31 | ifa_addr: ptr SockAddr # Address of interface 32 | ifa_netmask: ptr SockAddr # Netmask of interface 33 | ifu_broadaddr: ptr SockAddr # Broadcast address of interface 34 | ifa_data: pointer # Address-specific data 35 | 36 | type sockaddr_ll = object 37 | sll_family: uint16 # Always AF_PACKET 38 | sll_protocol: uint16 # Physical-layer protocol */ 39 | sll_ifindex: int32 # Interface number */ 40 | sll_hatype: uint16 # ARP hardware type */ 41 | ll_pkttype: uint8 # Packet type */ 42 | sll_halen: uint8 # Length of address */ 43 | sll_addr: array[8, uint8] # Physical-layer address */ 44 | 45 | type 46 | ifmap* = object 47 | mem_start*: culong 48 | mem_end*: culong 49 | base_addr*: cushort 50 | irq*: cuchar 51 | dma*: cuchar 52 | port*: cuchar ## # 3 bytes spare 53 | 54 | type 55 | INNER_C_UNION_9261176668105079294* {.union.} = object 56 | ifrn_name*: array[IFNAMSIZ, char] ## # Interface name, e.g. "en0". 57 | 58 | INNER_C_UNION_7660000764852079517* {.union.} = object 59 | ifru_addr*: SockAddr 60 | ifru_dstaddr*: SockAddr 61 | ifru_broadaddr*: SockAddr 62 | ifru_netmask*: SockAddr 63 | ifru_hwaddr*: SockAddr 64 | ifru_flags*: cshort 65 | ifru_ivalue*: cint 66 | ifru_mtu*: cint 67 | ifru_map*: ifmap 68 | ifru_slave*: array[IFNAMSIZ, char] ## # Just fits the size 69 | ifru_newname*: array[IFNAMSIZ, char] 70 | ifru_data*: pointer 71 | 72 | ifreq* = object 73 | ifr_ifrn*: INNER_C_UNION_9261176668105079294 74 | ifr_ifru*: INNER_C_UNION_7660000764852079517 75 | 76 | 77 | ################################################################################ 78 | proc ioctl*(f: FileHandle, device: uint, 79 | data: pointer): int {.header: "".} 80 | proc getifaddrs(ifap: var ptr ifaddrs): int {.header: "".} 81 | proc freeifaddrs(ifap: ptr ifaddrs): void {.header: "".} 82 | 83 | proc psutil_convert_ipaddr(address: ptr SockAddr, 84 | family: posix.TSa_Family): string 85 | 86 | 87 | proc pid_exists*(pid: int): bool = 88 | ## Check whether pid exists in the current process table. 89 | if pid == 0: 90 | # According to "man 2 kill" PID 0 has a special meaning: 91 | # it refers to <> so we don't want to go any further. 93 | # If we get here it means this UNIX platform *does* have 94 | # a process with id 0. 95 | return true 96 | 97 | let ret_code = kill(pid.int32, 0) 98 | 99 | if ret_code == 0: return true 100 | 101 | # ESRCH == No such process 102 | if errno == ESRCH: return false 103 | 104 | # EPERM clearly means there's a process to deny access to 105 | elif errno == EPERM: return true 106 | 107 | # According to "man 2 kill" possible error values are 108 | # (EINVAL, EPERM, ESRCH) therefore we should never get 109 | # here. If we do let's be explicit in considering this 110 | # an error. 111 | else: raise newException(OSError, "Unknown error from pid_exists: " & $errno) 112 | 113 | 114 | proc net_if_addrs*(): Table[string, seq[Address]] = 115 | ## Return the addresses associated to each NIC (network interface card) 116 | ## installed on the system as a table whose keys are the NIC names and 117 | ## value is a seq of Addresses for each address assigned to the NIC. 118 | ## 119 | ## *family* can be either AF_INET, AF_INET6, AF_LINK, which refers to a MAC address. 120 | ## *address* is the primary address and it is always set. 121 | ## *netmask*, *broadcast* and *ptp* may be ``None``. 122 | ## *ptp* stands for "point to point" and references the destination address on a point to point interface (typically a VPN). 123 | ## *broadcast* and *ptp* are mutually exclusive. 124 | ## *netmask*, *broadcast* and *ptp* are not supported on Windows and are set to nil. 125 | var interfaces: ptr ifaddrs 126 | var current: ptr ifaddrs 127 | let ret_code = getifaddrs(interfaces) 128 | if ret_code == -1: 129 | echo("net_if_addrs error: ", strerror(errno)) 130 | return result 131 | 132 | result = initTable[string, seq[Address]]() 133 | 134 | current = interfaces 135 | while current != nil: 136 | let name = $current.ifa_name 137 | let family = current.ifa_addr.sa_family 138 | let address = psutil_convert_ipaddr(current.ifa_addr, family) 139 | let netmask = psutil_convert_ipaddr(current.ifa_netmask, family) 140 | let bc_or_ptp = psutil_convert_ipaddr(current.ifu_broadaddr, family) 141 | let broadcast = if (current.ifa_flags and IFF_BROADCAST) != 142 | 0: bc_or_ptp else: "" 143 | # ifu_broadcast and ifu_ptp are a union in C, but we don't really care what C calls it 144 | let ptp = if (current.ifa_flags and IFF_POINTOPOINT) != 145 | 0: bc_or_ptp else: "" 146 | 147 | if not(name in result): result[name] = newSeq[Address]() 148 | result[name].add(Address(family: family, # psutil_posix.nim(138, 42) Error: type mismatch: got but expected 'TSa_Family = uint16' 149 | address: address, 150 | netmask: netmask, 151 | broadcast: broadcast, 152 | ptp: ptp)) 153 | 154 | current = current.pifaddrs 155 | 156 | freeifaddrs(interfaces) 157 | 158 | 159 | proc psutil_convert_ipaddr(address: ptr SockAddr, 160 | family: posix.TSa_Family): string = 161 | result = newString(NI_MAXHOST) 162 | var addrlen: Socklen 163 | var resultLen: Socklen = NI_MAXHOST.uint32 164 | 165 | if address == nil: 166 | return "" 167 | 168 | if family.int == AF_INET or family.int == AF_INET6: 169 | if family.int == AF_INET: 170 | addrlen = sizeof(SockAddr_in).uint32 171 | else: 172 | addrlen = sizeof(SockAddr_in6).uint32 173 | 174 | let err = getnameinfo(address, addrlen, result, resultLen, nil, 0, NI_NUMERICHOST) 175 | if err != 0: 176 | # // XXX we get here on FreeBSD when processing 'lo' / AF_INET6 177 | # // broadcast. Not sure what to do other than returning None. 178 | # // ifconfig does not show anything BTW. 179 | return "" 180 | 181 | else: 182 | return result.strip(chars = Whitespace + {'\x00'}) 183 | 184 | elif defined(linux) and family.int == AF_PACKET: 185 | var hw_address = cast[ptr sockaddr_ll](address) 186 | # TODO - this is going to break on non-Ethernet addresses (e.g. mac firewire - 8 bytes) 187 | # psutil actually handles this, i just wanted to test that it was working 188 | return "$1:$2:$3:$4:$5:$6".format(hw_address.sll_addr[0].int.toHex(2), 189 | hw_address.sll_addr[1].int.toHex(2), 190 | hw_address.sll_addr[2].int.toHex(2), 191 | hw_address.sll_addr[3].int.toHex(2), 192 | hw_address.sll_addr[4].int.toHex(2), 193 | hw_address.sll_addr[5].int.toHex( 194 | 2)).tolowerAscii() 195 | 196 | 197 | elif (defined(freebsd) or defined(openbsd) or defined(darwin) or defined( 198 | netbsd)) and family.int == AF_PACKET: 199 | # struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr; 200 | # len = dladdr->sdl_alen; 201 | # data = LLADDR(dladdr); 202 | discard 203 | 204 | else: 205 | # unknown family 206 | return "" 207 | 208 | 209 | proc disk_usage*(path: string): DiskUsage = 210 | ## Return disk usage associated with path. 211 | ## Note: UNIX usually reserves 5% disk space which is not accessible 212 | ## by user. In this function "total" and "used" values reflect the 213 | ## total and used disk space whereas "free" and "percent" represent 214 | ## the "free" and "used percent" user disk space. 215 | 216 | var st: Statvfs 217 | let ret_code = statvfs(path, st) 218 | if ret_code == -1: 219 | raise newException(OSError, "disk_usage error: $1" % [$strerror(errno)]) 220 | 221 | # Total space which is only available to root (unless changed at system level). 222 | let total = (st.f_blocks * st.f_frsize) 223 | # Remaining free space usable by root. 224 | let avail_to_root = (st.f_bfree * st.f_frsize) 225 | # Remaining free space usable by user. 226 | let avail_to_user = (st.f_bavail * st.f_frsize) 227 | # Total space being used in general. 228 | let used = (total - avail_to_root) 229 | # Total space which is available to user (same as 'total' but for the user). 230 | let total_user = used + avail_to_user 231 | # User usage percent compared to the total amount of space 232 | # the user can use. This number would be higher if compared 233 | # to root's because the user has less space (usually -5%). 234 | let usage_percent_user = usage_percent(used, total_user, places = 1) 235 | 236 | # NB: the percentage is -5% than what shown by df due to 237 | # reserved blocks that we are currently not considering: 238 | # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462 239 | return DiskUsage(total: total.int, used: used.int, free: avail_to_user.int, 240 | percent: usage_percent_user.float) 241 | 242 | 243 | proc ioctlsocket*(iface_name: string, ioctl: uint, ifr: var ifreq): bool = 244 | ## 245 | let sock = socket(posix.AF_INET, posix.SOCK_DGRAM, 0) 246 | if sock == SocketHandle(-1): return false 247 | var interface_name = iface_name 248 | copyMem(addr ifr.ifr_ifrn.ifrn_name, addr(interface_name[0]), len(iface_name)) 249 | 250 | let ret = ioctl(sock.cint, ioctl, addr ifr) 251 | if ret == -1: return false 252 | discard close(sock) 253 | return true 254 | 255 | 256 | proc net_if_mtu*(name: string): int = 257 | ## Return NIC MTU. 258 | ## References: http://www.i-scream.org/libstatgrab/ 259 | 260 | var ifr: ifreq 261 | if ioctlsocket(name, SIOCGIFMTU, ifr): 262 | result = ifr.ifr_ifru.ifru_mtu 263 | else: 264 | result = 0 265 | 266 | 267 | proc net_if_flags*(name: string): bool = 268 | ## Inspect NIC flags, returns a bool indicating whether the NIC is running. 269 | ## References: http://www.i-scream.org/libstatgrab/ 270 | 271 | var ifr: ifreq 272 | if ioctlsocket(name, SIOCGIFFLAGS, ifr): 273 | result = (ifr.ifr_ifru.ifru_flags and IFF_UP.cshort) != 0 274 | else: 275 | result = false 276 | 277 | ## 278 | # net_if_stats() macOS/BSD implementation. 279 | ## 280 | 281 | when bsdPlatform: 282 | {.compile: "arch/bsd_osx.c".} 283 | import system / ansi_c 284 | proc psutil_get_nic_speed*(ifm_active: cint): cint {.importc: "psutil_get_nic_speed".} 285 | 286 | type ifmediareq {.importc: "struct ifmediareq", header: "", 287 | nodecl, pure.} = object 288 | ifm_name*: array[IFNAMSIZ, char] # if name, e.g. "en0" 289 | ifm_current: cint # current media options 290 | ifm_mask: cint # don't care mask 291 | ifm_status: cint # media status 292 | ifm_active: cint # active options 293 | ifm_count: cint # entries in ifm_ulist array 294 | ifm_ulist: ptr cint # media words 295 | 296 | const SIOCGIFMEDIA = 0xc0286938'u32 297 | const IFM_FDX = 0x00100000'u32 298 | const IFM_HDX = 2097152'u32 299 | 300 | proc net_if_duplex_speed*(nic_name: string): tuple[duplex: NicDuplex, speed: int] = 301 | var 302 | sock: int = -1 303 | ret: int 304 | duplex: int 305 | speed: int 306 | ifr: ifreq 307 | ifmed: ifmediareq 308 | 309 | sock = socket(posix.AF_INET, posix.SOCK_DGRAM, 0).int 310 | # if (sock == -1) 311 | # return PyErr_SetFromErrno(PyExc_OSError); 312 | # PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); 313 | # https://github.com/giampaolo/psutil/blob/9efb453e7163690c82226be3440cd8cb6bdffb5b/psutil/_psutil_common.h#L19 314 | copyMem(ifmed.ifm_name.addr, nic_name.cstring, sizeof( 315 | ifr.ifr_ifrn.ifrn_name) - 1) 316 | ifmed.ifm_name[sizeof(ifr.ifr_ifrn.ifrn_name) - 1] = '\0' 317 | # speed / duplex 318 | c_memset(cast[pointer](ifmed.addr), 0, sizeof(ifmediareq).csize_t); 319 | # strlcpy(ifmed.ifm_name, nic_name, sizeof(ifmed.ifm_name)); 320 | copyMem(ifmed.ifm_name.addr, nic_name.cstring, sizeof(ifmed.ifm_name)) 321 | ret = ioctl(sock.FileHandle, SIOCGIFMEDIA, ifmed.addr) 322 | if ret == -1: 323 | speed = 0 324 | duplex = 0 325 | else: 326 | speed = psutil_get_nic_speed(ifmed.ifm_active) 327 | if ((ifmed.ifm_active or IFM_FDX.cint) == ifmed.ifm_active): 328 | duplex = 2 329 | elif ((ifmed.ifm_active or IFM_HDX.cint) == ifmed.ifm_active): 330 | duplex = 1 331 | else: 332 | duplex = 0 333 | discard close(sock.SocketHandle) 334 | return (duplex.NicDuplex, speed) 335 | 336 | -------------------------------------------------------------------------------- /src/psutil/psutil_windows.nim: -------------------------------------------------------------------------------- 1 | import math 2 | import sequtils 3 | import strformat 4 | import strutils 5 | import tables 6 | 7 | import winim except `&` 8 | 9 | import common 10 | 11 | 12 | var AF_PACKET* = -1 13 | const LO_T = 1e-7 14 | const HI_T = 429.4967296 15 | 16 | # Make some constants for process architecture 17 | const PROCESS_ARCH_UNKNOWN* = 0 # architecture is unknown 18 | const PROCESS_ARCH_X86* = 1 # architecture is 32 bit 19 | const PROCESS_ARCH_X64* = 2 # architecture is 64 bit 20 | 21 | # The last 3 fields are unexposed traditionally so this has the potential 22 | # to break in the future, but this is how psutil does it too. 23 | type SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION * {.pure.} = object 24 | IdleTime*: LARGE_INTEGER 25 | KernelTime*: LARGE_INTEGER 26 | UserTime*: LARGE_INTEGER 27 | DpcTime*: LARGE_INTEGER 28 | InterruptTime*: LARGE_INTEGER 29 | InterruptCount*: ULONG 30 | 31 | 32 | proc raiseError() = 33 | var error_message: LPWSTR = newStringOfCap(256) 34 | let error_code = GetLastError() 35 | discard FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 36 | NULL, 37 | error_code, 38 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT).DWORD, 39 | error_message, 40 | 256, 41 | NULL) 42 | discard SetErrorMode(0) 43 | raise newException(OSError, "ERROR ($1): $2" % [$error_code, $error_message]) 44 | 45 | 46 | proc psutil_get_drive_type*(drive_type: UINT): string = 47 | case drive_type 48 | of DRIVE_FIXED: "fixed" 49 | of DRIVE_CDROM: "cdrom" 50 | of DRIVE_REMOVABLE: "removable" 51 | of DRIVE_UNKNOWN: "unknown" 52 | of DRIVE_NO_ROOT_DIR: "unmounted" 53 | of DRIVE_REMOTE: "remote" 54 | of DRIVE_RAMDISK: "ramdisk" 55 | else: "?" 56 | 57 | proc psutil_get_drive_type*(drive: string): string = 58 | var drive_type = GetDriveType(drive) 59 | case drive_type 60 | of DRIVE_FIXED: "fixed" 61 | of DRIVE_CDROM: "cdrom" 62 | of DRIVE_REMOVABLE: "removable" 63 | of DRIVE_UNKNOWN: "unknown" 64 | of DRIVE_NO_ROOT_DIR: "unmounted" 65 | of DRIVE_REMOTE: "remote" 66 | of DRIVE_RAMDISK: "ramdisk" 67 | else: "?" 68 | 69 | proc getnativearch*(): int = 70 | ## Get the native architecture of the system we are running on 71 | var pGetNativeSystemInfo: SYSTEM_INFO 72 | result = PROCESS_ARCH_X86 73 | 74 | GetNativeSystemInfo(pGetNativeSystemInfo.addr) 75 | 76 | if pGetNativeSystemInfo.isNil: 77 | raiseError() 78 | 79 | result = case pGetNativeSystemInfo.union1.struct1.wProcessorArchitecture 80 | of PROCESSOR_ARCHITECTURE_AMD64: 81 | PROCESS_ARCH_X64 82 | of PROCESSOR_ARCHITECTURE_IA64: 83 | PROCESS_ARCH_X64 84 | of PROCESSOR_ARCHITECTURE_INTEL: 85 | PROCESS_ARCH_X64 86 | else: 87 | PROCESS_ARCH_UNKNOWN 88 | 89 | proc pids*(): seq[int] = 90 | ## Returns a list of PIDs currently running on the system. 91 | var procArray: seq[DWORD] 92 | var procArrayLen = 0 93 | # Stores the byte size of the returned array from enumprocesses 94 | var enumReturnSz: DWORD = 0 95 | 96 | while enumReturnSz == DWORD(procArrayLen * sizeof(DWORD)): 97 | procArrayLen += 1024 98 | procArray = newSeq[DWORD](procArrayLen) 99 | 100 | if EnumProcesses(addr procArray[0], 101 | DWORD(procArrayLen * sizeof(DWORD)), 102 | addr enumReturnSz) == 0: 103 | raiseError() 104 | 105 | # The number of elements is the returned size / size of each element 106 | let numberOfReturnedPIDs = int(int(enumReturnSz) / sizeof(DWORD)) 107 | for i in 0.. 0: 452 | opts &= "," 453 | opts &= psutil_get_drive_type(drive_type) 454 | 455 | result.add(DiskPartition(mountpoint: drive_letter, 456 | device: drive_letter, 457 | fstype: $fs_type, # either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS 458 | opts: opts)) 459 | discard SetErrorMode(0) 460 | 461 | 462 | proc disk_usage*(path: string): DiskUsage = 463 | ## Return disk usage associated with path. 464 | var total, free: ULARGE_INTEGER 465 | 466 | let ret_code = GetDiskFreeSpaceExW(path, nil, addr total, addr free) 467 | if ret_code != 1: raiseError() 468 | 469 | let used = total.QuadPart - free.QuadPart 470 | let percent = usage_percent(used.int, total.QuadPart.int, places = 1) 471 | return DiskUsage(total: total.QuadPart.int, used: used.int, 472 | free: free.QuadPart.int, percent: percent) 473 | 474 | 475 | proc virtual_memory*(): VirtualMemory = 476 | ## System virtual memory 477 | var memInfo: MEMORYSTATUSEX 478 | memInfo.dwLength = sizeof(MEMORYSTATUSEX).DWORD 479 | 480 | if GlobalMemoryStatusEx(addr memInfo) == 0: 481 | raiseError() 482 | 483 | let used = int(memInfo.ullTotalPhys - memInfo.ullAvailPhys) 484 | let percent = usage_percent(used, memInfo.ullTotalPhys.int, places = 1) 485 | return VirtualMemory(total: memInfo.ullTotalPhys.int, 486 | avail: memInfo.ullAvailPhys.int, 487 | percent: percent, 488 | used: used, 489 | free: memInfo.ullAvailPhys.int) 490 | 491 | 492 | proc swap_memory*(): SwapMemory = 493 | ## Swap system memory as a (total, used, free, sin, sout) 494 | var memInfo: MEMORYSTATUSEX 495 | memInfo.dwLength = sizeof(MEMORYSTATUSEX).DWORD 496 | 497 | if GlobalMemoryStatusEx(addr memInfo) == 0: 498 | raiseError() 499 | 500 | let total = memInfo.ullTotalPageFile.int 501 | let free = memInfo.ullAvailPageFile.int 502 | let used = total - free 503 | let percent = usage_percent(used, total, places = 1) 504 | return SwapMemory(total: total, used: used, free: free, percent: percent, 505 | sin: 0, sout: 0) 506 | 507 | 508 | proc toUnixTime(ft: FILETIME): float = 509 | # HUGE thanks to: 510 | # http://johnstewien.spaces.live.com/blog/cns!E6885DB5CEBABBC8!831.entry 511 | # This function converts the FILETIME structure to the 32 bit 512 | # Unix time structure. 513 | # The time_t is a 32-bit value for the number of seconds since 514 | # January 1, 1970. A FILETIME is a 64-bit for the number of 515 | # 100-nanosecond periods since January 1, 1601. Convert by 516 | # subtracting the number of 100-nanosecond period between 01-01-1970 517 | # and 01-01-1601, from time_t then divide by 1e+7 to get to the same 518 | # base granularity. 519 | let ll = (int64(ft.dwHighDateTime) shl 32) + int64(ft.dwLowDateTime) 520 | result = int(ll - 116444736000000000) / 10000000 521 | 522 | 523 | proc boot_time*(): float = 524 | ## Return the system boot time expressed in seconds since the epoch 525 | var fileTime: FILETIME 526 | GetSystemTimeAsFileTime(addr fileTime) 527 | 528 | let pt = toUnixTime(fileTime) 529 | let uptime = int(GetTickCount64()) / 1000 530 | 531 | return pt - uptime 532 | 533 | 534 | proc uptime*(): int = 535 | ## Return the system uptime expressed in seconds, Integer type. 536 | int(GetTickCount64().float / 1000.float) 537 | 538 | 539 | proc per_cpu_times*(): seq[CPUTimes] = 540 | ## Return system per-CPU times as a sequence of CPUTimes. 541 | 542 | let ncpus = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS) 543 | if ncpus == 0: 544 | return result 545 | 546 | # allocates an array of _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION structures, one per processor 547 | var sppi = newSeq[SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION](ncpus) 548 | let buffer_size = ULONG(ncpus * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)) 549 | 550 | # gets cpu time informations 551 | let status = NtQuerySystemInformation(systemProcessorPerformanceInformation, 552 | addr sppi[0], buffer_size, NULL) 553 | if status != 0: 554 | raiseError() 555 | 556 | # computes system global times summing each processor value 557 | for i in 0 ..< ncpus: 558 | let user = (HI_T * sppi[i].UserTime.HighPart.float) + 559 | (LO_T * sppi[i].UserTime.LowPart.float) 560 | let idle = (HI_T * sppi[i].IdleTime.HighPart.float) + 561 | (LO_T * sppi[i].IdleTime.LowPart.float) 562 | let kernel = (HI_T * sppi[i].KernelTime.HighPart.float) + 563 | (LO_T * sppi[i].KernelTime.LowPart.float) 564 | let interrupt = (HI_T * sppi[i].InterruptTime.HighPart.float) + 565 | (LO_T * sppi[i].InterruptTime.LowPart.float) 566 | let dpc = (HI_T * sppi[i].DpcTime.HighPart.float) + 567 | (LO_T * sppi[i].DpcTime.LowPart.float) 568 | 569 | # kernel time includes idle time on windows 570 | # we return only busy kernel time subtracting idle time from kernel time 571 | let system = kernel - idle 572 | 573 | result.add(CPUTimes(user: user, system: system, idle: idle, 574 | interrupt: interrupt, dpc: dpc)) 575 | 576 | 577 | proc cpu_times*(): CPUTimes = 578 | ## Retrieves system CPU timing information . On a multiprocessor system, 579 | ## the values returned are the 580 | ## sum of the designated times across all processors. 581 | 582 | var idle_time: FILETIME 583 | var kernel_time: FILETIME 584 | var user_time: FILETIME 585 | 586 | if GetSystemTimes(addr idle_time, addr kernel_time, addr user_time).bool == false: 587 | raiseError() 588 | 589 | let idle = (HI_T * idle_time.dwHighDateTime.float) + (LO_T * 590 | idle_time.dwLowDateTime.float) 591 | let user = (HI_T * user_time.dwHighDateTime.float) + (LO_T * 592 | user_time.dwLowDateTime.float) 593 | let kernel = (HI_T * kernel_time.dwHighDateTime.float) + (LO_T * 594 | kernel_time.dwLowDateTime.float) 595 | 596 | # Kernel time includes idle time. 597 | # We return only busy kernel time subtracting idle time from kernel time. 598 | let system = kernel - idle 599 | 600 | # Internally, GetSystemTimes() is used, and it doesn't return interrupt and dpc times. 601 | # per_cpu_times() does, so we rely on it to get those only. 602 | let per_times = per_cpu_times() 603 | let interrupt_sum = sum(per_times.mapIt(it.interrupt)) 604 | let dpc_sum = sum(per_times.mapIt(it.dpc)) 605 | return CPUTimes(user: user, system: system, idle: idle, 606 | interrupt: interrupt_sum, dpc: dpc_sum) 607 | 608 | 609 | proc cpu_count_logical*(): int = 610 | return cast[int](GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)) 611 | 612 | 613 | proc cpu_count_physical*(): int = 614 | var length: DWORD = 0 615 | var rc = GetLogicalProcessorInformationEx(relationAll, NULL, addr length) 616 | 617 | var buffer = cast[PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX](alloc0(length)) 618 | rc = GetLogicalProcessorInformationEx(relationAll, buffer, addr length) 619 | 620 | if rc == 0: 621 | dealloc(buffer) 622 | raiseError() 623 | 624 | var currentPtr = buffer 625 | var offset = 0 626 | var prevProcessorInfoSize = 0 627 | while offset < length: 628 | # Advance ptr by the size of the previous SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct. 629 | currentPtr = cast[PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX](cast[int]( 630 | currentPtr) + prevProcessorInfoSize) 631 | 632 | if currentPtr.Relationship == relationProcessorCore: 633 | result += 1 634 | 635 | # When offset == length, we've reached the last processor info struct in the buffer. 636 | offset += currentPtr.Size 637 | prevProcessorInfoSize = currentPtr.Size 638 | 639 | dealloc(buffer) 640 | 641 | 642 | type WTS_CONNECTSTATE_CLASS {.pure.} = enum 643 | WTSActive, 644 | WTSConnected, 645 | WTSConnectQuery, 646 | WTSShadow, 647 | WTSDisconnected, 648 | WTSIdle, 649 | WTSListen, 650 | WTSReset, 651 | WTSDown, 652 | WTSInit 653 | 654 | 655 | type WTS_SESSION_INFO = object 656 | sessionId: DWORD 657 | pWinStationName: LPWSTR 658 | state: WTS_CONNECTSTATE_CLASS 659 | 660 | 661 | type PWTS_SESSION_INFO = ptr WTS_SESSION_INFO 662 | 663 | 664 | type WTS_CLIENT_ADDRESS = object 665 | addressFamily: DWORD 666 | address: array[20, BYTE] 667 | 668 | 669 | type PWTS_CLIENT_ADDRESS = ptr WTS_CLIENT_ADDRESS 670 | 671 | 672 | const WTS_CURRENT_SERVER_HANDLE: HANDLE = 0 673 | 674 | 675 | type WTS_INFO_CLASS {.pure.} = enum 676 | WTSInitialProgram = 0, 677 | WTSApplicationName = 1, 678 | WTSWorkingDirectory = 2, 679 | WTSOEMId = 3, 680 | WTSSessionId = 4, 681 | WTSUserName = 5, 682 | WTSWinStationName = 6, 683 | WTSDomainName = 7, 684 | WTSConnectState = 8, 685 | WTSClientBuildNumber = 9, 686 | WTSClientName = 10, 687 | WTSClientDirectory = 11, 688 | WTSClientProductId = 12, 689 | WTSClientHardwareId = 13, 690 | WTSClientAddress = 14, 691 | WTSClientDisplay = 15, 692 | WTSClientProtocolType = 16, 693 | WTSIdleTime = 17, 694 | WTSLogonTime = 18, 695 | WTSIncomingBytes = 19, 696 | WTSOutgoingBytes = 20, 697 | WTSIncomingFrames = 21, 698 | WTSOutgoingFrames = 22, 699 | WTSClientInfo = 23, 700 | WTSSessionInfo = 24 701 | 702 | 703 | type WINSTATION_INFO_CLASS = enum 704 | WinStationInformation = 8 705 | 706 | 707 | type WINSTATION_INFO = object 708 | Reserved1: array[72, BYTE] 709 | SessionId: ULONG 710 | Reserved2: array[4, BYTE] 711 | ConnectTime: FILETIME 712 | DisconnectTime: FILETIME 713 | LastInputTime: FILETIME 714 | LoginTime: FILETIME 715 | Reserved3: array[1096, BYTE] 716 | CurrentTime: FILETIME 717 | 718 | 719 | proc WTSEnumerateSessionsW( 720 | hServer: HANDLE, 721 | reserved: DWORD, 722 | version: DWORD, 723 | ppSessionInfo: ptr PWTS_SESSION_INFO, 724 | pCount: PDWORD): WINBOOL {.winapi, stdcall, dynlib: "wtsapi32", importc.} 725 | 726 | 727 | proc WTSQuerySessionInformationW( 728 | hServer: HANDLE, 729 | sessionId: DWORD, 730 | wtsInfoClass: WTS_INFO_CLASS, 731 | ppBuffer: ptr LPWSTR, 732 | pBytesReturned: ptr DWORD): WINBOOL {.winapi, stdcall, dynlib: "wtsapi32", importc.} 733 | 734 | 735 | proc WTSFreeMemory(pMemory: PVOID) {.winapi, stdcall, dynlib: "wtsapi32", importc.} 736 | 737 | 738 | proc WinStationQueryInformation( 739 | serverHandle: HANDLE, 740 | sessionId: ULONG, 741 | winStationInformationClass: WINSTATION_INFO_CLASS, 742 | pWinStationInformation: ptr WINSTATION_INFO, 743 | winStationInformationLength: ULONG, 744 | pReturnLength: PULONG): BOOLEAN {.winapi, stdcall, dynlib: "winsta", 745 | importc: "WinStationQueryInformationW".} 746 | 747 | 748 | proc getUserForSession(server: HANDLE, sessionId: DWORD): string = 749 | var buffer_user: PWCHAR = NULL 750 | var bytes: DWORD = 0 751 | if WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, sessionId, 752 | WTSUserName, addr buffer_user, addr bytes) == 0: 753 | raiseError() 754 | 755 | if bytes <= 2: 756 | return "" 757 | 758 | result = $buffer_user 759 | 760 | WTSFreeMemory(buffer_user) 761 | 762 | 763 | proc getAddressForSession(server: HANDLE, sessionId: DWORD): string = 764 | var bytes: DWORD = 0 765 | var buffer_addr: LPWSTR = NULL 766 | if WTSQuerySessionInformationW(server, sessionId, 767 | WTS_INFO_CLASS.WTSClientAddress, addr buffer_addr, addr bytes) == 0: 768 | raiseError() 769 | 770 | let address = cast[PWTS_CLIENT_ADDRESS](buffer_addr).address 771 | let addressFamily = cast[PWTS_CLIENT_ADDRESS](buffer_addr).addressFamily 772 | 773 | if addressFamily == 0: 774 | result = &"{address[0]}.{address[1]}.{address[2]}.{address[3]}" 775 | 776 | WTSFreeMemory(buffer_addr) 777 | 778 | 779 | proc getLoginTimeForSession(server: HANDLE, sessionId: DWORD): float = 780 | var station_info: WINSTATION_INFO 781 | var returnLen: ULONG 782 | if WinStationQueryInformation(server, sessionId, WinStationInformation, 783 | addr station_info, sizeof(station_info).ULONG, addr returnLen) == 0: 784 | return -1 785 | 786 | result = toUnixTime(station_info.ConnectTime) 787 | 788 | 789 | proc users*(): seq[User] = 790 | var count: DWORD = 0 791 | var sessions: PWTS_SESSION_INFO 792 | if WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, addr sessions, 793 | addr count) == 0: 794 | raiseError() 795 | 796 | for i in 0 ..< count: 797 | let currentSession = cast[PWTS_SESSION_INFO](cast[int](sessions) + (sizeof( 798 | WTS_SESSION_INFO)*i)) 799 | let sessionId = currentSession.sessionId 800 | 801 | let user = getUserForSession(WTS_CURRENT_SERVER_HANDLE, sessionId) 802 | if user == "": continue 803 | 804 | let address = getAddressForSession(WTS_CURRENT_SERVER_HANDLE, sessionId) 805 | let login_time = getLoginTimeForSession(WTS_CURRENT_SERVER_HANDLE, sessionId) 806 | 807 | result.add(User(name: user, host: address, started: login_time)) 808 | 809 | WTSFreeMemory(sessions) 810 | 811 | 812 | 813 | ## ToDo - These are all stubbed out so things compile. 814 | ## It also shows what needs to be done for feature parity with Linux 815 | proc cpu_stats*(): tuple[ctx_switches, interrupts, soft_interrupts, 816 | syscalls: int] = 817 | raise newException(Exception, "Function is unimplemented!") 818 | 819 | proc net_connections*(kind = "inet", pid = -1): seq[Connection] = 820 | raise newException(Exception, "Function is unimplemented!") 821 | 822 | proc net_if_addrs*(): Table[string, seq[common.Address]] = 823 | raise newException(Exception, "Function is unimplemented!") 824 | 825 | proc net_if_stats*(): TableRef[string, NICstats] = 826 | raise newException(Exception, "Function is unimplemented!") 827 | 828 | proc per_disk_io_counters*(): TableRef[string, DiskIO] = 829 | raise newException(Exception, "Function is unimplemented!") 830 | 831 | proc per_nic_net_io_counters*(): TableRef[string, NetIO] = 832 | raise newException(Exception, "Function is unimplemented!") 833 | 834 | proc process_exists*(processName: string): bool = 835 | 836 | var exists = false 837 | var entry: PROCESSENTRY32 838 | entry.dwSize = DWORD(PROCESSENTRY32.sizeof) 839 | 840 | var snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 841 | 842 | if Process32First(snapshot, entry.addr): 843 | while Process32Next(snapshot, entry.addr): 844 | var name: string 845 | for c in entry.szExeFile: 846 | if cast[char](c) == '\0': 847 | break 848 | 849 | name.add(cast[char](c)) 850 | 851 | if name == processName: 852 | exists = true 853 | 854 | CloseHandle(snapshot) 855 | return exists 856 | 857 | proc pid_exists*(pid: int): bool = 858 | var p = OpenProcess(SYNCHRONIZE, FALSE, DWORD(pid)); 859 | var r = WaitForSingleObject(p, 0); 860 | CloseHandle(p); 861 | return r == WAIT_TIMEOUT 862 | 863 | proc pid_cmdline*(pid: int): string = 864 | raise newException(Exception, "Function is unimplemented!") 865 | -------------------------------------------------------------------------------- /src/psutil/psutil_linux.nim: -------------------------------------------------------------------------------- 1 | {.deadCodeElim: on.} 2 | import algorithm, math, net, os, posix, sequtils, sets, strutils, tables, times 3 | import strformat 4 | import common, psutil_posix 5 | import unpack 6 | 7 | ################################################################################ 8 | const PROCFS_PATH = "/proc" 9 | 10 | const UT_LINESIZE = 32 11 | const UT_NAMESIZE = 32 12 | const UT_HOSTSIZE = 256 13 | const USER_PROCESS = 7 # Normal process. 14 | const PATH_MAX = 4096 15 | 16 | var MOUNTED {.header: "".}: cstring 17 | var DUPLEX_FULL {.header: "".}: uint8 18 | var DUPLEX_HALF {.header: "".}: uint8 19 | var DUPLEX_UNKNOWN {.header: "".}: uint8 20 | var ETHTOOL_GSET {.header: "".}: uint8 21 | var SIOCETHTOOL {.header: "".}: uint16 22 | 23 | let tcp4 = ("tcp", posix.AF_INET, posix.SOCK_STREAM) 24 | let tcp6 = ("tcp6", posix.AF_INET6, posix.SOCK_STREAM) 25 | let udp4 = ("udp", posix.AF_INET, posix.SOCK_DGRAM) 26 | let udp6 = ("udp6", posix.AF_INET6, posix.SOCK_DGRAM) 27 | let unix = ("unix", posix.AF_UNIX, posix.SOCK_RAW) # raw probably isn't right 28 | let tmap = { 29 | "all": @[tcp4, tcp6, udp4, udp6, unix], 30 | "tcp": @[tcp4, tcp6], 31 | "tcp4": @[tcp4, ], 32 | "tcp6": @[tcp6, ], 33 | "udp": @[udp4, udp6], 34 | "udp4": @[udp4, ], 35 | "udp6": @[udp6, ], 36 | "unix": @[unix, ], 37 | "inet": @[tcp4, tcp6, udp4, udp6], 38 | "inet4": @[tcp4, udp4], 39 | "inet6": @[tcp6, udp6], 40 | }.toOrderedTable() 41 | 42 | const TCP_STATUSES = { 43 | "01": "ESTABLISHED", 44 | "02": "SYN_SENT", 45 | "03": "SYN_RECV", 46 | "04": "FIN_WAIT1", 47 | "05": "FIN_WAIT2", 48 | "06": "TIME_WAIT", 49 | "07": "CLOSE", 50 | "08": "CLOSE_WAIT", 51 | "09": "LAST_ACK", 52 | "0A": "LISTEN", 53 | "0B": "CLOSING" 54 | }.toOrderedTable() 55 | 56 | let CLOCK_TICKS = sysconf(SC_CLK_TCK) 57 | let PAGESIZE = sysconf(SC_PAGE_SIZE) 58 | 59 | proc get_sector_size(): int = 60 | try: 61 | return "/sys/block/sda/queue/hw_sector_size".readFile().parseInt() 62 | except: 63 | # man iostat states that sectors are equivalent with blocks and 64 | # have a size of 512 bytes since 2.4 kernels. This value is 65 | # needed to calculate the amount of disk I/O in bytes. 66 | return 512 67 | 68 | let SECTOR_SIZE = get_sector_size() 69 | 70 | type timeval_32 = object 71 | tv_sec: int32 # Seconds. 72 | tv_usec: int32 # Microseconds. 73 | 74 | type exit_status = object 75 | e_termination: int16 # Process termination status. 76 | e_exit: int16 # Process exit status. 77 | 78 | type utmp = object 79 | ut_type: int16 # Type of login. 80 | ut_pid: Pid # Process ID of login process. 81 | ut_line: array[UT_LINESIZE, char] # Devicename. 82 | ut_id: array[4, char] # Inittab ID. 83 | ut_user: array[UT_NAMESIZE, char] # Username. 84 | ut_host: array[UT_HOSTSIZE, char] # Hostname for remote login. 85 | ut_exit: exit_status # Exit status of a process marked as DEAD_PROCESS. 86 | ut_session: int32 # Session ID, used for windowing. 87 | ut_tv: timeval_32 # Time entry was made. 88 | ut_addr_v6: array[4, int32] # Internet address of remote host. 89 | unused: array[20, char] # Reserved for future use. 90 | 91 | type SysInfo = object 92 | uptime*: uint # Seconds since boot 93 | loads*: array[3, uint] # 1, 5, and 15 minute load averages 94 | totalram*: uint # Total usable main memory size 95 | freeram*: uint # Available memory size 96 | sharedram*: uint # Amount of shared memory 97 | bufferram*: uint # Memory used by buffers 98 | totalswap*: uint # Total swap space size 99 | freeswap*: uint # swap space still available 100 | procs*: uint16 # Number of current processes 101 | totalhigh*: uint # Total high memory size 102 | freehigh*: uint # Available high memory size 103 | mem_unit*: uint # Memory unit size in bytes 104 | f: array[20-2*sizeof(int)-sizeof(int32), char] #Padding to 64 bytes 105 | 106 | type mntent = ref object 107 | mnt_fsname*: cstring # name of mounted filesystem 108 | mnt_dir*: cstring # filesystem path prefix 109 | mnt_type*: cstring # mount type (see mntent.h) 110 | mnt_opts*: cstring # mount options (see mntent.h) 111 | mnt_freq*: int # dump frequency in days 112 | mnt_passno*: int # pass number on parallel fsck 113 | 114 | type ethtool_cmd = object 115 | cmd*: uint32 116 | supported*: uint32 117 | advertising*: uint32 118 | speed*: uint16 119 | duplex*: uint8 120 | port*: uint8 121 | phy_address*: uint8 122 | transceiver*: uint8 123 | autoneg*: uint8 124 | mdio_support*: uint8 125 | maxtxpkt*: uint32 126 | maxrxpkt*: uint32 127 | speed_hi*: uint16 128 | eth_tp_mdix*: uint8 129 | eth_tp_mdix_ctrl*: uint8 130 | lp_advertising*: uint32 131 | reserved*: array[2, uint32] 132 | 133 | ################################################################################ 134 | proc getutent(): ptr utmp {.header: "".} 135 | proc setutent() {.header: "".} 136 | proc endutent() {.header: "".} 137 | proc sysinfo(info: var SysInfo): cint {.header: "".} 138 | proc setmntent(filename: cstring, `type`: cstring): File {.header: "".} 139 | proc getmntent(stream: File): mntent {.header: "".} 140 | proc endmntent(streamp: File): int {.header: "".} 141 | proc readlink(path: cstring, buf: array, 142 | bufsiz: int): int {.header: "".} 143 | proc getpwuid(uid: int): ptr Passwd {.header: "".} 144 | 145 | proc boot_time*(): int = 146 | ## Return the system boot time expressed in seconds since the epoch, Integer type. 147 | let stat_path = PROCFS_PATH / "stat" 148 | for line in stat_path.lines: 149 | if line.strip.startswith("btime"): 150 | return line.strip.split()[1].parseInt() 151 | 152 | raise newException(OSError, "line 'btime' not found in $1" % stat_path) 153 | 154 | proc uptime*(): int = 155 | ## Return the system uptime expressed in seconds, Integer type. 156 | epochTime().int - boot_time() 157 | 158 | proc isnumber(s: string): bool = 159 | #[ 160 | function for check if string is a number 161 | ]# 162 | for c in s: 163 | if isdigit(c) == false: 164 | return false 165 | 166 | return true 167 | 168 | proc pids*(): seq[int] = 169 | ## Returns a list of PIDs currently running on the system. 170 | let all_files = toSeq(walkDir(PROCFS_PATH, relative = true)) 171 | return mapIt(filterIt(all_files, isnumber(it.path)), parseInt(it.path)) 172 | 173 | 174 | proc pid_exists*(pid: int): bool = 175 | ## Check For the existence of a unix pid 176 | 177 | let exists = psutil_posix.pid_exists(pid) 178 | if not exists: return false 179 | 180 | try: 181 | # Note: already checked that this is faster than using a regular expr. 182 | # Also (a lot) faster than doing "return pid in pids()" 183 | let status_path = PROCFS_PATH / $pid / "status" 184 | for line in status_path.lines: 185 | if line.startswith("Tgid:"): 186 | let tgid = parseInt(line.split()[1]) 187 | return tgid == pid 188 | 189 | raise newException(OSError, "Tgid line not found in " & status_path) 190 | except: 191 | return pid in pids() 192 | 193 | proc pid_cmdline*(pid: int): string = 194 | 195 | ## Function for getting the cmdline of a pid 196 | ## this gets path of command and arguments 197 | 198 | let cmdline_path = PROCFS_PATH / $pid / "cmdline" 199 | return cmdline_path.readFile() 200 | 201 | proc pids_cmdline*(pids: seq[int]): seq[string] = 202 | 203 | ## function for getting the cmdline of a sequence of pids 204 | ## this gets path of command and arguments 205 | var ret: seq[string] 206 | for pid in pids: 207 | ret.add(pid_cmdline(pid)) 208 | 209 | proc pid_name*(pid: int): string = 210 | ## Function for getting the process name of a pid 211 | ## not to be mixed with pid_cmdline. This only gets the 212 | ## program name. Not the path and arguments 213 | let p_path = PROCFS_PATH / $pid / "status" 214 | var data = p_path.readFile() 215 | for line in data.split("\n"): 216 | if "Name:" in line: 217 | var name = line.split("Name:")[1].strip() 218 | result = name 219 | 220 | 221 | proc pid_names*(pids: seq[int]): seq[string] = 222 | ## Function for getting the process name of a sequence of pids 223 | ## not to be mmixed with pids_cmdline. This only gets the 224 | ## program name. Not the path and arguments. 225 | var ret: seq[string] 226 | for pid in pids: 227 | ret.add(pid_name(pid)) 228 | 229 | return ret 230 | 231 | proc pid_path*(pid: int): string = 232 | 233 | ## Function for getting the path of the elf of the running pid 234 | var p_path: cstring = PROCFS_PATH / $pid / "exe" 235 | var buf: array[PATH_MAX, char] 236 | if readlink(p_path, buf, PATH_MAX) == -1: 237 | raise newException(IOError, "Cannot read /proc/$1/exe | $2" % [$pid, 238 | $strerror(errno)]) 239 | for c in buf: 240 | if c != '\0': result.add(c) else: break 241 | 242 | proc try_pid_path*(pid: int): string = 243 | 244 | ## Function for getting the path of the elf of the running pid 245 | ## Note: Instead of raising an error. It will instread return "" 246 | var p_path: cstring = PROCFS_PATH / $pid / "exe" 247 | var buf: array[PATH_MAX, char] 248 | if readlink(p_path, buf, PATH_MAX) == -1: 249 | result = "" 250 | else: 251 | for c in buf: 252 | if c != '\0': result.add(c) else: break 253 | 254 | 255 | proc pid_paths*(pids: seq[int]): seq[string] = 256 | 257 | ## Function for getting the elf paths of the specified pids 258 | for pid in pids: 259 | result.add(pid_path(pid)) 260 | 261 | 262 | proc try_pid_paths*(pids: seq[int]): seq[string] = 263 | 264 | ## Function for getting the paths of the specified pids 265 | ## Note: If an error occurs for any of the pids. The result for the corresponding 266 | ## pid will be "" 267 | for pid in pids: 268 | result.add(try_pid_path(pid)) 269 | 270 | proc pid_user*(pid: int): string = 271 | 272 | ## Function for getting the username running the specified pid 273 | var p_path = PROCFS_PATH / $pid / "status" 274 | var uid = -1 275 | var data = p_path.readFile() 276 | for line in data.split("\n"): 277 | if "Uid:" in line: 278 | uid = parseInt(line.split("Uid:")[1].strip().split("\t")[0]) 279 | 280 | var pws = getpwuid(cast[Uid](uid)) 281 | if pws.isNil: 282 | raise newException(OSError, "UID $1 not found" % [$uid]) 283 | result = $pws.pw_name 284 | 285 | proc try_pid_user*(pid: int): string = 286 | 287 | ## Function for getting the username running the specified pid 288 | var p_path = PROCFS_PATH / $pid / "status" 289 | var uid = -1 290 | var data = p_path.readFile() 291 | for line in data.split("\n"): 292 | if "Uid:" in line: 293 | uid = parseInt(line.split("Uid:")[1].strip().split("\t")[0]) 294 | 295 | var pws = getpwuid(cast[Uid](uid)) 296 | if pws.isNil: 297 | result = "" 298 | else: 299 | result = $pws.pw_name 300 | 301 | proc pid_users*(pids: seq[int]): seq[string] = 302 | 303 | for pid in pids: 304 | result.add(pid_user(pid)) 305 | 306 | proc try_pid_users*(pids: seq[int]): seq[string] = 307 | 308 | for pid in pids: 309 | result.add(try_pid_user(pid)) 310 | 311 | proc pid_parent*(pid: int): int = 312 | 313 | ## Function for getting the parent pid of the specified pid 314 | var p_path = PROCFS_PATH / $pid / "status" 315 | var data = p_path.readFile() 316 | for line in data.split("\n"): 317 | if "PPid:" in line: 318 | result = parseInt(line.split("PPid:")[^1].strip()) 319 | 320 | proc pid_parents*(pids: seq[int]): seq[int] = 321 | 322 | ## Function for getting the parent pids of the corresponding pids specified. 323 | for pid in pids: 324 | result.add(pid_parent(pid)) 325 | 326 | proc process_exists*(processName: string): bool = 327 | 328 | let names_seq = pid_names(pids()) 329 | for name in names_seq: 330 | if processName == name: 331 | return true 332 | 333 | return false 334 | 335 | proc pids_with_names*(): (seq[int], seq[string]) = 336 | 337 | ## Function for returning tuple of pids and names 338 | 339 | var pids_seq = pids() 340 | var names_seq = pid_names(pids_seq) 341 | 342 | return (pids_seq, names_seq) 343 | 344 | proc users*(): seq[User] = 345 | result = newSeq[User]() 346 | 347 | setutent() 348 | 349 | var ut = getutent() 350 | while ut != nil: 351 | let is_user_proc = ut.ut_type == USER_PROCESS 352 | if not is_user_proc: 353 | ut = getutent() 354 | continue 355 | 356 | var hostname = $ut.ut_host 357 | if hostname == ":0.0" or hostname == ":0": 358 | hostname = "localhost" 359 | 360 | let user_tuple = User(name: ($ut.ut_user.join().strip.replace("\x00", "")), 361 | terminal: ($ut.ut_line.join().strip.replace("\x00", 362 | "")), 363 | started: ut.ut_tv.tv_sec.float) 364 | result.add(user_tuple) 365 | ut = getutent() 366 | 367 | endutent() 368 | 369 | 370 | proc parse_cpu_time_line(text: string): CPUTimes = 371 | let values = text.strip.splitWhitespace() 372 | let times = mapIt(values[1..len(values) - 1], parseFloat(it) / 373 | CLOCK_TICKS.float) 374 | if len(times) >= 7: 375 | result.user = parseFloat(fmt"{times[0]:.2f}") 376 | result.nice = parseFloat(fmt"{times[1]:.2f}") 377 | result.system = parseFloat(fmt"{times[2]:.2f}") 378 | result.idle = parseFloat(fmt"{times[3]:.2f}") 379 | result.iowait = parseFloat(fmt"{times[4]:.2f}") 380 | result.irq = parseFloat(fmt"{times[5]:.2f}") 381 | result.softirq = parseFloat(fmt"{times[6]:.2f}") 382 | if len(times) >= 8: 383 | result.steal = parseFloat(fmt"{times[7]:.2f}") 384 | if len(times) >= 9: 385 | result.guest = parseFloat(fmt"{times[8]:.2f}") 386 | if len(times) >= 10: 387 | result.guest_nice = parseFloat(fmt"{times[9]:.2f}") 388 | 389 | 390 | proc cpu_times*(): CPUTimes = 391 | # Return a tuple representing the following system-wide CPU times: 392 | # (user, nice, system, idle, iowait, irq, softirq [steal, [guest, 393 | # [guest_nice]]]) 394 | # Last 3 fields may not be available on all Linux kernel versions. 395 | for line in lines(PROCFS_PATH / "stat"): 396 | result = parse_cpu_time_line(line) 397 | break 398 | 399 | 400 | proc per_cpu_times*(): seq[CPUTimes] = 401 | ## Return a list of tuples representing the CPU times for every 402 | ## CPU available on the system. 403 | result = newSeq[CPUTimes]() 404 | for line in lines(PROCFS_PATH / "stat"): 405 | if not line.startswith("cpu"): continue 406 | let entry = parse_cpu_time_line(line) 407 | result.add(entry) 408 | # get rid of the first line which refers to system wide CPU stats 409 | result.delete(0) 410 | return result 411 | 412 | 413 | proc cpu_stats*(): tuple[ctx_switches, interrupts, soft_interrupts, 414 | syscalls: int] = 415 | var ctx_switches, interrupts, soft_interrupts = 0 416 | for line in lines(PROCFS_PATH / "stat"): 417 | if line.startswith("ctxt"): 418 | ctx_switches = parseint(line.split()[1]) 419 | elif line.startswith("intr"): 420 | interrupts = parseint(line.split()[1]) 421 | elif line.startswith("softirq"): 422 | soft_interrupts = parseint(line.split()[1]) 423 | if ctx_switches != 0 and soft_interrupts != 0 and interrupts != 0: 424 | break 425 | # syscalls = 0 426 | return (ctx_switches, interrupts, soft_interrupts, 0) 427 | 428 | 429 | proc cpu_count_logical*(): int = 430 | ## Return the number of logical CPUs in the system. 431 | try: 432 | return sysconf(SC_NPROCESSORS_ONLN) 433 | except ValueError: 434 | # as a second fallback we try to parse /proc/cpuinfo 435 | for line in lines(PROCFS_PATH / "cpuinfo"): 436 | if line.toLowerAscii().startswith("processor"): 437 | result += 1 438 | 439 | # unknown format (e.g. amrel/sparc architectures), see: 440 | # https://github.com/giampaolo/psutil/issues/200 441 | # try to parse /proc/stat as a last resort 442 | if result == 0: 443 | for line in lines(PROCFS_PATH / "stat"): 444 | if line.toLowerAscii().startswith("cpu"): 445 | result += 1 446 | # Remove one from the count for the top "cpu" line (with no digit) 447 | # Saves us the regular expression used in the python impl 448 | if result != 0: result -= 1 449 | 450 | return result 451 | 452 | 453 | proc cpu_count_physical*(): int = 454 | ## Return the number of physical cores in the system. 455 | var mapping = initTable[int, int]() 456 | var current_info = initTable[string, int]() 457 | for raw_line in lines(PROCFS_PATH / "cpuinfo"): 458 | let line = raw_line.strip().toLowerAscii() 459 | if line == "": 460 | # new section 461 | if "physical id" in current_info and "cpu cores" in current_info: 462 | mapping[current_info["physical id"]] = current_info["cpu cores"] 463 | current_info = initTable[string, int]() 464 | else: 465 | # ongoing section 466 | if line.startswith("physical id") or line.startswith("cpu cores"): 467 | let parts = line.split("\t:") 468 | current_info[parts[0].strip()] = parseInt(parts[1].strip()) 469 | 470 | let values = toSeq(mapping.values()) 471 | return sum(values) 472 | 473 | 474 | proc calculate_avail_vmem(mems: TableRef[string, int]): int = 475 | ## Fallback for kernels < 3.14 where /proc/meminfo does not provide 476 | ## "MemAvailable:" column (see: https://blog.famzah.net/2014/09/24/). 477 | ## This code reimplements the algorithm outlined here: 478 | ## https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ 479 | ## commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 480 | ## XXX: on recent kernels this calculation differs by ~1.5% than 481 | ## "MemAvailable:" as it's calculated slightly differently, see: 482 | ## https://gitlab.com/procps-ng/procps/issues/42 483 | ## https://github.com/famzah/linux-memavailable-procfs/issues/2 484 | ## It is still way more realistic than doing (free + cached) though. 485 | 486 | # Fallback for very old distros. According to 487 | # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ 488 | # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 489 | # ...long ago "avail" was calculated as (free + cached). 490 | # We might fallback in such cases: 491 | # "Active(file)" not available: 2.6.28 / Dec 2008 492 | # "Inactive(file)" not available: 2.6.28 / Dec 2008 493 | # "SReclaimable:" not available: 2.6.19 / Nov 2006 494 | # /proc/zoneinfo not available: 2.6.13 / Aug 2005 495 | let free = mems["MemFree:"] 496 | let fallback = free + mems.getOrDefault("Cached:") 497 | 498 | var lru_active_file = 0 499 | var lru_inactive_file = 0 500 | var slab_reclaimable = 0 501 | try: 502 | lru_active_file = mems["Active(file):"] 503 | lru_inactive_file = mems["Inactive(file):"] 504 | slab_reclaimable = mems["SReclaimable:"] 505 | except KeyError: 506 | return fallback 507 | 508 | var watermark_low = 0 509 | try: 510 | for line in lines(PROCFS_PATH / "zoneinfo"): 511 | if line.strip().startswith("low"): 512 | watermark_low += parseInt(line.splitWhitespace()[1]) 513 | except IOError: 514 | return fallback # kernel 2.6.13 515 | 516 | watermark_low *= PAGESIZE 517 | watermark_low = watermark_low 518 | 519 | var avail = free - watermark_low 520 | var pagecache = lru_active_file + lru_inactive_file 521 | pagecache -= min(int(pagecache / 2), watermark_low) 522 | avail += pagecache 523 | avail += slab_reclaimable - min(int(slab_reclaimable / 2), watermark_low) 524 | return int(avail) 525 | 526 | 527 | proc virtual_memory*(): VirtualMemory = 528 | ## Report virtual memory stats. 529 | ## This implementation matches "free" and "vmstat -s" cmdline 530 | ## utility values and procps-ng-3.3.12 source was used as a reference 531 | ## (2016-09-18): 532 | ## https://gitlab.com/procps-ng/procps/blob/ 533 | ## 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c 534 | ## For reference, procps-ng-3.3.10 is the version available on Ubuntu 535 | ## 16.04. 536 | ## Note about "available" memory: up until psutil 4.3 it was 537 | ## calculated as "avail = (free + buffers + cached)". Now 538 | ## "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as 539 | ## it's more accurate. 540 | ## That matches "available" column in newer versions of "free". 541 | 542 | var missing_fields = newSeq[string]() 543 | var mems = newTable[string, int]() 544 | for line in lines(PROCFS_PATH / "meminfo"): 545 | let fields = line.splitWhitespace() 546 | mems[fields[0]] = parseInt(fields[1]) * 1024 547 | 548 | # /proc doc states that the available fields in /proc/meminfo vary 549 | # by architecture and compile options, but these 3 values are also 550 | # returned by sysinfo(2); as such we assume they are always there. 551 | let total = mems["MemTotal:"] 552 | let free = mems["MemFree:"] 553 | let buffers = mems["Buffers:"] 554 | 555 | var cached = 0 556 | try: 557 | cached = mems["Cached:"] 558 | # "free" cmdline utility sums reclaimable to cached. 559 | # Older versions of procps used to add slab memory instead. 560 | # This got changed in: 561 | # https://gitlab.com/procps-ng/procps/commit/ 562 | # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 563 | cached += mems.getOrDefault("SReclaimable:") # since kernel 2.6.19 564 | except KeyError: 565 | missing_fields.add("cached") 566 | 567 | var shared = 0 568 | try: 569 | shared = mems["Shmem:"] # since kernel 2.6.32 570 | except KeyError: 571 | try: 572 | shared = mems["MemShared:"] # kernels 2.4 573 | except KeyError: 574 | missing_fields.add("shared") 575 | 576 | var active = 0 577 | try: 578 | active = mems["Active:"] 579 | except KeyError: 580 | missing_fields.add("active") 581 | 582 | var inactive = 0 583 | try: 584 | inactive = mems["Inactive:"] 585 | except KeyError: 586 | try: 587 | inactive = mems["Inact_dirty:"] + mems["Inact_clean:"] + mems["Inact_laundry:"] 588 | except KeyError: 589 | missing_fields.add("inactive") 590 | 591 | var used = total - free - cached - buffers 592 | if used < 0: 593 | # May be symptomatic of running within a LCX container where such 594 | # values will be dramatically distorted over those of the host. 595 | used = total - free 596 | 597 | # - starting from 4.4.0 we match free's "available" column. 598 | # Before 4.4.0 we calculated it as (free + buffers + cached) 599 | # which matched htop. 600 | # - free and htop available memory differs as per: 601 | # http://askubuntu.com/a/369589 602 | # http://unix.stackexchange.com/a/65852/168884 603 | # - MemAvailable has been introduced in kernel 3.14 604 | var avail = 0 605 | try: 606 | avail = mems["MemAvailable:"] 607 | except KeyError: 608 | avail = calculate_avail_vmem(mems) 609 | 610 | if avail < 0: 611 | avail = 0 612 | missing_fields.add("available") 613 | 614 | # If avail is greater than total or our calculation overflows, 615 | # that's symptomatic of running within a LCX container where such 616 | # values will be dramatically distorted over those of the host. 617 | # https://gitlab.com/procps-ng/procps/blob/ 618 | # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 619 | if avail > total: 620 | avail = free 621 | 622 | let percent = usage_percent( (total - avail), total, places = 1) 623 | 624 | # Warn about missing metrics which are set to 0. 625 | if len(missing_fields) > 0: 626 | echo(missing_fields.join(", "), 627 | " memory stats couldn't be determined and ", 628 | if len(missing_fields) == 1: "was" else: "were", 629 | " set to 0") 630 | 631 | return VirtualMemory(total: total, avail: avail, percent: percent, used: used, 632 | free: free, active: active, inactive: inactive, 633 | buffers: buffers, cached: cached, shared: shared) 634 | 635 | 636 | proc swap_memory*(): SwapMemory = 637 | var si: SysInfo 638 | if sysinfo(si) == -1: 639 | echo("Error calling sysinfo in swap_memory(): ", errno) 640 | return 641 | 642 | let total = si.totalswap * si.mem_unit 643 | let free = si.freeswap * si.mem_unit 644 | let used = total - free 645 | let percent = usage_percent(used.int, total.int, places = 1) 646 | 647 | result = SwapMemory(total: total.int, used: used.int, free: free.int, 648 | percent: percent, sin: 0, sout: 0) 649 | 650 | # try to get pgin/pgouts 651 | if not existsFile(PROCFS_PATH / "vmstat"): 652 | # see https://github.com/giampaolo/psutil/issues/722 653 | echo("'sin' and 'sout' swap memory stats couldn't be determined ", 654 | "and were set to 0") 655 | return result 656 | 657 | for line in lines(PROCFS_PATH / "vmstat"): 658 | # values are expressed in 4 kilo bytes, we want bytes instead 659 | if line.startswith("pswpin"): 660 | result.sin = parseInt(line.split()[1]) * 4 * 1024 661 | elif line.startswith("pswpout"): 662 | result.sout = parseInt(line.split()[1]) * 4 * 1024 663 | if result.sin != 0 and result.sout != 0: 664 | return result 665 | 666 | # we might get here when dealing with exotic Linux flavors, see: 667 | # https://github.com/giampaolo/psutil/issues/313 668 | echo("'sin' and 'sout' swap memory stats couldn't be determined ", 669 | "and were set to 0") 670 | 671 | 672 | proc disk_partitions*(all = false): seq[DiskPartition] = 673 | ## Return mounted disk partitions as a sequence of DiskPartitions 674 | var fstypes = initHashSet[string]() 675 | for raw_line in lines(PROCFS_PATH / "filesystems"): 676 | let line = raw_line.strip() 677 | if not line.startswith("nodev"): 678 | fstypes.incl(line) 679 | else: 680 | # ignore all lines starting with "nodev" except "nodev zfs" 681 | if line.split("\t")[1] == "zfs": 682 | fstypes.incl("zfs") 683 | 684 | result = newSeq[DiskPartition]() 685 | 686 | let file = setmntent(MOUNTED, "r"); 687 | var entry = getmntent(file) 688 | while entry != nil: 689 | let device = if entry.mnt_fsname == "none": "" else: $entry.mnt_fsname 690 | let mountpoint = $entry.mnt_dir 691 | let fstype = $entry.mnt_type 692 | let opts = $entry.mnt_opts 693 | 694 | if not all: 695 | if device == "" or not(fstype in fstypes): 696 | entry = getmntent(file) 697 | continue 698 | let partition = DiskPartition(device: device, mountpoint: mountpoint, 699 | fstype: fstype, opts: opts) 700 | result.add(partition) 701 | entry = getmntent(file) 702 | 703 | discard endmntent(file) 704 | 705 | 706 | proc per_nic_net_io_counters*(): TableRef[string, NetIO] = 707 | ## Return network I/O statistics for every network interface 708 | ## installed on the system as a dict of raw tuples. 709 | result = newTable[string, NetIO]() 710 | for line in lines(PROCFS_PATH / "net/dev"): 711 | if not(":" in line): continue 712 | let colon = line.rfind(':') 713 | let name = line[..colon].strip() 714 | let lst = line[(colon + 1)..len(line) - 1].strip.replace("\x00", 715 | "").splitWhitespace 716 | let fields = mapIt(lst, parseInt(it)) 717 | 718 | result[name] = NetIO(bytes_sent: fields[8], 719 | bytes_recv: fields[0], 720 | packets_sent: fields[9], 721 | packets_recv: fields[1], 722 | errin: fields[2], 723 | errout: fields[10], 724 | dropin: fields[3], 725 | dropout: fields[11]) 726 | 727 | 728 | proc net_if_duplex_speed*(name: string): tuple[duplex: NicDuplex, speed: int] = 729 | ## Return stats about a particular network interface. 730 | ## References: 731 | ## https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py 732 | ## http://www.i-scream.org/libstatgrab/ 733 | 734 | result = (NIC_DUPLEX_UNKNOWN, 0) 735 | 736 | var ifr: ifreq 737 | var ethcmd: ethtool_cmd 738 | ethcmd.cmd = ETHTOOL_GSET 739 | ifr.ifr_ifru.ifru_data = addr ethcmd 740 | if not ioctlsocket(name, SIOCETHTOOL, ifr): 741 | return result 742 | 743 | let duplex_map = {DUPLEX_FULL: NIC_DUPLEX_FULL, 744 | DUPLEX_HALF: NIC_DUPLEX_HALF, 745 | DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN}.toTable() 746 | result.duplex = duplex_map[ethcmd.duplex] 747 | result.speed = int(ethcmd.speed) 748 | 749 | 750 | proc net_if_stats*(): TableRef[string, NICstats] = 751 | ## Get NIC stats (isup, duplex, speed, mtu). 752 | let names = toSeq(per_nic_net_io_counters().keys()) 753 | result = newTable[string, NICStats]() 754 | for name in names: 755 | let (duplex, speed) = net_if_duplex_speed(name) 756 | result[name] = NICStats(isup: net_if_flags(name), 757 | duplex: duplex, 758 | speed: speed, 759 | mtu: net_if_mtu(name)) 760 | 761 | 762 | proc get_partitions*(): seq[string] = 763 | # Determine partitions to look for 764 | result = newSeq[string]() 765 | var lines = toSeq(lines(PROCFS_PATH / "partitions")) 766 | for line in reversed(lines[2.. ( "10.0.0.5", 22 ) 887 | ## "0000000000000000FFFF00000100007F:9E49" -> ( "::ffff:127.0.0.1", 40521 ) 888 | ## The IP address portion is a little or big endian four-byte 889 | ## hexadecimal number; that is, the least significant byte is listed 890 | ## first, so we need to reverse the order of the bytes to convert it 891 | ## to an IP address. 892 | ## The port is represented as a two-byte hexadecimal number. 893 | ## Reference: 894 | ## http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html 895 | var ipPortPair = address.split(":") 896 | let ip = parseHexIP(ipPortPair[0], family) 897 | let port = Port(parseHexInt(ipPortPair[1])) 898 | return (ip, port) 899 | 900 | 901 | iterator process_inet(file: string, family: int, socketType: int, 902 | inodes: OrderedTable[string, seq[tuple[pid: int, fd: int]]], 903 | filter_pid = -1): Connection = 904 | var laddr, raddr, status, inode: string 905 | var pid, fd: int 906 | 907 | ## Parse /proc/net/tcp* and /proc/net/udp* files. 908 | if file.endsWith("6") and not os.fileExists(file): 909 | # IPv6 not supported 910 | yield Connection() 911 | 912 | for line in file.lines: 913 | try: 914 | let strings = line.splitWhitespace()[..10] 915 | laddr = strings[1] 916 | raddr = strings[2] 917 | status = strings[3] 918 | inode = strings[9] 919 | if laddr == "local_address": 920 | continue 921 | 922 | except ValueError: 923 | raise 924 | 925 | if inodes.hasKey(inode): 926 | # # We assume inet sockets are unique, so we error 927 | # # out if there are multiple references to the 928 | # # same inode. We won't do this for UNIX sockets. 929 | # if len( inodes[inode]) > 1 and family != socket.AF_UNIX: 930 | # raise ValueError( "ambiguos inode with multiple " 931 | # "PIDs references" ) 932 | pid = inodes[inode][0].pid 933 | fd = inodes[inode][0].fd 934 | 935 | else: 936 | pid = -1 937 | fd = -1 938 | 939 | if filter_pid != -1 and filter_pid != pid: 940 | continue 941 | 942 | else: 943 | if socketType == posix.SOCK_STREAM: 944 | status = TCP_STATUSES[status] 945 | else: 946 | status = "NONE" 947 | let lpair = decode_address(laddr, family) 948 | let rpair = decode_address(raddr, family) 949 | yield Connection(fd: fd, family: family, `type`: socketType, 950 | laddr: lpair.ip, lport: lpair.port, 951 | raddr: rpair.ip, rport: rpair.port, 952 | status: status, pid: pid) 953 | 954 | 955 | iterator process_unix(file: string, family: int, inodes: OrderedTable[string, 956 | seq[tuple[pid: int, fd: int]]], filter_pid = -1): Connection = 957 | ## Parse /proc/net/unix files 958 | for line in file.lines: 959 | let tokens = line.splitWhitespace() 960 | var socketType: string 961 | var inode: string 962 | try: 963 | socketType = tokens[4] 964 | inode = tokens[6] 965 | except ValueError: 966 | if not(" " in line): 967 | # see: https://github.com/giampaolo/psutil/issues/766 968 | continue 969 | raise newException( 970 | Exception, "error while parsing $1; malformed line $2" % [file, line]) 971 | 972 | # We're parsing the header, skip it 973 | if socketType == "Type": continue 974 | 975 | var pairs: seq[tuple[pid: int, fd: int]] 976 | if inodes.hasKey(inode): 977 | # With UNIX sockets we can have a single inode 978 | # referencing many file descriptors. 979 | pairs = inodes[inode] 980 | else: 981 | pairs = @[(-1, -1)] 982 | 983 | for pid_fd_tuple in pairs: 984 | let (pid, fd) = pid_fd_tuple 985 | if filter_pid != -1 and filter_pid != pid: 986 | continue 987 | 988 | let path = if len(tokens) == 8: tokens[7] else: "" 989 | yield Connection(fd: fd, family: family, `type`: parseInt(socketType), 990 | laddr: path, status: "NONE", pid: pid) 991 | 992 | 993 | proc net_connections*(kind = "inet", pid = -1): seq[Connection] = 994 | var inodes: OrderedTable[string, seq[tuple[pid: int, fd: int]]] 995 | result = newSeq[Connection]() 996 | 997 | if not tmap.hasKey(kind): 998 | return result 999 | 1000 | if pid != -1: 1001 | inodes = get_proc_inodes(pid) 1002 | if inodes.len == 0: # no connections for this process 1003 | return result 1004 | else: 1005 | inodes = get_all_inodes() 1006 | 1007 | let conTypes = tmap[kind] 1008 | for f, family, socketType in conTypes.items(): 1009 | if family in {posix.AF_INET, posix.AF_INET6}: 1010 | for conn in process_inet("/proc/net/$1" % f, family, socketType, inodes, 1011 | filter_pid = pid): 1012 | result.add(conn) 1013 | else: 1014 | for conn in process_unix("/proc/net/$1" % f, family, inodes, 1015 | filter_pid = pid): 1016 | result.add(conn) 1017 | 1018 | return result 1019 | 1020 | 1021 | proc isSsd*(diskLetter: char): bool {.inline.} = 1022 | ## Returns ``true`` if disk is SSD (Solid). Linux only. 1023 | ## 1024 | ## .. code-block:: nim 1025 | ## echo isSsd('a') ## 'a' for /dev/sda, 'b' for /dev/sdb, ... 1026 | ## 1027 | try: readFile("/sys/block/sd" & $diskLetter & "/queue/rotational") == 1028 | "0\n" except: false 1029 | 1030 | --------------------------------------------------------------------------------