Commit 2f201d2d authored by Josué Ortega's avatar Josué Ortega

Import Upstream version 1.0.12

parents
/env/
/build/
/dist/
/.eggs/
/.vscode/
/.coverage
/.mypy_cache/
/.pytest_cache/
# See: https://packaging.python.org/distributing/#uploading-your-project-to-pypi
/.pypirc
/.*-env/
*.egg-info
*.pyc
__pycache__/
px.pex
# We need sudo to install pip system-wide
sudo: required
os:
- linux
- osx
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade python ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install python@2 ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install --upgrade virtualenv ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install shellcheck ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:fkrull/deadsnakes ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update ; fi
install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install python3.5 python3.5-dev --force-yes -y ; fi
- sudo -H pip2 install coveralls coverage
script: ./ci.sh
after_success:
- coveralls
MIT License
Copyright (c) 2016 Johan Walles <johan.walles@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This diff is collapsed.
#!/bin/bash
set -o pipefail
set -e
set -x
MYDIR=$(cd "$(dirname "$0")" ; pwd)
if [ "$VIRTUAL_ENV" ] ; then
echo 'ERROR: Already in a virtualenv, do "deactivate" first and try again'
exit 1
fi
# Don't produce a binary if something goes wrong
trap "rm -f px.pex" ERR
if [ $# != 1 ] ; then
if command -v shellcheck &> /dev/null ; then
shellcheck ./*.sh ./*/*.sh
fi
source ./tests/installtest.sh
# Run this script with two different Python interpreters
. ./scripts/set-other-python.sh
"${MYDIR}/scripts/parallelize.py" "$0 $OTHER_PYTHON" "$0 python"
cp .python-env/px.pex "${MYDIR}"
if ! head -n1 px.pex | grep -w python ; then
echo
echo "ERROR: px.pex should use \"python\" as its interpreter:"
file px.pex
false
exit 1
fi
echo
echo "All tests passed!"
echo
exit
fi
PYTHONBIN="$1"
# Prepare for making a virtualenv
ENVDIR="${MYDIR}/.${PYTHONBIN}-env"
for DEP in "$(command -v virtualenv)" "${PYTHON}" requirements*.txt ; do
if [ "$DEP" -nt "${ENVDIR}" ] ; then
# Drop our virtualenv, it's older than one of its dependencies
rm -rf "$ENVDIR"
fi
done
if [ ! -d "${ENVDIR}" ]; then
# No virtualenv, set it up
virtualenv --python="${PYTHONBIN}" "${ENVDIR}"
# shellcheck source=/dev/null
. "${ENVDIR}"/bin/activate
# Fix tools versions
pip install -r requirements-dev.txt
if python --version 2>&1 | grep " 3" ; then
pip install -r requirements-dev-py3.txt
fi
else
# Just activate the existing virtualenv
# shellcheck source=/dev/null
. "${ENVDIR}"/bin/activate
fi
if python --version 2>&1 | grep " 3" ; then
# Verson of "python" binary is 3, do static type analysis. Mypy requires
# Python 3, that's why we do this only on Python 3.
mypy ./*.py ./*/*.py
mypy ./*.py ./*/*.py --python-version=2.7
fi
# FIXME: We want to add to the coverage report, not overwrite it. How do we do
# that?
PYTEST_ADDOPTS="--cov=px -v" ./setup.py test
# Create px wheel...
rm -rf dist .deps/px-*.egg .deps/px-*.whl "${ENVDIR}"/px-*.whl build/lib/px
./setup.py bdist_wheel --universal --dist-dir="${ENVDIR}"
# ... and package everything in px.pex
#
# Note that we have to --disable-cache here since otherwise changing the code
# without changing the "git describe" output won't change the resulting binary.
# And since that happens all the time during development we can't have that.
#
# Also note that we need to specify the --python-shebang to get "python" as an
# interpreter. Just passing --python (or nothing) defaults to following the
# "python" symlink and putting "2.7" here.
PX_PEX="${ENVDIR}/px.pex"
rm -f "${PX_PEX}"
pex --python-shebang="#!/usr/bin/env $1" --disable-cache -r requirements.txt "${ENVDIR}"/pxpx-*.whl -m px.px -o "${PX_PEX}"
flake8 px tests scripts setup.py
echo
if unzip -qq -l "${PX_PEX}" '*.so' ; then
cat << EOF
ERROR: There are natively compiled dependencies in the .pex, this makes
distribution a lot harder. Please fix your dependencies.
EOF
exit 1
fi
echo
"${PX_PEX}"
echo
"${PX_PEX}" $$
echo
"${PX_PEX}" --version
if pip list | grep '^pxpx ' > /dev/null ; then
# Uninstall px before doing install testing
pip uninstall --yes pxpx
fi
pip install "${ENVDIR}"/pxpx-*.whl
px bash
px --version
# Design Decisions
# Implementation Language (Python 2.7)
Language requirements:
1. There must be a useable way to list processes for OS X and Linux
2. It must be available on a vanilla Ubuntu 12.4 Precise system
3. It must be available on a vanilla OS X 10.11 system
4. It must be possible to install the resulting product together with all its
dependencies in a single directory / executable file
5. It must be possible to get as much information as `ps` without being root, on
both OS X and Linux.
Regarding the last requirement of being able to match `ps` without being root,
the only way to do that on OS X is to actually call `ps` and parse its output.
A lot of information about processes can only be accessed as root, and `ps` just
happens to be installed setuid root to be able to get at this info.
Since `ps`' syntax and output are almost the same between Linux and OS X we can
just call `ps` on Linux as well.
Any language can run `ps` and parse its output really, so 1 and 5 in the above
list doesn't really limit our choice of languages.
What does limit it though is availability on various systems and packaging. One
environment that does fulfill everything in the above list is Python 2.7, which
is available on all of our target platforms.
And using [Pex](https://github.com/pantsbuild/pex) we can turn Python programs
into [platform independent
executables](https://pex.readthedocs.org/en/stable/whatispex.html#whatispex),
which is excellent for distribution.
## Installation
It must be simple to install in a random directory on a vanilla
Ubuntu 12.4 Precise system. In this case that's because what Github Enterprise
2.3.3 is running on.
"Simple to install" in this case means:
* no root access required
* single command line install (most likely `curl` based)
* everything should end up in one single directory of the user's choosing
* the install process should end with printing the path to the binary
* it should be possible to make symlinks to this binary and execute it through
those
Candidates are:
* Python 2.7 + build with [Pex](https://github.com/pantsbuild/pex) and parse
`ps` output manually.
.Dd August 26, 2018
.Dt PTOP 1
.Os
.Sh NAME
.Nm ptop
.Nd display and update sorted information about processes
.Sh SYNOPSIS
.\" FIXME: Other man pages don't need to use \p to break lines here,
.\" and use the Nm macro for the command name. Why can't we?
.Ic Nm
.Sh DESCRIPTION
.Nm
periodically displays a sorted list of system processes.
Processes are sorted with the highest CPU users at the top.
CPU usage is measured from when
.Nm
was started, to make the list stabilize over time.
.Pp
The top screen line shows you the system load.
The number tells you how many processes want to run right now, check
.Xr getloadavg 3
for details.
.Pp
The histogram shows you how system load has evolved over the last
fifteen minutes.
The current system load is at the right of the histogram.
.Pp
The green / yellow / red bar shows you the current system load in
relation to the number of cores on the system.
Green means load on physical cores, yellow means load on logical
cores, and red means that the system load is higher than the number
of cores available on the system.
.Pp
To exit
.Nm ,
press
.Ql q .
.Sh PROCESS NAMING
.Nm
tries to be helpful about naming processes, and avoid printing names
of various VMs.
.Pp
For example, if you do
.Ql java -jar foo.jar ,
.Nm
will show this process as
.Ql foo.jar
rather than
.Ql java .
.Pp
.Nm
parses command lines from:
.Bl -bullet
.It
Java
.It
Python
.It
Node
.It
Ruby
.It
Various shells
.It
Perl
.El
.Sh SEE ALSO
.Xr px 1 ,
.Xr top 1
.Sh HOMEPAGE
.Nm
lives at http://github.com/walles/px
.Dd August 24, 2018
.Dt PX 1
.Os
.Sh NAME
.Nm px
.Nd list running processes and show process metadata
.Sh SYNOPSIS
.\" FIXME: Other man pages don't need to use \p to break lines here,
.\" and use the Nm macro for the command name. Why can't we?
.Ic px \p
.Ic px Ar filter\p
.Ic px Ar PID
.Sh DESCRIPTION
The
.Nm
utility lists processes running on the system, to the standard
output.
If stdout is a terminal, output will be truncated at
terminal window width.
.Pp
Without any arguments,
.Nm
lists all processes on the system.
.Pp
If you specify a
.Ar filter
the output will contain only processes matching that filter.
.Pp
The
.Ar filter
can be a user name or part of a command line. For example,
.Ql px java
will list all Java processes, and
.Ql px root
will list all of root's processes.
.Pp
Running
.Nm
.Ar PID
will show you information about a given process:
.Bl -bullet
.It
The process tree; parents and children
.It
Start time, run time and CPU usage
.It
List of other processes started around the same time as this one
.It
List of users logged in when the process was started
.It
Where stdin, stdout and stderr is pointing
.It
Network connections
.It
IPC connections (sockets, pipes and local network connections) and
which processes are at the other end of those
.El
.Sh PROCESS NAMING
.Nm
tries to be helpful about naming processes, and avoid printing names
of various VMs.
.Pp
For example, if you do
.Ql java -jar foo.jar ,
.Nm
will show this process as
.Ql foo.jar
rather than
.Ql java .
.Pp
.Nm
parses command lines from:
.Bl -bullet
.It
Java
.It
Python
.It
Node
.It
Ruby
.It
Various shells
.It
Perl
.El
.Sh SEE ALSO
.Xr ptop 1
.Sh HOMEPAGE
.Nm
lives at http://github.com/walles/px
#!/bin/bash -e
#
# Download and install the latest release
# Give up on any failure
set -e
set -o pipefail
REPO="walles/px"
PXPREFIX=${PXPREFIX:-/usr/local/bin}
# Get the download URL for the latest release
TEMPFILE=$(mktemp || mktemp -t px-install-releasesjson.XXXXXXXX)
curl -s https://api.github.com/repos/$REPO/releases > "${TEMPFILE}"
if grep "API rate limit exceeded" "${TEMPFILE}" > /dev/null ; then
cat "${TEMPFILE}" >&2
exit 1
fi
URL=$(
grep browser_download_url "$TEMPFILE" \
| cut -d '"' -f 4 \
| head -n 1)
rm "${TEMPFILE}"
echo "Downloading the latest release..."
echo " $URL"
TEMPFILE=$(mktemp || mktemp -t px-install.XXXXXXXX)
curl -L -s "$URL" > "$TEMPFILE"
chmod a+x "$TEMPFILE"
echo "Installing the latest release..."
echo
echo "sudo install px.pex /usr/local/bin/px"
sudo install "$TEMPFILE" "${PXPREFIX}/px"
echo "sudo install px.pex /usr/local/bin/ptop"
sudo install "$TEMPFILE" "${PXPREFIX}/ptop"
rm -f "$TEMPFILE"
echo
echo "Installation done, now run one or both of:"
echo " px"
echo " ptop"
[mypy]
ignore_missing_imports = True
check_untyped_defs = True
# Fails in mypy-0.600, do re-enable whenever possible
#disallow_any_unimported = True
disallow_any_explicit = True
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_decorators = True
warn_incomplete_stub = True
warn_redundant_casts = True
warn_return_any = True
warn_unused_ignores = True
#!/usr/bin/python
"""px - ps and top for Human Beings
https://github.com/walles/px
Usage:
px
px <filter>
px <PID>
px --top
px --install
px --help
px --version
In the base case, px list all processes much like ps, but with the most
interesting processes last. A process is considered interesting if it has high
memory usage, has used lots of CPU or has been started recently.
If the optional filter parameter is specified, processes will be shown if:
* The filter matches the user name of the process
* The filter matches a substring of the command line
If the optional PID parameter is specified, you'll get detailed information
about that particular PID.
In --top mode, a new process list is shown every second. The most interesting
processes are on top. In this mode, CPU times are counted from when you first
invoked px, rather than from when each process started. This gives you a picture
of which processes are most active right now.
--top: Show a continuously refreshed process list
--install: Install /usr/local/bin/px and /usr/local/bin/ptop
--help: Print this help
--version: Print version information
"""
import sys
import pkg_resources
import os
import docopt
from . import px_top
from . import px_install
from . import px_process
from . import px_terminal
from . import px_processinfo
def install():
# Find full path to self
if not sys.argv:
sys.stderr.write("ERROR: Can't find myself, can't install\n")
return
px_pex = sys.argv[0]
if not px_pex.endswith(".pex"):
sys.stderr.write("ERROR: Not running from .pex file, can't install\n")
return
px_install.install(px_pex, "/usr/local/bin/px")
px_install.install(px_pex, "/usr/local/bin/ptop")
def get_version():
return pkg_resources.get_distribution("pxpx").version
def main():
if len(sys.argv) == 1 and os.path.basename(sys.argv[0]).endswith("top"):
sys.argv.append("--top")
args = docopt.docopt(__doc__, version=get_version())
if args['--install']:
install()
return
if args['--top']:
px_top.top()
return
filterstring = args['<filter>']
if filterstring:
try:
pid = int(filterstring)
px_processinfo.print_process_info(pid)
return
except ValueError:
# It's a search filter and not a PID, keep moving
pass
procs = px_process.get_all()
procs = filter(lambda p: p.match(filterstring), procs)
# Print the most interesting processes last; there are lots of processes and
# the end of the list is where your eyes will be when you get the prompt back.
columns = None
window_size = px_terminal.get_window_size()
if window_size is not None:
columns = window_size[1]
lines = px_terminal.to_screen_lines(px_process.order_best_last(procs), columns)
print("\n".join(lines))
if __name__ == "__main__":
main()
"""Extract information from command lines"""
import re
import os.path
# Match "[kworker/0:0H]", no grouping
LINUX_KERNEL_PROC = re.compile("^\[[^/ ]+/?[^/ ]+\]$")
# Match "(python2.7)", no grouping
OSX_PARENTHESIZED_PROC = re.compile("^\([^()]+\)$")
def to_array(commandline):
"""Splits a command line string into components"""
base_split = commandline.split(" ")
if len(base_split) == 1:
return base_split
# Try to reverse engineer executables with spaces in their names
merged_split = list(base_split)
while not os.path.isfile(merged_split[0]):
if len(merged_split) == 1:
# Nothing more to merge, give up
return base_split
# Merge the two first elements: http://stackoverflow.com/a/1142879/473672
merged_split[0:2] = [' '.join(merged_split[0:2])]
return merged_split
def get_command(commandline):
"""
Extracts the command from the command line.
This function most often returns the first component of the command line
with the path stripped away.
For some language runtimes, this function may return the name of the program
that the runtime is executing.
"""
if LINUX_KERNEL_PROC.match(commandline):
return commandline
if OSX_PARENTHESIZED_PROC.match(commandline):
return commandline
command = os.path.basename(to_array(commandline)[0])
if command.startswith('python') or command == 'Python':
return get_python_command(commandline)
if command == "java":
return get_java_command(commandline)
if command in ["bash", "sh", "ruby", "perl", "node"]:
return get_generic_script_command(commandline)
if len(command) < 25:
return command
command_split = command.split(".")
if len(command_split) > 1:
if len(command_split[-1]) > 4:
# Pretend all the dots are a kind of path and go for the last
# part only
command = command_split[-1]
else:
# Assume last part is a file suffix (like ".exe") and take the
# next to last part
command = command_split[-2]
return command
def get_python_command(commandline):
array = to_array(commandline)
array = list(filter(lambda s: s, array))
python = os.path.basename(array[0])
if len(array) == 1:
return python
if not array[1].startswith('-'):
return os.path.basename(array[1])
if len(array) > 2:
if array[1] == '-m' and not array[2].startswith('-'):
return os.path.basename(array[2])
return python
def prettify_fully_qualified_java_class(class_name):
split = class_name.split('.')
if len(split) == 1:
return split[-1]
if split[-1] == 'Main':
# Attempt to make "Main" class names more meaningful
return split[-2] + '.' + split[-1]
return split[-1]
def get_java_command(commandline):
array = to_array(commandline)
java = os.path.basename(array[0])
if len(array) == 1:
return java
state = "skip next"
for component in array:
if not component:
# Skip all empties
continue
if state == "skip next":
if component.startswith("-"):
# Skipping switches doesn't make sense. We're lost, fall back to
# just returning the command name
return java
state = "scanning"
continue
if state == "return next":
if component.startswith("-"):
# Returning switches doesn't make sense. We're lost, fall back
# to just returning the command name
return java
return os.path.basename(component)
elif state == "scanning":
if component.startswith('-X'):
continue
if component.startswith('-D'):
continue
if component.startswith('-ea'):
continue
if component.startswith('-da'):
continue
if component.startswith('-agentlib:'):
continue
if component.startswith('-javaagent:'):
continue
if component == "-server":
continue
if component == "-noverify":
continue
if component == "-cp" or component == "-classpath":
state = "skip next"
continue
if component == '-jar':
state = "return next"
continue
if component.startswith('-'):
# Unsupported switch, give up
return java
return prettify_fully_qualified_java_class(component)
else:
raise ValueError("Unhandled state <{}> at <{}> for: {}".format(state, component, array))
# We got to the end without being able to come up with a better name, give up
return java
def get_generic_script_command(commandline):
array = to_array(commandline)
vm = os.path.basename(array[0])
if len(array) == 1:
return vm