Packages

Query system, runtime and environment information

Current section

Files

Jump to
tinman src tinman.erl
Raw

src/tinman.erl

-module(tinman).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/tinman.gleam").
-export([os/0, hostname/0, endianness/0, runtime/0, process_id/0, is_test/0, is_online/0, used_memory/0, cpu_count/0, available_parallelism/0, end_of_line/0, path_delimiter/0, path_separator/0, executable_extension/0, parse_version/1, runtime_version/0, libc/0, user_name/0, user_id/0, group_id/0, is_admin/0, os_version/0, total_disk_space/0, free_disk_space/0, total_memory/0, free_memory/0, container_runtime/0, is_ci/0, is_in_ssh/0, is_tty/0, cpu_architecture/0]).
-export_type([operating_system/0, cpu_architecture/0, runtime/0, libc/0, endianness/0, container_runtime/0, version/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Tinman allows you to query information about the operating system, runtime\n"
" and environment.\n"
"\n"
" It's useful for working around platform quirks or setting up global\n"
" resource limits at startup. It's **not** suitable as a monitoring library.\n"
"\n"
).
-type operating_system() :: linux | macos | windows | free_bsd | open_bsd.
-type cpu_architecture() :: x64 | arm64 | arm | riscv64.
-type runtime() :: beam | atom_vm | node | deno | bun.
-type libc() :: glibc | musl.
-type endianness() :: big_endian | little_endian.
-type container_runtime() :: docker | podman | wsl.
-type version() :: {version, integer(), integer(), integer()}.
-file("src/tinman.gleam", 70).
?DOC(
" Returns the operating system platform.\n"
" Returns `Error(Nil)` if the platform cannot be determined or is unknown.\n"
).
-spec os() -> {ok, operating_system()} | {error, nil}.
os() ->
case tinman_ffi:platform() of
<<"linux"/utf8>> ->
{ok, linux};
<<"darwin"/utf8>> ->
{ok, macos};
<<"win32"/utf8>> ->
{ok, windows};
<<"freebsd"/utf8>> ->
{ok, free_bsd};
<<"openbsd"/utf8>> ->
{ok, open_bsd};
_ ->
{error, nil}
end.
-file("src/tinman.gleam", 128).
?DOC(" Returns the hostname of the system.\n").
-spec hostname() -> binary().
hostname() ->
tinman_ffi:hostname().
-file("src/tinman.gleam", 133).
?DOC(" Returns the byte order of the system.\n").
-spec endianness() -> endianness().
endianness() ->
tinman_ffi:endianness().
-file("src/tinman.gleam", 157).
?DOC(
" Returns the current runtime (Erlang, Node, Deno, or Bun).\n"
" Returns `Error(Nil)` if the runtime cannot be determined.\n"
).
-spec runtime() -> {ok, runtime()} | {error, nil}.
runtime() ->
case tinman_ffi:runtime() of
<<"BEAM"/utf8>> ->
{ok, beam};
<<"ATOM"/utf8>> ->
{ok, atom_vm};
<<"node"/utf8>> ->
{ok, node};
<<"deno"/utf8>> ->
{ok, deno};
<<"bun"/utf8>> ->
{ok, bun};
_ ->
{error, nil}
end.
-file("src/tinman.gleam", 188).
?DOC(" Returns the runtimes operating system process ID.\n").
-spec process_id() -> integer().
process_id() ->
tinman_ffi:process_id().
-file("src/tinman.gleam", 221).
?DOC(
" Returns whether the program is running in test mode.\n"
"\n"
" Checks if the entrypoint module is `_test`, which is the case when running\n"
" with `gleam test`.\n"
).
-spec is_test() -> boolean().
is_test() ->
tinman_ffi:is_test().
-file("src/tinman.gleam", 228).
?DOC(
" Returns whether the system appears to be online.\n"
"\n"
" Checks if there is an active non-loopback network interface available.\n"
).
-spec is_online() -> boolean().
is_online() ->
tinman_ffi:is_online().
-file("src/tinman.gleam", 261).
?DOC(" Returns the memory used by the runtime and your program.\n").
-spec used_memory() -> integer().
used_memory() ->
tinman_ffi:used_memory().
-file("src/tinman.gleam", 402).
?DOC(
" Returns the number of logical CPU cores.\n"
" Returns `Error(Nil)` if the CPU count cannot be determined.\n"
"\n"
" > *Note:* Usually you want to use `available_parallelism` instead!\n"
).
-spec cpu_count() -> {ok, integer()} | {error, nil}.
cpu_count() ->
tinman_ffi:cpu_count().
-file("src/tinman.gleam", 411).
?DOC(
" Returns the recommended level of parallelism.\n"
"\n"
" This is the number of available schedulers on Erlang, or the number of\n"
" runtime threads on Javascript. Note that Javascript itself is always\n"
" single-threaded by default.\n"
).
-spec available_parallelism() -> integer().
available_parallelism() ->
tinman_ffi:available_parallelism().
-file("src/tinman.gleam", 474).
?DOC(
" Returns the end-of-line marker for the current platform.\n"
" `\"\\n\"` on Unix-like systems, `\"\\r\\n\"` on Windows.\n"
).
-spec end_of_line() -> binary().
end_of_line() ->
case tinman_ffi:is_windows() of
true ->
<<"\r\n"/utf8>>;
false ->
<<"\n"/utf8>>
end.
-file("src/tinman.gleam", 484).
?DOC(
" Returns the path delimiter - the character used inside the `PATH` environment\n"
" variable to separate multiple paths - for the current platform.\n"
" `\":\"` on Unix-like systems, `\";\"` on Windows.\n"
).
-spec path_delimiter() -> binary().
path_delimiter() ->
case tinman_ffi:is_windows() of
true ->
<<";"/utf8>>;
false ->
<<":"/utf8>>
end.
-file("src/tinman.gleam", 494).
?DOC(
" Returns the path separator - the character used inside paths to separate\n"
" files and directories - for the current platform.\n"
" `\"/\"` on Unix-like systems, `\"\\\\\"` on Windows.\n"
).
-spec path_separator() -> binary().
path_separator() ->
case tinman_ffi:is_windows() of
true ->
<<"\\"/utf8>>;
false ->
<<"/"/utf8>>
end.
-file("src/tinman.gleam", 503).
?DOC(
" Returns the executable file extension for the current platform.\n"
" `\".exe\"` on Windows, `\"\"` (empty string) on Unix-like systems.\n"
).
-spec executable_extension() -> binary().
executable_extension() ->
case tinman_ffi:is_windows() of
true ->
<<".exe"/utf8>>;
false ->
<<""/utf8>>
end.
-file("src/tinman.gleam", 512).
-spec int(binary()) -> {ok, integer()} | {error, nil}.
int(S) ->
gleam_stdlib:parse_int(gleam@string:trim(S)).
-file("src/tinman.gleam", 527).
-spec split_whitespace(binary()) -> list(binary()).
split_whitespace(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:split(_pipe, <<" "/utf8>>),
gleam@list:filter(_pipe@1, fun(S) -> S /= <<""/utf8>> end).
-file("src/tinman.gleam", 531).
-spec extract_column(binary(), integer()) -> {ok, binary()} | {error, nil}.
extract_column(Line, Column) ->
_pipe = Line,
_pipe@1 = split_whitespace(_pipe),
_pipe@2 = gleam@list:drop(_pipe@1, Column - 1),
gleam@list:first(_pipe@2).
-file("src/tinman.gleam", 359).
-spec parse_df_column(binary(), integer()) -> {ok, integer()} | {error, nil}.
parse_df_column(Line, Column) ->
gleam@result:'try'(
extract_column(Line, Column),
fun(Kb_str) ->
gleam@result:'try'(int(Kb_str), fun(Kb) -> {ok, Kb * 1024} end)
end
).
-file("src/tinman.gleam", 540).
?DOC(false).
-spec parse_version(binary()) -> {ok, version()} | {error, nil}.
parse_version(Version_string) ->
Version_string@2 = case Version_string of
<<"v"/utf8, Version_string@1/binary>> ->
Version_string@1;
Other ->
Other
end,
case gleam@string:split(Version_string@2, <<"."/utf8>>) of
[Major, Minor, Patch | _] ->
gleam@result:'try'(
gleam_stdlib:parse_int(Major),
fun(Major@1) ->
gleam@result:'try'(
gleam_stdlib:parse_int(Minor),
fun(Minor@1) ->
gleam@result:'try'(
gleam_stdlib:parse_int(Patch),
fun(Patch@1) ->
{ok, {version, Major@1, Minor@1, Patch@1}}
end
)
end
)
end
);
[Major@2, Minor@2] ->
gleam@result:'try'(
gleam_stdlib:parse_int(Major@2),
fun(Major@3) ->
gleam@result:'try'(
gleam_stdlib:parse_int(Minor@2),
fun(Minor@3) -> {ok, {version, Major@3, Minor@3, 0}} end
)
end
);
[Major@4] ->
gleam@result:'try'(
gleam_stdlib:parse_int(Major@4),
fun(Major@5) -> {ok, {version, Major@5, 0, 0}} end
);
_ ->
{error, nil}
end.
-file("src/tinman.gleam", 177).
?DOC(
" Returns the runtime version.\n"
" Returns `Error(Nil)` if the version cannot be determined or parsed.\n"
"\n"
" > **Note:** On Erlang, this is the version of the Erlang runtime system (ERTS),\n"
" > not the version of the OTP release!\n"
).
-spec runtime_version() -> {ok, version()} | {error, nil}.
runtime_version() ->
parse_version(tinman_ffi:runtime_version()).
-file("src/tinman.gleam", 93).
?DOC(
" Returns the C standard library type (Linux only).\n"
" Returns `Error(Nil)` on non-Linux systems or if detection fails.\n"
).
-spec libc() -> {ok, libc()} | {error, nil}.
libc() ->
tinman_ffi:libc_type().
-file("src/tinman.gleam", 418).
?DOC(
" Returns the current username.\n"
" Returns `Error(Nil)` if the username cannot be determined.\n"
).
-spec user_name() -> binary().
user_name() ->
Whoami = gleam@string:trim(tinman_ffi:os_cmd(<<"whoami"/utf8>>)),
case tinman_ffi:is_windows() of
true ->
case gleam@string:split(Whoami, <<"\\"/utf8>>) of
[_, Name | _] ->
Name;
[Name@1] ->
Name@1;
[] ->
Whoami
end;
false ->
Whoami
end.
-file("src/tinman.gleam", 435).
?DOC(
" Returns the current user ID.\n"
" Returns `Error(Nil)` if the user ID cannot be determined.\n"
).
-spec user_id() -> {ok, integer()} | {error, nil}.
user_id() ->
case tinman_ffi:is_windows() of
true ->
{error, nil};
false ->
int(tinman_ffi:os_cmd(<<"id -u"/utf8>>))
end.
-file("src/tinman.gleam", 445).
?DOC(
" Returns the current group ID.\n"
" Returns `Error(Nil)` if the group ID cannot be determined.\n"
).
-spec group_id() -> {ok, integer()} | {error, nil}.
group_id() ->
case tinman_ffi:is_windows() of
true ->
{error, nil};
false ->
int(tinman_ffi:os_cmd(<<"id -g"/utf8>>))
end.
-file("src/tinman.gleam", 455).
?DOC(
" Returns whether the current user has administrator/root privileges.\n"
" On Unix-like systems, checks if the user ID is `0` (root).\n"
" On Windows, checks if we can successfully run a utility with elevated privileges.\n"
).
-spec is_admin() -> boolean().
is_admin() ->
case tinman_ffi:is_windows() of
true ->
Output = tinman_ffi:os_cmd(<<"fltmc >nul 2>&1 && echo OK"/utf8>>),
gleam_stdlib:contains_string(Output, <<"OK"/utf8>>);
false ->
case user_id() of
{ok, 0} ->
true;
_ ->
false
end
end.
-file("src/tinman.gleam", 516).
-spec powershell(binary()) -> binary().
powershell(Cmd) ->
case tinman_ffi:os_cmd(
<<<<"pwsh -NoProfile -NonInteractive -Command \""/utf8, Cmd/binary>>/binary,
"\" 2>nul"/utf8>>
) of
<<""/utf8>> ->
tinman_ffi:os_cmd(
<<<<"powershell -NoProfile -NonInteractive -Command \""/utf8,
Cmd/binary>>/binary,
"\""/utf8>>
);
Output ->
Output
end.
-file("src/tinman.gleam", 109).
?DOC(
" Returns the operating system version.\n"
" Returns `Error(Nil)` if the version cannot be determined or parsed.\n"
).
-spec os_version() -> {ok, version()} | {error, nil}.
os_version() ->
case tinman_ffi:is_windows() of
true ->
parse_version(
powershell(
<<"[System.Environment]::OSVersion.Version.ToString()"/utf8>>
)
);
false ->
tinman_ffi:version()
end.
-file("src/tinman.gleam", 327).
?DOC(
" Returns the total disk space in bytes for the root/system drive.\n"
" Uses `df` on Unix-like systems and PowerShell on Windows.\n"
" Returns `Error(Nil)` if the information cannot be determined.\n"
).
-spec total_disk_space() -> {ok, integer()} | {error, nil}.
total_disk_space() ->
case tinman_ffi:is_windows() of
true ->
int(
powershell(
<<"(Get-PSDrive C).Used + (Get-PSDrive C).Free"/utf8>>
)
);
false ->
Output = tinman_ffi:os_cmd(<<"df -k / | tail -n 1"/utf8>>),
parse_df_column(Output, 2)
end.
-file("src/tinman.gleam", 344).
?DOC(
" Returns the free disk space in bytes for the root/system drive.\n"
" Uses `df` on Unix-like systems and PowerShell on Windows.\n"
" Returns `Error(Nil)` if the information cannot be determined.\n"
).
-spec free_disk_space() -> {ok, integer()} | {error, nil}.
free_disk_space() ->
case tinman_ffi:is_windows() of
true ->
int(powershell(<<"(Get-PSDrive C).Free"/utf8>>));
false ->
Output = tinman_ffi:os_cmd(<<"df -k / | tail -n 1"/utf8>>),
parse_df_column(Output, 4)
end.
-file("src/tinman.gleam", 307).
-spec cgroup(binary()) -> {ok, integer()} | {error, nil}.
cgroup(Path) ->
case tinman_ffi:read_file(<<"/sys/fs/cgroup/"/utf8, Path/binary>>) of
{ok, Content} ->
int(Content);
{error, _} ->
{error, nil}
end.
-file("src/tinman.gleam", 293).
-spec cgroup_memory_limit() -> {ok, integer()} | {error, nil}.
cgroup_memory_limit() ->
case cgroup(<<"memory.max"/utf8>>) of
{ok, Val} ->
{ok, Val};
{error, _} ->
cgroup(<<"memory/memory.limit_in_bytes"/utf8>>)
end.
-file("src/tinman.gleam", 300).
-spec cgroup_memory_usage() -> {ok, integer()} | {error, nil}.
cgroup_memory_usage() ->
case cgroup(<<"memory.current"/utf8>>) of
{ok, Val} ->
{ok, Val};
{error, _} ->
cgroup(<<"memory/memory.usage_in_bytes"/utf8>>)
end.
-file("src/tinman.gleam", 314).
-spec meminfo(binary()) -> {ok, integer()} | {error, nil}.
meminfo(Key) ->
gleam@result:'try'(
tinman_ffi:read_file(<<"/proc/meminfo"/utf8>>),
fun(Content) ->
gleam@result:'try'(
gleam@list:last(gleam@string:split(Content, Key)),
fun(Rest) ->
gleam@result:'try'(
extract_column(Rest, 1),
fun(Kb_str) ->
gleam@result:'try'(
int(Kb_str),
fun(Kb) -> {ok, Kb * 1024} end
)
end
)
end
)
end
).
-file("src/tinman.gleam", 239).
?DOC(
" Returns the total available system memory in bytes. When running inside a\n"
" container, memory limits applied to the container are respected.\n"
"\n"
" > *Note:* This information is not exposed by the Erlang runtime system,\n"
" > and has to be queried from the operating system in various ways.\n"
" > Doing so might be unexpectedly slow, especially on Windows!\n"
).
-spec total_memory() -> {ok, integer()} | {error, nil}.
total_memory() ->
case os() of
{ok, linux} ->
case cgroup_memory_limit() of
{ok, Limit} ->
{ok, Limit};
{error, _} ->
meminfo(<<"MemTotal:"/utf8>>)
end;
{ok, macos} ->
int(tinman_ffi:os_cmd(<<"sysctl -n hw.memsize"/utf8>>));
{ok, free_bsd} ->
int(tinman_ffi:os_cmd(<<"sysctl -n hw.physmem64"/utf8>>));
{ok, open_bsd} ->
int(tinman_ffi:os_cmd(<<"sysctl -n hw.physmem64"/utf8>>));
{ok, windows} ->
int(
powershell(
<<"(Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize * 1024"/utf8>>
)
);
{error, _} ->
{error, nil}
end.
-file("src/tinman.gleam", 270).
?DOC(
" Returns the available system memory in bytes. When running inside a container,\n"
" memory limits applied to the container are respected.\n"
"\n"
" > *Note:* This information is not exposed by the Erlang runtime system,\n"
" > and has to be queried from the operating system in various ways.\n"
" > Doing so might be unexpectedly slow, especially on Windows!\n"
).
-spec free_memory() -> {ok, integer()} | {error, nil}.
free_memory() ->
case os() of
{ok, linux} ->
case {cgroup_memory_limit(), cgroup_memory_usage()} of
{{ok, Limit}, {ok, Usage}} ->
{ok, gleam@int:max(0, Limit - Usage)};
{{error, _}, _} ->
meminfo(<<"MemAvailable:"/utf8>>);
{_, {error, _}} ->
meminfo(<<"MemAvailable:"/utf8>>)
end;
{ok, macos} ->
Cmd = <<"vm_stat | awk 'NR==1{page_size=$8} /Pages free/{free=$3} /Pages inactive/{inactive=$3} END{print (free+inactive)*page_size}'"/utf8>>,
int(tinman_ffi:os_cmd(Cmd));
{ok, free_bsd} ->
int(tinman_ffi:os_cmd(<<"sysctl -n hw.usermem64"/utf8>>));
{ok, open_bsd} ->
int(tinman_ffi:os_cmd(<<"sysctl -n hw.usermem64"/utf8>>));
{ok, windows} ->
int(
powershell(
<<"(Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory * 1024"/utf8>>
)
);
{error, _} ->
{error, nil}
end.
-file("src/tinman.gleam", 138).
?DOC(
" Returns the detected container runtime.\n"
" Detects Docker, Podman, and WSL environments.\n"
" Returns `Error(Nil)` if no container could be detected.\n"
).
-spec container_runtime() -> {ok, container_runtime()} | {error, nil}.
container_runtime() ->
gleam@bool:guard(
tinman_ffi:file_exists(<<"/.dockerenv"/utf8>>),
{ok, docker},
fun() ->
gleam@bool:guard(
tinman_ffi:file_exists(<<"/run/.containerenv"/utf8>>),
{ok, podman},
fun() ->
Version = begin
_pipe = tinman_ffi:read_file(<<"/proc/version"/utf8>>),
_pipe@1 = gleam@result:unwrap(_pipe, <<""/utf8>>),
string:lowercase(_pipe@1)
end,
gleam@bool:guard(
gleam_stdlib:contains_string(
Version,
<<"microsoft"/utf8>>
)
orelse gleam_stdlib:contains_string(
Version,
<<"wsl"/utf8>>
),
{ok, wsl},
fun() -> {error, nil} end
)
end
)
end
).
-file("src/tinman.gleam", 195).
?DOC(
" Returns whether your program is running in a CI environment.\n"
"\n"
" Checks for `CI` and `CONTINUOUS_INTEGRATION` environment variables.\n"
).
-spec is_ci() -> boolean().
is_ci() ->
(tinman_ffi:env(<<"CI"/utf8>>) /= <<""/utf8>>) orelse (tinman_ffi:env(
<<"CONTINUOUS_INTEGRATION"/utf8>>
)
/= <<""/utf8>>).
-file("src/tinman.gleam", 202).
?DOC(
" Returns whether your program is running in an SSH session.\n"
"\n"
" Checks for `SSH_CONNECTION`, `SSH_CLIENT`, and `SSH_TTY` environment variables.\n"
).
-spec is_in_ssh() -> boolean().
is_in_ssh() ->
((tinman_ffi:env(<<"SSH_CONNECTION"/utf8>>) /= <<""/utf8>>) orelse (tinman_ffi:env(
<<"SSH_CLIENT"/utf8>>
)
/= <<""/utf8>>))
orelse (tinman_ffi:env(<<"SSH_TTY"/utf8>>) /= <<""/utf8>>).
-file("src/tinman.gleam", 207).
?DOC(" Checks whether the program is running in an interactive terminal (TTY).\n").
-spec is_tty() -> boolean().
is_tty() ->
tinman_ffi:is_tty() andalso (tinman_ffi:env(<<"TERM"/utf8>>) /= <<"DUMB"/utf8>>).
-file("src/tinman.gleam", 373).
?DOC(
" Returns the CPU architecture.\n"
" Returns `Error(Nil)` if the architecture cannot be determined or is unknown.\n"
).
-spec cpu_architecture() -> {ok, cpu_architecture()} | {error, nil}.
cpu_architecture() ->
Arch = case tinman_ffi:is_windows() of
true ->
string:lowercase(tinman_ffi:env(<<"PROCESSOR_ARCHITECTURE"/utf8>>));
false ->
tinman_ffi:arch()
end,
case Arch of
<<"x64"/utf8>> ->
{ok, x64};
<<"x86_64"/utf8>> ->
{ok, x64};
<<"amd64"/utf8>> ->
{ok, x64};
<<"arm64"/utf8>> ->
{ok, arm64};
<<"aarch64"/utf8>> ->
{ok, arm64};
<<"arm"/utf8>> ->
{ok, arm};
<<"riscv64"/utf8>> ->
{ok, riscv64};
_ ->
{error, nil}
end.