The operating system
The os module reports what the operating system provides beyond the
running process: environment variables, clocks, and facts about the
machine and the user. Bring it in with import os. The full API is in the
reference.
Environment variables
os.environment(name) returns a variable’s value as a String?: none when
it is not set, and an empty String when it is set to nothing.
os.environment_variables() returns them all as a HashMap<String, String>
from name to value.
import os;
main(): int {
# An optional: none when the variable is not set.
var runtime = os.environment("PEPPER_RUNTIME").value_or("runtime");
assert(!runtime.is_empty, "a fallback when unset");
assert(os.environment("PEPPER_SURELY_NOT_SET").is_empty(), "unset");
var count = 0;
for (var (name, value) in os.environment_variables()) {
if (name.starts_with("PEPPER_")) {
print($"{name}={value}");
}
count = count + 1;
}
assert(count > 0, "a process has an environment");
return 0;
}
- On Windows, names ignore case (
PathfindsPATH), and values are read as UTF-16, so text outside the ANSI code page survives. - On POSIX a value is whatever bytes the parent passed; one that is not valid UTF-8 is returned as it is.
Changing the environment
os.set_environment(name, value) sets a variable for the running process,
and os.remove_environment(name) removes one. Later reads see the change,
and so do the children the process starts afterwards.
import os;
main(): int {
var name = "PEPPER_EXAMPLE_GREETING";
os.set_environment(name, "hello");
assert(os.environment(name).value() == "hello", "set");
os.set_environment(name, "");
assert(os.environment(name).value() == "", "set to nothing");
os.remove_environment(name);
assert(os.environment(name).is_empty(), "removed");
return 0;
}
- Every read and change the runtime makes goes through one lock, so Pepper code never races with itself over the environment, from any thread.
- C code that calls
getenvitself is outside that lock. On POSIX,setenvraces with such a call on another thread (Rust 2024 marks itsset_varunsafefor this reason), so set variables before starting threads that run C code reading them. - A name that is empty or contains
=or a NUL byte, or a value that contains a NUL byte, panics. - On Windows names ignore case, and the C runtime’s own
getenvcopy does not see the change. On WASI the change stays inside the program. - To give only a child a different environment, use
process.Command’senvironment,remove_environmentandclear_environment(see Processes).
Time
Three value types cover time. Each is a value struct:
copied, compared with ==, < and the other comparisons, and never
allocated.
Durationis a span of time in nanoseconds, never negative. Make one withfrom_seconds,from_milliseconds,from_microsecondsorfrom_nanoseconds, read it back withseconds(),milliseconds()and the others (rounded down), and add, subtract, multiply or divide it. Printing one shows it in the largest unit it reaches:2.5s,15ms,40ns.Instantreads a monotonic clock, for measuring how long something takes. It only moves forward, whatever the system’s date is set to.later - earlieris theDurationbetween two instants, andstart.elapsed()the time since one.SystemTimereads the wall clock: nanoseconds since the Unix epoch (1970-01-01 00:00:00 UTC).unix_seconds()is what file systems and most protocols record. Since the wall clock can be set back,duration_since(earlier)is an optional: none whenearlieris later. Thetimemodule turns it into a date and time, in UTC or a time zone, and back.
import os;
main(): int {
# Measure with the monotonic clock: subtracting two instants gives a
# Duration.
var start = os.Instant.now();
long total = 0;
for (var i in 0..100000) {
total = total + i;
}
var elapsed = os.Instant.now() - start;
print($"summed in {elapsed}"); # such as "1.234ms"
var timeout = os.Duration.from_seconds(2) + os.Duration.from_milliseconds(500);
assert(timeout.milliseconds() == 2500, "whole milliseconds");
assert(timeout.to_string() == "2.5s", "the largest unit it reaches");
assert(elapsed < timeout, "durations compare");
# The wall clock counts from the Unix epoch.
var now = os.SystemTime.now();
assert(now.unix_seconds() > 1700000000, "after November 2023");
var tomorrow = now + os.Duration.from_seconds(86400);
assert(tomorrow.duration_since(now).value() == os.Duration.from_seconds(86400), "a day later");
assert(now.duration_since(tomorrow).is_empty(), "no negative durations");
return 0;
}
Subtracting a longer Duration, or a later Instant, panics: neither can
be negative.
The machine and the user
import os;
main(): int {
print($"{os.system_name()} {os.system_version()} on {os.architecture()}");
print($"host {os.hostname()}: {os.logical_cpus()} logical processors, {os.physical_cpus()} cores");
print($"{os.total_memory() / 1048576} MiB of memory in {os.page_size()}-byte pages");
var user = os.user_name().value_or("unknown");
var home = os.home_directory();
print($"user {user}, home {home.is_value() ? home.value().to_string() : "unknown"}");
print($"temporary files go in {os.temp_directory()}");
var executable = os.system_os() == os.OperatingSystem.Windows ? "tool.exe" : "tool";
assert(executable.starts_with("tool"), "picked a name");
return 0;
}
| Function | Returns |
|---|---|
system_os() | the OperatingSystem the program was built for: Linux, Mac, Windows or Wasi |
system_name() | the same as text: "linux", "macos" or "windows" |
system_version() | the kernel release on Linux, the product version on macOS (14.5), major.minor.build on Windows |
architecture() | "x64", "aarch64" or "wasm32", as [Target(Arch(...))] names them |
hostname() | the machine’s host name |
logical_cpus(), physical_cpus() | processor counts, the same as async’s, capped by a Linux container’s CPU quota |
total_memory(), page_size() | physical memory, or a Linux container’s lower memory limit, and the virtual memory page size, in bytes |
user_name() | the user the program runs as, or none |
home_directory() | HOME (on Windows USERPROFILE), or the user database’s entry, or none |
temp_directory() | the directory for temporary files, the same as io.Directory.temp() |
is_elevated() | whether the program runs as root, or elevated (“Run as administrator”) on Windows; never on WASI |
Containers
On Linux, total_memory() and logical_cpus() report what a container
lets the program use, as the JVM and Go do, so a pool or a cache sized from
them fits inside it. Both read the process’s cgroup and each cgroup above
it, under /sys/fs/cgroup, with cgroup v2 or v1:
total_memory()returns the smallest of the physical memory and every memory limit found (memory.max, or v1’smemory.limit_in_bytes), asdocker run --memorysets it.logical_cpus()returns the smaller of the online processors and the smallest CPU quota (cpu.max, or v1’scpu.cfs_quota_usovercpu.cfs_period_us), rounded up to whole processors:docker run --cpus=1.5counts as 2.physical_cpus()is never more than that.
Where no limit is set, or the files cannot be read, they report the machine.
Current limitations
async.timeoutstill takes milliseconds, not aDuration.