Shell mode (shell.py)
shell.py lives next to runner.py in the root folder of the Green Metrics Tool. It takes one shell command and measures it with the normal GMT measurement lifecycle. You do not need a usage_scenario.yml, a Dockerfile or a container for this.
Internally shell.py builds a usage scenario in memory. This scenario has a single flow called Shell Command with container: null. The command therefore runs directly on the host system in the shell of your choice and not inside a container.
When to use shell mode
Shell mode is meant for quick measurements. Typical cases are:
- Getting a first impression of how much energy a CLI tool, a script or a build command uses
- Trying out a command before you put the effort into containerizing it
- Checking that your metric providers deliver data on a machine
Shell mode results are not directly comparable to containerized runs. The command is not sandboxed and not isolated from the rest of the system. Everything else that runs on the machine at the same time ends up in the system level metrics. GMT stores a warning with every run that contains host flows, and shell.py itself reminds you on start to use the Docker version for exact measurements on the cluster.
For exact and reproducible measurements use runner.py → with a containerized usage_scenario.yml →.
Prerequisites
- A working GMT installation. See Installation on Linux →, Installation on macOS → or Installation on Windows →
- The GMT venv must be active. Like
runner.py,shell.pyrefuses to start outside of it. - The GMT database must be reachable.
shell.pyreads the permissions of the executing user from the database and stores the results there. - The executing user needs the
hostorchestrator capability. The default local user (ID 1) has it. See Host execution capability below. - Metric providers configured in your
config.yml. See Configuration → and Metric Providers →
Which metric providers make sense
A shell mode run has no services and therefore no containers. GMT disables every configured provider whose name ends in _container for such a run and stores a warning for each of them.
Only providers with the scopes system, component and machine deliver data. Examples from the config.yml.example are:
cpu_utilization_procfs_systemmemory_used_procfs_systemcpu_energy_rapl_msr_componentmemory_energy_rapl_msr_componentpsu_energy_ac_mcp_machinecarbon_intensity_static_machine
These providers measure the whole machine and not only your command.
Usage
Activate the venv and pass the command after --.
cd PATH_TO_GREEN_METRICS_TOOL
source venv/bin/activate
./shell.py -- 'echo "Hello" && sleep 10'More examples:
# Give the run a name that shows up in the Dashboard
./shell.py --name "stress-ng one core" -- 'stress-ng -c 1 -t 5 -q'
# Stop the command after 60 seconds
./shell.py --measurement-flow-process-duration 60 -- './build.sh'
# Use PowerShell instead of bash
./shell.py --shell-executable pwsh -- 'Get-ChildItem -Recurse | Measure-Object'
# Measure, but write nothing to the database
./shell.py --dev-no-save -- 'curl -s https://example.com > /dev/null'Always use -- and quote the command
Everything after -- is taken as the command. Always use it.
Without -- the argument parser of shell.py looks at the whole command line first. Options in your command that match a shell.py switch, or an abbreviation of one, are then taken by shell.py and removed from your command. For example ./shell.py curl --verbose https://example.com turns on --verbose-provider-boot and only runs curl https://example.com. If the first unknown argument starts with -, shell.py stops with an error and shows you the same command in the -- form.
Put the whole command into single quotes when it contains pipes, redirects, &&, ; or variables. If you pass a single argument, shell.py uses it unchanged as the command. If you pass several arguments, shell.py joins them into one command line and quotes them where needed. On Linux and macOS shell operators that are passed as separate arguments are quoted as well and are thus treated as plain text. Operators that are not quoted at all are already interpreted by your own terminal shell before shell.py sees them.
If no command is given, shell.py prints its help and exits.
Working directory and environment
The command runs in the directory from which you start shell.py. GMT stores this directory as the URI of the run. The command runs with the permissions and the environment of the user that started shell.py.
How the command is executed
The command runs in the shell given by --shell-executable. The default is bash. On Windows the default is powershell.
GMT applies strict shell options so that errors in your command do not go unnoticed. Which options are used depends on the type of shell.
- POSIX shells (
bash,zsh,shand every shell that is not PowerShell or cmd): The command is called asSHELL -o errexit -o nounset -o pipefail -c 'COMMAND'.errexitstops at the first command that failsnounsettreats the use of an unset variable as an errorpipefailmakes a pipeline fail when any part of it fails
- PowerShell (
powershellorpwsh): The command is called with-NoProfile -NonInteractive -Command. GMT puts$ErrorActionPreference = 'Stop'andSet-StrictMode -Version Latestin front of your command. PowerShell has no equivalent forpipefail. - cmd: The command is called as
cmd /d /s /c COMMAND. cmd cannot express these options, so none are applied.
shell.py has no switch to change these options. If your shell does not support them, the run fails with an error message that explains this and notes that your command was possibly not executed at all. If you need different options, write a small usage_scenario.yml with a host flow and set shell-options for the command. See Flows on the host →.
If the command exits with a non-zero exit code, GMT marks the run as failed.
The output of the command is captured and not streamed live. GMT prints stdout and stderr after the command has finished and stores them in the logs of the run.
Switches
--nameA name which will be stored in the database to discern this run from others- If omitted, the run is called
Runfollowed by the current date and time
- If omitted, the run is called
--user-idExecute the run as a specific user (Default: 1)- The user needs the
hostorchestrator capability. See Host execution capability
- The user needs the
--config-overrideUse a different configuration file instead ofconfig.yml- Pass the full path. The file must end in
.yml
- Pass the full path. The file must end in
--file-cleanupDelete the GMT temporary folder (/tmp/green-metrics-tool, or the system temp directory on Windows) after the run--verbose-provider-bootBoot the metric providers one after another- GMT adds a note for each provider and sleeps 10 seconds after each provider boot
--shell-executableThe shell that runs the command (Default:bash, on Windowspowershell)--measurement-flow-process-durationMaximum runtime of the command in seconds- If the command runs longer, it is stopped and
shell.pyexits with code124 - If omitted, there is no limit
- If the command runs longer, it is stopped and
Development switches with possible side effects
These behave like the switches with the same name in runner.py →.
--dev-no-metricsSkips loading the metric providers. Runs will be faster, but you will have no metrics--dev-no-phase-statsDo not calculate phase stats--dev-no-saveWill save no data to the database--dev-no-sleepsRemoves all sleeps. Resulting measurement data will be skewed.
All other runner.py switches, for instance --iterations, --variable, --print-logs or --debug, are not available in shell mode.
Fixed settings
Shell mode is a quick tool, so some settings are fixed and cannot be changed.
- The analysis for optimizations is skipped
- All system checks are skipped
- No container dependency information is collected
- No GMT dependencies (like Kaniko) are downloaded
- There is no repository checkout. The run gets the branch and author
[SHELL] - The check for running containers, the Docker network setup and the Docker image cleanup are skipped. Since there are no services, no images are built and no containers are started
- The measurement phases are kept short
| Setting | Shell mode | runner.py default |
|---|---|---|
| Sleep before the measurement starts | 1 s | 5 s |
[BASELINE] duration | 5 s | 60 s |
[IDLE] duration | 5 s | 60 s |
| Sleep after the measurement | 1 s | 5 s |
A shell mode run still goes through all the usual phases. Your command runs inside the [RUNTIME] phase in a sub phase called Shell Command. [INSTALLATION] and [BOOT] have nothing to do and therefore stay short.
Because system checks and container dependency collection are switched off through development switches, every shell mode run carries the warning that development switches were active.
Output
After a successful run shell.py prints a table with the phase stats of the Shell Command phase. The columns are metric, detail, type, value, unit, max and min. The table is not printed when --dev-no-metrics or --dev-no-phase-stats is set.
Then it prints the link to the full report in the Dashboard. The link is built from cluster.metrics_url in your config.yml.
Phase stats summary:
metric | detail | type | value | unit | max | min
...
Please access your report on the URL METRICS_URL/stats.html?id=RUN_IDWith --dev-no-save nothing is written to the database. In this case shell.py only prints that the run has finished and shows neither the table nor a link.
Exit codes
0The run finished successfully1The run failed. This includes a command that exited with a non-zero exit code, a missing host permission, a config override that could not be loaded and a call without a command2The argument parser rejected the arguments, for instance an unknown option before the command124The command exceeded--measurement-flow-process-duration127A file or executable was not found, for instance the shell given in--shell-executable130The run was aborted with Ctrl+C
If a helper process that GMT calls internally fails, shell.py exits with the exit code of that process.
A command that is not found inside the shell does not lead to 127. The shell reports it as a failed command and shell.py exits with 1.
Errors are printed to stderr. Unless --dev-no-save is set, they are also written to the error file configured in config.yml and to the system logs in the database.
Secrets in commands
The command is stored with the run. It ends up in the stored runner arguments, in the description and the flow of the stored usage scenario and in the logs. shell.py reminds you of this on every start. If your command contains passwords, tokens or other secrets, use --dev-no-save.
With --dev-no-save nothing about the run is written to the database, and errors are only printed to the terminal. shell.py has no switch for usage scenario variables, so the encrypted __GMT_VAR_SECRET_*__ variables of normal runs are not available in shell mode.
Host execution capability
Running commands directly on the host is security sensitive. GMT therefore only allows it for users that have the host orchestrator in their capabilities, stored under measurement.orchestrators.host. The key only has to exist. Its value is an empty object.
"measurement": {
"orchestrators": {
"docker": {
"allowed_run_args": []
},
"host": {}
}
}Only the default user with the ID 1 gets this capability, through the seed data on new installations and through a migration on existing ones. All other users do not have it.
If the user does not have the capability, the run stops with a PermissionError before the metric providers are started. This applies to shell.py and to every usage scenario that contains a host flow.
On a cluster you might not want to allow host execution at all. You can remove the capability from user 1 again with:
UPDATE users SET capabilities = capabilities #- '{measurement,orchestrators,host}' WHERE id = 1;See User Management → for how capabilities work.
Host flows in a usage_scenario.yml
Shell mode is only a shortcut. You can also run flows directly on the host in a normal usage_scenario.yml by setting container: null for a flow. This also lets you set shell-options, mix host and container flows and run more than one command. See Flows on the host →.