-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.