├── LICENSE
├── README.mess
├── bsd.lisp
├── darwin.lisp
├── docs
└── index.html
├── documentation.lisp
├── freebsd.lisp
├── linux.lisp
├── machine-state.asd
├── mezzano.lisp
├── nx.lisp
├── openbsd.lisp
├── opengl.lisp
├── package.lisp
├── posix.lisp
├── protocol.lisp
├── staple.ext.lisp
├── test.lisp
└── windows.lisp
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023 Yukari Hafner
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 | 2. Altered source versions must be plainly marked as such, and must not be
16 | misrepresented as being the original software.
17 | 3. This notice may not be removed or altered from any source distribution.
18 |
--------------------------------------------------------------------------------
/README.mess:
--------------------------------------------------------------------------------
1 | ## About machine-state
2 | This library implements various functions to access status information about the machine, process, etc.
3 |
4 | ## How To
5 | Since this is a toolkit library, please simply refer to the symbol index for available functions and their behaviour. The following groups of functions are available:
6 |
7 | - Process
8 | - ``process-info``
9 | - ``process-io-bytes``
10 | - ``process-room``
11 | - ``process-time``
12 | - ``process-priority``
13 | - Threads
14 | - ``thread-time``
15 | - ``thread-core-mask``
16 | - ``thread-priority``
17 | - Implementation
18 | - ``gc-room``
19 | - ``gc-time``
20 | - ``static-room``
21 | - ``stack-room``
22 | - GPU
23 | - ``gpu-info``
24 | - ``gpu-room``
25 | - ``gpu-time``
26 | - Physical Machine
27 | - ``machine-info``
28 | - ``machine-room``
29 | - ``machine-cores``
30 | - ``machine-uptime``
31 | - ``machine-time``
32 | - ``machine-battery``
33 | - Storage Devices
34 | - ``storage-device``
35 | - ``storage-device-path``
36 | - ``storage-room``
37 | - ``storage-io-bytes``
38 | - Network Devices
39 | - ``network-info``
40 | - ``network-devices``
41 | - ``network-io-bytes``
42 |
43 | ## Implementation Support
44 | Fetching information about threads that aren't the current one, or GC related information requires implementation support. The following implementations are fully or partially supported:
45 |
46 | - CCL
47 | - Clasp
48 | - ECL
49 | - SBCL
50 | - Mezzano
51 |
52 | ## Operating System Support
53 | Fetching information about various hardware devices is, of course, OS dependent. The following systems are fully or partially supported:
54 |
55 | - FreeBSD
56 | - Linux
57 | - MacOS (darwin)
58 | - Mezzano
59 | - OpenBSD
60 | - Other BSDs (POSIX)
61 | - Windows
62 |
--------------------------------------------------------------------------------
/bsd.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (defmacro with-gensyms (syms &body body)
4 | `(let ,(mapcar (lambda (sym) `(,sym (gensym (symbol-name ',sym)))) syms)
5 | ,@body))
6 |
7 | #-openbsd
8 | (cffi:defcvar (errno "errno") :int64)
9 |
10 | (defun strncmp-lisp (foreign-str lisp-str &key (max-chars (length lisp-str)))
11 | (= 0 (cffi:foreign-funcall "strncmp" :pointer foreign-str :string lisp-str :size max-chars :int)))
12 |
13 | (defun errno ()
14 | #-openbsd errno
15 | ;; errno is a thread local in openbsd, simple (defcvar errno) won't work
16 | ;; https://github.com/openbsd/src/blob/master/lib/libc/gen/errno.c#L57
17 | ;; https://github.com/openbsd/src/blob/master/include/errno.h#L54
18 | #+openbsd (cffi:mem-ref (cffi:foreign-funcall "__errno" (:pointer :int)) :int))
19 |
20 | (defun strerror (&optional (errno (errno)))
21 | (cffi:foreign-funcall "strerror" :int errno :string))
22 |
23 | (defmacro posix-call (function &rest args)
24 | (with-gensyms (%val)
25 | `(let ((,%val (cffi:foreign-funcall ,function ,@args)))
26 | (if (< ,%val 0)
27 | (fail (strerror) ,function)
28 | ,%val))))
29 |
30 | #+freebsd
31 | (progn
32 | (defun count-fields (str separator)
33 | (reduce (lambda (count ch)
34 | (if (char= ch separator)
35 | (1+ count)
36 | count))
37 | str
38 | :initial-value 1))
39 |
40 | (defun sysctl-name-to-mib (name &optional (mibn (count-fields name #\.)))
41 | (cffi:with-foreign-objects ((mibp :int mibn) (sizep :size))
42 | (setf (cffi:mem-ref sizep :size) mibn)
43 | (cffi:foreign-funcall "sysctlnametomib" :string name (:pointer :int) mibp (:pointer :size) sizep)
44 | (loop for i below mibn collect (cffi:mem-aref mibp :int i))))
45 |
46 | (defun sysctl-resolve-mib (mib)
47 | (etypecase mib
48 | (string (sysctl-name-to-mib mib))
49 | (list (mapcan (lambda (x)
50 | (etypecase x
51 | (string (sysctl-name-to-mib x))
52 | (number (list x))))
53 | mib)))))
54 |
55 | #+openbsd
56 | (defun sysctl-resolve-mib (mib) mib)
57 |
58 | (cffi:defcfun ("sysctl" c-sysctl) :int
59 | (mib (:pointer :int))
60 | (mibn :uint)
61 | (old :pointer)
62 | (oldlen (:pointer :size))
63 | (new :pointer)
64 | (newlen :size))
65 |
66 | (defun sysctl (mib out out-size &optional (handle-error t))
67 | (setf mib (sysctl-resolve-mib mib))
68 | (let ((mibn (length mib)))
69 |
70 | (cffi:with-foreign-objects ((%mib :int mibn) (oldlen :size))
71 | (loop
72 | for name in mib and i from 0
73 | do (setf (cffi:mem-aref %mib :int i) name))
74 |
75 | (when out
76 | (setf (cffi:mem-ref oldlen :size) out-size))
77 |
78 | (let ((ret (c-sysctl %mib mibn (or out (cffi:null-pointer)) oldlen (cffi:null-pointer) 0)))
79 | (when (and handle-error (< ret 0))
80 | (fail (strerror) "sysctl"))
81 | (values (or out (cffi:mem-ref oldlen :int)) ret)))))
82 |
83 | (defmacro with-sysctl ((mib out type &optional (count 1)) &body body)
84 | "Utility for SYSCTL, MIB is evaluated into a list."
85 | (with-gensyms (%mib %count)
86 | `(let* ((,%mib ,(etypecase mib
87 | (list `(list ,@mib))
88 | (string `(list ,mib))
89 | (t mib)))
90 | (,%count ,count))
91 | (cffi:with-foreign-object (,out ,type ,%count)
92 | (sysctl ,%mib ,out (* ,%count (cffi:foreign-type-size ,type)))
93 | ,@body))))
94 |
95 | (defmacro with-sysctls ((&rest sysctls) &body body)
96 | "Like with sysctl, but allows for multiple at once."
97 | (if sysctls
98 | `(with-sysctl (,@(car sysctls))
99 | (with-sysctls (,@(cdr sysctls)) ,@body))
100 | `(progn ,@body)))
101 |
102 | (defun sysctl-unchecked (mib out out-size)
103 | "Like SYSCTL but don't handle the ERRNO, the return value of SYSCTL is in the second value.
104 | Useful for when ERRNO has special meanings."
105 | (sysctl mib out out-size nil))
106 |
107 | (defun sysctl-size (mib)
108 | "Call sysctl for the size of what would be returned with MIB, in bytes"
109 | (sysctl mib nil nil t))
110 |
111 | (defun sysctl-ref (mib type &optional (offset 0))
112 | (with-sysctl (mib out type)
113 | (cffi:mem-ref out type offset)))
114 |
115 | (defun sysctl-string (mib size)
116 | "Like SYSCTL but return a string of SIZE characters."
117 | (with-sysctl (mib str :char size)
118 | (cffi:foreign-string-to-lisp str :max-chars size)))
119 |
120 | #+32-bit
121 | (cffi:defcstruct (timeval :conc-name timeval-)
122 | (sec :uint32)
123 | (usec :uint32))
124 |
125 | #+64-bit
126 | (cffi:defcstruct (timeval :conc-name timeval-)
127 | (sec :uint64)
128 | (usec :uint64))
129 |
130 | (defun timeval->seconds (tv)
131 | (+ (timeval-sec tv)
132 | (/ (timeval-usec tv) 1000000.0d0)))
133 |
134 | (defconstant +unix-epoch+ (encode-universal-time 0 0 0 1 1 1970 0))
135 | (defun get-unix-time () (- (get-universal-time) +unix-epoch+))
136 |
137 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/time.h#L480
138 | ;;;; https://github.com/openbsd/src/blob/master/sys/sys/time.h#L157
139 | (cffi:defcstruct (clockinfo :size #+openbsd 16
140 | #+freebsd 20 ;; FreeBSD has a reserved field
141 | :conc-name clockinfo-)
142 | (hz :int))
143 |
144 | (defun getpid () (cffi:foreign-funcall "getpid" :long)) ;; pid_t
145 | (defun page-size () (cffi:foreign-funcall "getpagesize" :int))
146 |
147 | (defconstant +maxcomlen+
148 | #+openbsd 24 ;; Actually _MAXCOMLEN, https://github.com/openbsd/src/blob/master/sys/sys/sysctl.h#L363
149 | #+freebsd 19) ;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/param.h#L125
150 |
151 | (defun process-nice->priority (value)
152 | (cond ((< value -8) :realtime)
153 | ((< value 0) :high)
154 | ((= value 0) :normal)
155 | ((< value +8) :low)
156 | (T :idle)))
157 |
158 | (defun priority->process-nice (priority)
159 | (ecase priority
160 | (:idle 19)
161 | (:low 5)
162 | (:normal 0)
163 | (:high -5)
164 | (:realtime -20)))
165 |
166 | (defun split-path (path &optional (delimiter #\:))
167 | (let (paths start)
168 | (do ((i 0 (1+ i)))
169 | ((= i (length path)) (nreverse paths))
170 | (when (char= (schar path i) delimiter)
171 | (push (subseq path (or start 0) i) paths)
172 | (setf start (1+ i))))))
173 |
174 | (defun resolve-executable (command)
175 | (let ((path (cffi:foreign-funcall "getenv" :string "PATH" :string)))
176 | (when path
177 | (dolist (dir (split-path path #\:))
178 | (let ((exec-path (make-pathname
179 | :defaults (pathname-utils:parse-native-namestring dir :as :directory)
180 | :name command)))
181 | (when (probe-file exec-path)
182 | (return-from resolve-executable exec-path)))))))
183 |
184 | (defun uid->user (uid) (cffi:foreign-funcall "user_from_uid" :uint32 uid :int 1 :string))
185 | (defun gid->group (gid) (cffi:foreign-funcall "group_from_gid" :uint32 gid :int 1 :string))
186 |
187 | #+openbsd
188 | (cffi:defcstruct (stat :size #+32-bit 108
189 | #+64-bit 128
190 | :conc-name stat-)
191 | (mode :int :offset 0) ;; st_mode
192 | (dev :int :offset 4)) ;; st_dev
193 |
194 | #+freebsd
195 | (cffi:defcstruct (stat :size #+64-bit 224
196 | #+32-bit 208
197 | :conc-name stat-)
198 | (dev :int :offset 0) ;; st_dev
199 | (mode :int :offset 24)) ;; st_mode
200 |
201 | (defconstant +mnt-wait+ 1)
202 | (defconstant +mnt-nowait+ 2)
203 |
204 | (defun pathname-force-file (path)
205 | (cond
206 | ((pathname-utils:root-p path) path)
207 | ((pathname-utils:file-p path) path)
208 | (T (let ((directories (pathname-directory path)))
209 | (make-pathname :defaults path
210 | :directory (butlast directories)
211 | :name (car (last directories)))))))
212 |
213 | (defun find-mount-root (path)
214 | (labels ((dev-id (path)
215 | (cffi:with-foreign-objects ((stat '(:struct stat)))
216 | (posix-call "stat" :string (pathname-utils:native-namestring path) :pointer stat :int)
217 | (stat-dev stat)))
218 | (rec (path &optional (id (dev-id path)))
219 | (if (pathname-utils:root-p path)
220 | path
221 | (let* ((parent (pathname-utils:parent path))
222 | (parent-id (dev-id parent)))
223 | (if (= parent-id id)
224 | (rec parent parent-id)
225 | path)))))
226 | (pathname-force-file (rec (truename path)))))
227 |
228 | (defun getfsstat (buf &optional (count 0) (wait? t))
229 | (let* ((flags (if wait? +mnt-wait+ +mnt-nowait+))
230 | (bufsize (* count (cffi:foreign-type-size '(:struct statfs)))))
231 | (posix-call "getfsstat" :pointer (or buf (cffi:null-pointer)) :size bufsize :int flags :int)))
232 |
233 | (defun mount-count ()
234 | (getfsstat nil))
235 |
236 | (defmacro do-filesystems ((fs) &body body)
237 | (with-gensyms (statfs count i)
238 | `(let ((,count (mount-count)))
239 | (cffi:with-foreign-object (,statfs '(:struct statfs) ,count)
240 | (getfsstat ,statfs ,count)
241 | (or (dotimes (,i ,count)
242 | (let ((,fs (cffi:mem-aptr ,statfs '(:struct statfs) ,i)))
243 | ,@body))
244 | (fail "Filesystem not found"))))))
245 |
246 | ;;;; https://github.com/openbsd/src/blob/master/include/ifaddrs.h#L31
247 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/include/ifaddrs.h#L32
248 | (cffi:defcstruct (ifaddrs :conc-name ifaddrs-)
249 | (next (:pointer (:struct ifaddrs))) ;; ifa_next
250 | (name :string) ;; ifa_name
251 | (flags :uint) ;; ifa_flags
252 | (address :pointer) ;; ifa_addr
253 | (netmask :pointer) ;; ifa_netmask
254 | (destination :pointer) ;; ifa_dstaddr/ifa_broadaddr
255 | (data :pointer)) ;; ifa_data
256 |
257 | (cffi:defcstruct (sockaddr :conc-name sockaddr-)
258 | (length :uint8) ;; sa_len
259 | (family :uint8) ;; sa_family
260 | (data (:array :char 14))) ;; sa_data
261 |
262 | (cffi:defcstruct (sockaddr-dl :size #+openbsd 32
263 | #+freebsd 54
264 | :conc-name sockaddr-dl-)
265 | (interface-name-length :unsigned-char :offset 5) ;; sdl_nlen
266 | (address-length :unsigned-char :offset 6) ;; sdl_alen
267 | (data (:array :unsigned-char #+openbsd 24 #+freebsd 46) :offset 8)) ;; sdl_data
268 |
269 | (defun sockaddr-dl-address (dl)
270 | (let* ((addr-start (sockaddr-dl-interface-name-length dl))
271 | (addr-length (sockaddr-dl-address-length dl)))
272 | (if (= 0 addr-length)
273 | nil
274 | (subseq (sockaddr-dl-data dl) addr-start (+ addr-start addr-length)))))
275 |
276 | (cffi:defcstruct (sockaddr4 :size 16 :conc-name sockaddr4-)
277 | (family :ushort :offset 1)
278 | (port :uint16 :offset 2)
279 | (addr (:array :uint8 4) :offset 4))
280 |
281 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/netinet6/in6.h#L128
282 | (cffi:defcstruct (sockaddr6 :size 28 :conc-name sockaddr6-)
283 | (family :ushort :offset 1)
284 | (port :uint16 :offset 2)
285 | (addr (:array :uint8 16) :offset 8))
286 |
287 | (defconstant +af-link+ 18)
288 | (defconstant +af-inet+ 2)
289 | (defconstant +af-inet6+ #+openbsd 24 #+freebsd 28)
290 |
291 | (defmacro do-ifaddrs ((ifaptr) &body body)
292 | (with-gensyms (ifap)
293 | `(cffi:with-foreign-object (,ifap :pointer)
294 | (posix-call "getifaddrs" :pointer ,ifap :int)
295 | (let ((,ifap (cffi:mem-ref ,ifap :pointer)))
296 | (unwind-protect
297 | (do ((,ifaptr ,ifap (ifaddrs-next ,ifaptr)))
298 | ((cffi:null-pointer-p ,ifaptr) nil)
299 | ,@body)
300 | (cffi:foreign-funcall "freeifaddrs" :pointer ,ifap))))))
301 |
302 | (define-implementation network-devices ()
303 | (let ((names nil))
304 | (do-ifaddrs (ifaddr)
305 | (pushnew (ifaddrs-name ifaddr) names :test #'string=))
306 | (nreverse names)))
307 |
308 | (defun ipv4->string (ipv4)
309 | (format nil "~{~d~^.~}" (coerce ipv4 'list)))
310 |
311 | (defun macaddr->string (macaddr)
312 | (format nil "~{~2,'0x~^:~}" (coerce macaddr 'list)))
313 |
314 | (defun ipv6->string (ipv6)
315 | (labels ((fmt-byte (x) (format nil "~2,'0x" x))
316 | (fmt-segment (x y) (format nil "~a~a" (fmt-byte x) (fmt-byte y))))
317 | (format nil "~{~a~^:~}"
318 | (loop
319 | for (x y) on (coerce ipv6 'list) by #'cddr
320 | collect (fmt-segment x y)))))
321 |
322 | (define-implementation network-address (device)
323 | (let (ipv4 ipv6 mac)
324 | (do-ifaddrs (ifaddr)
325 | (when (string= device (ifaddrs-name ifaddr))
326 | (let* ((sockaddr (ifaddrs-address ifaddr))
327 | (address-family (sockaddr-family sockaddr)))
328 | (case address-family
329 | (#.+af-inet+ (unless ipv4 (setf ipv4 (ipv4->string (sockaddr4-addr sockaddr)))))
330 | (#.+af-inet6+ (unless ipv6 (setf ipv6 (ipv6->string (sockaddr6-addr sockaddr)))))
331 | (#.+af-link+
332 | (unless mac
333 | (let ((addr (sockaddr-dl-address sockaddr)))
334 | (when addr
335 | (setf mac (macaddr->string addr))))))))))
336 | (values ipv4 ipv6 mac)))
337 |
338 | (defconstant +o-rdonly+ 0)
339 | (defconstant +o-wronly+ 1)
340 | (defconstant +o-rdwr+ 2)
341 |
342 | (defmacro with-fd ((fd file &key (direction :input)) &body body)
343 | `(let ((,fd (posix-call "open"
344 | :string (pathname-utils:native-namestring ,file)
345 | :int ,(ecase direction
346 | (:input +o-rdonly+)
347 | (:output +o-wronly+)
348 | (:io +o-rdwr+))
349 | :int)))
350 | (unwind-protect
351 | (progn ,@body)
352 | (posix-call "close" :int ,fd :int))))
353 |
--------------------------------------------------------------------------------
/darwin.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (defmacro sysctl (prop type &body body)
4 | `(cffi:with-foreign-objects ((ret ',type)
5 | (size :size))
6 | (setf (cffi:mem-ref size :size) (cffi:foreign-type-size ',type))
7 | (let ((status (cffi:foreign-funcall "sysctlbyname" :string ,prop :pointer ret :pointer size :pointer (cffi:null-pointer) :size 0 :int)))
8 | (cond ((/= 0 status)
9 | (fail (cffi:foreign-funcall "strerror" :int64 status :string)))
10 | (T ,@body)))))
11 |
12 | #++
13 | (define-implementation process-io-bytes ()
14 | )
15 |
16 | #++
17 | (define-implementation process-room ()
18 | )
19 |
20 | (cffi:defcstruct (vm-statistics :conc-name vm-statistics-)
21 | (free-count :uint32)
22 | (active-count :uint32)
23 | (inactive-count :uint32)
24 | (wire-count :uint32)
25 | (zero-fill-count :uint64)
26 | (reactivations :uint64)
27 | (page-ins :uint64)
28 | (page-outs :uint64)
29 | (faults :uint64)
30 | (cow-faults :uint64)
31 | (lookups :uint64)
32 | (hits :uint64)
33 | (purges :uint64)
34 | (purgeable-count :uint32)
35 | (speculative-count :uint32)
36 | (decompressions :uint64)
37 | (compressions :uint64)
38 | (swap-ins :uint64)
39 | (swap-outs :uint64)
40 | (compressor-page-count :uint32)
41 | (throttled-count :uint32)
42 | (external-page-count :uint32)
43 | (internal-page-count :uint32)
44 | (total-uncompressed-pages-in-compressor :uint64))
45 |
46 | (define-implementation machine-room ()
47 | (cffi:with-foreign-objects ((stats '(:struct vm-statistics))
48 | (count :uint))
49 | (setf (cffi:mem-ref count :uint)
50 | (/ (cffi:foreign-type-size '(:struct vm-statistics))
51 | (cffi:foreign-type-size :int32)))
52 | (cond ((/= 0 (cffi:foreign-funcall "host_statistics64"
53 | :size (cffi:foreign-funcall "mach_host_self" :size)
54 | :int 4 ; HOST_VM_INFO64
55 | :pointer stats
56 | :pointer count
57 | :int))
58 | (fail "Failed to retrieve host statistics"))
59 | (T
60 | (let* ((free-pages (vm-statistics-free-count stats))
61 | (free (* (cffi:foreign-funcall "getpagesize" :int) free-pages))
62 | (total (sysctl "hw.memsize" :int64 (cffi:mem-ref ret :int64))))
63 | (values (- total free) total))))))
64 |
65 | (define-implementation machine-uptime ()
66 | (sysctl "kern.boottime" (:struct timeval)
67 | (- (- (get-universal-time)
68 | (encode-universal-time 0 0 0 1 1 1970 0))
69 | (timeval-sec ret))))
70 |
71 | (define-implementation machine-cores ()
72 | (sysctl "hw.ncpu" :uint (cffi:mem-ref ret :uint)))
73 |
74 | #++
75 | (define-implementation storage-device (path)
76 | )
77 |
78 | #++
79 | (define-implementation storage-device-path (path)
80 | )
81 |
82 | (cffi:defcstruct (statvfs :size 64 :conc-name statvfs-)
83 | (bsize :uint64 :offset 0)
84 | (frsize :uint64 :offset 8)
85 | (blocks :uint32 :offset 16)
86 | (bfree :uint32 :offset 20)
87 | (bavail :uint32 :offset 24)
88 | (files :uint32 :offset 28)
89 | (ffree :uint32 :offset 32)
90 | (favail :uint32 :offset 36)
91 | (fsid :uint64 :offset 40)
92 | (flag :uint64 :offset 48)
93 | (namemax :uint64 :offset 56))
94 |
95 | (define-implementation storage-room (path)
96 | (when (stringp path)
97 | (setf path (storage-device-path path)))
98 | (cffi:with-foreign-objects ((statvfs '(:struct statvfs)))
99 | (posix-call "statvfs" :string (pathname-utils:native-namestring path) :pointer statvfs :int)
100 | (values (* (statvfs-bavail statvfs)
101 | (statvfs-bsize statvfs))
102 | (* (statvfs-blocks statvfs)
103 | (statvfs-bsize statvfs)))))
104 |
105 | #++
106 | (define-implementation storage-io-bytes (device)
107 | (when (pathnamep device)
108 | (setf device (storage-device device)))
109 | )
110 |
111 | #++
112 | (define-implementation network-io-bytes (device)
113 | )
114 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
Machine State About machine-state
This library implements various functions to access status information about the machine, process, etc.
How To
Since this is a toolkit library, please simply refer to the symbol index for available functions and their behaviour. The following groups of functions are available:
Process
Threads
Implementation
GPU
Physical Machine
Storage Devices
Network Devices
Implementation Support
Fetching information about threads that aren't the current one, or GC related information requires implementation support. The following implementations are fully or partially supported:
Operating System Support
Fetching information about various hardware devices is, of course, OS dependent. The following systems are fully or partially supported:
BSDs (POSIX)
Linux
MacOS (darwin)
Mezzano
Windows
Definition Index
-
No documentation provided.
-
Error signalled if a query should fail for some reason.
2 |
3 | This condition is *NOT* signalled if the function is simply
4 | unsupported. It is however signalled if an OS call failed for some
5 | reason such as lack of access permissions.
-
Returns the GC's memory usage statistics.
6 |
7 | This does not include foreign memory usage.
8 |
9 | Returns two values:
10 | The number of free bytes
11 | The total number of bytes available
12 |
13 | If the function is unsupported a constant 0 is returned for both
14 | values.
15 |
16 | See MACHINE-ROOM
17 | See PROCESS-ROOM
18 | See GPU-ROOM
19 | See STATIC-ROOM
20 | See STACK-ROOM
21 | See STORAGE-ROOM
-
-
Returns the GPU's memory usage statistics.
30 |
31 | Returns two values:
32 | The number of free bytes
33 | The total number of bytes available
34 |
35 | If the function is unsupported a constant 0 is returned for both
36 | values.
37 |
38 | You may want to load the machine-state/opengl library to make this
39 | function useful. In that case, it will only work if an OpenGL context
40 | is current to this thread.
41 |
42 | See MACHINE-ROOM
43 | See PROCESS-ROOM
44 | See GC-ROOM
45 | See STATIC-ROOM
46 | See STACK-ROOM
47 | See STORAGE-ROOM
-
Returns the amount of processing time spent on the GPU by this process.
48 |
49 | If the function is unsupported a constant 0.0d0 is returned.
50 |
51 | You may want to load the machine-state/opengl library to make this
52 | function useful. In that case, it will only work if an OpenGL context
53 | is current to this thread.
54 |
55 | See PROCESS-TIME
56 | See GC-TIME
57 | See MACHINE-TIME
58 | See THREAD-TIME
-
Returns the number of cores available on the machine.
59 |
60 | If the function is unsupported a constant 1 is returned.
61 |
62 | See THREAD-CORE-MASK
-
Returns the machine's primary memory usage statistics.
63 |
64 | Returns two values:
65 | The number of physical bytes occupied
66 | The total number of physical bytes available
67 |
68 | If the function is unsupported a constant 0 is returned for all
69 | values.
70 |
71 | See PROCESS-ROOM
72 | See GC-ROOM
73 | See GPU-ROOM
74 | See STATIC-ROOM
75 | See STACK-ROOM
76 | See STORAGE-ROOM
-
Returns the amount of time spent processing.
77 |
78 | Core may be T for an aggregate of all cores, or an integer of the core number.
79 |
80 | Returns two values:
81 | The time spent idle in seconds
82 | The total time spent in seconds
83 |
84 | If the function is unsupported a constant 0.0d0 is returned.
85 |
86 | See MACHINE-CORES
-
Returns the number of seconds since the machine was started up.
87 |
88 | If the function is unsupported a constant 0 is returned.
-
Returns a list of network device names.
89 |
90 | If the function is unsupported an empty list is returned.
91 |
92 | See NETWORK-IO-BYTES
-
Returns the number of bytes of IO performed on the network device.
93 |
94 | The argument may either be the system name of the device as a string
95 | or T to get an aggregate of all attached devices.
96 |
97 | Returns three values:
98 | The total number of IO bytes performed.
99 | The bytes read.
100 | The bytes written.
101 |
102 | If the function is unsupported a constant 0 is returned.
103 |
104 | See NETWORK-DEVICES
105 | See PROCESS-IO-BYTES
106 | See STORAGE-IO-BYTES
-
Returns the number of bytes of IO performed by the process.
107 |
108 | Returns three values:
109 | The total number of IO bytes performed.
110 | The bytes read.
111 | The bytes written.
112 |
113 | IO in this context refers to any activity to external devices such as
114 | drives, networking, etc.
115 |
116 | If the function is unsupported a constant 0 is returned for all
117 | values.
118 |
119 | See STORAGE-IO-BYTES
120 | See NETWORK-IO-BYTES
-
Accessor to the scheduler priority of the process.
121 |
122 | The priority can be one of the following values, in ascending order of
123 | importance:
124 |
125 | :IDLE
126 | :LOW
127 | :NORMAL
128 | :HIGH
129 | :REALTIME
130 |
131 | If the function is unsupported :NORMAL is returned in all cases.
132 |
133 | When setting this place, the *actual* priority of the process is
134 | returned, which may differ from the one you tried to set.
135 |
136 | See THREAD-PRIORITY
-
No documentation provided.
-
Returns the process' memory usage statistics.
137 |
138 | This includes foreign memory usage.
139 |
140 | Returns the number of bytes occupied.
141 |
142 | If the function is unsupported a constant 0 is returned.
143 |
144 | See MACHINE-ROOM
145 | See GC-ROOM
146 | See GPU-ROOM
147 | See STATIC-ROOM
148 | See STACK-ROOM
149 | See STORAGE-ROOM
-
Returns the amount of processing time spent by this process in seconds.
150 |
151 | This does not include time spent in the kernel.
152 |
153 | If the function is unsupported a constant 0.0d0 is returned.
154 |
155 | See MACHINE-TIME
156 | See THREAD-TIME
157 | See GC-TIME
158 | See GPU-TIME
-
-
-
Return the system device name of the device backing the path.
179 |
180 | Returns the device name as a string if it can be found and signals a
181 | QUERY-FAILED error otherwise.
182 |
183 | See STORAGE-DEVICE-PATH
184 | See STORAGE-ROOM
185 | See STORAGE-IO-BYTES
-
Return a path which the storage device is backing if any.
186 |
187 | Returns the path as a directory pathname if it can be found and
188 | signals a QUERY-FAILED error otherwise.
189 |
190 | See STORAGE-DEVICE
191 | See STORAGE-ROOM
192 | See STORAGE-IO-BYTES
-
Returns the number of bytes of IO performed on the storage device.
193 |
194 | The argument may either be a pathname to a file on the device to
195 | query, the system provided name for the device, or T to get an
196 | aggregate of all attached devices.
197 |
198 | Returns three values:
199 | The total number of IO bytes performed.
200 | The bytes read.
201 | The bytes written.
202 |
203 | If the function is unsupported a constant 0 is returned.
204 |
205 | See STORAGE-DEVICE
206 | See STORAGE-DEVICE-PATH
207 | See NETWORK-IO-BYTES
208 | See PROCESS-IO-BYTES
-
Return file system storage usage statistics.
209 |
210 | The argument may either be a pathname to a file on the device to
211 | query, or the system provided name for the device.
212 |
213 | Returns two values:
214 | The number of free bytes
215 | The total number of bytes available
216 |
217 | See STORAGE-DEVICE
218 | See STORAGE-DEVICE-PATH
219 | See MACHINE-ROOM
220 | See PROCESS-ROOM
221 | See GC-ROOM
222 | See GPU-ROOM
223 | See STATIC-ROOM
224 | See STACK-ROOM
-
Accessor to the CPU core affinity mask of the thread.
225 |
226 | The mask is a bitfield where each set bit in the integer designates a
227 | core that the thread may be executed on. For compatibility reasons
228 | only integers up to 64 bits are supported.
229 |
230 | Thread may be T for the current thread, or a BT:THREAD.
231 |
232 | If the function is unsupported a constant of all 1s is returned.
233 |
234 | When setting this place, the *actual* affinity mask of the thread is
235 | returned, which may differ from the one you tried to set.
236 |
237 | See MACHINE-CORES
-
No documentation provided.
-
Accessor to the scheduler priority of the thread.
238 |
239 | The priority can be one of the following values, in ascending order of
240 | importance:
241 |
242 | :IDLE
243 | :LOW
244 | :NORMAL
245 | :HIGH
246 | :REALTIME
247 |
248 | Thread may be T for the current thread, or a BT:THREAD.
249 |
250 | If the function is unsupported :NORMAL is returned in all cases.
251 |
252 | When setting this place, the *actual* priority of the thread is
253 | returned, which may differ from the one you tried to set.
254 |
255 | See PROCESS-PRIORITY
-
No documentation provided.
-
Returns the amount of processing time spent by this thread in seconds.
256 |
257 | This does not include time spent in the kernel.
258 |
259 | Thread may be T for the current thread, or a BT:THREAD.
260 |
261 | If the function is unsupported a constant 0.0d0 is returned.
262 |
263 | See MACHINE-TIME
264 | See PROCESS-TIME
265 | See GC-TIME
266 | See GPU-TIME
--------------------------------------------------------------------------------
/documentation.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (docs:define-docs
4 | (type query-failed
5 | "Error signalled if a query should fail for some reason.
6 |
7 | This condition is *NOT* signalled if the function is simply
8 | unsupported. It is however signalled if an OS call failed for some
9 | reason such as lack of access permissions.")
10 |
11 | (function process-io-bytes
12 | "Returns the number of bytes of IO performed by the process.
13 |
14 | Returns three values:
15 | The total number of IO bytes performed.
16 | The bytes read.
17 | The bytes written.
18 |
19 | IO in this context refers to any activity to external devices such as
20 | drives, networking, etc.
21 |
22 | If the function is unsupported a constant 0 is returned for all
23 | values.
24 |
25 | See STORAGE-IO-BYTES
26 | See NETWORK-IO-BYTES")
27 |
28 | (function process-time
29 | "Returns the amount of processing time spent by this process in seconds.
30 |
31 | This does not include time spent in the kernel.
32 |
33 | If the function is unsupported a constant 0.0d0 is returned.
34 |
35 | See MACHINE-TIME
36 | See THREAD-TIME
37 | See GC-TIME
38 | See GPU-TIME")
39 |
40 | (function process-room
41 | "Returns the process' memory usage statistics.
42 |
43 | This includes foreign memory usage.
44 |
45 | Returns the number of bytes occupied.
46 |
47 | If the function is unsupported a constant 0 is returned.
48 |
49 | See MACHINE-ROOM
50 | See GC-ROOM
51 | See GPU-ROOM
52 | See STATIC-ROOM
53 | See STACK-ROOM
54 | See STORAGE-ROOM")
55 |
56 | (function machine-room
57 | "Returns the machine's primary memory usage statistics.
58 |
59 | Returns two values:
60 | The number of physical bytes occupied
61 | The total number of physical bytes available
62 |
63 | If the function is unsupported a constant 0 is returned for all
64 | values.
65 |
66 | See PROCESS-ROOM
67 | See GC-ROOM
68 | See GPU-ROOM
69 | See STATIC-ROOM
70 | See STACK-ROOM
71 | See STORAGE-ROOM")
72 |
73 | (function machine-cores
74 | "Returns the number of cores available on the machine.
75 |
76 | If the function is unsupported a constant 1 is returned.
77 |
78 | See THREAD-CORE-MASK")
79 |
80 | (function machine-uptime
81 | "Returns the number of seconds since the machine was started up.
82 |
83 | If the function is unsupported a constant 0 is returned.")
84 |
85 | (function machine-time
86 | "Returns the amount of time spent processing.
87 |
88 | Core may be T for an aggregate of all cores, or an integer of the core number.
89 |
90 | Returns two values:
91 | The time spent idle in seconds
92 | The total time spent in seconds
93 |
94 | If the function is unsupported a constant 0.0d0 is returned.
95 |
96 | See MACHINE-CORES")
97 |
98 | (function thread-time
99 | "Returns the amount of processing time spent by this thread in seconds.
100 |
101 | This does not include time spent in the kernel.
102 |
103 | Thread may be T for the current thread, or a BT:THREAD.
104 |
105 | If the function is unsupported a constant 0.0d0 is returned.
106 |
107 | See MACHINE-TIME
108 | See PROCESS-TIME
109 | See GC-TIME
110 | See GPU-TIME")
111 |
112 | (function thread-core-mask
113 | "Accessor to the CPU core affinity mask of the thread.
114 |
115 | The mask is a bitfield where each set bit in the integer designates a
116 | core that the thread may be executed on. For compatibility reasons
117 | only integers up to 64 bits are supported.
118 |
119 | Thread may be T for the current thread, or a BT:THREAD.
120 |
121 | If the function is unsupported a constant of all 1s is returned.
122 |
123 | When setting this place, the *actual* affinity mask of the thread is
124 | returned, which may differ from the one you tried to set.
125 |
126 | See MACHINE-CORES")
127 |
128 | (function process-priority
129 | "Accessor to the scheduler priority of the process.
130 |
131 | The priority can be one of the following values, in ascending order of
132 | importance:
133 |
134 | :IDLE
135 | :LOW
136 | :NORMAL
137 | :HIGH
138 | :REALTIME
139 |
140 | If the function is unsupported :NORMAL is returned in all cases.
141 |
142 | When setting this place, the *actual* priority of the process is
143 | returned, which may differ from the one you tried to set.
144 |
145 | See THREAD-PRIORITY")
146 |
147 | (function thread-priority
148 | "Accessor to the scheduler priority of the thread.
149 |
150 | The priority can be one of the following values, in ascending order of
151 | importance:
152 |
153 | :IDLE
154 | :LOW
155 | :NORMAL
156 | :HIGH
157 | :REALTIME
158 |
159 | Thread may be T for the current thread, or a BT:THREAD.
160 |
161 | If the function is unsupported :NORMAL is returned in all cases.
162 |
163 | When setting this place, the *actual* priority of the thread is
164 | returned, which may differ from the one you tried to set.
165 |
166 | See PROCESS-PRIORITY")
167 |
168 | (function gc-room
169 | "Returns the GC's memory usage statistics.
170 |
171 | This does not include foreign memory usage.
172 |
173 | Returns two values:
174 | The number of free bytes
175 | The total number of bytes available
176 |
177 | If the function is unsupported a constant 0 is returned for both
178 | values.
179 |
180 | See MACHINE-ROOM
181 | See PROCESS-ROOM
182 | See GPU-ROOM
183 | See STATIC-ROOM
184 | See STACK-ROOM
185 | See STORAGE-ROOM")
186 |
187 | (function gc-time
188 | "Returns the amount of processing time spent in the GC.
189 |
190 | If the function is unsupported a constant 0.0d0 is returned.
191 |
192 | See MACHINE-TIME
193 | See PROCESS-TIME
194 | See GC-TIME
195 | See GPU-TIME
196 | See THREAD-TIME")
197 |
198 | (function gpu-room
199 | "Returns the GPU's memory usage statistics.
200 |
201 | Returns two values:
202 | The number of free bytes
203 | The total number of bytes available
204 |
205 | If the function is unsupported a constant 0 is returned for both
206 | values.
207 |
208 | You may want to load the machine-state/opengl library to make this
209 | function useful. In that case, it will only work if an OpenGL context
210 | is current to this thread.
211 |
212 | See MACHINE-ROOM
213 | See PROCESS-ROOM
214 | See GC-ROOM
215 | See STATIC-ROOM
216 | See STACK-ROOM
217 | See STORAGE-ROOM")
218 |
219 | (function gpu-time
220 | "Returns the amount of processing time spent on the GPU by this process.
221 |
222 | If the function is unsupported a constant 0.0d0 is returned.
223 |
224 | You may want to load the machine-state/opengl library to make this
225 | function useful. In that case, it will only work if an OpenGL context
226 | is current to this thread.
227 |
228 | See PROCESS-TIME
229 | See GC-TIME
230 | See MACHINE-TIME
231 | See THREAD-TIME")
232 |
233 | (function static-room
234 | "Returns the static space size as an integer.
235 |
236 | If the function is unsupported zero is returned.
237 |
238 | See MACHINE-ROOM
239 | See PROCESS-ROOM
240 | See GC-ROOM
241 | See GPU-ROOM
242 | See STACK-ROOM
243 | See STORAGE-ROOM")
244 |
245 | (function stack-room
246 | "Return the stack usage statistics.
247 |
248 | Returns two values:
249 | The number of free stack bytes
250 | The total stack space available
251 |
252 | See MACHINE-ROOM
253 | See PROCESS-ROOM
254 | See GC-ROOM
255 | See GPU-ROOM
256 | See STATIC-ROOM
257 | See STORAGE-ROOM")
258 |
259 | (function storage-room
260 | "Return file system storage usage statistics.
261 |
262 | The argument may either be a pathname to a file on the device to
263 | query, or the system provided name for the device.
264 |
265 | Returns two values:
266 | The number of free bytes
267 | The total number of bytes available
268 |
269 | See STORAGE-DEVICE
270 | See STORAGE-DEVICE-PATH
271 | See MACHINE-ROOM
272 | See PROCESS-ROOM
273 | See GC-ROOM
274 | See GPU-ROOM
275 | See STATIC-ROOM
276 | See STACK-ROOM")
277 |
278 | (function storage-device
279 | "Return the system device name of the device backing the path.
280 |
281 | Returns the device name as a string if it can be found and signals a
282 | QUERY-FAILED error otherwise.
283 |
284 | See STORAGE-DEVICE-PATH
285 | See STORAGE-ROOM
286 | See STORAGE-IO-BYTES")
287 |
288 | (function storage-device-path
289 | "Return a path which the storage device is backing if any.
290 |
291 | Returns the path as a directory pathname if it can be found and
292 | signals a QUERY-FAILED error otherwise.
293 |
294 | See STORAGE-DEVICE
295 | See STORAGE-ROOM
296 | See STORAGE-IO-BYTES")
297 |
298 | (function storage-io-bytes
299 | "Returns the number of bytes of IO performed on the storage device.
300 |
301 | The argument may either be a pathname to a file on the device to
302 | query, the system provided name for the device, or T to get an
303 | aggregate of all attached devices.
304 |
305 | Returns three values:
306 | The total number of IO bytes performed.
307 | The bytes read.
308 | The bytes written.
309 |
310 | If the function is unsupported a constant 0 is returned.
311 |
312 | See STORAGE-DEVICE
313 | See STORAGE-DEVICE-PATH
314 | See NETWORK-IO-BYTES
315 | See PROCESS-IO-BYTES")
316 |
317 | (function network-devices
318 | "Returns a list of network device names.
319 |
320 | If the function is unsupported an empty list is returned.
321 |
322 | See NETWORK-IO-BYTES
323 | See NETWORK-ADDRESS")
324 |
325 | (function network-io-bytes
326 | "Returns the number of bytes of IO performed on the network device.
327 |
328 | The argument may either be the system name of the device as a string
329 | or T to get an aggregate of all attached devices.
330 |
331 | Returns three values:
332 | The total number of IO bytes performed.
333 | The bytes read.
334 | The bytes written.
335 |
336 | If the function is unsupported a constant 0 is returned.
337 |
338 | See NETWORK-DEVICES
339 | See PROCESS-IO-BYTES
340 | See STORAGE-IO-BYTES")
341 |
342 | (function machine-info
343 | "Returns information about the host machine.
344 |
345 | Returns four values:
346 | The name of the vendor of the machine (or motherboard) as a string
347 | The name of the model of the machine (or motherboard) as a string
348 | The name of the operating system of the machine as a keyword:
349 | :WINDOWS
350 | :LINUX
351 | :DARWIN
352 | :ANDROID
353 | :IOS
354 | :NETBSD
355 | :FREEBSD
356 | :OPENBSD
357 | :BEOS
358 | :SOLARIS
359 | :REACT
360 | :PLAN9
361 | :MEZZANO
362 | :NX
363 | NIL
364 | The version of the operating system as a string
365 |
366 | If the function is unsupported,
367 | \"Unknown\"
368 | \"Unknown\"
369 | NIL
370 | \"Unknown\"
371 | are returned.
372 |
373 | See MACHINE-CORE-INFO
374 | See PROCESS-INFO
375 | See GPU-INFO
376 | See NETWORK-INFO")
377 |
378 | (function machine-battery
379 | "Returns information about the battery charge state, if any.
380 |
381 | Returns three values:
382 | Current charge
383 | Full charge
384 | Charging state:
385 | :CHARGING
386 | :DISCHARGING
387 | :FULL
388 | NIL
389 |
390 | If no battery is attached or the function is unsupported,
391 | 0.0d0
392 | 0.0d0
393 | NIL
394 | are returned.")
395 |
396 | (function machine-core-info
397 | "Returns information about the host machine's processor.
398 |
399 | Returns four values:
400 | The name of the vendor of the processor as a string
401 | The name of the model of the processor as a string
402 | The name of the architecture as a keyword:
403 | :X86
404 | :AMD64
405 | :ARM
406 | :ARM64
407 | :RISCV
408 | :RISCV64
409 | :PPC
410 | :SPARC
411 | NIL
412 | The version of the architecture as a string
413 |
414 | If the function is unsupported,
415 | \"Unknown\"
416 | \"Unknown\"
417 | NIL
418 | \"Unknown\"
419 | are returned.
420 |
421 | See MACHINE-INFO
422 | See PROCESS-INFO
423 | See GPU-INFO
424 | See NETWORK-INFO")
425 |
426 | (function process-info
427 | "Returns information about the current process.
428 |
429 | Returns four values:
430 | The path to the executable as a pathname
431 | The current working directory as a pathname
432 | The running user as a string or NIL
433 | The running group as a string or NIL
434 |
435 | If the function is unsupported,
436 | *default-pathname-defaults*
437 | *default-pathname-defaults*
438 | \"Unknown\"
439 | \"Unknown\"
440 | are returned.
441 |
442 | See MACHINE-INFO
443 | See MACHINE-CORE-INFO
444 | See GPU-INFO
445 | See NETWORK-INFO")
446 |
447 | (function gpu-info
448 | "Returns information about the graphics card.
449 |
450 | Returns three values:
451 | The vendor of the graphics card as a keyword:
452 | :NVIDIA
453 | :AMD (formerly ATI)
454 | :INTEL
455 | and others
456 | The model of the graphics card as a string
457 | The version of OpenGL and/or graphics card driver as a string
458 |
459 | If the function is unsupported,
460 | NIL
461 | \"Unknown\"
462 | \"Unknown\"
463 | are returned.
464 |
465 | You may want to load the machine-state/opengl library to make this
466 | function useful. In that case, it will only work if an OpenGL context
467 | is current to this thread.
468 |
469 | See MACHINE-INFO
470 | See MACHINE-CORE-INFO
471 | See PROCESS-INFO
472 | See NETWORK-INFO")
473 |
474 | (function network-info
475 | "Returns information about the machine's network state.
476 |
477 | Returns one value:
478 | The hostname of the machine as a string
479 |
480 | If the function is unsupported,
481 | NIL
482 | is returned.
483 |
484 | See MACHINE-INFO
485 | See MACHINE-CORE-INFO
486 | See PROCESS-INFO
487 | See GPU-INFO")
488 |
489 | (function network-address
490 | "Returns information about a network device's addresses.
491 |
492 | Returns three values:
493 | The device's MAC address as a string or NIL
494 | The device's IPv4 address as a string or NIL
495 | The device's IPv6 address as a string or NIL
496 |
497 | If the function is unsupported,
498 | NIL
499 | NIL
500 | NIL
501 | are returned.
502 |
503 | See NETWORK-DEVICES
504 | See NETWORK-INFO"))
505 |
--------------------------------------------------------------------------------
/freebsd.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (define-implementation machine-room ()
4 | (let ((page-size (sysctl-ref "hw.pagesize" :uint32))
5 | (physmem (sysctl-ref "hw.physmem" :uint64))
6 | (free (sysctl-ref "vm.stats.vm.v_free_count" :uint32)))
7 | (values (- physmem (* free page-size)) physmem)))
8 |
9 | (define-implementation machine-uptime ()
10 | (with-sysctl ("kern.boottime" tv '(:struct timeval))
11 | (- (get-unix-time) (timeval-sec tv))))
12 |
13 | (define-implementation machine-cores ()
14 | (sysctl-ref "hw.ncpu" :int))
15 |
16 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/resource.h#L172
17 | (defconstant +cpustates+ 5)
18 |
19 | (defun cpu-time ()
20 | (sysctl-ref "kern.cp_time" `(:array :uint64 ,+cpustates+)))
21 |
22 | (defun core-time (core)
23 | (let ((size (* (machine-cores) +cpustates+)))
24 | (with-sysctl ("kern.cp_times" cpustates :uint64 size)
25 | (cffi:mem-aref cpustates `(:array :uint64 ,+cpustates+) core))))
26 |
27 | (define-implementation machine-time (core)
28 | (with-sysctl ("kern.clockrate" clockinfo '(:struct clockinfo))
29 | (flet ((conv (x) (/ x (float (clockinfo-hz clockinfo) 0.0d0))))
30 | (let ((values (cond
31 | ((eq 't core) (cpu-time))
32 | ((>= core (machine-cores)) (fail "No such core."))
33 | (t (core-time core)))))
34 | (destructuring-bind (user nice sys intr idle) (coerce values 'list)
35 | (values (conv idle)
36 | (conv (+ user nice sys intr idle))))))))
37 |
38 | (define-implementation machine-info ()
39 | (values "Unknown" "Unknown" :freebsd (sysctl-string "kern.osrelease" 32)))
40 |
41 | (define-implementation machine-core-info ()
42 | (let ((processor (sysctl-string "hw.model" 128)))
43 | (values processor
44 | processor ;; There doesn't seem to be a separation between those
45 | (arch-type)
46 | (sysctl-string "hw.machine" 32))))
47 |
48 | (cffi:defcstruct (rusage :size 144 :conc-name rusage-)
49 | (user-time (:struct timeval))
50 | (system-time (:struct timeval)))
51 |
52 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/user.h#L118
53 | (cffi:defcstruct (kinfo-proc :size 1088 :conc-name kinfo-proc-)
54 | (user-id :uint32 :offset 168) ;; ki_uid
55 | (group-id :uint32 :offset 180) ;; ki_rgid
56 | (resident-set-size :int32 :offset 264) ;; ki_rssize
57 | (nice :int8 :offset 389) ;; ki_nice
58 | (command-name (:array :char #.(1+ +maxcomlen+)) :offset 447) ;; ki_comm
59 | ;; (runtime :uint64 :offset 328) ;; ki_runtime (microseconds)
60 | (thread-id :int32 :offset 600) ;; ki_tid
61 | (rusage (:struct rusage) :offset 608)) ;; ki_rusage
62 |
63 | (defmacro with-current-process ((proc) &body body)
64 | `(with-sysctl (("kern.proc.pid" (getpid)) ,proc '(:struct kinfo-proc))
65 | ,@body))
66 |
67 | (define-implementation process-room ()
68 | (with-current-process (proc)
69 | (* (page-size) (kinfo-proc-resident-set-size proc))))
70 |
71 | (define-implementation process-priority ()
72 | (with-current-process (proc)
73 | (process-nice->priority (kinfo-proc-nice proc))))
74 |
75 | (define-implementation process-time ()
76 | (with-current-process (proc)
77 | (let* ((rusage (cffi:foreign-slot-pointer proc '(:struct kinfo-proc) 'rusage))
78 | (tv (cffi:foreign-slot-pointer rusage '(:struct rusage) 'user-time)))
79 | (timeval->seconds tv))))
80 |
81 | (define-implementation process-info ()
82 | (with-current-process (proc)
83 | (values (let ((command (cffi:foreign-string-to-lisp
84 | (cffi:foreign-slot-pointer proc '(:struct kinfo-proc) 'command-name))))
85 | (or (resolve-executable command) command))
86 | (cffi:with-foreign-object (cwd :char 1024)
87 | (cffi:foreign-funcall "getcwd" (:pointer :char) cwd :size 1024)
88 | (pathname-utils:parse-native-namestring (cffi:foreign-string-to-lisp cwd :max-chars 1024) :as :directory))
89 | (uid->user (kinfo-proc-user-id proc))
90 | (gid->group (kinfo-proc-group-id proc)))))
91 |
92 | (defconstant +mfsnamelen+ 16) ;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/mount.h#L78
93 | (defconstant +mnamelen+ 1024) ;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/mount.h#L79
94 |
95 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/sys/mount.h#L81
96 | (cffi:defcstruct (statfs :size 2344 :conc-name statfs-)
97 | (block-size :uint32 :offset 16) ;; f_bsize
98 | (blocks :uint64 :offset 32) ;; f_blocks
99 | (available-blocks :int64 :offset 48) ;; f_bavail ;; Blocks available to non-superuser
100 | (fs-type (:array :char #.+mfsnamelen+) :offset 280) ;; f_fstypename
101 | (device (:array :char #.+mnamelen+) :offset 296) ;; f_mntfromname
102 | (mountpoint (:array :char #.+mnamelen+) :offset 1320)) ;; f_mntonname
103 |
104 | (define-implementation storage-device (path)
105 | (let ((mount-root (pathname-utils:native-namestring (pathname-force-file (find-mount-root path)))))
106 | (do-filesystems (fs)
107 | (let ((fs-mountpoint (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint)))
108 | (when (strncmp-lisp fs-mountpoint mount-root :max-chars +mnamelen+)
109 | (return (cffi:foreign-string-to-lisp
110 | (cffi:foreign-slot-pointer fs '(:struct statfs) 'device) :max-chars +mnamelen+)))))))
111 |
112 | (define-implementation storage-device-path (device)
113 | (do-filesystems (fs)
114 | (let ((fs-device (cffi:foreign-slot-pointer fs '(:struct statfs) 'device)))
115 | (when (strncmp-lisp fs-device device :max-chars +mnamelen+)
116 | (return (pathname-utils:parse-native-namestring
117 | (cffi:foreign-string-to-lisp
118 | (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint) :max-chars +mnamelen+)))))))
119 |
120 | (define-implementation storage-room (path)
121 | (when (stringp path)
122 | (setf path (storage-device-path path)))
123 |
124 | (let ((mount-root (pathname-utils:native-namestring (pathname-force-file (find-mount-root path)))))
125 | (do-filesystems (fs)
126 | (let ((fs-mountpoint (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint)))
127 | (when (strncmp-lisp fs-mountpoint mount-root :max-chars +mnamelen+)
128 | (flet ((block->bytes (n)
129 | (* n (statfs-block-size fs))))
130 | (return-from storage-room
131 | (values (block->bytes (statfs-available-blocks fs))
132 | (block->bytes (statfs-blocks fs))))))))))
133 |
134 | (define-implementation network-info ()
135 | (sysctl-string "kern.hostname" 255))
136 |
137 | (cffi:defcstruct (if-data :size 152 ;; #+openbsd 136
138 | :conc-name if-data-)
139 | (ibytes :uint64 :offset 64)
140 | (obytes :uint64 :offset 72))
141 |
142 | (define-implementation network-io-bytes (device)
143 | (let ((read 0) (written 0))
144 | (do-ifaddrs (ifaddr)
145 | (when (string= device (ifaddrs-name ifaddr))
146 | (let ((data (ifaddrs-data ifaddr)))
147 | (incf read (if-data-ibytes data))
148 | (incf written (if-data-obytes data)))))
149 | (values (+ read written) read written)))
150 |
151 | ;;;; Reference:
152 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/usr.sbin/acpi/acpiconf/acpiconf.c
153 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/dev/acpica/acpiio.h
154 |
155 | (cffi:defcstruct (acpi-battinfo :size 16))
156 | (cffi:defcstruct (acpi-bif :size 164))
157 |
158 | ;; https://github.com/freebsd/freebsd-src/blob/main/usr.sbin/acpi/acpiconf/acpiconf.c#L82
159 | (defconstant +unknown-cap+ #xffffffff)
160 | (defconstant +unknown-voltage+ #xffffffff)
161 |
162 | (cffi:defcstruct (acpi-bst :size 16 :conc-name acpi-bst-)
163 | (state :uint32) ;; state
164 | (rate :uint32) ;; rate
165 | (capacity :uint32) ;; cap
166 | (voltage :uint32)) ;; volts
167 |
168 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/dev/acpica/acpiio.h#L76
169 | (cffi:defcstruct (acpi-bix :size 256 :conc-name acpi-bix-)
170 | (units :uint32 :offset 0) ;; units
171 | (design-capacity :uint32 :offset 4) ;; dcap
172 | (last-full-capacity :uint32 :offset 8) ;; lfcap
173 | (design-volts :uint32 :offset 16)) ;; dvol
174 |
175 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/dev/acpica/acpiio.h#L175
176 | (cffi:defcunion acpi-battery-ioctl-arg
177 | (unit :int)
178 | (battinfo (:struct acpi-battinfo))
179 | (bix (:struct acpi-bix))
180 | (bif (:struct acpi-bif))
181 | (bst (:struct acpi-bst)))
182 |
183 | (defconstant +acpi-bix-units-mw+ 0)
184 | (defconstant +acpi-bix-units-ma+ 1)
185 | (defun acpi-bix-last-full-capacity-mAh (bix)
186 | (flet ((mWh->mAh (mWh mV)
187 | (/ mWh (* 1000 mV))))
188 | (let ((last (acpi-bix-last-full-capacity bix))
189 | (unit (acpi-bix-units bix)))
190 | (cond
191 | ((= +unknown-cap+ last) 0.0d0)
192 | ((= +acpi-bix-units-mw+ unit)
193 | (let ((mV (acpi-bix-design-volts bix)))
194 | (if (= +unknown-voltage+ mV)
195 | 0.0d0
196 | (mWh->mAh last mV))))
197 | ((= +acpi-bix-units-ma+ unit) last)
198 | (t 0.0d0)))))
199 |
200 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/dev/acpica/acpiio.h#L188
201 | (defconstant +acpiio-bat-get-bix+ 3238019600)
202 | (defconstant +acpiio-bat-get-bst+ 3238019601)
203 |
204 | (defun battery-last-full-capacity (acpifd battio battn)
205 | (setf (cffi:foreign-slot-value battio '(:union acpi-battery-ioctl-arg) 'unit) battn)
206 | (posix-call "ioctl" :int acpifd :unsigned-long +acpiio-bat-get-bix+ :pointer battio :int)
207 | (let ((bix (cffi:foreign-slot-pointer battio '(:union acpi-battery-ioctl-arg) 'bix)))
208 | (acpi-bix-last-full-capacity-mAh bix)))
209 |
210 | ;;;; https://github.com/freebsd/freebsd-src/blob/main/sys/dev/acpica/acpiio.h#L157
211 | (defconstant +acpi-batt-stat-discharg+ 1)
212 | (defconstant +acpi-batt-stat-charging+ 2)
213 | (defconstant +acpi-batt-stat-critical+ 4)
214 | (defconstant +acpi-batt-stat-invalid+ (logior +acpi-batt-stat-discharg+ +acpi-batt-stat-charging+))
215 | (defconstant +acpi-batt-stat-bst-mask+ (logior +acpi-batt-stat-invalid+ +acpi-batt-stat-critical+))
216 | (defconstant +acpi-batt-stat-not-present+ +acpi-batt-stat-bst-mask+)
217 |
218 | (defun battery-state (acpifd battio battn)
219 | (setf (cffi:foreign-slot-value battio '(:union acpi-battery-ioctl-arg) 'unit) battn)
220 | (posix-call "ioctl" :int acpifd :unsigned-long +acpiio-bat-get-bst+ :pointer battio :int)
221 | (let* ((bst (cffi:foreign-slot-pointer battio '(:union acpi-battery-ioctl-arg) 'bix))
222 | (remaining-capacity (acpi-bst-capacity bst))
223 | (state (acpi-bst-state bst)))
224 | (values (if (or (= remaining-capacity +unknown-cap+)
225 | (= remaining-capacity -1))
226 | 0.0d0
227 | remaining-capacity)
228 | (if (= +acpi-batt-stat-not-present+ state)
229 | nil
230 | (ecase (logand state +acpi-batt-stat-bst-mask+)
231 | (0 :charging) ;; 0 is reported as "high" on acpiconf
232 | (#.+acpi-batt-stat-charging+ :charging)
233 | (#.+acpi-batt-stat-discharg+ :discharging)
234 | (t nil))))))
235 |
236 | (define-implementation machine-battery ()
237 | (with-fd (acpifd #P"/dev/acpi" :direction :input)
238 | (cffi:with-foreign-object (battio '(:union acpi-battery-ioctl-arg))
239 | (let ((last-full-capacity (battery-last-full-capacity acpifd battio 0)))
240 | (multiple-value-bind (remaining-capacity state) (battery-state acpifd battio 0)
241 | (values (float remaining-capacity 0.0d0)
242 | (float last-full-capacity 0.0d0)
243 | ;; There doesn't seem to be a "full" state, full capacity still shows as charging
244 | ;; so detect it manually
245 | (if (= remaining-capacity last-full-capacity)
246 | :full
247 | state)))))))
248 |
--------------------------------------------------------------------------------
/linux.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (defmacro with-proc ((file &rest fields) &body body)
4 | `(cffi:with-foreign-object (io :char 2048)
5 | (let ((file (cffi:foreign-funcall "fopen" :string ,file :string "rb" :pointer)))
6 | (when (cffi:null-pointer-p file)
7 | (fail (strerror)))
8 | (cffi:foreign-funcall "fread" :pointer io :size 1 :size 2048 :pointer file :size)
9 | (cffi:foreign-funcall "fclose" :pointer file :void))
10 | (let ,(loop for (var field) in fields
11 | collect `(,var (let* ((field ,field)
12 | (start (cffi:foreign-funcall "strstr" :pointer io :string field :pointer))
13 | (ptr (cffi:inc-pointer start (length field))))
14 | (cffi:foreign-funcall "atol" :pointer ptr :long))))
15 | ,@body)))
16 |
17 | (defmacro do-proc (vars (file fgetsspec &optional return) &body body)
18 | `(cffi:with-foreign-objects ((io :char 2048)
19 | ,@vars)
20 | (let ((file (cffi:foreign-funcall "fopen" :string ,file :string "rb" :pointer)))
21 | (when (cffi:null-pointer-p file)
22 | (fail (strerror)))
23 | (unwind-protect
24 | (loop while (/= 0 (cffi:foreign-funcall "fgets" :pointer io :size 2048 :pointer file :int))
25 | do (when (= ,(length vars) (cffi:foreign-funcall "sscanf" :pointer io :string ,fgetsspec
26 | ,@(loop for (name) in vars collect :pointer collect name)
27 | :int))
28 | (let ,(loop for (var type count) in vars
29 | when (null count)
30 | collect `(,var (cffi:mem-ref ,var ',type)))
31 | ,@body))
32 | finally (progn (return ,return)))
33 | (cffi:foreign-funcall "fclose" :pointer file :void)))))
34 |
35 | (define-implementation process-io-bytes ()
36 | (with-proc ("/proc/self/io" (read "rchar: ") (write "wchar: "))
37 | (values (+ read write) read write)))
38 |
39 | ;;;; For whatever reason on Linux rusage is useless for this, so redefine it here.
40 | (define-implementation process-room ()
41 | (with-proc ("/proc/self/smaps_rollup" (rss "Rss: "))
42 | (* 1024 rss)))
43 |
44 | (define-implementation machine-time (core)
45 | (let ((scale (/ (float (posix-call "sysconf" :int 2 :long) 0d0))))
46 | (flet ((conv (x) (* x scale)))
47 | (etypecase core
48 | ((eql T)
49 | (do-proc ((user :int) (nice :int) (system :int) (idle :int) (iowait :int) (irq :int) (softirq :int))
50 | ("/proc/stat" "cpu %d %d %d %d %d %d %d")
51 | (return (values (conv idle) (conv (+ user nice system idle iowait irq softirq))))))
52 | (integer
53 | (do-proc ((c :int) (user :int) (nice :int) (system :int) (idle :int) (iowait :int) (irq :int) (softirq :int))
54 | ("/proc/stat" "cpu%d %d %d %d %d %d %d %d" (fail "No such core."))
55 | (when (= c core)
56 | (return (values (conv idle) (conv (+ user nice system idle iowait irq softirq)))))))))))
57 |
58 | (cffi:defcstruct (stat :size 144 :conc-name stat-)
59 | (dev :uint64 :offset 0)
60 | (mode :uint32 :offset 24))
61 |
62 | (defun pathname-force-file (path)
63 | (cond
64 | ((pathname-utils:root-p path) path)
65 | ((pathname-utils:file-p path) path)
66 | (T (let ((directories (pathname-directory path)))
67 | (make-pathname :defaults path
68 | :directory (butlast directories)
69 | :name (car (last directories)))))))
70 |
71 | (defun find-mount-root (path)
72 | (labels ((dev-id (path)
73 | (cffi:with-foreign-objects ((stat '(:struct stat)))
74 | (posix-call "stat" :string (pathname-utils:native-namestring path) :pointer stat :int)
75 | (stat-dev stat)))
76 | (rec (path &optional (id (dev-id path)))
77 | (if (pathname-utils:root-p path)
78 | path
79 | (let* ((parent (pathname-utils:parent path))
80 | (parent-id (dev-id parent)))
81 | (if (= parent-id id)
82 | (rec parent parent-id)
83 | path)))))
84 | (pathname-force-file (rec (truename path)))))
85 |
86 | (define-implementation storage-device (path)
87 | (let* ((mount-root (pathname-utils:native-namestring (pathname-force-file (find-mount-root path)))))
88 | (do-proc ((mountpoint :char 512) (name :char 32))
89 | ("/proc/self/mountinfo" "%*d %*d %*d:%*d / %s %*[^-]- %*s /dev/%s"
90 | (fail "Device not found in mountinfo table"))
91 | (when (= 0 (cffi:foreign-funcall "strncmp" :pointer mountpoint :string mount-root :size 32 :int))
92 | (return (cffi:foreign-string-to-lisp name :max-chars 32))))))
93 |
94 | (define-implementation storage-device-path (device)
95 | (do-proc ((mount :char 512) (name :char 32))
96 | ("/proc/self/mountinfo" "%*d %*d %*d:%*d / %s %*[^-]- %*s /dev/%s"
97 | (fail "Device not found in mountinfo table"))
98 | (when (= 0 (cffi:foreign-funcall "strncmp" :pointer name :string device :size 32 :int))
99 | (return (pathname-utils:parse-native-namestring
100 | (cffi:foreign-string-to-lisp mount :max-chars 32)
101 | :as :directory)))))
102 |
103 | (cffi:defcstruct (statvfs :size #+64-bit 112
104 | #+32-bit 72
105 | :conc-name statvfs-)
106 | (bsize :unsigned-long)
107 | (frsize :unsigned-long)
108 | (blocks :unsigned-long)
109 | (bfree :unsigned-long)
110 | (bavail :unsigned-long)
111 | (files :unsigned-long)
112 | (ffree :unsigned-long)
113 | (favail :unsigned-long)
114 | (fsid :unsigned-long)
115 | (flag :unsigned-long)
116 | (namemax :unsigned-long))
117 |
118 | (define-implementation storage-room (path)
119 | (when (stringp path)
120 | (setf path (storage-device-path path)))
121 | (cffi:with-foreign-objects ((statvfs '(:struct statvfs)))
122 | (posix-call "statvfs" :string (pathname-utils:native-namestring path) :pointer statvfs :int)
123 | (values (* (statvfs-bavail statvfs)
124 | (statvfs-bsize statvfs))
125 | (* (statvfs-blocks statvfs)
126 | (statvfs-bsize statvfs)))))
127 |
128 | (define-implementation storage-io-bytes (device)
129 | (when (pathnamep device)
130 | (setf device (storage-device device)))
131 | ;; device-mapper crap might have the device be a symlink, so resolve it.
132 | (setf device (pathname-name (truename (make-pathname :name device :directory '(:absolute "dev")))))
133 | (cffi:with-foreign-objects ((lasttop :char 32))
134 | (setf (cffi:mem-aref lasttop :char 0) 1)
135 | (etypecase device
136 | ((eql T)
137 | (let ((read 0) (write 0))
138 | (do-proc ((name :char 32)
139 | (reads :unsigned-long-long)
140 | (writes :unsigned-long-long))
141 | ("/proc/diskstats" "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu"
142 | (values (* 512 (+ read write))
143 | (* 512 read)
144 | (* 512 write)))
145 | (when (/= 0 (cffi:foreign-funcall "strncmp" :pointer lasttop :pointer name :size (cffi:foreign-funcall "strlen" :pointer lasttop :size) :int))
146 | (cffi:foreign-funcall "strncpy" :pointer lasttop :pointer name :size 32)
147 | (incf read reads)
148 | (incf write writes)))))
149 | (string
150 | (do-proc ((name :char 32)
151 | (reads :unsigned-long-long)
152 | (writes :unsigned-long-long))
153 | ("/proc/diskstats" "%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu" (fail "No such device."))
154 | (when (= 0 (cffi:foreign-funcall "strncmp" :pointer name :string device :size 32 :int))
155 | (return (values (* 512 (+ reads writes))
156 | (* 512 reads)
157 | (* 512 writes)))))))))
158 |
159 | (define-implementation network-devices ()
160 | (let ((list ()))
161 | (do-proc ((name :char 32))
162 | ("/proc/net/dev" "%31s" (cddr (nreverse list)))
163 | (push (cffi:foreign-string-to-lisp name :count (1- (cffi:foreign-funcall "strlen" :pointer name :size))) list))))
164 |
165 | (define-implementation network-io-bytes (device)
166 | (etypecase device
167 | ((eql T)
168 | (let ((read 0) (write 0))
169 | (do-proc ((name :char 32)
170 | (reads :unsigned-long-long)
171 | (writes :unsigned-long-long))
172 | ("/proc/net/dev" "%31s %llu %*u %*u %*u %*u %*u %*u %*u %llu %*u"
173 | (values (+ read write) read write))
174 | (unless (= 0 (cffi:foreign-funcall "strncmp" :pointer name :string "lo:" :size 32 :int))
175 | (incf read reads)
176 | (incf write writes)))))
177 | (string
178 | (do-proc ((name :char 32)
179 | (reads :unsigned-long-long)
180 | (writes :unsigned-long-long))
181 | ("/proc/net/dev" "%31s %llu %*u %*u %*u %*u %*u %*u %*u %llu %*u"
182 | (fail "No such device."))
183 | (when (= 0 (cffi:foreign-funcall "strncmp" :pointer name :string device :size (min 32 (length device)) :int))
184 | (return (values (+ reads writes)
185 | reads
186 | writes)))))))
187 |
188 | (define-implementation machine-info ()
189 | (flet ((maybe-read-line (file)
190 | (if (probe-file file)
191 | (with-open-file (o file) (read-line o))
192 | "Unknown")))
193 | (values (maybe-read-line "/sys/devices/virtual/dmi/id/board_vendor")
194 | (maybe-read-line "/sys/devices/virtual/dmi/id/board_name")
195 | :linux
196 | (do-proc ((version :char 64)) ("/proc/version" "Linux version %s")
197 | (return (cffi:foreign-string-to-lisp version :max-chars 64))))))
198 |
199 | (defun prefix-p (prefix str)
200 | (and (<= (length prefix) (length str))
201 | (string= str prefix :end1 (length prefix))))
202 |
203 | (define-implementation machine-core-info ()
204 | (let (vendor model version)
205 | (do-proc ((key :char 32) (value :char 512)) ("/proc/cpuinfo" #.(format NIL "%[^:]: %512[^~a]" #\Linefeed))
206 | (let ((key (cffi:foreign-string-to-lisp key :max-chars 32)))
207 | (flet ((val ()
208 | (cffi:foreign-string-to-lisp value :max-chars 512)))
209 | (cond ((prefix-p "vendor_id" key) (setf vendor (val)))
210 | ((prefix-p "model name" key) (setf model (val)))
211 | ((prefix-p "microcode" key) (setf version (val))))))
212 | (when (and vendor model version) (return)))
213 | (values (or vendor "Unknown")
214 | (or model "Unknown")
215 | (arch-type)
216 | (or version "Unknown"))))
217 |
218 | (define-implementation self ()
219 | (truename "/proc/self/exe"))
220 |
221 | (cffi:defcstruct (sockaddr4 :conc-name sockaddr4-)
222 | (family :ushort)
223 | (port :uint16)
224 | (addr :uint32))
225 |
226 | (cffi:defcstruct (sockaddr6 :conc-name sockaddr6-)
227 | (family :ushort)
228 | (port :uint16)
229 | (flow-info :uint32)
230 | (addr :uint16 :count 8)
231 | (scope-id :uint32))
232 |
233 | (cffi:defcstruct (ifaddrs :conc-name ifaddrs-)
234 | (next :pointer)
235 | (name :string)
236 | (flags :uint)
237 | (addr :pointer)
238 | (netmask :pointer)
239 | (bcast :pointer)
240 | (data :pointer))
241 |
242 | (defun ipv4-str (ipv4)
243 | (format NIL "~d.~d.~d.~d"
244 | (ldb (byte 8 0) ipv4)
245 | (ldb (byte 8 8) ipv4)
246 | (ldb (byte 8 16) ipv4)
247 | (ldb (byte 8 24) ipv4)))
248 |
249 | (defun ipv6-str (ipv6)
250 | (flet ((be->le (i)
251 | (rotatef (ldb (byte 8 0) i) (ldb (byte 8 8) i))
252 | i))
253 | (format NIL "~x:~x:~x:~x:~x:~x:~x:~x"
254 | (be->le (cffi:mem-aref ipv6 :uint16 0))
255 | (be->le (cffi:mem-aref ipv6 :uint16 1))
256 | (be->le (cffi:mem-aref ipv6 :uint16 2))
257 | (be->le (cffi:mem-aref ipv6 :uint16 3))
258 | (be->le (cffi:mem-aref ipv6 :uint16 4))
259 | (be->le (cffi:mem-aref ipv6 :uint16 5))
260 | (be->le (cffi:mem-aref ipv6 :uint16 6))
261 | (be->le (cffi:mem-aref ipv6 :uint16 7)))))
262 |
263 | (define-implementation network-address (device)
264 | (cffi:with-foreign-object (ifaddrs :pointer)
265 | (posix-call "getifaddrs" :pointer ifaddrs :int)
266 | (let ((ifaddrs (cffi:mem-ref ifaddrs :pointer)) ipv4 ipv6)
267 | (unwind-protect
268 | (let ((ifaddrs ifaddrs))
269 | (loop until (cffi:null-pointer-p ifaddrs)
270 | do (when (string= device (ifaddrs-name ifaddrs))
271 | (let ((address (ifaddrs-addr ifaddrs)))
272 | (unless (cffi:null-pointer-p address) ;; Address can be NULL for Wireguard for example
273 | (case (sockaddr4-family address)
274 | (2 (setf ipv4 (ipv4-str (sockaddr4-addr address))))
275 | (10 (setf ipv6 (ipv6-str (sockaddr6-addr address))))))))
276 | (setf ifaddrs (ifaddrs-next ifaddrs)))
277 | (values (with-open-file (o (format NIL "/sys/class/net/~a/address" device) :if-does-not-exist NIL)
278 | (if o
279 | (let ((addr (read-line o)))
280 | (when (> (length addr) 0) ;; This file can be empty
281 | addr))
282 | (fail "No such device.")))
283 | ipv4
284 | ipv6))
285 | (cffi:foreign-funcall "freeifaddrs" :pointer ifaddrs)))))
286 |
287 | (define-implementation machine-battery ()
288 | (values (with-open-file (o "/sys/class/power_supply/BAT0/energy_now" :if-does-not-exist NIL)
289 | (if o (float (parse-integer (read-line o)) 0d0) 0d0))
290 | (with-open-file (o "/sys/class/power_supply/BAT0/energy_full" :if-does-not-exist NIL)
291 | (if o (float (parse-integer (read-line o)) 0d0) 0d0))
292 | (with-open-file (o "/sys/class/power_supply/BAT0/status" :if-does-not-exist NIL)
293 | (when o (let ((status (read-line o)))
294 | (cond ((string-equal status "charging") :charging)
295 | ((string-equal status "discharging") :discharging)
296 | ((string-equal status "full") :full)))))))
297 |
--------------------------------------------------------------------------------
/machine-state.asd:
--------------------------------------------------------------------------------
1 | (asdf:defsystem machine-state
2 | :version "1.2.0"
3 | :license "zlib"
4 | :author "Yukari Hafner "
5 | :maintainer "Yukari Hafner "
6 | :description "Retrieve machine state information about CPU time, memory usage, etc."
7 | :homepage "https://shinmera.github.io/machine-state/"
8 | :bug-tracker "https://github.com/shinmera/machine-state/issues"
9 | :source-control (:git "https://github.com/shinmera/machine-state.git")
10 | :serial T
11 | :components ((:file "package")
12 | (:file "protocol")
13 | (:file "windows" :if-feature (:or :windows :win32))
14 | (:file "posix" :if-feature (:and (:not :openbsd)
15 | (:or :posix :linux :darwin :bsd)))
16 | (:file "darwin" :if-feature :darwin)
17 | (:file "bsd" :if-feature (:and :bsd (:not :darwin)))
18 | (:file "freebsd" :if-feature :freebsd)
19 | (:file "openbsd" :if-feature :openbsd)
20 | (:file "linux" :if-feature :linux)
21 | (:file "nx" :if-feature :nx)
22 | (:file "mezzano" :if-feature :mezzano)
23 | (:file "documentation"))
24 | :defsystem-depends-on (:trivial-features)
25 | :depends-on (:documentation-utils
26 | (:feature (:not :mezzano) :cffi)
27 | ;; 32bit SBCL does not support threads on OpenBSD
28 | (:feature (:not (:and :sbcl :openbsd :32-bit)) :bordeaux-threads)
29 | :pathname-utils
30 | (:feature :windows :com-on))
31 | :in-order-to ((asdf:test-op (asdf:test-op :machine-state/test))))
32 |
33 | (asdf:defsystem machine-state/opengl
34 | :version "1.0.0"
35 | :license "zlib"
36 | :author "Yukari Hafner "
37 | :maintainer "Yukari Hafner "
38 | :description "Additions for GPU state information using OpenGL"
39 | :components ((:file "opengl"))
40 | :depends-on (:machine-state :cl-opengl))
41 |
42 | (asdf:defsystem machine-state/test
43 | :version "1.0.0"
44 | :license "zlib"
45 | :author "Yukari Hafner "
46 | :maintainer "Yukari Hafner "
47 | :description "Tests for the machine-state library"
48 | :components ((:file "test"))
49 | :depends-on (:machine-state :parachute)
50 | :perform (asdf:test-op (op c) (uiop:symbol-call :parachute :test :org.shirakumo.machine-state.test)))
51 |
--------------------------------------------------------------------------------
/mezzano.lisp:
--------------------------------------------------------------------------------
1 | (in-package :machine-state)
2 |
3 | (define-implementation machine-cores ()
4 | (mezzano.supervisor:logical-core-count))
5 |
6 | (define-implementation machine-room ()
7 | (multiple-value-bind (free total)
8 | (mezzano.supervisor:physical-memory-statistics)
9 | (values (* total 4096) (* free 4096))))
10 |
11 | ;;this is technically incorrect, should I do what mezzano's ROOM does?
12 | ;;ROOM is very slow...
13 | (define-implementation gc-room () (machine-room))
14 | (define-implementation process-room () (machine-room))
15 |
16 | (define-implementation gc-time ()
17 | mezzano.internals::*gc-time*)
18 |
19 | (define-implementation thread-time (thread)
20 | (let ((the-thread (if (eql thread t) (bt:current-thread) thread)))
21 | (float
22 | (/ (mezzano.supervisor:thread-run-time the-thread) (* 1000 1000))
23 | 0d0)))
24 |
25 | (define-implementation machine-info ()
26 | (values "Unknown"
27 | "Unknown"
28 | :mezzano
29 | (lisp-implementation-version)))
30 |
31 | (define-implementation machine-core-info ()
32 | (multiple-value-bind (cpuid-max vendor-1 vendor-3 vendor-2) (mezzano.internals::cpuid 0)
33 | (let (model version)
34 | (when (>= cpuid-max 1)
35 | (multiple-value-bind (a b c d) (mezzano.internals::cpuid 1)
36 | (let ((model (ldb (byte 4 4) a))
37 | (family-id (ldb (byte 4 8) a))
38 | (extended-model-id (ldb (byte 4 16) a))
39 | (extended-family-id (ldb (byte 8 20) a)))
40 | (setf model (format NIL "~X ~X"
41 | (if (or (= family-id #x6) (= family-id #xF))
42 | (+ (ash extended-model-id 4) model)
43 | model)
44 | (if (= family-id #xF)
45 | (+ family-id extended-family-id)
46 | family-id))))))
47 | (values (mezzano.internals::decode-cpuid-vendor vendor-1 vendor-2 vendor-3)
48 | (or model "Unknown")
49 | (arch-type)
50 | (or version "Unknown")))))
51 |
52 | (define-implementation machine-uptime ()
53 | (truncate (get-internal-run-time) INTERNAL-TIME-UNITS-PER-SECOND))
54 |
55 | ;; (define-implementation storage-device (path)
56 | ;; (fail))
57 |
58 | ;; (define-implementation storage-device-path (device)
59 | ;; (fail))
60 |
61 | ;; (define-implementation storage-room (device)
62 | ;; (fail))
63 |
64 | (define-implementation network-devices ()
65 | (mapcar #'princ-to-string (mezzano.sync:watchable-set-items mezzano.driver.network-card::*nics*)))
66 |
67 | (defun network-card (device)
68 | (or (find device (mezzano.sync:watchable-set-items mezzano.driver.network-card::*nics*)
69 | :key #'princ-to-string
70 | :test #'equal)
71 | (error "No such device.")))
72 |
73 | (define-implementation network-io-bytes (device)
74 | (etypecase device
75 | ((eql T)
76 | (let ((read 0) (write 0))
77 | (dolist (card (mezzano.sync:watchable-set-items mezzano.driver.network-card::*nics*))
78 | (multiple-value-bind (rx-bytes rx-packets rx-errors tx-bytes tx-packets tx-errors collisions)
79 | (mezzano.driver.network-card:statistics card)
80 | (declare (ignore rx-packets rx-errors tx-packets tx-errors collisions))
81 | (incf read rx-bytes)
82 | (incf write tx-bytes)))
83 | (values (+ read write) read write)))
84 | (string
85 | (multiple-value-bind (rx-bytes rx-packets rx-errors tx-bytes tx-packets tx-errors collisions)
86 | (mezzano.driver.network-card:statistics (network-card device))
87 | (declare (ignore rx-packets rx-errors tx-packets tx-errors collisions))
88 | (values (+ rx-bytes tx-bytes) rx-bytes tx-bytes)))))
89 |
90 | (define-implementation network-info ()
91 | (values (machine-instance)))
92 |
93 | (define-implementation network-address (device)
94 | (let ((card (network-card device)))
95 | (values (format NIL "~/mezzano.network.ethernet:format-mac-address/"
96 | (mezzano.driver.network-card:mac-address card))
97 | (mezzano.network.ip:ipv4-interface-address card nil)
98 | NIL)))
99 |
--------------------------------------------------------------------------------
/nx.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (defmacro nxgl-call (func &rest args)
4 | `(when (= 0 (cffi:foreign-funcall ,func ,@args :int))
5 | (fail)))
6 |
7 | (define-implementation process-room ()
8 | (cffi:with-foreign-objects ((free :size) (total :size))
9 | (nxgl-call "nxgl_ram" :pointer free :pointer total)
10 | (- (cffi:mem-ref total :size) (cffi:mem-ref free :size))))
11 |
12 | (define-implementation machine-room ()
13 | (cffi:with-foreign-objects ((free :size) (total :size))
14 | (nxgl-call "nxgl_ram" :pointer free :pointer total)
15 | (values (- (cffi:mem-ref total :size) (cffi:mem-ref free :size))
16 | (cffi:mem-ref total :size))))
17 |
18 | (define-implementation machine-cores ()
19 | (cffi:foreign-funcall "nxgl_core_count" :int))
20 |
21 | (defmacro with-thread-handle ((handle thread &optional (default 0)) &body body)
22 | `(if (or (eql ,thread T)
23 | (eql ,thread (bt:current-thread)))
24 | (let ((,handle (cffi:null-pointer)))
25 | ,@body)
26 | ,default))
27 |
28 | (define-implementation thread-core-mask (thread)
29 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
30 | (cffi:with-foreign-objects ((mask :uint64))
31 | (nxgl-call "nxgl_get_core_mask" :pointer handle :pointer mask)
32 | (cffi:mem-ref mask :uint64))))
33 |
34 | (define-implementation (setf thread-core-mask) (mask-int thread)
35 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
36 | (cffi:with-foreign-objects ((mask :uint64))
37 | (setf (cffi:mem-ref mask :uint64) mask-int)
38 | (nxgl-call "nxgl_set_core_mask" :pointer handle :pointer mask)
39 | (cffi:mem-ref mask :uint64))))
40 |
41 | (define-implementation gpu-room ()
42 | (cffi:with-foreign-objects ((free :size) (total :size))
43 | (nxgl-call "nxgl_vram" :pointer free :pointer total)
44 | (values (cffi:mem-ref free :size)
45 | (cffi:mem-ref total :size))))
46 |
47 | (define-implementation process-info ()
48 | (values (make-pathname :name "sbcl" :device "rom" :directory '(:absolute))
49 | (make-pathname :device "tmp" :directory '(:absolute))
50 | (cffi:with-foreign-object (nick :char 33)
51 | (when (cffi:foreign-funcall "nxgl_username" :pointer nick :int 33 :bool)
52 | (cffi:foreign-string-to-lisp nick :max-chars 33)))
53 | "Unknown"))
54 |
55 | (define-implementation network-devices ()
56 | (list "net"))
57 |
58 | (defun mac-str (octets)
59 | (format NIL "~{~2,'0x~^:~}" (coerce octets 'list)))
60 |
61 | (defun ipv4-str (ipv4)
62 | (format NIL "~d.~d.~d.~d"
63 | (ldb (byte 8 0) ipv4)
64 | (ldb (byte 8 8) ipv4)
65 | (ldb (byte 8 16) ipv4)
66 | (ldb (byte 8 24) ipv4)))
67 |
68 | (define-implementation network-address (device)
69 | (values (cffi:with-foreign-object (mac :uint8 6)
70 | (when (cffi:foreign-funcall "nxgl_mac_address" :pointer mac :bool)
71 | (mac-str (cffi:foreign-array-to-lisp mac '(:array :uint8 6)))))
72 | (cffi:with-foreign-object (addr :uint32)
73 | (when (cffi:foreign-funcall "nxgl_ip_address" :pointer addr :bool)
74 | (ipv4-str (cffi:mem-ref addr :uint32))))
75 | NIL))
76 |
--------------------------------------------------------------------------------
/openbsd.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | ;;;; https://github.com/openbsd/src/blob/master/sys/sys/sysctl.h#L103
4 | (defconstant +ctl-kern+ 1)
5 | (defconstant +kern-proc-pid+ 1)
6 | (defconstant +kern-osrelease+ 2)
7 | (defconstant +kern-hostname+ 10)
8 | (defconstant +kern-clockrate+ 12)
9 | (defconstant +kern-boottime+ 21)
10 | (defconstant +kern-cptime+ 40)
11 | (defconstant +kern-proc+ 66)
12 | (defconstant +kern-cptime2+ 71)
13 | (defconstant +kern-proc-cwd+ 78)
14 | (defconstant +kern-cpustats+ 85)
15 | (defconstant +kern-proc-show-threads+ #x40000000)
16 |
17 | ;;;; https://github.com/openbsd/src/blob/master/sys/sys/sysctl.h#L919
18 | (defconstant +ctl-hw+ 6)
19 | (defconstant +hw-machine+ 1)
20 | (defconstant +hw-model+ 2)
21 | (defconstant +hw-diskstats+ 9)
22 | (defconstant +hw-diskcount+ 10)
23 | (defconstant +hw-sensors+ 11)
24 | (defconstant +hw-vendor+ 14)
25 | (defconstant +hw-product+ 15)
26 | (defconstant +hw-physmem64+ 19)
27 | (defconstant +hw-ncpuonline+ 25)
28 |
29 | ;;;; https://github.com/openbsd/src/blob/master/sys/uvm/uvmexp.h#L7
30 | (defconstant +ctl-vm+ 2)
31 | (defconstant +vm-uvmexp+ 4)
32 |
33 | ;;;; https://github.com/openbsd/src/blob/master/sys/sys/sysctl.h#L370
34 | (cffi:defcstruct (kinfo-proc :size 644 :conc-name kinfo-proc-)
35 | (user-id :uint32 :offset 128) ;; p_uid
36 | (group-id :uint32 :offset 136) ;; p_gid
37 | (nice :uint8 :offset 307) ;; p_nice
38 | (command-name (:array :char #.+maxcomlen+) :offset 312) ;; p_comm
39 | (resident-set-size :int32 :offset 384) ;; p_vm_rssize
40 | (user-time-seconds :uint32 :offset 420) ;; p_uutime_sec
41 | (user-time-microseconds :uint32 :offset 424) ;; p_uutime_usec
42 | (thread-id :int32 :offset 608) ;; p_tid
43 | (thread-name (:array :char #.+maxcomlen+) :offset 624)) ;; p_name
44 |
45 | (defmacro with-current-process ((proc) &body body)
46 | (let ((size (cffi:foreign-type-size '(:struct kinfo-proc))))
47 | `(with-sysctl ((+ctl-kern+ +kern-proc+ +kern-proc-pid+ (getpid) ,size 1) ,proc '(:struct kinfo-proc))
48 | ,@body)))
49 |
50 | (define-implementation process-room ()
51 | (with-current-process (proc)
52 | (* (page-size) (kinfo-proc-resident-set-size proc))))
53 |
54 | ;;;; These functions are shared between the processes and threads impls
55 | ;;;; User time only, will differ from output of PS or TOP
56 | (defun %process-time (kinfo-proc)
57 | (+ (kinfo-proc-user-time-seconds kinfo-proc)
58 | (/ (kinfo-proc-user-time-microseconds kinfo-proc) 1000000.0d0)))
59 |
60 | (define-implementation process-time ()
61 | (with-current-process (proc)
62 | (%process-time proc)))
63 |
64 | (define-implementation process-priority ()
65 | (with-current-process (proc)
66 | (let ((value (- (kinfo-proc-nice proc) 20))) ;; Will return between 0 (-20) and 40 (20)
67 | (process-nice->priority value))))
68 |
69 | (define-implementation (setf process-priority) (priority)
70 | (let ((prio (priority->process-nice priority)))
71 | (posix-call "setpriority" :int 0 :uint32 (getpid) :int prio :int))
72 | (process-priority)) ;; Get the actual priority
73 |
74 | #+thread-support
75 | (progn
76 | (defmacro with-threads ((thread &optional pid) &body body)
77 | (with-gensyms (mib %pid i nproc procs size)
78 | ;; Call sysctl once to find how many bytes will be returned
79 | `(let* ((,%pid (or ,pid (getpid)))
80 | (,size (cffi:foreign-type-size '(:struct kinfo-proc)))
81 | (,mib (sysctl-resolve-mib
82 | (list +ctl-kern+ +kern-proc+ (logior +kern-proc-pid+ +kern-proc-show-threads+) ,%pid ,size 0)))
83 | (,nproc (ceiling (/ (sysctl-size ,mib) ,size))))
84 |
85 | (rplaca (last ,mib) ,nproc)
86 |
87 | (with-sysctl (,mib ,procs '(:struct kinfo-proc) ,nproc)
88 | (dotimes (,i ,nproc)
89 | (let ((,thread (cffi:mem-aptr ,procs '(:struct kinfo-proc) ,i)))
90 | (when (> (kinfo-proc-thread-id ,thread) 1)
91 | ,@body)))))))
92 |
93 | (defmacro with-current-thread ((thread) &body body)
94 | (with-gensyms (tid)
95 | `(let ((,tid (cffi:foreign-funcall "getthrid" :long)))
96 | (with-threads (,thread)
97 | (when (= ,tid (kinfo-proc-thread-id ,thread))
98 | (return ,@body))))))
99 |
100 | (defmacro with-current-thread-handle ((handle thread &optional (default 0)) &body body)
101 | (with-gensyms (%thread)
102 | `(let ((,%thread ,thread))
103 | (if (or (eql ,%thread T)
104 | (eql ,%thread (bt:current-thread)))
105 | (with-current-thread (,handle)
106 | ,@body)
107 | ,default))))
108 | (define-implementation thread-time (thread)
109 | (with-current-thread-handle (handle thread 0.0d0)
110 | (%process-time handle))))
111 |
112 | ;;;; process-io-bytes, thread-priority and thread-core-mask are unsupported
113 | ;;;; Reference: https://github.com/openbsd/src/blob/master/include/unistd.h#L100
114 |
115 | (defun split-path (path &optional (delimiter #\:))
116 | (let (paths start)
117 | (do ((i 0 (1+ i)))
118 | ((= i (length path)) (nreverse paths))
119 | (when (char= (schar path i) delimiter)
120 | (push (subseq path (or start 0) i) paths)
121 | (setf start (1+ i))))))
122 |
123 | (define-implementation process-info ()
124 | (with-current-process (proc)
125 | (values (let ((command (cffi:foreign-string-to-lisp
126 | (cffi:foreign-slot-pointer proc '(:struct kinfo-proc) 'command-name))))
127 | (or (resolve-executable command) command))
128 | (pathname-utils:parse-native-namestring (sysctl-string (list +ctl-kern+ +kern-proc-cwd+ (getpid)) 1024) :as :directory)
129 | (uid->user (kinfo-proc-user-id proc))
130 | (gid->group (kinfo-proc-group-id proc)))))
131 |
132 | (cffi:defcstruct (uvmexp :size 344 :conc-name uvmexp-)
133 | (pagesize :int :offset 0)
134 | (npages :int :offset 12)
135 | (free :int :offset 16)
136 | (inactive :int :offset 24))
137 |
138 | (define-implementation machine-room ()
139 | (with-sysctl ((+ctl-vm+ +vm-uvmexp+) uvm '(:struct uvmexp))
140 | (flet ((pages->bytes (n)
141 | (* n (uvmexp-pagesize uvm))))
142 | (let* ((total-pages (uvmexp-npages uvm))
143 | (free-pages (+ (uvmexp-free uvm) (uvmexp-inactive uvm)))
144 | (total-bytes (pages->bytes total-pages))
145 | (free-bytes (pages->bytes free-pages)))
146 | (values (- total-bytes free-bytes)
147 | total-bytes)))))
148 |
149 | (define-implementation machine-uptime ()
150 | (with-sysctl ((+ctl-kern+ +kern-boottime+) tv '(:struct timeval))
151 | (- (get-unix-time) (timeval-sec tv))))
152 |
153 | (define-implementation machine-cores ()
154 | (sysctl-ref (list +ctl-hw+ +hw-ncpuonline+) :int))
155 |
156 | (defconstant +cpustates+ 6)
157 |
158 | (cffi:defcstruct (cpustats :size 56 :conc-name cpustats-)
159 | (times (:array :uint64 #.+cpustates+))) ;; cs_time
160 |
161 | (defun core-time (core)
162 | (with-sysctl ((+ctl-kern+ +kern-cpustats+ core) cpustats '(:struct cpustats))
163 | (cpustats-times cpustats)))
164 |
165 | (defun cpu-time ()
166 | (sysctl-ref (list +ctl-kern+ +kern-cptime+) `(:array :long ,+cpustates+)))
167 |
168 | ;;;; KERN_CPTIME2 returns wrong values for some reason, KERN_CPUSTATS works better
169 | (define-implementation machine-time (core)
170 | (with-sysctl ((+ctl-kern+ +kern-clockrate+) clockinfo '(:struct clockinfo))
171 | (flet ((conv (x) (/ x (float (clockinfo-hz clockinfo) 0.0d0))))
172 | (let ((values (cond
173 | ((eq 't core) (cpu-time))
174 | ((>= core (machine-cores)) (fail "No such core."))
175 | (t (core-time core)))))
176 | (destructuring-bind (user nice sys spin intr idle) (coerce values 'list)
177 | (values (conv idle)
178 | (conv (+ user nice sys spin intr idle))))))))
179 |
180 | ;;;; Reference:
181 | ;;;; https://github.com/openbsd/src/blob/master/sys/sys/sensors.h
182 | ;;;; https://github.com/openbsd/src/blob/master/sbin/sysctl/sysctl.c#L2554
183 |
184 | (defconstant +sensor-type-volts-dc+ 2)
185 | (defconstant +sensor-type-amphour+ 8)
186 | (defconstant +sensor-type-integer+ 10)
187 |
188 | (cffi:defcstruct (sensor :size 68 :conc-name sensor-)
189 | (value :int64 :offset 44))
190 |
191 | (defconstant +sensor-name-size+ 16)
192 | (cffi:defcstruct (sensordev :size 116 :conc-name sensordev-)
193 | (name (:array :char #.+sensor-name-size+) :offset 4)) ;; xname
194 |
195 | (defconstant +enoent+ 2)
196 | (defconstant +enxio+ 6)
197 |
198 | (defun find-sensor-number (name &optional (dev 0))
199 | (cffi:with-foreign-object (sensordev '(:struct sensordev))
200 | (multiple-value-bind (sensordev ret)
201 | (sysctl-unchecked (list +ctl-hw+ +hw-sensors+ dev) sensordev (cffi:foreign-type-size '(:struct sensordev)))
202 | (print ret)
203 | (when (= -1 ret)
204 | (return-from find-sensor-number
205 | (if (= +enxio+ (errno))
206 | (find-sensor-number name (1+ dev))
207 | nil)))
208 | (let ((sensor-name (cffi:foreign-slot-pointer sensordev '(:struct sensordev) 'name)))
209 | (if (strncmp-lisp sensor-name name :max-chars +sensor-name-size+)
210 | dev
211 | (find-sensor-number name (1+ dev)))))))
212 |
213 | (defun find-sensor-value (device sensor-type sensor-index)
214 | (with-sysctl ((+ctl-hw+ +hw-sensors+ device sensor-type sensor-index) sensor '(:struct sensor))
215 | (sensor-value sensor)))
216 |
217 | (define-implementation machine-battery ()
218 | (let ((battery-n (find-sensor-number "acpibat0")))
219 | (if battery-n
220 | (let ((last-full-capacity (find-sensor-value battery-n +sensor-type-amphour+ 0))
221 | (remaining-capacity (find-sensor-value battery-n +sensor-type-amphour+ 3))
222 | (state (find-sensor-value battery-n +sensor-type-integer+ 0)))
223 | (values (float remaining-capacity 0.0d0)
224 | (float last-full-capacity 0.0d0)
225 | (case state
226 | (0 :full)
227 | (1 :discharging)
228 | (2 :charging))))
229 | (values 0.0d0 0.0d0 nil))))
230 |
231 | (define-implementation machine-info ()
232 | (values
233 | (sysctl-string (list +ctl-hw+ +hw-vendor+) 128)
234 | (sysctl-string (list +ctl-hw+ +hw-product+) 128)
235 | :openbsd
236 | (sysctl-string (list +ctl-kern+ +kern-osrelease+) 16)))
237 |
238 | (define-implementation machine-core-info ()
239 | (let ((processor (sysctl-string (list +ctl-hw+ +hw-model+) 128)))
240 | (values processor
241 | processor ;; There doesn't seem to be a separation between those
242 | (arch-type)
243 | (sysctl-string (list +ctl-hw+ +hw-machine+) 32))))
244 |
245 | (defconstant +mfsnamelen+ 16)
246 | (defconstant +mnamelen+ 90)
247 |
248 | #+32-bit
249 | (cffi:defcstruct (statfs :size 564 :conc-name statfs-)
250 | (block-size :uint32 :offset 4)
251 | (blocks :uint64 :offset 12)
252 | (available-blocks :int64 :offset 28) ;; Blocks available to non-superuser
253 | (synchronous-writes :uint64 :offset 60)
254 | (synchronous-reads :uint64 :offset 68)
255 | (asynchronous-writes :uint64 :offset 76)
256 | (asynchronous-reads :uint64 :offset 84)
257 | (mountpoint (:array :char #.+mnamelen+) :offset 132)
258 | (device (:array :char #.+mnamelen+) :offset 222))
259 |
260 | #+64-bit
261 | (cffi:defcstruct (statfs :size 568 :conc-name statfs-)
262 | (block-size :uint32 :offset 4)
263 | (blocks :uint64 :offset 16)
264 | (available-blocks :int64 :offset 32) ;; Blocks available to non-superuser
265 | (synchronous-writes :uint64 :offset 64)
266 | (synchronous-reads :uint64 :offset 72)
267 | (asynchronous-writes :uint64 :offset 80)
268 | (asynchronous-reads :uint64 :offset 88)
269 | (mountpoint (:array :char #.+mnamelen+) :offset 136)
270 | (device (:array :char #.+mnamelen+) :offset 226))
271 |
272 | (define-implementation storage-device (path)
273 | (let ((mount-root (pathname-utils:native-namestring (pathname-force-file (find-mount-root path)))))
274 | (do-filesystems (fs)
275 | (let ((fs-mountpoint (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint)))
276 | (when (strncmp-lisp fs-mountpoint mount-root :max-chars +mnamelen+)
277 | (return (pathname-name
278 | (cffi:foreign-string-to-lisp
279 | (cffi:foreign-slot-pointer fs '(:struct statfs) 'device) :max-chars +mnamelen+))))))))
280 |
281 | (define-implementation storage-device-path (device)
282 | (let ((device-path (pathname-utils:native-namestring (make-pathname :defaults #P"/dev/" :name device))))
283 | (do-filesystems (fs)
284 | (let ((fs-device (cffi:foreign-slot-pointer fs '(:struct statfs) 'device)))
285 | (when (strncmp-lisp fs-device device-path :max-chars +mnamelen+)
286 | (return (pathname-utils:parse-native-namestring
287 | (cffi:foreign-string-to-lisp
288 | (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint) :max-chars +mnamelen+))))))))
289 |
290 | (define-implementation storage-io-bytes (path)
291 | (setf path (pathname-utils:native-namestring
292 | (pathname-force-file
293 | (etypecase path
294 | (pathname (find-mount-root path))
295 | (string (storage-device-path path))))))
296 |
297 | (do-filesystems (fs)
298 | (let ((fs-mountpoint (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint)))
299 | (when (strncmp-lisp fs-mountpoint path :max-chars +mnamelen+)
300 | (let ((reads (+ (statfs-synchronous-reads fs)
301 | (statfs-asynchronous-reads fs)))
302 | (writes (+ (statfs-synchronous-writes fs)
303 | (statfs-asynchronous-writes fs))))
304 | (return-from storage-io-bytes
305 | (values (+ reads writes) reads writes)))))))
306 |
307 | (define-implementation storage-room (path)
308 | (when (stringp path)
309 | (setf path (storage-device-path path)))
310 |
311 | (let ((mount-root (pathname-utils:native-namestring (pathname-force-file (find-mount-root path)))))
312 | (do-filesystems (fs)
313 | (let ((fs-mountpoint (cffi:foreign-slot-pointer fs '(:struct statfs) 'mountpoint)))
314 | (when (strncmp-lisp fs-mountpoint mount-root :max-chars +mnamelen+)
315 | (flet ((block->bytes (n)
316 | (* n (statfs-block-size fs))))
317 | (return-from storage-room
318 | (values (block->bytes (statfs-available-blocks fs))
319 | (block->bytes (statfs-blocks fs))))))))))
320 |
321 | (define-implementation network-info ()
322 | (sysctl-string (list +ctl-kern+ +kern-hostname+) 255))
323 |
324 | ;;;; network-io-bytes is unsupported
325 | ;;;; `struct ifaddrs' has a `void* ifa_data' field which should be an `if_data' struct
326 | ;;;; but this is NULL on OpenBSD, see the FreeBSD code for how it should work
327 |
--------------------------------------------------------------------------------
/opengl.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (defun gl-vendor ()
4 | (let ((vendor (gl:get-string :vendor)))
5 | (cond ((search "Intel" vendor) :intel)
6 | ((search "NVIDIA" vendor) :nvidia)
7 | ((search "ATI" vendor) :amd)
8 | ((search "AMD" vendor) :amd)
9 | (T :unknown))))
10 |
11 | (declaim (notinline gpu-room))
12 | (define-implementation gpu-room ()
13 | (macrolet ((jit (thing)
14 | `(multiple-value-prog1 ,thing
15 | (compile 'gpu-room '(lambda () ,thing)))))
16 | (case (gl-vendor)
17 | ;; https://www.khronos.org/registry/OpenGL/extensions/ATI/ATI_meminfo.txt
18 | (:amd
19 | (jit (let* ((vbo-free-memory-ati (gl:get-integer #x87FB 4))
20 | (tex-free-memory-ati (gl:get-integer #x87FC 4))
21 | (buf-free-memory-ati (gl:get-integer #x87FD 4))
22 | (total (+ (aref vbo-free-memory-ati 0)
23 | (aref tex-free-memory-ati 0)
24 | (aref buf-free-memory-ati 0))))
25 | (values (* 1024 total) (* 1024 total)))))
26 | ;; http://developer.download.nvidia.com/opengl/specs/GL_NVX_gpu_memory_info.txt
27 | (:nvidia
28 | (jit (let ((vidmem-total (gl:get-integer #x9047 1))
29 | (vidmem-free (gl:get-integer #x9049 1)))
30 | (values (* 1024 vidmem-free)
31 | (* 1024 vidmem-total)))))
32 | (:intel
33 | (jit (gc-room)))
34 | (T (jit (values 0 0))))))
35 |
36 | (let ((+gpu-time-query-object+ NIL)
37 | (+gpu-time+ 0))
38 | (define-implementation gpu-time ()
39 | (cond (+gpu-time-query-object+
40 | (handler-case
41 | (progn
42 | (gl:end-query :time-elapsed)
43 | (incf +gpu-time+ (gl:get-query-object +gpu-time-query-object+ :query-result)))
44 | (gl:opengl-error ()
45 | (setf +gpu-time-query-object+ (first (gl:gen-queries 1))))))
46 | (T
47 | (setf +gpu-time-query-object+ (first (gl:gen-queries 1)))))
48 | (gl:begin-query :time-elapsed +gpu-time-query-object+)
49 | (* (float +gpu-time+ 0d0) 1e-9)))
50 |
51 | (define-implementation gpu-info ()
52 | (values (let ((vendor (gl:get-string :vendor)))
53 | (cond ((search "intel" vendor :test #'char-equal) :intel)
54 | ((search "nvidia" vendor :test #'char-equal) :nvidia)
55 | ((search "ati" vendor :test #'char-equal) :amd)
56 | ((search "amd" vendor :test #'char-equal) :amd)))
57 | (gl:get-string :renderer)
58 | (gl:get-string :version)))
59 |
--------------------------------------------------------------------------------
/package.lisp:
--------------------------------------------------------------------------------
1 | (defpackage #:org.shirakumo.machine-state
2 | (:use #:cl)
3 | #+windows
4 | (:local-nicknames (#:com #:org.shirakumo.com-on))
5 | (:export
6 | #:query-failed
7 | #:process-io-bytes
8 | #:process-room
9 | #:process-time
10 | #:machine-room
11 | #:machine-cores
12 | #:machine-uptime
13 | #:machine-time
14 | #:thread-time
15 | #:thread-core-mask
16 | #:process-priority
17 | #:thread-priority
18 | #:gc-room
19 | #:gc-time
20 | #:gpu-room
21 | #:gpu-time
22 | #:static-room
23 | #:stack-room
24 | #:storage-device
25 | #:storage-device-path
26 | #:storage-room
27 | #:storage-io-bytes
28 | #:network-devices
29 | #:network-io-bytes
30 | #:machine-info
31 | #:machine-battery
32 | #:machine-core-info
33 | #:process-info
34 | #:gpu-info
35 | #:network-info
36 | #:network-address))
37 |
--------------------------------------------------------------------------------
/posix.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (cffi:defcvar (errno "errno") :int64)
4 |
5 | (defun strerror ()
6 | (cffi:foreign-funcall "strerror" :int64 errno :string))
7 |
8 | (defmacro posix-call (function &rest args)
9 | `(let ((val (cffi:foreign-funcall ,function ,@args)))
10 | (if (< val 0)
11 | (fail (strerror))
12 | val)))
13 |
14 | (defmacro posix-call0 (function &rest args)
15 | `(let ((val (cffi:foreign-funcall ,function ,@args)))
16 | (if (/= 0 val)
17 | (fail (strerror))
18 | val)))
19 |
20 | (cffi:defcstruct (timeval :conc-name timeval-)
21 | (sec :uint64)
22 | (usec :uint64))
23 |
24 | (cffi:defcstruct (rusage :conc-name rusage-)
25 | (utime (:struct timeval))
26 | (stime (:struct timeval))
27 | ;; Linux fields
28 | (maxrss :long)
29 | (ixrss :long)
30 | (idrss :long)
31 | (isrss :long)
32 | (minflt :long)
33 | (majflt :long)
34 | (nswap :long)
35 | (inblock :long)
36 | (oublock :long)
37 | (msgsnd :long)
38 | (msgrcv :long)
39 | (nsignals :long)
40 | (nvcsw :long)
41 | (nivcsw :long))
42 |
43 | (define-implementation process-room ()
44 | (cffi:with-foreign-object (rusage '(:struct rusage))
45 | (posix-call "getrusage" :int 0 :pointer rusage :int)
46 | (* 1024 (+ (rusage-ixrss rusage)
47 | (rusage-idrss rusage)
48 | (rusage-isrss rusage)))))
49 |
50 | (define-implementation process-time ()
51 | (cffi:with-foreign-object (rusage '(:struct rusage))
52 | (posix-call "getrusage" :int 0 :pointer rusage :int)
53 | (+ (timeval-sec rusage)
54 | (* (timeval-usec rusage) 10d-7))))
55 |
56 | (cffi:defcstruct (sysinfo :conc-name sysinfo-)
57 | (uptime :long)
58 | (loads :ulong :count 3)
59 | (total-ram :ulong)
60 | (free-ram :ulong)
61 | (shared-ram :ulong)
62 | (buffer-ram :ulong)
63 | (total-swap :ulong)
64 | (free-swap :ulong)
65 | (processes :ushort)
66 | (total-high :ulong)
67 | (free-high :ulong)
68 | (memory-unit :uint)
69 | (_pad :char :count 22))
70 |
71 | #-darwin
72 | (define-implementation machine-room ()
73 | (cffi:with-foreign-objects ((sysinfo '(:struct sysinfo)))
74 | (posix-call "sysinfo" :pointer sysinfo :int)
75 | (let* ((unit (sysinfo-memory-unit sysinfo))
76 | (total (* unit (sysinfo-total-ram sysinfo)))
77 | (free (* unit (sysinfo-free-ram sysinfo))))
78 | (values (- total free) total))))
79 |
80 | #-darwin
81 | (define-implementation machine-uptime ()
82 | (cffi:with-foreign-objects ((sysinfo '(:struct sysinfo)))
83 | (posix-call "sysinfo" :pointer sysinfo :int)
84 | (sysinfo-uptime sysinfo)))
85 |
86 | #-darwin
87 | (define-implementation machine-cores ()
88 | ;; _SC_NPROCESSORS_ONLN 84
89 | (posix-call "sysconf" :int 84 :long))
90 |
91 | (define-implementation process-priority ()
92 | (let ((err errno)
93 | (value (cffi:foreign-funcall "getpriority" :int 0 :uint32 0 :int)))
94 | (when (and (= -1 value) (/= err errno))
95 | (fail (cffi:foreign-funcall "strerror" :int64 errno)))
96 | (cond ((< value -8) :realtime)
97 | ((< value 0) :high)
98 | ((= value 0) :normal)
99 | ((< value +8) :low)
100 | (T :idle))))
101 |
102 | (define-implementation (setf process-priority) (priority)
103 | (let ((prio (ecase priority
104 | (:idle 19)
105 | (:low 5)
106 | (:normal 0)
107 | (:high -5)
108 | (:realtime -20))))
109 | (posix-call0 "setpriority" :int 0 :uint32 0 :int prio :int))
110 | priority)
111 |
112 | #+thread-support
113 | (progn
114 | (defmacro with-thread-handle ((handle thread &optional (default 0)) &body body)
115 | `(if (or (eql ,thread T)
116 | (eql ,thread (bt:current-thread)))
117 | (let ((,handle (cffi:foreign-funcall "pthread_self" :pointer)))
118 | (declare (ignorable ,handle))
119 | ,@body)
120 | ,default))
121 |
122 | (define-implementation thread-time (thread)
123 | (with-thread-handle (handle thread 0d0)
124 | (cffi:with-foreign-object (rusage '(:struct rusage))
125 | (posix-call "getrusage" :int 1 :pointer rusage :int)
126 | (+ (timeval-sec rusage)
127 | (* (timeval-usec rusage) 10d-7)))))
128 |
129 | (define-implementation thread-core-mask (thread)
130 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
131 | (cffi:with-foreign-objects ((cpuset :uint64))
132 | (posix-call0 "pthread_getaffinity_np" :pointer handle :size (cffi:foreign-type-size :uint64) :pointer cpuset :int)
133 | (cffi:mem-ref cpuset :uint64))))
134 |
135 | (define-implementation (setf thread-core-mask) (mask thread)
136 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
137 | (cffi:with-foreign-objects ((cpuset :uint64))
138 | (setf (cffi:mem-ref cpuset :uint64) mask)
139 | (posix-call0 "pthread_setaffinity_np" :pointer handle :size (cffi:foreign-type-size :uint64) :pointer cpuset :int)
140 | (cffi:mem-ref cpuset :uint64))))
141 |
142 | (define-implementation thread-priority (thread)
143 | (with-thread-handle (handle thread :normal)
144 | (cffi:with-foreign-objects ((policy :int)
145 | (param :int))
146 | (posix-call0 "pthread_getschedparam" :pointer handle :pointer policy :pointer param :int)
147 | (let ((priority (cffi:mem-ref param :int)))
148 | (cond ((< priority 20) :idle)
149 | ((< priority 50) :low)
150 | ((= priority 50) :normal)
151 | ((< priority 70) :high)
152 | (T :realtime))))))
153 |
154 | (define-implementation (setf thread-priority) (thread priority)
155 | (with-thread-handle (handle thread :normal)
156 | (cffi:with-foreign-objects ((policy :int)
157 | (param :int))
158 | (posix-call0 "pthread_getschedparam" :pointer handle :pointer policy :pointer param :int)
159 | (let ((policy (cffi:mem-ref policy :int)))
160 | (setf (cffi:mem-ref param :int) (ecase priority
161 | (:idle 1)
162 | (:low 40)
163 | (:normal 50)
164 | (:high 60)
165 | (:realtime 99)))
166 | (posix-call0 "pthread_setschedparam" :pointer handle :int policy :pointer param :int)))
167 | priority)))
168 |
169 | (define-implementation network-info ()
170 | (cffi:with-foreign-object (hostname :char 512)
171 | (posix-call "gethostname" :pointer hostname :size 512 :int)
172 | (cffi:foreign-string-to-lisp hostname :max-chars 512)))
173 |
174 | (define-protocol-fun self () (pathname)
175 | *default-pathname-defaults*)
176 |
177 | (define-implementation process-info ()
178 | (values
179 | (self)
180 | (pathname-utils:parse-native-namestring
181 | (cffi:with-foreign-object (path :char 1024)
182 | (cffi:foreign-funcall "getcwd" :pointer path :size 1024)
183 | (cffi:foreign-string-to-lisp path :max-chars 1024))
184 | :as :directory)
185 | (cffi:foreign-funcall "getlogin" :string)
186 | (let ((gid (cffi:foreign-funcall "getpwuid" :size (cffi:foreign-funcall "getgid" :size) :pointer)))
187 | (if (cffi:null-pointer-p gid)
188 | "Unknown"
189 | (cffi:mem-ref gid :string)))))
190 |
--------------------------------------------------------------------------------
/protocol.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (define-condition query-failed (error)
4 | ((function :initarg :function :initform NIL)
5 | (message :initarg :message :initform NIL))
6 | (:report (lambda (c s) (format s "The machine state query~@[ for ~a~] failed~@[:~%~% ~a~]"
7 | (slot-value c 'function) (slot-value c 'message)))))
8 |
9 | (defun fail (&optional message function)
10 | (error 'query-failed :function function :message message))
11 |
12 | (defmacro define-protocol-fun (name args vals &body default)
13 | `(progn
14 | (declaim (ftype (function ,(mapcar #'second args) (values ,@vals &optional)) ,name))
15 | (declaim (inline ,name))
16 | (setf (fdefinition ',name)
17 | (lambda ,(mapcar #'first args)
18 | ,@default))))
19 |
20 | (defmacro define-implementation (fun args &body body)
21 | `(defun ,fun ,args
22 | (flet ((fail (&optional message (function ',fun))
23 | (error 'query-failed :function function :message message)))
24 | (declare (ignorable #'fail))
25 | ,@body)))
26 |
27 | (define-protocol-fun process-io-bytes () ((unsigned-byte 64) (unsigned-byte 64) (unsigned-byte 64))
28 | (values 0 0 0))
29 |
30 | (define-protocol-fun process-room () ((unsigned-byte 64))
31 | 0)
32 |
33 | (define-protocol-fun process-time () (double-float)
34 | 0d0)
35 |
36 | (define-protocol-fun machine-room () ((unsigned-byte 64) (unsigned-byte 64))
37 | (values 0 0))
38 |
39 | (define-protocol-fun machine-cores () ((unsigned-byte 16))
40 | 1)
41 |
42 | (define-protocol-fun machine-uptime () ((unsigned-byte 64))
43 | 0)
44 |
45 | (define-protocol-fun machine-time ((core T)) (double-float double-float)
46 | (values 0d0 0d0))
47 |
48 | (define-protocol-fun thread-time ((thread T)) (double-float)
49 | 0d0)
50 |
51 | (define-protocol-fun thread-core-mask ((thread T)) ((unsigned-byte 64))
52 | (1- (ash 1 (machine-cores))))
53 |
54 | (define-protocol-fun (setf thread-core-mask) ((mask (unsigned-byte 64)) (thread T)) ((unsigned-byte 64))
55 | (thread-core-mask thread))
56 |
57 | (define-protocol-fun process-priority () ((member :idle :low :normal :high :realtime))
58 | :normal)
59 |
60 | (define-protocol-fun thread-priority ((thread T)) ((member :idle :low :normal :high :realtime))
61 | :normal)
62 |
63 | (define-protocol-fun (setf process-priority) ((priority (member :idle :low :normal :high :realtime))) ((member :idle :low :normal :high :realtime))
64 | :normal)
65 |
66 | (define-protocol-fun (setf thread-priority) ((priority (member :idle :low :normal :high :realtime)) (thread T)) ((member :idle :low :normal :high :realtime))
67 | :normal)
68 |
69 | (define-protocol-fun gc-room () ((unsigned-byte 64) (unsigned-byte 64))
70 | #+sbcl
71 | (values (- (sb-ext:dynamic-space-size) (sb-kernel:dynamic-usage))
72 | (sb-ext:dynamic-space-size))
73 | #+ccl (let ((free (ccl::%freebytes))
74 | (used (ccl::%usedbytes)))
75 | (values free
76 | (+ free used)))
77 | #+ecl
78 | (values #+boehm-gc (si:gc-stats T)
79 | #-boehm-gc 0
80 | (ext:get-limit 'ext:heap-size))
81 | #+clasp
82 | (values (- (sys:dynamic-space-size) (sys:dynamic-usage))
83 | (sys:dynamic-space-size))
84 | #+abcl
85 | (let* ((runtime (java:jstatic "getRuntime"
86 | (java:jclass "java.lang.Runtime")))
87 | ;; TODO: maxMemory? What does this method mean?
88 | (total-memory (java:jcall "totalMemory" runtime))
89 | (free-memory (java:jcall "freeMemory" runtime)))
90 | (values free-memory total-memory))
91 | #+clisp
92 | (multiple-value-bind (used room)
93 | (sys::%room)
94 | (values used (+ used room)))
95 | #-(or ccl sbcl ecl clasp abcl clisp)
96 | (values 0 0))
97 |
98 | (eval-when (:compile-toplevel :load-toplevel :execute)
99 | (defun find-symbol* (name package)
100 | (find-symbol (string name) (string package))))
101 |
102 | (define-protocol-fun gc-time () (double-float)
103 | #+sbcl
104 | (/ (float #.(or (find-symbol* "*GC-REAL-TIME*" "SB-EXT")
105 | (find-symbol* "*GC-RUN-TIME*" "SB-EXT")
106 | 0d0)
107 | 0d0)
108 | INTERNAL-TIME-UNITS-PER-SECOND)
109 | #+ccl
110 | (/ (float (ccl:gctime) 0d0)
111 | INTERNAL-TIME-UNITS-PER-SECOND)
112 | #+(and ecl (not boehm-gc))
113 | (/ (float (si::gc-time) 0d0)
114 | INTERNAL-TIME-UNITS-PER-SECOND)
115 | #+clasp
116 | (/ (float (sys:gc-real-time) 0d0)
117 | INTERNAL-TIME-UNITS-PER-SECOND)
118 | #-(or ccl sbcl (and ecl (not boehm-gc)) clasp)
119 | 0d0)
120 |
121 | (define-protocol-fun gpu-room () ((unsigned-byte 64) (unsigned-byte 64))
122 | (values 0 0))
123 |
124 | (define-protocol-fun gpu-time () (double-float)
125 | 0d0)
126 |
127 | (define-protocol-fun stack-room () ((unsigned-byte 64) (unsigned-byte 64))
128 | #+ccl
129 | (multiple-value-bind (stack stack-used)
130 | (ccl::%stack-space)
131 | (values (- stack stack-used) stack))
132 | #+ecl
133 | (values 0 (ext:get-limit 'ext:lisp-stack))
134 | #+sbcl
135 | (let* ((stack-total (- sb-vm::*control-stack-end* sb-vm::*control-stack-start*))
136 | (spaces (ignore-errors
137 | (symbol-value
138 | (or (find-symbol* :+all-spaces+ :sb-vm)
139 | (find-symbol* :+stack-spaces+ :sb-vm))))))
140 | (values
141 | ;; FIXME: This is implemented the way it is because sometimes
142 | ;; either of +all-spaces+ or +stack-spaces+ is undefined due to
143 | ;; SBCL-internal magic -- aartaka
144 | (- stack-total
145 | (if spaces
146 | (funcall (third (find :control-stack spaces :key #'first)))
147 | stack-total))
148 | stack-total))
149 | #-(or ccl ecl sbcl)
150 | (values 0 0))
151 |
152 | (define-protocol-fun static-room () ((unsigned-byte 64))
153 | #+ccl
154 | (multiple-value-bind (heap-used static-used staticlib-used frozen-space-size)
155 | (ccl::%usedbytes)
156 | (declare (ignorable heap-used))
157 | (+ static-used staticlib-used frozen-space-size))
158 | #+clisp
159 | (nth-value 2 (sys::%room))
160 | #+sbcl
161 | (let ((spaces (ignore-errors (symbol-value (find-symbol* :+all-spaces+ :sb-vm)))))
162 | (if spaces
163 | (funcall (third (find :static spaces :key #'first)))
164 | 0))
165 | #+(or ccl clisp sbcl)
166 | 0)
167 |
168 | (define-protocol-fun storage-device ((path (or string pathname))) (string)
169 | (declare (ignore path))
170 | (fail "Not implemented."))
171 |
172 | (define-protocol-fun storage-device-path ((device string)) (pathname)
173 | (declare (ignore device))
174 | (fail "Not implemented."))
175 |
176 | (define-protocol-fun storage-room ((path (or string pathname))) ((unsigned-byte 64) (unsigned-byte 64))
177 | (declare (ignore path))
178 | (values 0 0))
179 |
180 | (define-protocol-fun storage-io-bytes ((path T)) ((unsigned-byte 64) (unsigned-byte 64) (unsigned-byte 64))
181 | (declare (ignore path))
182 | (values 0 0 0))
183 |
184 | (define-protocol-fun network-devices () (list)
185 | ())
186 |
187 | (define-protocol-fun network-io-bytes ((device T)) ((unsigned-byte 64) (unsigned-byte 64) (unsigned-byte 64))
188 | (declare (ignore device))
189 | (values 0 0 0))
190 |
191 | (defun os-type ()
192 | (or #+(or win32 windows) :WINDOWS
193 | #+(and linux (not android)) :LINUX
194 | #+(and (or darwin macos) (not ios) (not mach)) :DARWIN
195 | #+android :ANDROID
196 | #+ios :IOS
197 | #+(or netbsd net-bsd) :NETBSD
198 | #+(or freebsd free-bsd) :FREEBSD
199 | #+(or openbsd open-bsd) :OPENBSD
200 | #+beos :BEOS
201 | #+solaris :SOLARIS
202 | #+(or react reactos) :REACT
203 | #+(or plan9 p9) :PLAN9
204 | #+mezzano :MEZZANO
205 | #+nx :NX))
206 |
207 | (define-protocol-fun machine-info () (string string symbol string)
208 | (values "Unknown" "Unknown" (os-type) "Unknown"))
209 |
210 | (define-protocol-fun machine-battery () (double-float double-float symbol)
211 | (values 0.0d0 0.0d0 NIL))
212 |
213 | (defun arch-type ()
214 | (or #+(and x86 (not (or x86-64 amd64))) :X86
215 | #+(or x86-64 amd64) :AMD64
216 | #+(and (or arm armv7 armv6 armv5) (not arm64)) :ARM
217 | #+arm64 :ARM64
218 | #+(and riscv (not riscv64)) :RISCV
219 | #+(or riscv64 rv64) :RISCV64
220 | #+(or ppc power powerpc) :PPC
221 | #+sparc :SPARC))
222 |
223 | (define-protocol-fun machine-core-info () (string string symbol string)
224 | (values "Unknown" "Unknown" (arch-type) "Unknown"))
225 |
226 | (define-protocol-fun process-info () (pathname pathname string string)
227 | (values *default-pathname-defaults* *default-pathname-defaults* "Unknown" "Unknown"))
228 |
229 | (define-protocol-fun gpu-info () (symbol string string)
230 | (values NIL "Unknown" "Unknown"))
231 |
232 | (define-protocol-fun network-info () ((or string null))
233 | (values NIL))
234 |
235 | (define-protocol-fun network-address ((device string)) ((or string null) (or string null) (or string null))
236 | (declare (ignore device))
237 | (values NIL NIL NIL))
238 |
--------------------------------------------------------------------------------
/staple.ext.lisp:
--------------------------------------------------------------------------------
1 | (defmethod staple:subsystems ((system (eql (asdf:find-system :machine-state)))) ())
2 | (defmethod staple:packages ((system (eql (asdf:find-system :machine-state)))) (list (find-package :org.shirakumo.machine-state)))
3 |
--------------------------------------------------------------------------------
/test.lisp:
--------------------------------------------------------------------------------
1 | (defpackage #:org.shirakumo.machine-state.test
2 | (:use #:cl #:parachute)
3 | (:local-nicknames
4 | (#:machine-state #:org.shirakumo.machine-state))
5 | (:export))
6 |
7 | (in-package #:org.shirakumo.machine-state.test)
8 |
9 | (define-test machine-state
10 |
11 | ;;;; Machine
12 |
13 | (define-test machine-time
14 | (flet ((test (idle total)
15 | (of-type 'double-float idle)
16 | (of-type 'double-float total)
17 | (is >= 0.0d0 idle)
18 | (is >= 0.0d0 total)
19 | (is > idle total)))
20 |
21 | ;; Test the aggregate
22 | (let ((core-times (loop for i below (machine-state:machine-cores)
23 | collect (multiple-value-list (machine-state:machine-time i)))))
24 |
25 | (multiple-value-bind (cpu-idle cpu-total) (machine-state:machine-time t)
26 | (test cpu-idle cpu-total)
27 |
28 | (let ((nproc (machine-state:machine-cores)))
29 | (dolist (core core-times)
30 | (destructuring-bind (core-idle core-total) core
31 |
32 | ;; Test core against CPU
33 | (is <= cpu-idle core-idle)
34 | (is <= cpu-total core-total)
35 |
36 | ;; Test cores
37 | (test core-idle core-total)))
38 |
39 | (fail (machine-state:machine-time nproc) 'machine-state:query-failed
40 | "Non existant core expected to fail"))))))
41 |
42 | (define-test machine-cores
43 | (let ((nproc (machine-state:machine-cores)))
44 | (of-type '(unsigned-byte 64) nproc)
45 | (is >= 1 nproc)))
46 |
47 | (define-test machine-uptime
48 | (let ((uptime (machine-state:machine-uptime)))
49 | (of-type '(unsigned-byte 64) uptime)))
50 |
51 | (define-test machine-room
52 | (multiple-value-bind (used total) (machine-state:machine-room)
53 | (of-type '(unsigned-byte 64) used)
54 | (of-type '(unsigned-byte 64) total)
55 | (is >= used total)))
56 |
57 | (define-test machine-info
58 | (multiple-value-bind (board model os version) (machine-state:machine-info)
59 | (of-type 'string board)
60 | (of-type 'string model)
61 | (is member '(:WINDOWS :LINUX :DARWIN :ANDROID :IOS :NETBSD :FREEBSD
62 | :OPENBSD :BEOS :SOLARIS :REACT :PLAN9 :MEZZANO :NX
63 | nil)
64 | os)
65 | (of-type 'string version)))
66 |
67 | (define-test machine-core-info
68 | (multiple-value-bind (vendor model arch version) (machine-state:machine-core-info)
69 | (of-type 'string vendor)
70 | (of-type 'string model)
71 | (is member '(:X86 :AMD64 :ARM :ARM64 :RISCV :RISCV64 :PPC :SPARC nil) arch)
72 | (of-type 'string version)))
73 |
74 | (define-test machine-battery
75 | (multiple-value-bind (current full state) (machine-state:machine-battery)
76 | (of-type 'double-float current)
77 | (of-type 'double-float full)
78 | (is member '(:CHARGING :DISCHARGING :FULL nil) state)
79 | (is >= 0.0d0 current)
80 | (is >= 0.0d0 full)
81 | (is <= full current)))
82 |
83 | ;;;; Processes
84 |
85 | (define-test process-io-bytes
86 | (multiple-value-bind (total r w) (machine-state:process-io-bytes)
87 | (of-type '(unsigned-byte 64) total)
88 | (of-type '(unsigned-byte 64) r)
89 | (of-type '(unsigned-byte 64) w)
90 |
91 | (is = (+ r w) total)
92 | (is >= r total)
93 | (is >= w total)))
94 |
95 | (define-test process-time
96 | (let ((time (machine-state:process-time)))
97 | (of-type 'double-float time)
98 | (is >= 0.0d0 time)))
99 |
100 | (define-test process-room
101 | (let ((room (machine-state:process-room)))
102 | (of-type '(unsigned-byte 64) room)))
103 |
104 | (define-test process-priority
105 | (is member '(:IDLE :LOW :NORMAL :HIGH :REALTIME) (machine-state:process-priority)))
106 |
107 | (define-test process-info
108 | (multiple-value-bind (command cwd user group) (machine-state:process-info)
109 | (of-type 'pathname command)
110 | (of-type 'pathname cwd)
111 | (of-type 'string user)
112 | (of-type 'string group)))
113 |
114 | ;;;; Threads
115 |
116 | (define-test thread-time
117 | (let ((time (machine-state:thread-time t)))
118 | (of-type 'double-float time)
119 | (is >= 0.0d0 time)))
120 |
121 | (define-test thread-core-mask
122 | (let ((mask (machine-state:thread-core-mask t)))
123 | (of-type '(unsigned-byte 64) mask) ;; Max 64 bits
124 | (is > 0 mask)))
125 |
126 | (define-test thread-priority
127 | (is member '(:IDLE :LOW :NORMAL :HIGH :REALTIME) (machine-state:thread-priority t)))
128 |
129 | ;;;; GC
130 |
131 | (define-test gc-room
132 | (multiple-value-bind (free total) (machine-state:gc-room)
133 | (of-type '(unsigned-byte 64) free)
134 | (of-type '(unsigned-byte 64) total)
135 | (is <= total free)))
136 |
137 | (define-test gc-time
138 | (let ((time (machine-state:gc-time)))
139 | (of-type 'double-float time)
140 | (is >= 0.0d0 time)))
141 |
142 | ;;;; GPU
143 |
144 | (define-test gpu-room
145 | (multiple-value-bind (free total) (machine-state:gpu-room)
146 | (of-type '(unsigned-byte 64) free)
147 | (of-type '(unsigned-byte 64) total)
148 | (is <= total free)))
149 |
150 | (define-test gpu-time
151 | (let ((time (machine-state:gpu-time)))
152 | (of-type 'double-float time)
153 | (is >= 0.0d0 time)))
154 |
155 | (define-test gpu-info
156 | (multiple-value-bind (vendor model version) (machine-state:gpu-info)
157 | (of-type 'symbol vendor)
158 | (of-type 'string model)
159 | (of-type 'string version)))
160 |
161 | ;;;; Storage
162 |
163 | (define-test storage-room
164 | (multiple-value-bind (free total) (machine-state:storage-room *default-pathname-defaults*)
165 | (of-type '(unsigned-byte 64) free)
166 | (of-type '(unsigned-byte 64) total)
167 | (is <= total free)))
168 |
169 | (define-test storage-io-bytes
170 | (multiple-value-bind (total r w) (machine-state:storage-io-bytes *default-pathname-defaults*)
171 | (of-type '(unsigned-byte 64) total)
172 | (of-type '(unsigned-byte 64) r)
173 | (of-type '(unsigned-byte 64) w)
174 | (is = (+ r w) total)
175 | (is >= r total)
176 | (is >= w total)))
177 |
178 | ;;;; Network
179 |
180 | (define-test network-devices
181 | (let ((devices (machine-state:network-devices)))
182 | (of-type 'list devices)
183 | (is every devices #'stringp)))
184 |
185 | (define-test network-io-bytes
186 | (dolist (dev (machine-state:network-devices))
187 | (multiple-value-bind (total r w) (machine-state:network-io-bytes dev)
188 | (of-type '(unsigned-byte 64) total)
189 | (of-type '(unsigned-byte 64) r)
190 | (of-type '(unsigned-byte 64) w)
191 | (is = (+ r w) total)
192 | (is >= r total)
193 | (is >= w total))))
194 |
195 | (define-test network-address
196 | (dolist (dev (machine-state:network-devices))
197 | (multiple-value-bind (mac ipv4 ipv6) (machine-state:network-address dev)
198 | (of-type '(or null string) mac)
199 | (of-type '(or null string) ipv4)
200 | (of-type '(or null string) ipv6))))
201 |
202 | (define-test network-info
203 | (of-type '(or null string) (machine-state:network-info)))
204 |
205 | ;;;; Others
206 |
207 | (define-test static-room
208 | (let ((size (machine-state:static-room)))
209 | (of-type '(unsigned-byte 64) size)))
210 |
211 | (define-test stack-room
212 | (multiple-value-bind (free total) (machine-state:stack-room)
213 | (of-type '(unsigned-byte 64) free)
214 | (of-type '(unsigned-byte 64) total)
215 | (is <= total free))))
216 |
--------------------------------------------------------------------------------
/windows.lisp:
--------------------------------------------------------------------------------
1 | (in-package #:org.shirakumo.machine-state)
2 |
3 | (cffi:define-foreign-library psapi
4 | (:windows "Psapi.dll"))
5 |
6 | (cffi:define-foreign-library ntdll
7 | (:windows "Ntdll.dll"))
8 |
9 | (cffi:define-foreign-library pdh
10 | (:windows "Pdh.dll"))
11 |
12 | (cffi:define-foreign-library iphlpapi
13 | (:windows "Iphlpapi.dll"))
14 |
15 | (cffi:define-foreign-library secur32
16 | (:windows "Secur32.dll"))
17 |
18 | (cffi:define-foreign-library advapi32
19 | (:windows "Advapi32.dll"))
20 |
21 | (cffi:define-foreign-library oleaut32
22 | (:windows "OleAut32.dll"))
23 |
24 | (cffi:use-foreign-library psapi)
25 | (cffi:use-foreign-library ntdll)
26 |
27 | (set 'cl-user::*foreign-system-libraries*
28 | (union (when (boundp 'cl-user::*foreign-system-libraries*)
29 | (symbol-value 'cl-user::*foreign-system-libraries*))
30 | '(psapi ntdll pdh iphlpapi secur32 advapi32 oleaut32)))
31 |
32 | (defmacro windows-call (function &rest args)
33 | `(unless (cffi:foreign-funcall ,function ,@args)
34 | (fail (org.shirakumo.com-on:error-message))))
35 |
36 | (defmacro nt-call (function &rest args)
37 | `(let ((value (cffi:foreign-funcall ,function ,@args)))
38 | (unless (= 0 value)
39 | (fail (org.shirakumo.com-on:error-message
40 | (cffi:foreign-funcall "LsaNtStatusToWinError" :uint32 value :ulong))))))
41 |
42 | (defmacro pdh-call (&rest args)
43 | `(let ((ret (cffi:foreign-funcall ,@args :size)))
44 | (unless (= 0 ret)
45 | (let ((msg (com:error-message ret 'pdh)))
46 | (fail (if (string/= "" msg)
47 | msg
48 | (format NIL "Performance counter call failed with ~d" ret))
49 | 'machine-time)))))
50 |
51 | (defun process ()
52 | (cffi:foreign-funcall "GetCurrentProcess" :pointer))
53 |
54 | (cffi:defcstruct (io-counters :conc-name io-counters-)
55 | (reads :ullong)
56 | (writes :ullong)
57 | (others :ullong)
58 | (read-bytes :ullong)
59 | (write-bytes :ullong)
60 | (other-bytes :ullong))
61 |
62 | (define-implementation process-io-bytes ()
63 | (cffi:with-foreign-object (io-counters '(:struct io-counters))
64 | (windows-call "GetProcessIoCounters"
65 | :pointer (process)
66 | :pointer io-counters
67 | :bool)
68 | (values (+ (io-counters-read-bytes io-counters)
69 | (io-counters-write-bytes io-counters)
70 | (io-counters-other-bytes io-counters))
71 | (io-counters-read-bytes io-counters)
72 | (io-counters-write-bytes io-counters))))
73 |
74 | (cffi:defcstruct (memory-counters :conc-name memory-counters-)
75 | (cb :uint32)
76 | (page-fault-count :uint32)
77 | (peak-working-set-size :size)
78 | (working-set-size :size)
79 | (quota-peak-paged-pool-usage :size)
80 | (quota-paged-pool-usage :size)
81 | (quota-peak-non-paged-pool-usage :size)
82 | (quota-non-paged-pool-usage :size)
83 | (pagefile-usage :size)
84 | (peak-page-file-usage :size))
85 |
86 | (define-implementation process-room ()
87 | (cffi:with-foreign-objects ((memory-counters '(:struct memory-counters)))
88 | (windows-call "GetProcessMemoryInfo"
89 | :pointer (process)
90 | :pointer memory-counters
91 | :uint32 (cffi:foreign-type-size '(:struct memory-counters))
92 | :bool)
93 | (memory-counters-working-set-size memory-counters)))
94 |
95 | (define-implementation process-time ()
96 | (cffi:with-foreign-objects ((creation-time :uint64)
97 | (exit-time :uint64)
98 | (kernel-time :uint64)
99 | (user-time :uint64))
100 | (windows-call "GetProcessTimes"
101 | :pointer (process)
102 | :pointer creation-time
103 | :pointer exit-time
104 | :pointer kernel-time
105 | :pointer user-time
106 | :bool)
107 | (convert-file-time (cffi:mem-ref user-time :uint64))))
108 |
109 | (cffi:defcstruct (memory-status :conc-name memory-status-)
110 | (length :uint32)
111 | (memory-load :uint32)
112 | (total-physical :uint64)
113 | (available-physical :uint64)
114 | (total-page-file :uint64)
115 | (available-page-file :uint64)
116 | (total-virtual :uint64)
117 | (available-virtual :uint64)
118 | (available-extended-virtual :uint64))
119 |
120 | (define-implementation machine-room ()
121 | (cffi:with-foreign-objects ((memory-status '(:struct memory-status)))
122 | (setf (memory-status-length memory-status)
123 | (cffi:foreign-type-size '(:struct memory-status)))
124 | (windows-call "GlobalMemoryStatusEx"
125 | :pointer memory-status
126 | :bool)
127 | (let ((available (memory-status-available-physical memory-status))
128 | (total (memory-status-total-physical memory-status)))
129 | (values (- total available)
130 | total))))
131 |
132 | (cffi:defcstruct (system-info :conc-name system-info-)
133 | (oem-id :uint32)
134 | (page-size :uint32)
135 | (minimum-application-address :pointer)
136 | (maximum-application-address :pointer)
137 | (active-processor-mask :uint64)
138 | (number-of-processors :uint32)
139 | (processor-type :uint32)
140 | (allocation-granularity :uint32)
141 | (processor-level :uint16)
142 | (processor-revision :uint16))
143 |
144 | (define-implementation machine-cores ()
145 | (cffi:with-foreign-objects ((system-info '(:struct system-info)))
146 | (cffi:foreign-funcall "GetSystemInfo"
147 | :pointer system-info
148 | :void)
149 | (system-info-number-of-processors system-info)))
150 |
151 | (define-implementation machine-uptime ()
152 | (cffi:with-foreign-objects ((time :long-long)
153 | (freq :long-long))
154 | (windows-call "QueryUnbiasedInterruptTime" :pointer time :bool)
155 | (values (round (cffi:mem-ref time :long-long) 10000000))))
156 |
157 | (declaim (inline convert-file-time))
158 | (defun convert-file-time (time)
159 | (* 10d-9 (float time 0d0)))
160 |
161 | (define-implementation machine-time (core)
162 | (etypecase core
163 | ((eql T)
164 | (cffi:with-foreign-objects ((idle-time :uint64)
165 | (kernel-time :uint64)
166 | (user-time :uint64))
167 | (windows-call "GetSystemTimes"
168 | :pointer idle-time
169 | :pointer kernel-time
170 | :pointer user-time
171 | :bool)
172 | (values (convert-file-time (cffi:mem-ref idle-time :uint64))
173 | (convert-file-time (+ (cffi:mem-ref kernel-time :uint64)
174 | (cffi:mem-ref idle-time :uint64)
175 | (cffi:mem-ref user-time :uint64))))))
176 | (integer
177 | (unless (cffi:foreign-library-loaded-p 'pdh)
178 | (cffi:load-foreign-library 'pdh))
179 | (cffi:with-foreign-objects ((handle :pointer)
180 | (counters :pointer 3)
181 | (type :uint32)
182 | (data :double))
183 | (pdh-call "PdhOpenQueryW"
184 | :pointer (cffi:null-pointer)
185 | :pointer (cffi:null-pointer)
186 | :pointer handle)
187 | (let ((handle (cffi:mem-ref handle :pointer)))
188 | (unwind-protect
189 | (progn
190 | (flet ((add-counter (i name)
191 | (pdh-call "PdhAddCounterW"
192 | :pointer handle
193 | com:wstring name
194 | :pointer (cffi:null-pointer)
195 | :pointer (cffi:mem-aptr counters :pointer i))))
196 | (add-counter 0 (format NIL "\\Processor(~d)\\% Processor Time" core))
197 | (add-counter 1 (format NIL "\\Processor(~d)\\% Idle Time" core))
198 | (add-counter 2 (format NIL "\\Processor(~d)\\% Privileged Time" core)))
199 | (pdh-call "PdhCollectQueryData" :pointer handle)
200 | (flet ((get-counter (i)
201 | (pdh-call "PdhGetFormattedCounterValue"
202 | :pointer (cffi:mem-aptr counters :pointer i)
203 | :uint32 #x00000200 #|PDH_FMT_DOUBLE|#
204 | :pointer type
205 | :pointer data)
206 | (cffi:mem-ref data :double)))
207 | (let ((proc (get-counter 0))
208 | (idle (get-counter 1))
209 | (priv (get-counter 2)))
210 | (values idle (+ proc idle priv)))))
211 | (cffi:foreign-funcall "PdhCloseQuery" :pointer handle)))))))
212 |
213 | (defmacro with-thread-handle ((handle thread &optional (default 0)) &body body)
214 | `(if (or (eql ,thread T)
215 | (eql ,thread (bt:current-thread)))
216 | (let ((,handle (cffi:foreign-funcall "GetCurrentThread" :pointer)))
217 | ,@body)
218 | ,default))
219 |
220 | (define-implementation thread-time (thread)
221 | (with-thread-handle (handle thread)
222 | (cffi:with-foreign-objects ((creation-time :uint64)
223 | (exit-time :uint64)
224 | (kernel-time :uint64)
225 | (user-time :uint64))
226 | (windows-call "GetThreadTimes"
227 | :pointer handle
228 | :pointer creation-time
229 | :pointer exit-time
230 | :pointer kernel-time
231 | :pointer user-time
232 | :bool)
233 | (convert-file-time (cffi:mem-ref user-time :uint64)))))
234 |
235 | (cffi:defcstruct (thread-info :conc-name thread-info-)
236 | (exit-status :uint32)
237 | (base-address :pointer)
238 | (process :pointer)
239 | (thread :pointer)
240 | (affinity-mask :uint64)
241 | (priority :long)
242 | (base-priority :long))
243 |
244 | (define-implementation thread-core-mask (thread)
245 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
246 | (cffi:with-foreign-objects ((info '(:struct thread-info)))
247 | (cffi:foreign-funcall "NtQueryInformationThread"
248 | :pointer handle
249 | :int #x04
250 | :pointer info
251 | :ulong (cffi:foreign-type-size '(:struct thread-info))
252 | :uint32)
253 | (thread-info-affinity-mask info))))
254 |
255 | (define-implementation (setf thread-core-mask) (mask thread)
256 | (with-thread-handle (handle thread (1- (ash 1 (machine-cores))))
257 | (if (= 0 (cffi:foreign-funcall "SetThreadAffinityMask"
258 | :pointer handle
259 | :uint64 mask
260 | :uint64))
261 | (fail (org.shirakumo.com-on:error-message))
262 | mask)))
263 |
264 | (define-implementation process-priority ()
265 | (let ((value (cffi:foreign-funcall "GetPriorityClass" :pointer (process))))
266 | (case (cffi:foreign-funcall "GetPriorityClass" :pointer (process))
267 | (#x00000000 (fail (org.shirakumo.com-on:error-message)))
268 | (#x00000040 :idle)
269 | (#x00004000 :low)
270 | (#x00000020 :normal)
271 | (#x00000080 :high)
272 | (#x00000100 :realtime)
273 | (T :normal))))
274 |
275 | (define-implementation (setf process-priority) (priority)
276 | (windows-call "SetPriorityClass"
277 | :pointer (process)
278 | :uint16 (ecase priority
279 | (:idle #x00000040)
280 | (:low #x00004000)
281 | (:normal #x00000020)
282 | (:high #x00000080)
283 | (:realtime #x00000100))
284 | :bool)
285 | priority)
286 |
287 | (define-implementation thread-priority (thread)
288 | (with-thread-handle (handle thread :normal)
289 | (let ((value (cffi:foreign-funcall "GetThreadPriority" :pointer handle :uint)))
290 | (when (= value 2147483647)
291 | (fail (org.shirakumo.com-on:error-message)))
292 | (cond ((< value -8) :idle)
293 | ((< value 0) :low)
294 | ((= value 0) :normal)
295 | ((< value +8) :high)
296 | (T :realtime)))))
297 |
298 | (define-implementation (setf thread-priority) (thread priority)
299 | (with-thread-handle (handle thread :normal)
300 | (windows-call "SetThreadPriority"
301 | :pointer handle
302 | :int (ecase priority
303 | (:idle -15)
304 | (:low -1)
305 | (:normal 0)
306 | (:high 2)
307 | (:realtime 15))
308 | :bool)
309 | priority))
310 |
311 | (define-implementation storage-device (path)
312 | (etypecase path
313 | (pathname (or (pathname-device path)
314 | (pathname-device *default-pathname-defaults*)
315 | "C"))
316 | (string (or (pathname-device (pathname-utils:parse-native-namestring path))
317 | (pathname-device *default-pathname-defaults*)
318 | "C"))))
319 |
320 | (define-implementation storage-device-path (device)
321 | (make-pathname :device device :directory '(:absolute)))
322 |
323 | (define-implementation storage-room (path)
324 | (cffi:with-foreign-objects ((available-to-caller :int64)
325 | (total :int64)
326 | (available :int64))
327 | (windows-call "GetDiskFreeSpaceExW"
328 | com:wstring (etypecase path
329 | (string (format NIL "~a:/" path))
330 | (pathname
331 | (pathname-utils:native-namestring (pathname-utils:to-directory path))))
332 | :pointer available-to-caller
333 | :pointer total
334 | :pointer available
335 | :bool)
336 | (values (cffi:mem-ref available :int64)
337 | (cffi:mem-ref total :int64))))
338 |
339 | (cffi:defcstruct (disk-performance :conc-name disk-performance-)
340 | (bytes-read :int64)
341 | (bytes-written :int64)
342 | (read-time :int64)
343 | (write-time :int64)
344 | (idle-time :int64)
345 | (read-count :uint32)
346 | (write-count :uint32)
347 | (queue-depth :uint32)
348 | (split-count :uint32)
349 | (query-time :int64)
350 | (storage-device-number :uint32)
351 | (storage-manager-name :uint16 :count 8))
352 |
353 | (define-implementation storage-io-bytes (device)
354 | (when (pathnamep device)
355 | (setf device (storage-device device)))
356 | (let ((handle (cffi:foreign-funcall "CreateFileA"
357 | :string (format NIL "\\\\.\\~a:" device)
358 | :uint32 0
359 | :uint32 3 #| FILE_SHARE_READ | FILE_SHARE_WRITE |#
360 | :pointer (cffi:null-pointer)
361 | :uint32 3 #| OPEN_EXISTING |#
362 | :uint32 #x02000000 #| FILE_FLAG_BACKUP_SEMANTICS |#
363 | :pointer (cffi:null-pointer)
364 | :pointer)))
365 | (when (= (cffi:pointer-address handle) #+64-bit (1- (ash 1 64)) #-64-bit (1- (ash 1 32)))
366 | (fail (org.shirakumo.com-on:error-message)))
367 | (unwind-protect
368 | (cffi:with-foreign-objects ((perf '(:struct disk-performance)))
369 | (windows-call "DeviceIoControl"
370 | :pointer handle
371 | :uint32 458784 #| IOCTL_DISK_PERFORMANCE |#
372 | :pointer (cffi:null-pointer)
373 | :uint32 0
374 | :pointer perf
375 | :uint32 (cffi:foreign-type-size '(:struct disk-performance))
376 | :pointer (cffi:null-pointer)
377 | :pointer (cffi:null-pointer)
378 | :bool)
379 | (values (+ (disk-performance-bytes-read perf)
380 | (disk-performance-bytes-written perf))
381 | (disk-performance-bytes-read perf)
382 | (disk-performance-bytes-written perf)))
383 | (cffi:foreign-funcall "CloseHandle" :pointer handle))))
384 |
385 | (cffi:defcstruct (ifrow :size 1352 :conc-name ifrow-)
386 | (alias :uint16 :count 257 :offset 28)
387 | (in-octets :uint64 :offset 1208)
388 | (out-octets :uint64 :offset 1280))
389 |
390 | (cffi:defcstruct (iftable :conc-name iftable-)
391 | (entries :ulong)
392 | (table (:struct ifrow) :count 128))
393 |
394 | (define-implementation network-devices ()
395 | (unless (cffi:foreign-library-loaded-p 'iphlpapi)
396 | (cffi:load-foreign-library 'iphlpapi))
397 | (cffi:with-foreign-objects ((table :pointer))
398 | (let ((ret (cffi:foreign-funcall "GetIfTable2" :pointer table :size)))
399 | (unless (= 0 ret)
400 | (let ((msg (com:error-message ret 'iphlpapi)))
401 | (fail (if (string/= "" msg) msg
402 | (format NIL "GetIfTable2 call failed with ~d" ret))))))
403 | (let ((table (cffi:mem-ref table :pointer)))
404 | (unwind-protect
405 | (let ((list ()))
406 | (dotimes (i (iftable-entries table) (nreverse list))
407 | (let* ((row (cffi:mem-aptr (cffi:foreign-slot-pointer table '(:struct iftable) 'table)
408 | '(:struct ifrow) i))
409 | (name (com:wstring->string
410 | (cffi:foreign-slot-pointer row '(:struct ifrow) 'alias)
411 | 256)))
412 | (push name list))))
413 | (cffi:foreign-funcall "FreeMibTable" :pointer table)))))
414 |
415 | (define-implementation network-io-bytes (device)
416 | (unless (cffi:foreign-library-loaded-p 'iphlpapi)
417 | (cffi:load-foreign-library 'iphlpapi))
418 | (cffi:with-foreign-objects ((table :pointer))
419 | (let ((ret (cffi:foreign-funcall "GetIfTable2" :pointer table :size)))
420 | (unless (= 0 ret)
421 | (let ((msg (com:error-message ret 'iphlpapi)))
422 | (fail (if (string/= "" msg) msg
423 | (format NIL "GetIfTable2 call failed with ~d" ret))))))
424 | (let ((table (cffi:mem-ref table :pointer)))
425 | (unwind-protect
426 | (etypecase device
427 | ((eql T)
428 | (let ((read 0) (write 0))
429 | (declare (type (unsigned-byte 64) read write))
430 | (dotimes (i (iftable-entries table) (values (+ read write) read write))
431 | (let ((row (cffi:mem-aptr (cffi:foreign-slot-pointer table '(:struct iftable) 'table)
432 | '(:struct ifrow) i)))
433 | (incf read (ifrow-in-octets row))
434 | (incf write (ifrow-out-octets row))))))
435 | (string
436 | (dotimes (i (iftable-entries table) (fail "No such device found."))
437 | (let* ((row (cffi:mem-aptr (cffi:foreign-slot-pointer table '(:struct iftable) 'table)
438 | '(:struct ifrow) i))
439 | (name (com:wstring->string
440 | (cffi:foreign-slot-pointer row '(:struct ifrow) 'alias)
441 | 256)))
442 | (when (string= name device)
443 | (return (values (+ (ifrow-in-octets row)
444 | (ifrow-out-octets row))
445 | (ifrow-in-octets row)
446 | (ifrow-out-octets row))))))))
447 | (cffi:foreign-funcall "FreeMibTable" :pointer table)))))
448 |
449 | (cffi:defcstruct (version-info :conc-name version-info-)
450 | (size :ulong)
451 | (major :ulong)
452 | (minor :ulong)
453 | (build-number :ulong)
454 | (platform-id :ulong)
455 | (csd-version :uint16 :count 128))
456 |
457 | (cffi:defcstruct (variant :conc-name variant-)
458 | (type :ushort)
459 | (reserved1 :uint16)
460 | (reserved2 :uint16)
461 | (reserved3 :uint16)
462 | (value :pointer))
463 |
464 | (com:define-guid clsid-wbem-locator #x4590f811 #x1d3a #x11d0 #x89 #x1f #x00 #xaa #x00 #x4b #x2e #x24)
465 | (com:define-guid iid-iwbem-locator #xdc12a687 #x737f #x11cf #x88 #x4d #x00 #xaa #x00 #x4b #x2e #x24)
466 |
467 | (com:define-comstruct i-wbem-locator
468 | (connect-server (network-resource com:wstring)
469 | (user com:wstring)
470 | (password com:wstring)
471 | (locale com:wstring)
472 | (security-flags :long)
473 | (authority com:wstring)
474 | (context :pointer)
475 | (namespace :pointer)))
476 |
477 | (com:define-comstruct i-wbem-services
478 | open-namespace
479 | cancel-async-call
480 | query-object-sink
481 | get-object
482 | get-object-async
483 | put-class
484 | put-class-async
485 | delete-class
486 | delete-class-async
487 | create-class-enum
488 | create-class-enum-async
489 | put-instance
490 | put-instance-async
491 | delete-instance
492 | delete-instance-async
493 | create-instance-enum
494 | create-instance-enum-async
495 | (exec-query (query-language com:wstring) (query com:wstring) (flags :long) (context :pointer) (enumerator :pointer))
496 | exec-query-async
497 | exec-notification-query
498 | exec-notification-query-async
499 | exec-method
500 | exec-method-async)
501 |
502 | (com:define-comstruct i-enum-wbem-class-object
503 | reset
504 | (next (timeout :long) (count :ulong) (objects :pointer) (returned :pointer))
505 | next-async
506 | clone
507 | skip)
508 |
509 | (com:define-comstruct i-wbem-class-object
510 | get-qualifier-set
511 | (get (name com:wstring) (flags :long) (value :pointer) (type :pointer) (flavor :pointer))
512 | put
513 | delete
514 | get-names
515 | begin-enumeration
516 | next
517 | end-enumeration
518 | get-property-qualifier-set
519 | clone
520 | get-object-text
521 | spawn-derived-class
522 | spawn-instance
523 | compare-to
524 | get-property-origin
525 | inherits-from
526 | get-method
527 | put-method
528 | delete-method
529 | begin-method-enumeration
530 | next-method
531 | end-method-enumeration
532 | get-method-qualifier-set
533 | get-method-origin)
534 |
535 | (defun wmi-query (query &rest props)
536 | (unless (cffi:foreign-library-loaded-p 'oleaut32)
537 | (cffi:load-foreign-library 'oleaut32))
538 | (com:with-com (locator (com:create clsid-wbem-locator iid-iwbem-locator))
539 | (cffi:with-foreign-objects ((value :pointer)
540 | (status :ulong)
541 | (variant '(:struct variant)))
542 | (i-wbem-locator-connect-server locator "Root\\CIMV2" NIL NIL NIL 0 NIL (cffi:null-pointer) value)
543 | (com:with-com (services (cffi:mem-ref value :pointer))
544 | (cffi:foreign-funcall "CoSetProxyBlanket" :pointer services
545 | :uint32 10 #| RPC_C_AUTHN_WINNT |#
546 | :uint32 0 #| RPC_C_AUTHZ_NONE |#
547 | :pointer (cffi:null-pointer)
548 | :uint32 3 #| RPC_C_AUTHN_LEVEL_CALL |#
549 | :uint32 3 #| RPC_C_IMP_LEVEL_IMPERSONATE |#
550 | :pointer (cffi:null-pointer)
551 | :uint32 0 #| EOAC_NONE |#)
552 | (i-wbem-services-exec-query services "WQL" query
553 | (logior #x20 #| WBEM_FLAG_FORWARD_ONLY |#
554 | #x10 #| WBEM_FLAG_RETURN_IMMEDIATELY |#)
555 | (cffi:null-pointer) value)
556 | (com:with-com (enumerator (cffi:mem-ref value :pointer))
557 | (when (cffi:null-pointer-p enumerator)
558 | (fail "WMI query failed."))
559 | (i-enum-wbem-class-object-next enumerator -1 #| WBEM_INFINITE |# 1 value status)
560 | (unless (= 0 (cffi:mem-ref status :ulong))
561 | (com:with-com (object (cffi:mem-ref value :pointer))
562 | (cffi:foreign-funcall "VariantInit" :pointer variant)
563 | (loop for prop in props
564 | do (cffi:foreign-funcall "VariantClear" :pointer variant)
565 | (i-wbem-class-object-get object prop 0 variant (cffi:null-pointer) (cffi:null-pointer))
566 | collect (case (variant-type variant)
567 | (8 (com:wstring->string (variant-value variant))))))))))))
568 |
569 | (define-implementation machine-info ()
570 | (destructuring-bind (&optional vendor model) (wmi-query "SELECT * FROM Win32_BaseBoard" "Manufacturer" "Product")
571 | (values vendor
572 | model
573 | :windows
574 | (cffi:with-foreign-objects ((version '(:struct version-info)))
575 | (setf (version-info-size version) (cffi:foreign-type-size '(:struct version-info)))
576 | (nt-call "RtlGetVersion" :pointer version :size)
577 | (format NIL "~d.~d-~a"
578 | (version-info-major version) (version-info-minor version)
579 | (version-info-build-number version))))))
580 |
581 | (define-implementation machine-core-info ()
582 | (destructuring-bind (&optional vendor model version) (wmi-query "SELECT * FROM Win32_Processor" "Manufacturer" "Name" "Version")
583 | (values (or vendor "Unknown")
584 | (or model "Unknown")
585 | (arch-type)
586 | (or version "Unknown"))))
587 |
588 | (define-implementation network-info ()
589 | (cffi:with-foreign-object (hostname :char 512)
590 | (cffi:foreign-funcall "gethostname" :pointer hostname :size 512 :int)
591 | (cffi:foreign-string-to-lisp hostname :max-chars 512)))
592 |
593 | (define-implementation process-info ()
594 | (unless (cffi:foreign-library-loaded-p 'advapi32)
595 | (cffi:load-foreign-library 'advapi32))
596 | (values
597 | (cffi:with-foreign-object (name :uint16 1024)
598 | (cffi:foreign-funcall "GetModuleFileNameW" :pointer (cffi:null-pointer) :pointer name :uint32 1024 :uint32)
599 | (pathname-utils:parse-native-namestring (com:wstring->string name)))
600 | (pathname-utils:parse-native-namestring
601 | (cffi:with-foreign-object (path :uint16 1024)
602 | (cffi:foreign-funcall "_wgetcwd" :pointer path :size 1024)
603 | (com:wstring->string path))
604 | :as :directory)
605 | (cffi:with-foreign-objects ((name :uint16 512)
606 | (length :uint32))
607 | (setf (cffi:mem-ref length :uint32) 512)
608 | (windows-call "GetUserNameW" :pointer name :pointer length :bool)
609 | (com:wstring->string name (cffi:mem-ref length :uint32)))
610 | "Unknown"))
611 |
612 | (cffi:defcstruct (sockaddr4 :conc-name sockaddr4-)
613 | (family :uint16)
614 | (port :uint16)
615 | (addr :uint32))
616 |
617 | (cffi:defcstruct (sockaddr6 :conc-name sockaddr6-)
618 | (family :ushort)
619 | (port :ushort)
620 | (flow-info :ulong)
621 | (addr :uint16 :count 8)
622 | (scope-id :long))
623 |
624 | (cffi:defcstruct (address :conc-name address-)
625 | (length :ulong)
626 | (flags :uint32)
627 | (next :pointer)
628 | (sockaddr :pointer)
629 | (sockaddr-length :int))
630 |
631 | (cffi:defcstruct (adapter :conc-name adapter-)
632 | (padding :unsigned-long-long)
633 | (next :pointer)
634 | (name :string)
635 | (first-unicast-address :pointer)
636 | (first-anycast-address :pointer)
637 | (first-multicast-address :pointer)
638 | (first-dns-server-address :pointer)
639 | (dns-suffix com:wstring)
640 | (description com:wstring)
641 | (friendly-name com:wstring)
642 | (physical-address :uint8 :count 8)
643 | (physical-address-length :ulong))
644 |
645 | (defun mac-str (octets)
646 | (format NIL "~{~2,'0x~^:~}" (coerce octets 'list)))
647 |
648 | (defun ipv4-str (ipv4)
649 | (format NIL "~d.~d.~d.~d"
650 | (ldb (byte 8 0) ipv4)
651 | (ldb (byte 8 8) ipv4)
652 | (ldb (byte 8 16) ipv4)
653 | (ldb (byte 8 24) ipv4)))
654 |
655 | (defun ipv6-str (ipv6)
656 | (flet ((be->le (i)
657 | (rotatef (ldb (byte 8 0) i) (ldb (byte 8 8) i))
658 | i))
659 | (format NIL "~x:~x:~x:~x:~x:~x:~x:~x"
660 | (be->le (cffi:mem-aref ipv6 :uint16 0))
661 | (be->le (cffi:mem-aref ipv6 :uint16 1))
662 | (be->le (cffi:mem-aref ipv6 :uint16 2))
663 | (be->le (cffi:mem-aref ipv6 :uint16 3))
664 | (be->le (cffi:mem-aref ipv6 :uint16 4))
665 | (be->le (cffi:mem-aref ipv6 :uint16 5))
666 | (be->le (cffi:mem-aref ipv6 :uint16 6))
667 | (be->le (cffi:mem-aref ipv6 :uint16 7)))))
668 |
669 | (define-implementation network-address (device)
670 | (cffi:with-foreign-objects ((size :ulong))
671 | (setf (cffi:mem-ref size :ulong) 0)
672 | (cffi:foreign-funcall "GetAdaptersAddresses" :ulong 0 :ulong 0 :pointer (cffi:null-pointer) :pointer (cffi:null-pointer) :pointer size)
673 | (cffi:with-foreign-objects ((adapter :char (cffi:mem-ref size :ulong)))
674 | (when (/= 0 (cffi:foreign-funcall "GetAdaptersAddresses" :ulong 0 :ulong 0 :pointer (cffi:null-pointer) :pointer adapter :pointer size :ulong))
675 | (fail "Failed to query adapters."))
676 | (loop until (cffi:null-pointer-p adapter)
677 | do (when (or (string-equal device (adapter-friendly-name adapter))
678 | (string-equal device (adapter-name adapter))
679 | (string-equal device (adapter-description adapter)))
680 | (let ((mac (mac-str (cffi:foreign-array-to-lisp (adapter-physical-address adapter) (list :array :uint8 (adapter-physical-address-length adapter)))))
681 | (addr (adapter-first-unicast-address adapter)) ipv4 ipv6)
682 | (loop until (cffi:null-pointer-p addr)
683 | do (let ((sockaddr (address-sockaddr addr)))
684 | (case (sockaddr4-family sockaddr)
685 | (2 (setf ipv4 (ipv4-str (sockaddr4-addr sockaddr))))
686 | (23 (setf ipv6 (ipv6-str (sockaddr6-addr sockaddr))))))
687 | (setf addr (address-next addr)))
688 | (return (values mac ipv4 ipv6))))
689 | (setf adapter (adapter-next adapter))
690 | finally (fail "No such device.")))))
691 |
692 | (cffi:defcstruct (system-power :conc-name system-power-)
693 | (line-status :uint8)
694 | (battery-flag :uint8)
695 | (battery-life-percent :uint8)
696 | (system-status-flag :uint8)
697 | (battery-life-time :uint32)
698 | (battery-full-life-time :uint32))
699 |
700 | (define-implementation machine-battery ()
701 | (cffi:with-foreign-objects ((power '(:struct system-power)))
702 | (windows-call "GetSystemPowerStatus" :pointer power :bool)
703 | (let ((current (system-power-battery-life-percent power)))
704 | (if (= 255 current)
705 | (values 0d0 0d0 NIL)
706 | (values (float current 0d0) 100d0
707 | (cond ((= 100 current)
708 | :full)
709 | ((logbitp 3 (system-power-battery-flag power))
710 | :charging)
711 | (T
712 | :discharging)))))))
713 |
--------------------------------------------------------------------------------